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/cli.js
CHANGED
|
@@ -1170,6 +1170,187 @@ function generateUnifiedContractMarkdown(contract) {
|
|
|
1170
1170
|
lines.push("");
|
|
1171
1171
|
}
|
|
1172
1172
|
}
|
|
1173
|
+
lines.push("## Filtering with WHERE Clauses");
|
|
1174
|
+
lines.push("");
|
|
1175
|
+
lines.push("The SDK provides type-safe WHERE clause filtering with support for various operators.");
|
|
1176
|
+
lines.push("");
|
|
1177
|
+
lines.push("### Basic Filtering");
|
|
1178
|
+
lines.push("");
|
|
1179
|
+
lines.push("**Direct equality:**");
|
|
1180
|
+
lines.push("");
|
|
1181
|
+
lines.push("```typescript");
|
|
1182
|
+
lines.push("// Find users with specific email");
|
|
1183
|
+
lines.push("const users = await sdk.users.list({");
|
|
1184
|
+
lines.push(" where: { email: 'user@example.com' }");
|
|
1185
|
+
lines.push("});");
|
|
1186
|
+
lines.push("");
|
|
1187
|
+
lines.push("// Multiple conditions (AND)");
|
|
1188
|
+
lines.push("const activeUsers = await sdk.users.list({");
|
|
1189
|
+
lines.push(" where: {");
|
|
1190
|
+
lines.push(" status: 'active',");
|
|
1191
|
+
lines.push(" role: 'admin'");
|
|
1192
|
+
lines.push(" }");
|
|
1193
|
+
lines.push("});");
|
|
1194
|
+
lines.push("```");
|
|
1195
|
+
lines.push("");
|
|
1196
|
+
lines.push("### Comparison Operators");
|
|
1197
|
+
lines.push("");
|
|
1198
|
+
lines.push("Use comparison operators for numeric, date, and other comparable fields:");
|
|
1199
|
+
lines.push("");
|
|
1200
|
+
lines.push("```typescript");
|
|
1201
|
+
lines.push("// Greater than / Less than");
|
|
1202
|
+
lines.push("const adults = await sdk.users.list({");
|
|
1203
|
+
lines.push(" where: { age: { $gt: 18 } }");
|
|
1204
|
+
lines.push("});");
|
|
1205
|
+
lines.push("");
|
|
1206
|
+
lines.push("// Range queries");
|
|
1207
|
+
lines.push("const workingAge = await sdk.users.list({");
|
|
1208
|
+
lines.push(" where: {");
|
|
1209
|
+
lines.push(" age: { $gte: 18, $lte: 65 }");
|
|
1210
|
+
lines.push(" }");
|
|
1211
|
+
lines.push("});");
|
|
1212
|
+
lines.push("");
|
|
1213
|
+
lines.push("// Not equal");
|
|
1214
|
+
lines.push("const notPending = await sdk.orders.list({");
|
|
1215
|
+
lines.push(" where: { status: { $ne: 'pending' } }");
|
|
1216
|
+
lines.push("});");
|
|
1217
|
+
lines.push("```");
|
|
1218
|
+
lines.push("");
|
|
1219
|
+
lines.push("### String Operators");
|
|
1220
|
+
lines.push("");
|
|
1221
|
+
lines.push("Pattern matching for string fields:");
|
|
1222
|
+
lines.push("");
|
|
1223
|
+
lines.push("```typescript");
|
|
1224
|
+
lines.push("// Case-sensitive LIKE");
|
|
1225
|
+
lines.push("const johnsmiths = await sdk.users.list({");
|
|
1226
|
+
lines.push(" where: { name: { $like: '%Smith%' } }");
|
|
1227
|
+
lines.push("});");
|
|
1228
|
+
lines.push("");
|
|
1229
|
+
lines.push("// Case-insensitive ILIKE");
|
|
1230
|
+
lines.push("const gmailUsers = await sdk.users.list({");
|
|
1231
|
+
lines.push(" where: { email: { $ilike: '%@gmail.com' } }");
|
|
1232
|
+
lines.push("});");
|
|
1233
|
+
lines.push("```");
|
|
1234
|
+
lines.push("");
|
|
1235
|
+
lines.push("### Array Operators");
|
|
1236
|
+
lines.push("");
|
|
1237
|
+
lines.push("Filter by multiple possible values:");
|
|
1238
|
+
lines.push("");
|
|
1239
|
+
lines.push("```typescript");
|
|
1240
|
+
lines.push("// IN - match any value in array");
|
|
1241
|
+
lines.push("const specificUsers = await sdk.users.list({");
|
|
1242
|
+
lines.push(" where: {");
|
|
1243
|
+
lines.push(" id: { $in: ['id1', 'id2', 'id3'] }");
|
|
1244
|
+
lines.push(" }");
|
|
1245
|
+
lines.push("});");
|
|
1246
|
+
lines.push("");
|
|
1247
|
+
lines.push("// NOT IN - exclude values");
|
|
1248
|
+
lines.push("const nonSystemUsers = await sdk.users.list({");
|
|
1249
|
+
lines.push(" where: {");
|
|
1250
|
+
lines.push(" role: { $nin: ['admin', 'system'] }");
|
|
1251
|
+
lines.push(" }");
|
|
1252
|
+
lines.push("});");
|
|
1253
|
+
lines.push("```");
|
|
1254
|
+
lines.push("");
|
|
1255
|
+
lines.push("### NULL Checks");
|
|
1256
|
+
lines.push("");
|
|
1257
|
+
lines.push("Check for null or non-null values:");
|
|
1258
|
+
lines.push("");
|
|
1259
|
+
lines.push("```typescript");
|
|
1260
|
+
lines.push("// IS NULL");
|
|
1261
|
+
lines.push("const activeRecords = await sdk.records.list({");
|
|
1262
|
+
lines.push(" where: { deleted_at: { $is: null } }");
|
|
1263
|
+
lines.push("});");
|
|
1264
|
+
lines.push("");
|
|
1265
|
+
lines.push("// IS NOT NULL");
|
|
1266
|
+
lines.push("const deletedRecords = await sdk.records.list({");
|
|
1267
|
+
lines.push(" where: { deleted_at: { $isNot: null } }");
|
|
1268
|
+
lines.push("});");
|
|
1269
|
+
lines.push("```");
|
|
1270
|
+
lines.push("");
|
|
1271
|
+
lines.push("### Combining Operators");
|
|
1272
|
+
lines.push("");
|
|
1273
|
+
lines.push("Mix multiple operators for complex queries:");
|
|
1274
|
+
lines.push("");
|
|
1275
|
+
lines.push("```typescript");
|
|
1276
|
+
lines.push("const filteredUsers = await sdk.users.list({");
|
|
1277
|
+
lines.push(" where: {");
|
|
1278
|
+
lines.push(" age: { $gte: 18, $lt: 65 },");
|
|
1279
|
+
lines.push(" email: { $ilike: '%@company.com' },");
|
|
1280
|
+
lines.push(" status: { $in: ['active', 'pending'] },");
|
|
1281
|
+
lines.push(" deleted_at: { $is: null }");
|
|
1282
|
+
lines.push(" },");
|
|
1283
|
+
lines.push(" limit: 50,");
|
|
1284
|
+
lines.push(" offset: 0");
|
|
1285
|
+
lines.push("});");
|
|
1286
|
+
lines.push("```");
|
|
1287
|
+
lines.push("");
|
|
1288
|
+
lines.push("### Available Operators");
|
|
1289
|
+
lines.push("");
|
|
1290
|
+
lines.push("| Operator | Description | Example | Types |");
|
|
1291
|
+
lines.push("|----------|-------------|---------|-------|");
|
|
1292
|
+
lines.push("| `$eq` | Equal to | `{ age: { $eq: 25 } }` | All |");
|
|
1293
|
+
lines.push("| `$ne` | Not equal to | `{ status: { $ne: 'inactive' } }` | All |");
|
|
1294
|
+
lines.push("| `$gt` | Greater than | `{ price: { $gt: 100 } }` | Number, Date |");
|
|
1295
|
+
lines.push("| `$gte` | Greater than or equal | `{ age: { $gte: 18 } }` | Number, Date |");
|
|
1296
|
+
lines.push("| `$lt` | Less than | `{ quantity: { $lt: 10 } }` | Number, Date |");
|
|
1297
|
+
lines.push("| `$lte` | Less than or equal | `{ age: { $lte: 65 } }` | Number, Date |");
|
|
1298
|
+
lines.push("| `$in` | In array | `{ id: { $in: ['a', 'b'] } }` | All |");
|
|
1299
|
+
lines.push("| `$nin` | Not in array | `{ role: { $nin: ['admin'] } }` | All |");
|
|
1300
|
+
lines.push("| `$like` | Pattern match (case-sensitive) | `{ name: { $like: '%John%' } }` | String |");
|
|
1301
|
+
lines.push("| `$ilike` | Pattern match (case-insensitive) | `{ email: { $ilike: '%@GMAIL%' } }` | String |");
|
|
1302
|
+
lines.push("| `$is` | IS NULL | `{ deleted_at: { $is: null } }` | Nullable fields |");
|
|
1303
|
+
lines.push("| `$isNot` | IS NOT NULL | `{ created_by: { $isNot: null } }` | Nullable fields |");
|
|
1304
|
+
lines.push("");
|
|
1305
|
+
lines.push("### Logical Operators");
|
|
1306
|
+
lines.push("");
|
|
1307
|
+
lines.push("Combine conditions using `$or` and `$and` (supports 2 levels of nesting):");
|
|
1308
|
+
lines.push("");
|
|
1309
|
+
lines.push("| Operator | Description | Example |");
|
|
1310
|
+
lines.push("|----------|-------------|---------|");
|
|
1311
|
+
lines.push("| `$or` | Match any condition | `{ $or: [{ status: 'active' }, { role: 'admin' }] }` |");
|
|
1312
|
+
lines.push("| `$and` | Match all conditions (explicit) | `{ $and: [{ age: { $gte: 18 } }, { status: 'verified' }] }` |");
|
|
1313
|
+
lines.push("");
|
|
1314
|
+
lines.push("```typescript");
|
|
1315
|
+
lines.push("// OR - match any condition");
|
|
1316
|
+
lines.push("const results = await sdk.users.list({");
|
|
1317
|
+
lines.push(" where: {");
|
|
1318
|
+
lines.push(" $or: [");
|
|
1319
|
+
lines.push(" { email: { $ilike: '%@gmail.com' } },");
|
|
1320
|
+
lines.push(" { status: 'premium' }");
|
|
1321
|
+
lines.push(" ]");
|
|
1322
|
+
lines.push(" }");
|
|
1323
|
+
lines.push("});");
|
|
1324
|
+
lines.push("");
|
|
1325
|
+
lines.push("// Mixed AND + OR (implicit AND at root level)");
|
|
1326
|
+
lines.push("const complex = await sdk.users.list({");
|
|
1327
|
+
lines.push(" where: {");
|
|
1328
|
+
lines.push(" status: 'active', // AND");
|
|
1329
|
+
lines.push(" $or: [");
|
|
1330
|
+
lines.push(" { age: { $lt: 18 } },");
|
|
1331
|
+
lines.push(" { age: { $gt: 65 } }");
|
|
1332
|
+
lines.push(" ]");
|
|
1333
|
+
lines.push(" }");
|
|
1334
|
+
lines.push("});");
|
|
1335
|
+
lines.push("");
|
|
1336
|
+
lines.push("// Nested (2 levels max)");
|
|
1337
|
+
lines.push("const nested = await sdk.users.list({");
|
|
1338
|
+
lines.push(" where: {");
|
|
1339
|
+
lines.push(" $and: [");
|
|
1340
|
+
lines.push(" {");
|
|
1341
|
+
lines.push(" $or: [");
|
|
1342
|
+
lines.push(" { firstName: { $ilike: '%john%' } },");
|
|
1343
|
+
lines.push(" { lastName: { $ilike: '%john%' } }");
|
|
1344
|
+
lines.push(" ]");
|
|
1345
|
+
lines.push(" },");
|
|
1346
|
+
lines.push(" { status: 'active' }");
|
|
1347
|
+
lines.push(" ]");
|
|
1348
|
+
lines.push(" }");
|
|
1349
|
+
lines.push("});");
|
|
1350
|
+
lines.push("```");
|
|
1351
|
+
lines.push("");
|
|
1352
|
+
lines.push("**Note:** The WHERE clause types are fully type-safe. TypeScript will only allow operators that are valid for each field type.");
|
|
1353
|
+
lines.push("");
|
|
1173
1354
|
lines.push("## Resources");
|
|
1174
1355
|
lines.push("");
|
|
1175
1356
|
for (const resource of contract.resources) {
|
|
@@ -1826,115 +2007,131 @@ async function initCommand(args) {
|
|
|
1826
2007
|
}
|
|
1827
2008
|
var CONFIG_TEMPLATE = `/**
|
|
1828
2009
|
* PostgreSDK Configuration
|
|
1829
|
-
*
|
|
1830
|
-
* This file configures how postgresdk generates your SDK
|
|
2010
|
+
*
|
|
2011
|
+
* This file configures how postgresdk generates your type-safe API and SDK
|
|
2012
|
+
* from your PostgreSQL database schema.
|
|
2013
|
+
*
|
|
2014
|
+
* QUICK START:
|
|
2015
|
+
* 1. Update the connectionString below
|
|
2016
|
+
* 2. Run: postgresdk generate
|
|
2017
|
+
* 3. Start using your generated SDK!
|
|
2018
|
+
*
|
|
2019
|
+
* CLI COMMANDS:
|
|
2020
|
+
* postgresdk init Initialize this config file
|
|
2021
|
+
* postgresdk generate Generate API and SDK from your database
|
|
2022
|
+
* postgresdk pull Pull SDK from a remote API
|
|
2023
|
+
* postgresdk help Show help and examples
|
|
2024
|
+
*
|
|
1831
2025
|
* Environment variables are automatically loaded from .env files.
|
|
1832
2026
|
*/
|
|
1833
2027
|
|
|
1834
2028
|
export default {
|
|
1835
2029
|
// ========== DATABASE CONNECTION (Required) ==========
|
|
1836
|
-
|
|
2030
|
+
|
|
1837
2031
|
/**
|
|
1838
2032
|
* PostgreSQL connection string
|
|
1839
2033
|
* Format: postgres://user:password@host:port/database
|
|
2034
|
+
*
|
|
2035
|
+
* Tip: Use environment variables to keep credentials secure
|
|
1840
2036
|
*/
|
|
1841
2037
|
connectionString: process.env.DATABASE_URL || "postgres://user:password@localhost:5432/mydb",
|
|
1842
|
-
|
|
2038
|
+
|
|
1843
2039
|
// ========== BASIC OPTIONS ==========
|
|
1844
|
-
|
|
2040
|
+
|
|
1845
2041
|
/**
|
|
1846
2042
|
* Database schema to introspect
|
|
1847
|
-
*
|
|
2043
|
+
* Default: "public"
|
|
1848
2044
|
*/
|
|
1849
2045
|
// schema: "public",
|
|
1850
|
-
|
|
2046
|
+
|
|
1851
2047
|
/**
|
|
1852
2048
|
* Output directory for server-side code (routes, validators, etc.)
|
|
1853
|
-
*
|
|
2049
|
+
* Default: "./api/server"
|
|
1854
2050
|
*/
|
|
1855
2051
|
// outServer: "./api/server",
|
|
1856
|
-
|
|
2052
|
+
|
|
1857
2053
|
/**
|
|
1858
2054
|
* Output directory for client SDK
|
|
1859
|
-
*
|
|
2055
|
+
* Default: "./api/client"
|
|
1860
2056
|
*/
|
|
1861
2057
|
// outClient: "./api/client",
|
|
1862
|
-
|
|
2058
|
+
|
|
1863
2059
|
// ========== ADVANCED OPTIONS ==========
|
|
1864
|
-
|
|
2060
|
+
|
|
1865
2061
|
/**
|
|
1866
2062
|
* Column name for soft deletes. When set, DELETE operations will update
|
|
1867
2063
|
* this column instead of removing rows.
|
|
1868
|
-
*
|
|
1869
|
-
*
|
|
2064
|
+
*
|
|
2065
|
+
* Default: null (hard deletes)
|
|
2066
|
+
* Example: "deleted_at"
|
|
1870
2067
|
*/
|
|
1871
2068
|
// softDeleteColumn: null,
|
|
1872
|
-
|
|
2069
|
+
|
|
1873
2070
|
/**
|
|
1874
2071
|
* Maximum depth for nested relationship includes to prevent infinite loops
|
|
1875
|
-
*
|
|
2072
|
+
* Default: 2
|
|
1876
2073
|
*/
|
|
1877
2074
|
// includeMethodsDepth: 2,
|
|
1878
|
-
|
|
1879
|
-
|
|
2075
|
+
|
|
2076
|
+
|
|
1880
2077
|
/**
|
|
1881
2078
|
* Server framework for generated API routes
|
|
1882
|
-
*
|
|
1883
|
-
*
|
|
1884
|
-
*
|
|
1885
|
-
*
|
|
2079
|
+
* Options:
|
|
2080
|
+
* - "hono": Lightweight, edge-compatible web framework (default)
|
|
2081
|
+
* - "express": Traditional Node.js framework (planned)
|
|
2082
|
+
* - "fastify": High-performance Node.js framework (planned)
|
|
2083
|
+
*
|
|
2084
|
+
* Default: "hono"
|
|
1886
2085
|
*/
|
|
1887
2086
|
// serverFramework: "hono",
|
|
1888
|
-
|
|
2087
|
+
|
|
1889
2088
|
/**
|
|
1890
2089
|
* Use .js extensions in server imports (for Vercel Edge, Deno, etc.)
|
|
1891
|
-
*
|
|
2090
|
+
* Default: false
|
|
1892
2091
|
*/
|
|
1893
2092
|
// useJsExtensions: false,
|
|
1894
|
-
|
|
2093
|
+
|
|
1895
2094
|
/**
|
|
1896
2095
|
* Use .js extensions in client SDK imports (rarely needed)
|
|
1897
|
-
*
|
|
2096
|
+
* Default: false
|
|
1898
2097
|
*/
|
|
1899
2098
|
// useJsExtensionsClient: false,
|
|
1900
|
-
|
|
2099
|
+
|
|
1901
2100
|
// ========== TEST GENERATION ==========
|
|
1902
|
-
|
|
2101
|
+
|
|
1903
2102
|
/**
|
|
1904
|
-
* Generate basic SDK tests
|
|
1905
|
-
* Uncomment to enable test generation
|
|
2103
|
+
* Generate basic SDK tests with Docker setup
|
|
2104
|
+
* Uncomment to enable test generation
|
|
1906
2105
|
*/
|
|
1907
2106
|
// tests: {
|
|
1908
2107
|
// generate: true,
|
|
1909
2108
|
// output: "./api/tests",
|
|
1910
2109
|
// framework: "vitest" // or "jest" or "bun"
|
|
1911
2110
|
// },
|
|
1912
|
-
|
|
2111
|
+
|
|
1913
2112
|
// ========== AUTHENTICATION ==========
|
|
1914
|
-
|
|
2113
|
+
|
|
1915
2114
|
/**
|
|
1916
2115
|
* Authentication configuration for your API
|
|
1917
|
-
*
|
|
1918
|
-
*
|
|
2116
|
+
*
|
|
2117
|
+
* SIMPLE SYNTAX (recommended):
|
|
1919
2118
|
* auth: { apiKey: process.env.API_KEY }
|
|
1920
2119
|
* auth: { jwt: process.env.JWT_SECRET }
|
|
1921
|
-
*
|
|
1922
|
-
* Multiple API keys:
|
|
1923
2120
|
* auth: { apiKeys: [process.env.KEY1, process.env.KEY2] }
|
|
1924
|
-
*
|
|
1925
|
-
*
|
|
2121
|
+
*
|
|
2122
|
+
* FULL SYNTAX (advanced):
|
|
1926
2123
|
*/
|
|
1927
2124
|
// auth: {
|
|
1928
2125
|
// // Strategy: "none" | "api-key" | "jwt-hs256"
|
|
1929
2126
|
// strategy: "none",
|
|
1930
|
-
//
|
|
2127
|
+
//
|
|
1931
2128
|
// // For API Key authentication
|
|
1932
2129
|
// apiKeyHeader: "x-api-key", // Header name for API key
|
|
1933
2130
|
// apiKeys: [ // List of valid API keys
|
|
1934
2131
|
// process.env.API_KEY_1,
|
|
1935
2132
|
// process.env.API_KEY_2,
|
|
1936
2133
|
// ],
|
|
1937
|
-
//
|
|
2134
|
+
//
|
|
1938
2135
|
// // For JWT (HS256) authentication
|
|
1939
2136
|
// jwt: {
|
|
1940
2137
|
// sharedSecret: process.env.JWT_SECRET, // Secret for signing/verifying
|
|
@@ -1942,12 +2139,12 @@ export default {
|
|
|
1942
2139
|
// audience: "my-users", // Optional: validate 'aud' claim
|
|
1943
2140
|
// }
|
|
1944
2141
|
// },
|
|
1945
|
-
|
|
2142
|
+
|
|
1946
2143
|
// ========== SDK DISTRIBUTION (Pull Configuration) ==========
|
|
1947
|
-
|
|
2144
|
+
|
|
1948
2145
|
/**
|
|
1949
2146
|
* Configuration for pulling SDK from a remote API
|
|
1950
|
-
* Used when running
|
|
2147
|
+
* Used when running: postgresdk pull
|
|
1951
2148
|
*/
|
|
1952
2149
|
// pull: {
|
|
1953
2150
|
// from: "https://api.myapp.com", // API URL to pull SDK from
|
|
@@ -2237,7 +2434,14 @@ function buildGraph(model) {
|
|
|
2237
2434
|
|
|
2238
2435
|
// src/emit-include-spec.ts
|
|
2239
2436
|
function emitIncludeSpec(graph) {
|
|
2240
|
-
let out =
|
|
2437
|
+
let out = `/**
|
|
2438
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2439
|
+
*
|
|
2440
|
+
* This file was automatically generated by PostgreSDK.
|
|
2441
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2442
|
+
*
|
|
2443
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2444
|
+
*/
|
|
2241
2445
|
`;
|
|
2242
2446
|
const tables = Object.keys(graph);
|
|
2243
2447
|
for (const table of tables) {
|
|
@@ -2266,7 +2470,14 @@ function toPascal(s) {
|
|
|
2266
2470
|
|
|
2267
2471
|
// src/emit-include-builder.ts
|
|
2268
2472
|
function emitIncludeBuilder(graph, maxDepth) {
|
|
2269
|
-
return
|
|
2473
|
+
return `/**
|
|
2474
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2475
|
+
*
|
|
2476
|
+
* This file was automatically generated by PostgreSDK.
|
|
2477
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2478
|
+
*
|
|
2479
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2480
|
+
*/
|
|
2270
2481
|
export const RELATION_GRAPH = ${JSON.stringify(graph, null, 2)} as const;
|
|
2271
2482
|
type TableName = keyof typeof RELATION_GRAPH;
|
|
2272
2483
|
|
|
@@ -2414,7 +2625,14 @@ function emitHonoRoutes(table, _graph, opts) {
|
|
|
2414
2625
|
const hasAuth = opts.authStrategy && opts.authStrategy !== "none";
|
|
2415
2626
|
const ext = opts.useJsExtensions ? ".js" : "";
|
|
2416
2627
|
const authImport = hasAuth ? `import { authMiddleware } from "../auth${ext}";` : "";
|
|
2417
|
-
return
|
|
2628
|
+
return `/**
|
|
2629
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2630
|
+
*
|
|
2631
|
+
* This file was automatically generated by PostgreSDK.
|
|
2632
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2633
|
+
*
|
|
2634
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2635
|
+
*/
|
|
2418
2636
|
import { Hono } from "hono";
|
|
2419
2637
|
import { z } from "zod";
|
|
2420
2638
|
import { Insert${Type}Schema, Update${Type}Schema } from "../zod/${fileTableName}${ext}";
|
|
@@ -2594,7 +2812,7 @@ function emitClient(table, graph, opts, model) {
|
|
|
2594
2812
|
let includeMethodsCode = "";
|
|
2595
2813
|
for (const method of includeMethods) {
|
|
2596
2814
|
const isGetByPk = method.name.startsWith("getByPk");
|
|
2597
|
-
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?:
|
|
2815
|
+
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: Where<Select${Type}>; orderBy?: string; order?: "asc" | "desc"; }, "include">`;
|
|
2598
2816
|
if (isGetByPk) {
|
|
2599
2817
|
const pkWhere = hasCompositePk ? `{ ${safePk.map((col) => `${col}: pk.${col}`).join(", ")} }` : `{ ${safePk[0] || "id"}: pk }`;
|
|
2600
2818
|
const baseReturnType = method.returnType.replace(" | null", "");
|
|
@@ -2616,8 +2834,16 @@ function emitClient(table, graph, opts, model) {
|
|
|
2616
2834
|
`;
|
|
2617
2835
|
}
|
|
2618
2836
|
}
|
|
2619
|
-
return
|
|
2837
|
+
return `/**
|
|
2838
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2839
|
+
*
|
|
2840
|
+
* This file was automatically generated by PostgreSDK.
|
|
2841
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2842
|
+
*
|
|
2843
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2844
|
+
*/
|
|
2620
2845
|
import { BaseClient } from "./base-client${ext}";
|
|
2846
|
+
import type { Where } from "./where-types${ext}";
|
|
2621
2847
|
${typeImports}
|
|
2622
2848
|
${otherTableImports.join(`
|
|
2623
2849
|
`)}
|
|
@@ -2637,10 +2863,11 @@ export class ${Type}Client extends BaseClient {
|
|
|
2637
2863
|
return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
|
2638
2864
|
}
|
|
2639
2865
|
|
|
2640
|
-
async list(params?: {
|
|
2641
|
-
|
|
2866
|
+
async list(params?: {
|
|
2867
|
+
include?: any;
|
|
2868
|
+
limit?: number;
|
|
2642
2869
|
offset?: number;
|
|
2643
|
-
where?:
|
|
2870
|
+
where?: Where<Select${Type}>;
|
|
2644
2871
|
orderBy?: string;
|
|
2645
2872
|
order?: "asc" | "desc";
|
|
2646
2873
|
}): Promise<Select${Type}[]> {
|
|
@@ -2661,7 +2888,14 @@ ${includeMethodsCode}}
|
|
|
2661
2888
|
}
|
|
2662
2889
|
function emitClientIndex(tables, useJsExtensions) {
|
|
2663
2890
|
const ext = useJsExtensions ? ".js" : "";
|
|
2664
|
-
let out =
|
|
2891
|
+
let out = `/**
|
|
2892
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2893
|
+
*
|
|
2894
|
+
* This file was automatically generated by PostgreSDK.
|
|
2895
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2896
|
+
*
|
|
2897
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2898
|
+
*/
|
|
2665
2899
|
`;
|
|
2666
2900
|
out += `import { BaseClient, type AuthConfig } from "./base-client${ext}";
|
|
2667
2901
|
`;
|
|
@@ -2702,6 +2936,17 @@ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${
|
|
|
2702
2936
|
out += `export { BaseClient } from "./base-client${ext}";
|
|
2703
2937
|
`;
|
|
2704
2938
|
out += `
|
|
2939
|
+
// Include specification types for custom queries
|
|
2940
|
+
`;
|
|
2941
|
+
out += `export type {
|
|
2942
|
+
`;
|
|
2943
|
+
for (const t of tables) {
|
|
2944
|
+
out += ` ${pascal(t.name)}IncludeSpec,
|
|
2945
|
+
`;
|
|
2946
|
+
}
|
|
2947
|
+
out += `} from "./include-spec${ext}";
|
|
2948
|
+
`;
|
|
2949
|
+
out += `
|
|
2705
2950
|
// Zod schemas for form validation
|
|
2706
2951
|
`;
|
|
2707
2952
|
for (const t of tables) {
|
|
@@ -2723,7 +2968,14 @@ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${
|
|
|
2723
2968
|
|
|
2724
2969
|
// src/emit-base-client.ts
|
|
2725
2970
|
function emitBaseClient() {
|
|
2726
|
-
return
|
|
2971
|
+
return `/**
|
|
2972
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2973
|
+
*
|
|
2974
|
+
* This file was automatically generated by PostgreSDK.
|
|
2975
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2976
|
+
*
|
|
2977
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2978
|
+
*/
|
|
2727
2979
|
|
|
2728
2980
|
export type HeaderMap = Record<string, string>;
|
|
2729
2981
|
export type AuthHeadersProvider = () => Promise<HeaderMap> | HeaderMap;
|
|
@@ -2870,7 +3122,14 @@ function emitIncludeLoader(graph, model, maxDepth, useJsExtensions) {
|
|
|
2870
3122
|
fkIndex[t.name] = t.fks.map((f) => ({ from: f.from, toTable: f.toTable, to: f.to }));
|
|
2871
3123
|
}
|
|
2872
3124
|
const ext = useJsExtensions ? ".js" : "";
|
|
2873
|
-
return
|
|
3125
|
+
return `/**
|
|
3126
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
3127
|
+
*
|
|
3128
|
+
* This file was automatically generated by PostgreSDK.
|
|
3129
|
+
* Any manual changes will be overwritten on the next generation.
|
|
3130
|
+
*
|
|
3131
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
3132
|
+
*/
|
|
2874
3133
|
import { RELATION_GRAPH } from "./include-builder${ext}";
|
|
2875
3134
|
|
|
2876
3135
|
// Minimal types to keep the file self-contained
|
|
@@ -3191,7 +3450,14 @@ function emitTypes(table, opts) {
|
|
|
3191
3450
|
return ` ${col.name}: ${valueType};`;
|
|
3192
3451
|
}).join(`
|
|
3193
3452
|
`);
|
|
3194
|
-
return
|
|
3453
|
+
return `/**
|
|
3454
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
3455
|
+
*
|
|
3456
|
+
* This file was automatically generated by PostgreSDK.
|
|
3457
|
+
* Any manual changes will be overwritten on the next generation.
|
|
3458
|
+
*
|
|
3459
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
3460
|
+
*/
|
|
3195
3461
|
export type Insert${Type} = {
|
|
3196
3462
|
${insertFields}
|
|
3197
3463
|
};
|
|
@@ -3206,7 +3472,14 @@ ${selectFields}
|
|
|
3206
3472
|
|
|
3207
3473
|
// src/emit-logger.ts
|
|
3208
3474
|
function emitLogger() {
|
|
3209
|
-
return
|
|
3475
|
+
return `/**
|
|
3476
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
3477
|
+
*
|
|
3478
|
+
* This file was automatically generated by PostgreSDK.
|
|
3479
|
+
* Any manual changes will be overwritten on the next generation.
|
|
3480
|
+
*
|
|
3481
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
3482
|
+
*/
|
|
3210
3483
|
const DEBUG = process.env.SDK_DEBUG === "1" || process.env.SDK_DEBUG === "true";
|
|
3211
3484
|
|
|
3212
3485
|
export const logger = {
|
|
@@ -3239,6 +3512,76 @@ export function safe<T extends (c: any) => any>(handler: T) {
|
|
|
3239
3512
|
`;
|
|
3240
3513
|
}
|
|
3241
3514
|
|
|
3515
|
+
// src/emit-where-types.ts
|
|
3516
|
+
function emitWhereTypes() {
|
|
3517
|
+
return `/**
|
|
3518
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
3519
|
+
*
|
|
3520
|
+
* This file was automatically generated by PostgreSDK.
|
|
3521
|
+
* Any manual changes will be overwritten on the next generation.
|
|
3522
|
+
*
|
|
3523
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
3524
|
+
*/
|
|
3525
|
+
|
|
3526
|
+
/**
|
|
3527
|
+
* WHERE clause operators for filtering
|
|
3528
|
+
*/
|
|
3529
|
+
export type WhereOperator<T> = {
|
|
3530
|
+
/** Equal to */
|
|
3531
|
+
$eq?: T;
|
|
3532
|
+
/** Not equal to */
|
|
3533
|
+
$ne?: T;
|
|
3534
|
+
/** Greater than */
|
|
3535
|
+
$gt?: T;
|
|
3536
|
+
/** Greater than or equal to */
|
|
3537
|
+
$gte?: T;
|
|
3538
|
+
/** Less than */
|
|
3539
|
+
$lt?: T;
|
|
3540
|
+
/** Less than or equal to */
|
|
3541
|
+
$lte?: T;
|
|
3542
|
+
/** In array */
|
|
3543
|
+
$in?: T[];
|
|
3544
|
+
/** Not in array */
|
|
3545
|
+
$nin?: T[];
|
|
3546
|
+
/** LIKE pattern match (strings only) */
|
|
3547
|
+
$like?: T extends string ? string : never;
|
|
3548
|
+
/** Case-insensitive LIKE (strings only) */
|
|
3549
|
+
$ilike?: T extends string ? string : never;
|
|
3550
|
+
/** IS NULL */
|
|
3551
|
+
$is?: null;
|
|
3552
|
+
/** IS NOT NULL */
|
|
3553
|
+
$isNot?: null;
|
|
3554
|
+
};
|
|
3555
|
+
|
|
3556
|
+
/**
|
|
3557
|
+
* WHERE condition - can be a direct value or an operator object
|
|
3558
|
+
*/
|
|
3559
|
+
export type WhereCondition<T> = T | WhereOperator<T>;
|
|
3560
|
+
|
|
3561
|
+
/**
|
|
3562
|
+
* Field-level WHERE conditions (without logical operators)
|
|
3563
|
+
*/
|
|
3564
|
+
export type WhereFieldConditions<T> = {
|
|
3565
|
+
[K in keyof T]?: WhereCondition<T[K]>;
|
|
3566
|
+
};
|
|
3567
|
+
|
|
3568
|
+
/**
|
|
3569
|
+
* WHERE clause type with support for $or/$and logical operators (2 levels max)
|
|
3570
|
+
*
|
|
3571
|
+
* Examples:
|
|
3572
|
+
* - Basic OR: { $or: [{ name: 'Alice' }, { name: 'Bob' }] }
|
|
3573
|
+
* - Mixed AND + OR: { status: 'active', $or: [{ age: { $gt: 65 } }, { age: { $lt: 18 } }] }
|
|
3574
|
+
* - Nested (2 levels): { $and: [{ $or: [{ name: 'Alice' }, { name: 'Bob' }] }, { status: 'active' }] }
|
|
3575
|
+
*/
|
|
3576
|
+
export type Where<T> = WhereFieldConditions<T> & {
|
|
3577
|
+
/** OR - at least one condition must be true */
|
|
3578
|
+
$or?: (WhereFieldConditions<T>)[];
|
|
3579
|
+
/** AND - all conditions must be true (alternative to implicit root-level AND) */
|
|
3580
|
+
$and?: (WhereFieldConditions<T> | { $or?: WhereFieldConditions<T>[] })[];
|
|
3581
|
+
};
|
|
3582
|
+
`;
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3242
3585
|
// src/emit-auth.ts
|
|
3243
3586
|
function emitAuth(cfgAuth) {
|
|
3244
3587
|
const strategy = cfgAuth?.strategy ?? "none";
|
|
@@ -3253,7 +3596,14 @@ function emitAuth(cfgAuth) {
|
|
|
3253
3596
|
const JWT_SHARED_SECRET = JSON.stringify(jwtShared);
|
|
3254
3597
|
const JWT_ISSUER = jwtIssuer === undefined ? "undefined" : JSON.stringify(jwtIssuer);
|
|
3255
3598
|
const JWT_AUDIENCE = jwtAudience === undefined ? "undefined" : JSON.stringify(jwtAudience);
|
|
3256
|
-
return
|
|
3599
|
+
return `/**
|
|
3600
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
3601
|
+
*
|
|
3602
|
+
* This file was automatically generated by PostgreSDK.
|
|
3603
|
+
* Any manual changes will be overwritten on the next generation.
|
|
3604
|
+
*
|
|
3605
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
3606
|
+
*/
|
|
3257
3607
|
import type { Context, Next } from "hono";
|
|
3258
3608
|
|
|
3259
3609
|
// ---- Config inlined by generator ----
|
|
@@ -3410,7 +3760,14 @@ function emitHonoRouter(tables, hasAuth, useJsExtensions) {
|
|
|
3410
3760
|
return `export { register${Type}Routes } from "./routes/${name}${ext}";`;
|
|
3411
3761
|
}).join(`
|
|
3412
3762
|
`);
|
|
3413
|
-
return
|
|
3763
|
+
return `/**
|
|
3764
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
3765
|
+
*
|
|
3766
|
+
* This file was automatically generated by PostgreSDK.
|
|
3767
|
+
* Any manual changes will be overwritten on the next generation.
|
|
3768
|
+
*
|
|
3769
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
3770
|
+
*/
|
|
3414
3771
|
import { Hono } from "hono";
|
|
3415
3772
|
import { SDK_MANIFEST } from "./sdk-bundle${ext}";
|
|
3416
3773
|
import { getContract } from "./contract${ext}";
|
|
@@ -3544,7 +3901,14 @@ function emitSdkBundle(clientFiles, clientDir) {
|
|
|
3544
3901
|
}
|
|
3545
3902
|
const version = `1.0.0`;
|
|
3546
3903
|
const generated = new Date().toISOString();
|
|
3547
|
-
return
|
|
3904
|
+
return `/**
|
|
3905
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
3906
|
+
*
|
|
3907
|
+
* This file was automatically generated by PostgreSDK.
|
|
3908
|
+
* Any manual changes will be overwritten on the next generation.
|
|
3909
|
+
*
|
|
3910
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
3911
|
+
*/
|
|
3548
3912
|
|
|
3549
3913
|
export const SDK_MANIFEST = {
|
|
3550
3914
|
version: "${version}",
|
|
@@ -3648,6 +4012,155 @@ export async function getByPk(
|
|
|
3648
4012
|
}
|
|
3649
4013
|
}
|
|
3650
4014
|
|
|
4015
|
+
/**
|
|
4016
|
+
* Build WHERE clause recursively, supporting $or/$and operators
|
|
4017
|
+
* Returns { sql: string, params: any[], nextParamIndex: number }
|
|
4018
|
+
*/
|
|
4019
|
+
function buildWhereClause(
|
|
4020
|
+
whereClause: any,
|
|
4021
|
+
startParamIndex: number
|
|
4022
|
+
): { sql: string; params: any[]; nextParamIndex: number } {
|
|
4023
|
+
const whereParts: string[] = [];
|
|
4024
|
+
const whereParams: any[] = [];
|
|
4025
|
+
let paramIndex = startParamIndex;
|
|
4026
|
+
|
|
4027
|
+
if (!whereClause || typeof whereClause !== 'object') {
|
|
4028
|
+
return { sql: '', params: [], nextParamIndex: paramIndex };
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
// Separate logical operators from field conditions
|
|
4032
|
+
const { $or, $and, ...fieldConditions } = whereClause;
|
|
4033
|
+
|
|
4034
|
+
// Process field-level conditions
|
|
4035
|
+
for (const [key, value] of Object.entries(fieldConditions)) {
|
|
4036
|
+
if (value === undefined) {
|
|
4037
|
+
continue;
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
// Handle operator objects like { $gt: 5, $like: "%test%" }
|
|
4041
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
4042
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
4043
|
+
if (opValue === undefined) continue;
|
|
4044
|
+
|
|
4045
|
+
switch (op) {
|
|
4046
|
+
case '$eq':
|
|
4047
|
+
whereParts.push(\`"\${key}" = $\${paramIndex}\`);
|
|
4048
|
+
whereParams.push(opValue);
|
|
4049
|
+
paramIndex++;
|
|
4050
|
+
break;
|
|
4051
|
+
case '$ne':
|
|
4052
|
+
whereParts.push(\`"\${key}" != $\${paramIndex}\`);
|
|
4053
|
+
whereParams.push(opValue);
|
|
4054
|
+
paramIndex++;
|
|
4055
|
+
break;
|
|
4056
|
+
case '$gt':
|
|
4057
|
+
whereParts.push(\`"\${key}" > $\${paramIndex}\`);
|
|
4058
|
+
whereParams.push(opValue);
|
|
4059
|
+
paramIndex++;
|
|
4060
|
+
break;
|
|
4061
|
+
case '$gte':
|
|
4062
|
+
whereParts.push(\`"\${key}" >= $\${paramIndex}\`);
|
|
4063
|
+
whereParams.push(opValue);
|
|
4064
|
+
paramIndex++;
|
|
4065
|
+
break;
|
|
4066
|
+
case '$lt':
|
|
4067
|
+
whereParts.push(\`"\${key}" < $\${paramIndex}\`);
|
|
4068
|
+
whereParams.push(opValue);
|
|
4069
|
+
paramIndex++;
|
|
4070
|
+
break;
|
|
4071
|
+
case '$lte':
|
|
4072
|
+
whereParts.push(\`"\${key}" <= $\${paramIndex}\`);
|
|
4073
|
+
whereParams.push(opValue);
|
|
4074
|
+
paramIndex++;
|
|
4075
|
+
break;
|
|
4076
|
+
case '$in':
|
|
4077
|
+
if (Array.isArray(opValue) && opValue.length > 0) {
|
|
4078
|
+
whereParts.push(\`"\${key}" = ANY($\${paramIndex})\`);
|
|
4079
|
+
whereParams.push(opValue);
|
|
4080
|
+
paramIndex++;
|
|
4081
|
+
}
|
|
4082
|
+
break;
|
|
4083
|
+
case '$nin':
|
|
4084
|
+
if (Array.isArray(opValue) && opValue.length > 0) {
|
|
4085
|
+
whereParts.push(\`"\${key}" != ALL($\${paramIndex})\`);
|
|
4086
|
+
whereParams.push(opValue);
|
|
4087
|
+
paramIndex++;
|
|
4088
|
+
}
|
|
4089
|
+
break;
|
|
4090
|
+
case '$like':
|
|
4091
|
+
whereParts.push(\`"\${key}" LIKE $\${paramIndex}\`);
|
|
4092
|
+
whereParams.push(opValue);
|
|
4093
|
+
paramIndex++;
|
|
4094
|
+
break;
|
|
4095
|
+
case '$ilike':
|
|
4096
|
+
whereParts.push(\`"\${key}" ILIKE $\${paramIndex}\`);
|
|
4097
|
+
whereParams.push(opValue);
|
|
4098
|
+
paramIndex++;
|
|
4099
|
+
break;
|
|
4100
|
+
case '$is':
|
|
4101
|
+
if (opValue === null) {
|
|
4102
|
+
whereParts.push(\`"\${key}" IS NULL\`);
|
|
4103
|
+
}
|
|
4104
|
+
break;
|
|
4105
|
+
case '$isNot':
|
|
4106
|
+
if (opValue === null) {
|
|
4107
|
+
whereParts.push(\`"\${key}" IS NOT NULL\`);
|
|
4108
|
+
}
|
|
4109
|
+
break;
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
} else if (value === null) {
|
|
4113
|
+
// Direct null value
|
|
4114
|
+
whereParts.push(\`"\${key}" IS NULL\`);
|
|
4115
|
+
} else {
|
|
4116
|
+
// Direct value (simple equality)
|
|
4117
|
+
whereParts.push(\`"\${key}" = $\${paramIndex}\`);
|
|
4118
|
+
whereParams.push(value);
|
|
4119
|
+
paramIndex++;
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
|
|
4123
|
+
// Handle $or operator
|
|
4124
|
+
if ($or && Array.isArray($or)) {
|
|
4125
|
+
if ($or.length === 0) {
|
|
4126
|
+
// Empty OR is logically FALSE - matches nothing
|
|
4127
|
+
whereParts.push('FALSE');
|
|
4128
|
+
} else {
|
|
4129
|
+
const orParts: string[] = [];
|
|
4130
|
+
for (const orCondition of $or) {
|
|
4131
|
+
const result = buildWhereClause(orCondition, paramIndex);
|
|
4132
|
+
if (result.sql) {
|
|
4133
|
+
orParts.push(result.sql);
|
|
4134
|
+
whereParams.push(...result.params);
|
|
4135
|
+
paramIndex = result.nextParamIndex;
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
if (orParts.length > 0) {
|
|
4139
|
+
whereParts.push(\`(\${orParts.join(' OR ')})\`);
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
// Handle $and operator
|
|
4145
|
+
if ($and && Array.isArray($and) && $and.length > 0) {
|
|
4146
|
+
const andParts: string[] = [];
|
|
4147
|
+
for (const andCondition of $and) {
|
|
4148
|
+
const result = buildWhereClause(andCondition, paramIndex);
|
|
4149
|
+
if (result.sql) {
|
|
4150
|
+
andParts.push(result.sql);
|
|
4151
|
+
whereParams.push(...result.params);
|
|
4152
|
+
paramIndex = result.nextParamIndex;
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
if (andParts.length > 0) {
|
|
4156
|
+
whereParts.push(\`(\${andParts.join(' AND ')})\`);
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
const sql = whereParts.join(' AND ');
|
|
4161
|
+
return { sql, params: whereParams, nextParamIndex: paramIndex };
|
|
4162
|
+
}
|
|
4163
|
+
|
|
3651
4164
|
/**
|
|
3652
4165
|
* LIST operation - Get multiple records with optional filters
|
|
3653
4166
|
*/
|
|
@@ -3657,60 +4170,54 @@ export async function listRecords(
|
|
|
3657
4170
|
): Promise<{ data?: any; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
|
|
3658
4171
|
try {
|
|
3659
4172
|
const { where: whereClause, limit = 50, offset = 0, include } = params;
|
|
3660
|
-
|
|
4173
|
+
|
|
3661
4174
|
// Build WHERE clause
|
|
3662
|
-
const whereParts: string[] = [];
|
|
3663
|
-
const whereParams: any[] = [];
|
|
3664
4175
|
let paramIndex = 1;
|
|
3665
|
-
|
|
4176
|
+
const whereParts: string[] = [];
|
|
4177
|
+
let whereParams: any[] = [];
|
|
4178
|
+
|
|
3666
4179
|
// Add soft delete filter if applicable
|
|
3667
4180
|
if (ctx.softDeleteColumn) {
|
|
3668
4181
|
whereParts.push(\`"\${ctx.softDeleteColumn}" IS NULL\`);
|
|
3669
4182
|
}
|
|
3670
|
-
|
|
4183
|
+
|
|
3671
4184
|
// Add user-provided where conditions
|
|
3672
|
-
if (whereClause
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
continue;
|
|
3679
|
-
} else {
|
|
3680
|
-
whereParts.push(\`"\${key}" = $\${paramIndex}\`);
|
|
3681
|
-
whereParams.push(value);
|
|
3682
|
-
paramIndex++;
|
|
3683
|
-
}
|
|
4185
|
+
if (whereClause) {
|
|
4186
|
+
const result = buildWhereClause(whereClause, paramIndex);
|
|
4187
|
+
if (result.sql) {
|
|
4188
|
+
whereParts.push(result.sql);
|
|
4189
|
+
whereParams = result.params;
|
|
4190
|
+
paramIndex = result.nextParamIndex;
|
|
3684
4191
|
}
|
|
3685
4192
|
}
|
|
3686
|
-
|
|
4193
|
+
|
|
3687
4194
|
const whereSQL = whereParts.length > 0 ? \`WHERE \${whereParts.join(" AND ")}\` : "";
|
|
3688
|
-
|
|
4195
|
+
|
|
3689
4196
|
// Add limit and offset params
|
|
3690
4197
|
const limitParam = \`$\${paramIndex}\`;
|
|
3691
4198
|
const offsetParam = \`$\${paramIndex + 1}\`;
|
|
3692
4199
|
const allParams = [...whereParams, limit, offset];
|
|
3693
|
-
|
|
4200
|
+
|
|
3694
4201
|
const text = \`SELECT * FROM "\${ctx.table}" \${whereSQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
|
|
3695
4202
|
log.debug(\`LIST \${ctx.table} SQL:\`, text, "params:", allParams);
|
|
3696
|
-
|
|
4203
|
+
|
|
3697
4204
|
const { rows } = await ctx.pg.query(text, allParams);
|
|
3698
|
-
|
|
4205
|
+
|
|
3699
4206
|
if (!include) {
|
|
3700
4207
|
log.debug(\`LIST \${ctx.table} rows:\`, rows.length);
|
|
3701
4208
|
return { data: rows, status: 200 };
|
|
3702
4209
|
}
|
|
3703
|
-
|
|
4210
|
+
|
|
3704
4211
|
// Include logic will be handled by the include-loader
|
|
3705
4212
|
// For now, just return the rows with a note that includes need to be applied
|
|
3706
4213
|
log.debug(\`LIST \${ctx.table} include spec:\`, include);
|
|
3707
4214
|
return { data: rows, needsIncludes: true, includeSpec: include, status: 200 };
|
|
3708
4215
|
} catch (e: any) {
|
|
3709
4216
|
log.error(\`LIST \${ctx.table} error:\`, e?.stack ?? e);
|
|
3710
|
-
return {
|
|
3711
|
-
error: e?.message ?? "Internal error",
|
|
4217
|
+
return {
|
|
4218
|
+
error: e?.message ?? "Internal error",
|
|
3712
4219
|
...(DEBUG ? { stack: e?.stack } : {}),
|
|
3713
|
-
status: 500
|
|
4220
|
+
status: 500
|
|
3714
4221
|
};
|
|
3715
4222
|
}
|
|
3716
4223
|
}
|
|
@@ -4612,6 +5119,7 @@ async function generate(configPath) {
|
|
|
4612
5119
|
files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
|
|
4613
5120
|
files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
|
|
4614
5121
|
files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
|
|
5122
|
+
files.push({ path: join(clientDir, "where-types.ts"), content: emitWhereTypes() });
|
|
4615
5123
|
files.push({
|
|
4616
5124
|
path: join(serverDir, "include-builder.ts"),
|
|
4617
5125
|
content: emitIncludeBuilder(graph, cfg.includeMethodsDepth || 2)
|