postgresdk 0.9.9 → 0.10.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 +13 -0
- package/dist/cli.js +338 -50
- package/dist/emit-where-types.d.ts +4 -0
- package/dist/index.js +279 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -160,6 +160,7 @@ const authors = await sdk.authors.list({
|
|
|
160
160
|
### Filtering & Pagination
|
|
161
161
|
|
|
162
162
|
```typescript
|
|
163
|
+
// Simple equality filtering
|
|
163
164
|
const users = await sdk.users.list({
|
|
164
165
|
where: { status: "active" },
|
|
165
166
|
orderBy: "created_at",
|
|
@@ -167,8 +168,20 @@ const users = await sdk.users.list({
|
|
|
167
168
|
limit: 20,
|
|
168
169
|
offset: 40
|
|
169
170
|
});
|
|
171
|
+
|
|
172
|
+
// Advanced WHERE operators
|
|
173
|
+
const filtered = await sdk.users.list({
|
|
174
|
+
where: {
|
|
175
|
+
age: { $gte: 18, $lt: 65 }, // Range queries
|
|
176
|
+
email: { $ilike: '%@company.com' }, // Pattern matching
|
|
177
|
+
status: { $in: ['active', 'pending'] }, // Array matching
|
|
178
|
+
deleted_at: { $is: null } // NULL checks
|
|
179
|
+
}
|
|
180
|
+
});
|
|
170
181
|
```
|
|
171
182
|
|
|
183
|
+
See the generated SDK documentation for all available operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$like`, `$ilike`, `$is`, `$isNot`.
|
|
184
|
+
|
|
172
185
|
## Authentication
|
|
173
186
|
|
|
174
187
|
postgresdk supports API key and JWT authentication:
|
package/dist/cli.js
CHANGED
|
@@ -1170,6 +1170,140 @@ 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("**Note:** The WHERE clause types are fully type-safe. TypeScript will only allow operators that are valid for each field type.");
|
|
1306
|
+
lines.push("");
|
|
1173
1307
|
lines.push("## Resources");
|
|
1174
1308
|
lines.push("");
|
|
1175
1309
|
for (const resource of contract.resources) {
|
|
@@ -1826,115 +1960,131 @@ async function initCommand(args) {
|
|
|
1826
1960
|
}
|
|
1827
1961
|
var CONFIG_TEMPLATE = `/**
|
|
1828
1962
|
* PostgreSDK Configuration
|
|
1829
|
-
*
|
|
1830
|
-
* This file configures how postgresdk generates your SDK
|
|
1963
|
+
*
|
|
1964
|
+
* This file configures how postgresdk generates your type-safe API and SDK
|
|
1965
|
+
* from your PostgreSQL database schema.
|
|
1966
|
+
*
|
|
1967
|
+
* QUICK START:
|
|
1968
|
+
* 1. Update the connectionString below
|
|
1969
|
+
* 2. Run: postgresdk generate
|
|
1970
|
+
* 3. Start using your generated SDK!
|
|
1971
|
+
*
|
|
1972
|
+
* CLI COMMANDS:
|
|
1973
|
+
* postgresdk init Initialize this config file
|
|
1974
|
+
* postgresdk generate Generate API and SDK from your database
|
|
1975
|
+
* postgresdk pull Pull SDK from a remote API
|
|
1976
|
+
* postgresdk help Show help and examples
|
|
1977
|
+
*
|
|
1831
1978
|
* Environment variables are automatically loaded from .env files.
|
|
1832
1979
|
*/
|
|
1833
1980
|
|
|
1834
1981
|
export default {
|
|
1835
1982
|
// ========== DATABASE CONNECTION (Required) ==========
|
|
1836
|
-
|
|
1983
|
+
|
|
1837
1984
|
/**
|
|
1838
1985
|
* PostgreSQL connection string
|
|
1839
1986
|
* Format: postgres://user:password@host:port/database
|
|
1987
|
+
*
|
|
1988
|
+
* Tip: Use environment variables to keep credentials secure
|
|
1840
1989
|
*/
|
|
1841
1990
|
connectionString: process.env.DATABASE_URL || "postgres://user:password@localhost:5432/mydb",
|
|
1842
|
-
|
|
1991
|
+
|
|
1843
1992
|
// ========== BASIC OPTIONS ==========
|
|
1844
|
-
|
|
1993
|
+
|
|
1845
1994
|
/**
|
|
1846
1995
|
* Database schema to introspect
|
|
1847
|
-
*
|
|
1996
|
+
* Default: "public"
|
|
1848
1997
|
*/
|
|
1849
1998
|
// schema: "public",
|
|
1850
|
-
|
|
1999
|
+
|
|
1851
2000
|
/**
|
|
1852
2001
|
* Output directory for server-side code (routes, validators, etc.)
|
|
1853
|
-
*
|
|
2002
|
+
* Default: "./api/server"
|
|
1854
2003
|
*/
|
|
1855
2004
|
// outServer: "./api/server",
|
|
1856
|
-
|
|
2005
|
+
|
|
1857
2006
|
/**
|
|
1858
2007
|
* Output directory for client SDK
|
|
1859
|
-
*
|
|
2008
|
+
* Default: "./api/client"
|
|
1860
2009
|
*/
|
|
1861
2010
|
// outClient: "./api/client",
|
|
1862
|
-
|
|
2011
|
+
|
|
1863
2012
|
// ========== ADVANCED OPTIONS ==========
|
|
1864
|
-
|
|
2013
|
+
|
|
1865
2014
|
/**
|
|
1866
2015
|
* Column name for soft deletes. When set, DELETE operations will update
|
|
1867
2016
|
* this column instead of removing rows.
|
|
1868
|
-
*
|
|
1869
|
-
*
|
|
2017
|
+
*
|
|
2018
|
+
* Default: null (hard deletes)
|
|
2019
|
+
* Example: "deleted_at"
|
|
1870
2020
|
*/
|
|
1871
2021
|
// softDeleteColumn: null,
|
|
1872
|
-
|
|
2022
|
+
|
|
1873
2023
|
/**
|
|
1874
2024
|
* Maximum depth for nested relationship includes to prevent infinite loops
|
|
1875
|
-
*
|
|
2025
|
+
* Default: 2
|
|
1876
2026
|
*/
|
|
1877
2027
|
// includeMethodsDepth: 2,
|
|
1878
|
-
|
|
1879
|
-
|
|
2028
|
+
|
|
2029
|
+
|
|
1880
2030
|
/**
|
|
1881
2031
|
* Server framework for generated API routes
|
|
1882
|
-
*
|
|
1883
|
-
*
|
|
1884
|
-
*
|
|
1885
|
-
*
|
|
2032
|
+
* Options:
|
|
2033
|
+
* - "hono": Lightweight, edge-compatible web framework (default)
|
|
2034
|
+
* - "express": Traditional Node.js framework (planned)
|
|
2035
|
+
* - "fastify": High-performance Node.js framework (planned)
|
|
2036
|
+
*
|
|
2037
|
+
* Default: "hono"
|
|
1886
2038
|
*/
|
|
1887
2039
|
// serverFramework: "hono",
|
|
1888
|
-
|
|
2040
|
+
|
|
1889
2041
|
/**
|
|
1890
2042
|
* Use .js extensions in server imports (for Vercel Edge, Deno, etc.)
|
|
1891
|
-
*
|
|
2043
|
+
* Default: false
|
|
1892
2044
|
*/
|
|
1893
2045
|
// useJsExtensions: false,
|
|
1894
|
-
|
|
2046
|
+
|
|
1895
2047
|
/**
|
|
1896
2048
|
* Use .js extensions in client SDK imports (rarely needed)
|
|
1897
|
-
*
|
|
2049
|
+
* Default: false
|
|
1898
2050
|
*/
|
|
1899
2051
|
// useJsExtensionsClient: false,
|
|
1900
|
-
|
|
2052
|
+
|
|
1901
2053
|
// ========== TEST GENERATION ==========
|
|
1902
|
-
|
|
2054
|
+
|
|
1903
2055
|
/**
|
|
1904
|
-
* Generate basic SDK tests
|
|
1905
|
-
* Uncomment to enable test generation
|
|
2056
|
+
* Generate basic SDK tests with Docker setup
|
|
2057
|
+
* Uncomment to enable test generation
|
|
1906
2058
|
*/
|
|
1907
2059
|
// tests: {
|
|
1908
2060
|
// generate: true,
|
|
1909
2061
|
// output: "./api/tests",
|
|
1910
2062
|
// framework: "vitest" // or "jest" or "bun"
|
|
1911
2063
|
// },
|
|
1912
|
-
|
|
2064
|
+
|
|
1913
2065
|
// ========== AUTHENTICATION ==========
|
|
1914
|
-
|
|
2066
|
+
|
|
1915
2067
|
/**
|
|
1916
2068
|
* Authentication configuration for your API
|
|
1917
|
-
*
|
|
1918
|
-
*
|
|
2069
|
+
*
|
|
2070
|
+
* SIMPLE SYNTAX (recommended):
|
|
1919
2071
|
* auth: { apiKey: process.env.API_KEY }
|
|
1920
2072
|
* auth: { jwt: process.env.JWT_SECRET }
|
|
1921
|
-
*
|
|
1922
|
-
* Multiple API keys:
|
|
1923
2073
|
* auth: { apiKeys: [process.env.KEY1, process.env.KEY2] }
|
|
1924
|
-
*
|
|
1925
|
-
*
|
|
2074
|
+
*
|
|
2075
|
+
* FULL SYNTAX (advanced):
|
|
1926
2076
|
*/
|
|
1927
2077
|
// auth: {
|
|
1928
2078
|
// // Strategy: "none" | "api-key" | "jwt-hs256"
|
|
1929
2079
|
// strategy: "none",
|
|
1930
|
-
//
|
|
2080
|
+
//
|
|
1931
2081
|
// // For API Key authentication
|
|
1932
2082
|
// apiKeyHeader: "x-api-key", // Header name for API key
|
|
1933
2083
|
// apiKeys: [ // List of valid API keys
|
|
1934
2084
|
// process.env.API_KEY_1,
|
|
1935
2085
|
// process.env.API_KEY_2,
|
|
1936
2086
|
// ],
|
|
1937
|
-
//
|
|
2087
|
+
//
|
|
1938
2088
|
// // For JWT (HS256) authentication
|
|
1939
2089
|
// jwt: {
|
|
1940
2090
|
// sharedSecret: process.env.JWT_SECRET, // Secret for signing/verifying
|
|
@@ -1942,12 +2092,12 @@ export default {
|
|
|
1942
2092
|
// audience: "my-users", // Optional: validate 'aud' claim
|
|
1943
2093
|
// }
|
|
1944
2094
|
// },
|
|
1945
|
-
|
|
2095
|
+
|
|
1946
2096
|
// ========== SDK DISTRIBUTION (Pull Configuration) ==========
|
|
1947
|
-
|
|
2097
|
+
|
|
1948
2098
|
/**
|
|
1949
2099
|
* Configuration for pulling SDK from a remote API
|
|
1950
|
-
* Used when running
|
|
2100
|
+
* Used when running: postgresdk pull
|
|
1951
2101
|
*/
|
|
1952
2102
|
// pull: {
|
|
1953
2103
|
// from: "https://api.myapp.com", // API URL to pull SDK from
|
|
@@ -2594,7 +2744,7 @@ function emitClient(table, graph, opts, model) {
|
|
|
2594
2744
|
let includeMethodsCode = "";
|
|
2595
2745
|
for (const method of includeMethods) {
|
|
2596
2746
|
const isGetByPk = method.name.startsWith("getByPk");
|
|
2597
|
-
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?:
|
|
2747
|
+
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: Where<Select${Type}>; orderBy?: string; order?: "asc" | "desc"; }, "include">`;
|
|
2598
2748
|
if (isGetByPk) {
|
|
2599
2749
|
const pkWhere = hasCompositePk ? `{ ${safePk.map((col) => `${col}: pk.${col}`).join(", ")} }` : `{ ${safePk[0] || "id"}: pk }`;
|
|
2600
2750
|
const baseReturnType = method.returnType.replace(" | null", "");
|
|
@@ -2618,6 +2768,7 @@ function emitClient(table, graph, opts, model) {
|
|
|
2618
2768
|
}
|
|
2619
2769
|
return `/* Generated. Do not edit. */
|
|
2620
2770
|
import { BaseClient } from "./base-client${ext}";
|
|
2771
|
+
import type { Where } from "./where-types${ext}";
|
|
2621
2772
|
${typeImports}
|
|
2622
2773
|
${otherTableImports.join(`
|
|
2623
2774
|
`)}
|
|
@@ -2637,10 +2788,11 @@ export class ${Type}Client extends BaseClient {
|
|
|
2637
2788
|
return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
|
2638
2789
|
}
|
|
2639
2790
|
|
|
2640
|
-
async list(params?: {
|
|
2641
|
-
|
|
2791
|
+
async list(params?: {
|
|
2792
|
+
include?: any;
|
|
2793
|
+
limit?: number;
|
|
2642
2794
|
offset?: number;
|
|
2643
|
-
where?:
|
|
2795
|
+
where?: Where<Select${Type}>;
|
|
2644
2796
|
orderBy?: string;
|
|
2645
2797
|
order?: "asc" | "desc";
|
|
2646
2798
|
}): Promise<Select${Type}[]> {
|
|
@@ -2702,6 +2854,17 @@ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${
|
|
|
2702
2854
|
out += `export { BaseClient } from "./base-client${ext}";
|
|
2703
2855
|
`;
|
|
2704
2856
|
out += `
|
|
2857
|
+
// Include specification types for custom queries
|
|
2858
|
+
`;
|
|
2859
|
+
out += `export type {
|
|
2860
|
+
`;
|
|
2861
|
+
for (const t of tables) {
|
|
2862
|
+
out += ` ${pascal(t.name)}IncludeSpec,
|
|
2863
|
+
`;
|
|
2864
|
+
}
|
|
2865
|
+
out += `} from "./include-spec${ext}";
|
|
2866
|
+
`;
|
|
2867
|
+
out += `
|
|
2705
2868
|
// Zod schemas for form validation
|
|
2706
2869
|
`;
|
|
2707
2870
|
for (const t of tables) {
|
|
@@ -3239,6 +3402,54 @@ export function safe<T extends (c: any) => any>(handler: T) {
|
|
|
3239
3402
|
`;
|
|
3240
3403
|
}
|
|
3241
3404
|
|
|
3405
|
+
// src/emit-where-types.ts
|
|
3406
|
+
function emitWhereTypes() {
|
|
3407
|
+
return `/* Generated. Do not edit. */
|
|
3408
|
+
|
|
3409
|
+
/**
|
|
3410
|
+
* WHERE clause operators for filtering
|
|
3411
|
+
*/
|
|
3412
|
+
export type WhereOperator<T> = {
|
|
3413
|
+
/** Equal to */
|
|
3414
|
+
$eq?: T;
|
|
3415
|
+
/** Not equal to */
|
|
3416
|
+
$ne?: T;
|
|
3417
|
+
/** Greater than */
|
|
3418
|
+
$gt?: T;
|
|
3419
|
+
/** Greater than or equal to */
|
|
3420
|
+
$gte?: T;
|
|
3421
|
+
/** Less than */
|
|
3422
|
+
$lt?: T;
|
|
3423
|
+
/** Less than or equal to */
|
|
3424
|
+
$lte?: T;
|
|
3425
|
+
/** In array */
|
|
3426
|
+
$in?: T[];
|
|
3427
|
+
/** Not in array */
|
|
3428
|
+
$nin?: T[];
|
|
3429
|
+
/** LIKE pattern match (strings only) */
|
|
3430
|
+
$like?: T extends string ? string : never;
|
|
3431
|
+
/** Case-insensitive LIKE (strings only) */
|
|
3432
|
+
$ilike?: T extends string ? string : never;
|
|
3433
|
+
/** IS NULL */
|
|
3434
|
+
$is?: null;
|
|
3435
|
+
/** IS NOT NULL */
|
|
3436
|
+
$isNot?: null;
|
|
3437
|
+
};
|
|
3438
|
+
|
|
3439
|
+
/**
|
|
3440
|
+
* WHERE condition - can be a direct value or an operator object
|
|
3441
|
+
*/
|
|
3442
|
+
export type WhereCondition<T> = T | WhereOperator<T>;
|
|
3443
|
+
|
|
3444
|
+
/**
|
|
3445
|
+
* WHERE clause type for a given table type
|
|
3446
|
+
*/
|
|
3447
|
+
export type Where<T> = {
|
|
3448
|
+
[K in keyof T]?: WhereCondition<T[K]>;
|
|
3449
|
+
};
|
|
3450
|
+
`;
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3242
3453
|
// src/emit-auth.ts
|
|
3243
3454
|
function emitAuth(cfgAuth) {
|
|
3244
3455
|
const strategy = cfgAuth?.strategy ?? "none";
|
|
@@ -3671,12 +3882,88 @@ export async function listRecords(
|
|
|
3671
3882
|
// Add user-provided where conditions
|
|
3672
3883
|
if (whereClause && typeof whereClause === 'object') {
|
|
3673
3884
|
for (const [key, value] of Object.entries(whereClause)) {
|
|
3674
|
-
if (value ===
|
|
3675
|
-
whereParts.push(\`"\${key}" IS NULL\`);
|
|
3676
|
-
} else if (value === undefined) {
|
|
3885
|
+
if (value === undefined) {
|
|
3677
3886
|
// Skip undefined values
|
|
3678
3887
|
continue;
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
// Handle operator objects like { $gt: 5, $like: "%test%" }
|
|
3891
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
3892
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
3893
|
+
if (opValue === undefined) continue;
|
|
3894
|
+
|
|
3895
|
+
switch (op) {
|
|
3896
|
+
case '$eq':
|
|
3897
|
+
whereParts.push(\`"\${key}" = $\${paramIndex}\`);
|
|
3898
|
+
whereParams.push(opValue);
|
|
3899
|
+
paramIndex++;
|
|
3900
|
+
break;
|
|
3901
|
+
case '$ne':
|
|
3902
|
+
whereParts.push(\`"\${key}" != $\${paramIndex}\`);
|
|
3903
|
+
whereParams.push(opValue);
|
|
3904
|
+
paramIndex++;
|
|
3905
|
+
break;
|
|
3906
|
+
case '$gt':
|
|
3907
|
+
whereParts.push(\`"\${key}" > $\${paramIndex}\`);
|
|
3908
|
+
whereParams.push(opValue);
|
|
3909
|
+
paramIndex++;
|
|
3910
|
+
break;
|
|
3911
|
+
case '$gte':
|
|
3912
|
+
whereParts.push(\`"\${key}" >= $\${paramIndex}\`);
|
|
3913
|
+
whereParams.push(opValue);
|
|
3914
|
+
paramIndex++;
|
|
3915
|
+
break;
|
|
3916
|
+
case '$lt':
|
|
3917
|
+
whereParts.push(\`"\${key}" < $\${paramIndex}\`);
|
|
3918
|
+
whereParams.push(opValue);
|
|
3919
|
+
paramIndex++;
|
|
3920
|
+
break;
|
|
3921
|
+
case '$lte':
|
|
3922
|
+
whereParts.push(\`"\${key}" <= $\${paramIndex}\`);
|
|
3923
|
+
whereParams.push(opValue);
|
|
3924
|
+
paramIndex++;
|
|
3925
|
+
break;
|
|
3926
|
+
case '$in':
|
|
3927
|
+
if (Array.isArray(opValue) && opValue.length > 0) {
|
|
3928
|
+
whereParts.push(\`"\${key}" = ANY($\${paramIndex})\`);
|
|
3929
|
+
whereParams.push(opValue);
|
|
3930
|
+
paramIndex++;
|
|
3931
|
+
}
|
|
3932
|
+
break;
|
|
3933
|
+
case '$nin':
|
|
3934
|
+
if (Array.isArray(opValue) && opValue.length > 0) {
|
|
3935
|
+
whereParts.push(\`"\${key}" != ALL($\${paramIndex})\`);
|
|
3936
|
+
whereParams.push(opValue);
|
|
3937
|
+
paramIndex++;
|
|
3938
|
+
}
|
|
3939
|
+
break;
|
|
3940
|
+
case '$like':
|
|
3941
|
+
whereParts.push(\`"\${key}" LIKE $\${paramIndex}\`);
|
|
3942
|
+
whereParams.push(opValue);
|
|
3943
|
+
paramIndex++;
|
|
3944
|
+
break;
|
|
3945
|
+
case '$ilike':
|
|
3946
|
+
whereParts.push(\`"\${key}" ILIKE $\${paramIndex}\`);
|
|
3947
|
+
whereParams.push(opValue);
|
|
3948
|
+
paramIndex++;
|
|
3949
|
+
break;
|
|
3950
|
+
case '$is':
|
|
3951
|
+
if (opValue === null) {
|
|
3952
|
+
whereParts.push(\`"\${key}" IS NULL\`);
|
|
3953
|
+
}
|
|
3954
|
+
break;
|
|
3955
|
+
case '$isNot':
|
|
3956
|
+
if (opValue === null) {
|
|
3957
|
+
whereParts.push(\`"\${key}" IS NOT NULL\`);
|
|
3958
|
+
}
|
|
3959
|
+
break;
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
} else if (value === null) {
|
|
3963
|
+
// Direct null value
|
|
3964
|
+
whereParts.push(\`"\${key}" IS NULL\`);
|
|
3679
3965
|
} else {
|
|
3966
|
+
// Direct value (simple equality)
|
|
3680
3967
|
whereParts.push(\`"\${key}" = $\${paramIndex}\`);
|
|
3681
3968
|
whereParams.push(value);
|
|
3682
3969
|
paramIndex++;
|
|
@@ -4612,6 +4899,7 @@ async function generate(configPath) {
|
|
|
4612
4899
|
files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
|
|
4613
4900
|
files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
|
|
4614
4901
|
files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
|
|
4902
|
+
files.push({ path: join(clientDir, "where-types.ts"), content: emitWhereTypes() });
|
|
4615
4903
|
files.push({
|
|
4616
4904
|
path: join(serverDir, "include-builder.ts"),
|
|
4617
4905
|
content: emitIncludeBuilder(graph, cfg.includeMethodsDepth || 2)
|
package/dist/index.js
CHANGED
|
@@ -1169,6 +1169,140 @@ 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("**Note:** The WHERE clause types are fully type-safe. TypeScript will only allow operators that are valid for each field type.");
|
|
1305
|
+
lines.push("");
|
|
1172
1306
|
lines.push("## Resources");
|
|
1173
1307
|
lines.push("");
|
|
1174
1308
|
for (const resource of contract.resources) {
|
|
@@ -1850,7 +1984,7 @@ function emitClient(table, graph, opts, model) {
|
|
|
1850
1984
|
let includeMethodsCode = "";
|
|
1851
1985
|
for (const method of includeMethods) {
|
|
1852
1986
|
const isGetByPk = method.name.startsWith("getByPk");
|
|
1853
|
-
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?:
|
|
1987
|
+
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: Where<Select${Type}>; orderBy?: string; order?: "asc" | "desc"; }, "include">`;
|
|
1854
1988
|
if (isGetByPk) {
|
|
1855
1989
|
const pkWhere = hasCompositePk ? `{ ${safePk.map((col) => `${col}: pk.${col}`).join(", ")} }` : `{ ${safePk[0] || "id"}: pk }`;
|
|
1856
1990
|
const baseReturnType = method.returnType.replace(" | null", "");
|
|
@@ -1874,6 +2008,7 @@ function emitClient(table, graph, opts, model) {
|
|
|
1874
2008
|
}
|
|
1875
2009
|
return `/* Generated. Do not edit. */
|
|
1876
2010
|
import { BaseClient } from "./base-client${ext}";
|
|
2011
|
+
import type { Where } from "./where-types${ext}";
|
|
1877
2012
|
${typeImports}
|
|
1878
2013
|
${otherTableImports.join(`
|
|
1879
2014
|
`)}
|
|
@@ -1893,10 +2028,11 @@ export class ${Type}Client extends BaseClient {
|
|
|
1893
2028
|
return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
|
1894
2029
|
}
|
|
1895
2030
|
|
|
1896
|
-
async list(params?: {
|
|
1897
|
-
|
|
2031
|
+
async list(params?: {
|
|
2032
|
+
include?: any;
|
|
2033
|
+
limit?: number;
|
|
1898
2034
|
offset?: number;
|
|
1899
|
-
where?:
|
|
2035
|
+
where?: Where<Select${Type}>;
|
|
1900
2036
|
orderBy?: string;
|
|
1901
2037
|
order?: "asc" | "desc";
|
|
1902
2038
|
}): Promise<Select${Type}[]> {
|
|
@@ -1958,6 +2094,17 @@ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${
|
|
|
1958
2094
|
out += `export { BaseClient } from "./base-client${ext}";
|
|
1959
2095
|
`;
|
|
1960
2096
|
out += `
|
|
2097
|
+
// Include specification types for custom queries
|
|
2098
|
+
`;
|
|
2099
|
+
out += `export type {
|
|
2100
|
+
`;
|
|
2101
|
+
for (const t of tables) {
|
|
2102
|
+
out += ` ${pascal(t.name)}IncludeSpec,
|
|
2103
|
+
`;
|
|
2104
|
+
}
|
|
2105
|
+
out += `} from "./include-spec${ext}";
|
|
2106
|
+
`;
|
|
2107
|
+
out += `
|
|
1961
2108
|
// Zod schemas for form validation
|
|
1962
2109
|
`;
|
|
1963
2110
|
for (const t of tables) {
|
|
@@ -2495,6 +2642,54 @@ export function safe<T extends (c: any) => any>(handler: T) {
|
|
|
2495
2642
|
`;
|
|
2496
2643
|
}
|
|
2497
2644
|
|
|
2645
|
+
// src/emit-where-types.ts
|
|
2646
|
+
function emitWhereTypes() {
|
|
2647
|
+
return `/* Generated. Do not edit. */
|
|
2648
|
+
|
|
2649
|
+
/**
|
|
2650
|
+
* WHERE clause operators for filtering
|
|
2651
|
+
*/
|
|
2652
|
+
export type WhereOperator<T> = {
|
|
2653
|
+
/** Equal to */
|
|
2654
|
+
$eq?: T;
|
|
2655
|
+
/** Not equal to */
|
|
2656
|
+
$ne?: T;
|
|
2657
|
+
/** Greater than */
|
|
2658
|
+
$gt?: T;
|
|
2659
|
+
/** Greater than or equal to */
|
|
2660
|
+
$gte?: T;
|
|
2661
|
+
/** Less than */
|
|
2662
|
+
$lt?: T;
|
|
2663
|
+
/** Less than or equal to */
|
|
2664
|
+
$lte?: T;
|
|
2665
|
+
/** In array */
|
|
2666
|
+
$in?: T[];
|
|
2667
|
+
/** Not in array */
|
|
2668
|
+
$nin?: T[];
|
|
2669
|
+
/** LIKE pattern match (strings only) */
|
|
2670
|
+
$like?: T extends string ? string : never;
|
|
2671
|
+
/** Case-insensitive LIKE (strings only) */
|
|
2672
|
+
$ilike?: T extends string ? string : never;
|
|
2673
|
+
/** IS NULL */
|
|
2674
|
+
$is?: null;
|
|
2675
|
+
/** IS NOT NULL */
|
|
2676
|
+
$isNot?: null;
|
|
2677
|
+
};
|
|
2678
|
+
|
|
2679
|
+
/**
|
|
2680
|
+
* WHERE condition - can be a direct value or an operator object
|
|
2681
|
+
*/
|
|
2682
|
+
export type WhereCondition<T> = T | WhereOperator<T>;
|
|
2683
|
+
|
|
2684
|
+
/**
|
|
2685
|
+
* WHERE clause type for a given table type
|
|
2686
|
+
*/
|
|
2687
|
+
export type Where<T> = {
|
|
2688
|
+
[K in keyof T]?: WhereCondition<T[K]>;
|
|
2689
|
+
};
|
|
2690
|
+
`;
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2498
2693
|
// src/emit-auth.ts
|
|
2499
2694
|
function emitAuth(cfgAuth) {
|
|
2500
2695
|
const strategy = cfgAuth?.strategy ?? "none";
|
|
@@ -2927,12 +3122,88 @@ export async function listRecords(
|
|
|
2927
3122
|
// Add user-provided where conditions
|
|
2928
3123
|
if (whereClause && typeof whereClause === 'object') {
|
|
2929
3124
|
for (const [key, value] of Object.entries(whereClause)) {
|
|
2930
|
-
if (value ===
|
|
2931
|
-
whereParts.push(\`"\${key}" IS NULL\`);
|
|
2932
|
-
} else if (value === undefined) {
|
|
3125
|
+
if (value === undefined) {
|
|
2933
3126
|
// Skip undefined values
|
|
2934
3127
|
continue;
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
// Handle operator objects like { $gt: 5, $like: "%test%" }
|
|
3131
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
3132
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
3133
|
+
if (opValue === undefined) continue;
|
|
3134
|
+
|
|
3135
|
+
switch (op) {
|
|
3136
|
+
case '$eq':
|
|
3137
|
+
whereParts.push(\`"\${key}" = $\${paramIndex}\`);
|
|
3138
|
+
whereParams.push(opValue);
|
|
3139
|
+
paramIndex++;
|
|
3140
|
+
break;
|
|
3141
|
+
case '$ne':
|
|
3142
|
+
whereParts.push(\`"\${key}" != $\${paramIndex}\`);
|
|
3143
|
+
whereParams.push(opValue);
|
|
3144
|
+
paramIndex++;
|
|
3145
|
+
break;
|
|
3146
|
+
case '$gt':
|
|
3147
|
+
whereParts.push(\`"\${key}" > $\${paramIndex}\`);
|
|
3148
|
+
whereParams.push(opValue);
|
|
3149
|
+
paramIndex++;
|
|
3150
|
+
break;
|
|
3151
|
+
case '$gte':
|
|
3152
|
+
whereParts.push(\`"\${key}" >= $\${paramIndex}\`);
|
|
3153
|
+
whereParams.push(opValue);
|
|
3154
|
+
paramIndex++;
|
|
3155
|
+
break;
|
|
3156
|
+
case '$lt':
|
|
3157
|
+
whereParts.push(\`"\${key}" < $\${paramIndex}\`);
|
|
3158
|
+
whereParams.push(opValue);
|
|
3159
|
+
paramIndex++;
|
|
3160
|
+
break;
|
|
3161
|
+
case '$lte':
|
|
3162
|
+
whereParts.push(\`"\${key}" <= $\${paramIndex}\`);
|
|
3163
|
+
whereParams.push(opValue);
|
|
3164
|
+
paramIndex++;
|
|
3165
|
+
break;
|
|
3166
|
+
case '$in':
|
|
3167
|
+
if (Array.isArray(opValue) && opValue.length > 0) {
|
|
3168
|
+
whereParts.push(\`"\${key}" = ANY($\${paramIndex})\`);
|
|
3169
|
+
whereParams.push(opValue);
|
|
3170
|
+
paramIndex++;
|
|
3171
|
+
}
|
|
3172
|
+
break;
|
|
3173
|
+
case '$nin':
|
|
3174
|
+
if (Array.isArray(opValue) && opValue.length > 0) {
|
|
3175
|
+
whereParts.push(\`"\${key}" != ALL($\${paramIndex})\`);
|
|
3176
|
+
whereParams.push(opValue);
|
|
3177
|
+
paramIndex++;
|
|
3178
|
+
}
|
|
3179
|
+
break;
|
|
3180
|
+
case '$like':
|
|
3181
|
+
whereParts.push(\`"\${key}" LIKE $\${paramIndex}\`);
|
|
3182
|
+
whereParams.push(opValue);
|
|
3183
|
+
paramIndex++;
|
|
3184
|
+
break;
|
|
3185
|
+
case '$ilike':
|
|
3186
|
+
whereParts.push(\`"\${key}" ILIKE $\${paramIndex}\`);
|
|
3187
|
+
whereParams.push(opValue);
|
|
3188
|
+
paramIndex++;
|
|
3189
|
+
break;
|
|
3190
|
+
case '$is':
|
|
3191
|
+
if (opValue === null) {
|
|
3192
|
+
whereParts.push(\`"\${key}" IS NULL\`);
|
|
3193
|
+
}
|
|
3194
|
+
break;
|
|
3195
|
+
case '$isNot':
|
|
3196
|
+
if (opValue === null) {
|
|
3197
|
+
whereParts.push(\`"\${key}" IS NOT NULL\`);
|
|
3198
|
+
}
|
|
3199
|
+
break;
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
} else if (value === null) {
|
|
3203
|
+
// Direct null value
|
|
3204
|
+
whereParts.push(\`"\${key}" IS NULL\`);
|
|
2935
3205
|
} else {
|
|
3206
|
+
// Direct value (simple equality)
|
|
2936
3207
|
whereParts.push(\`"\${key}" = $\${paramIndex}\`);
|
|
2937
3208
|
whereParams.push(value);
|
|
2938
3209
|
paramIndex++;
|
|
@@ -3868,6 +4139,7 @@ async function generate(configPath) {
|
|
|
3868
4139
|
files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
|
|
3869
4140
|
files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
|
|
3870
4141
|
files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
|
|
4142
|
+
files.push({ path: join(clientDir, "where-types.ts"), content: emitWhereTypes() });
|
|
3871
4143
|
files.push({
|
|
3872
4144
|
path: join(serverDir, "include-builder.ts"),
|
|
3873
4145
|
content: emitIncludeBuilder(graph, cfg.includeMethodsDepth || 2)
|