alepha 0.14.0 → 0.14.2
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 +3 -3
- package/dist/api/audits/index.d.ts +80 -1
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +80 -1
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +236 -157
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +21 -1
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +451 -4
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +252 -249
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +4 -0
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +128 -128
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cli/index.d.ts +304 -115
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +650 -531
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +210 -13
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +306 -69
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +7 -6
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +7 -6
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.browser.js +26 -5
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.d.ts +294 -215
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +522 -523
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/redis/index.js +2 -4
- package/dist/queue/redis/index.js.map +1 -1
- package/dist/redis/index.d.ts +400 -29
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +412 -21
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.js.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +155 -155
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +0 -1
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/helmet/index.d.ts +4 -1
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/helmet/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/security/index.d.ts +9 -9
- package/dist/server/security/index.js.map +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/topic/redis/index.js +3 -3
- package/dist/topic/redis/index.js.map +1 -1
- package/dist/vite/index.js +9 -6
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +7 -7
- package/dist/websocket/index.js.map +1 -1
- package/package.json +3 -3
- package/src/api/users/index.ts +4 -0
- package/src/cli/apps/AlephaCli.ts +36 -14
- package/src/cli/apps/AlephaPackageBuilderCli.ts +5 -1
- package/src/cli/assets/appRouterTs.ts +1 -1
- package/src/cli/atoms/changelogOptions.ts +45 -0
- package/src/cli/commands/{ViteCommands.ts → build.ts} +4 -93
- package/src/cli/commands/changelog.ts +244 -0
- package/src/cli/commands/clean.ts +14 -0
- package/src/cli/commands/{DrizzleCommands.ts → db.ts} +37 -124
- package/src/cli/commands/deploy.ts +118 -0
- package/src/cli/commands/dev.ts +57 -0
- package/src/cli/commands/format.ts +17 -0
- package/src/cli/commands/{CoreCommands.ts → init.ts} +2 -40
- package/src/cli/commands/lint.ts +17 -0
- package/src/cli/commands/root.ts +32 -0
- package/src/cli/commands/run.ts +24 -0
- package/src/cli/commands/test.ts +42 -0
- package/src/cli/commands/typecheck.ts +19 -0
- package/src/cli/commands/{VerifyCommands.ts → verify.ts} +1 -13
- package/src/cli/defineConfig.ts +24 -0
- package/src/cli/index.ts +17 -5
- package/src/cli/services/AlephaCliUtils.ts +4 -21
- package/src/cli/services/GitMessageParser.ts +77 -0
- package/src/command/helpers/EnvUtils.ts +37 -0
- package/src/command/index.ts +3 -1
- package/src/command/primitives/$command.ts +172 -6
- package/src/command/providers/CliProvider.ts +424 -91
- package/src/core/Alepha.ts +8 -5
- package/src/file/providers/NodeFileSystemProvider.ts +3 -1
- package/src/orm/index.browser.ts +1 -1
- package/src/orm/index.ts +18 -10
- package/src/orm/interfaces/PgQueryWhere.ts +1 -26
- package/src/orm/providers/{PostgresTypeProvider.ts → DatabaseTypeProvider.ts} +25 -3
- package/src/orm/providers/drivers/BunPostgresProvider.ts +225 -0
- package/src/orm/providers/drivers/BunSqliteProvider.ts +180 -0
- package/src/orm/providers/drivers/DatabaseProvider.ts +25 -0
- package/src/orm/providers/drivers/NodePostgresProvider.ts +0 -25
- package/src/orm/services/QueryManager.ts +10 -125
- package/src/queue/redis/providers/RedisQueueProvider.ts +2 -7
- package/src/redis/index.ts +65 -3
- package/src/redis/providers/BunRedisProvider.ts +304 -0
- package/src/redis/providers/BunRedisSubscriberProvider.ts +94 -0
- package/src/redis/providers/NodeRedisProvider.ts +280 -0
- package/src/redis/providers/NodeRedisSubscriberProvider.ts +94 -0
- package/src/redis/providers/RedisProvider.ts +134 -140
- package/src/redis/providers/RedisSubscriberProvider.ts +58 -49
- package/src/server/core/providers/BunHttpServerProvider.ts +0 -3
- package/src/server/core/providers/ServerBodyParserProvider.ts +3 -1
- package/src/server/core/providers/ServerProvider.ts +7 -4
- package/src/server/multipart/providers/ServerMultipartProvider.ts +3 -1
- package/src/server/proxy/providers/ServerProxyProvider.ts +1 -1
- package/src/topic/redis/providers/RedisTopicProvider.ts +3 -3
- package/src/vite/tasks/buildServer.ts +1 -0
- package/src/cli/commands/BiomeCommands.ts +0 -29
- package/src/cli/commands/ChangelogCommands.ts +0 -389
- package/src/orm/services/PgJsonQueryManager.ts +0 -511
|
@@ -1,511 +0,0 @@
|
|
|
1
|
-
import type { TObject } from "alepha";
|
|
2
|
-
import { type SQL, sql } from "drizzle-orm";
|
|
3
|
-
import type { PgColumn } from "drizzle-orm/pg-core";
|
|
4
|
-
import type { FilterOperators } from "../interfaces/FilterOperators.ts";
|
|
5
|
-
import type { PgQueryWhere } from "../interfaces/PgQueryWhere.ts";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Manages JSONB query generation for nested object and array queries in PostgreSQL.
|
|
9
|
-
* This class handles complex nested queries using PostgreSQL's JSONB operators.
|
|
10
|
-
*/
|
|
11
|
-
export class PgJsonQueryManager {
|
|
12
|
-
/**
|
|
13
|
-
* Check if a query contains nested JSONB queries.
|
|
14
|
-
* A nested query is when the value is an object with operator keys.
|
|
15
|
-
*/
|
|
16
|
-
public hasNestedQuery(where: PgQueryWhere<TObject>): boolean {
|
|
17
|
-
for (const [key, value] of Object.entries(where)) {
|
|
18
|
-
// Skip logical operators
|
|
19
|
-
if (key === "and" || key === "or" || key === "not") {
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Check if value is an object with nested properties
|
|
24
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
25
|
-
// Check if it has operator keys or nested object keys
|
|
26
|
-
const keys = Object.keys(value);
|
|
27
|
-
const hasOperators = keys.some((k) =>
|
|
28
|
-
[
|
|
29
|
-
"eq",
|
|
30
|
-
"ne",
|
|
31
|
-
"gt",
|
|
32
|
-
"gte",
|
|
33
|
-
"lt",
|
|
34
|
-
"lte",
|
|
35
|
-
"like",
|
|
36
|
-
"ilike",
|
|
37
|
-
"isNull",
|
|
38
|
-
"isNotNull",
|
|
39
|
-
"inArray",
|
|
40
|
-
"notInArray",
|
|
41
|
-
].includes(k),
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
// If it doesn't have operators, it might be a nested query
|
|
45
|
-
if (!hasOperators && keys.length > 0) {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Build a JSONB query condition for nested object queries.
|
|
55
|
-
* Supports deep nesting like: { profile: { contact: { email: { eq: "test@example.com" } } } }
|
|
56
|
-
*
|
|
57
|
-
* @param column The JSONB column
|
|
58
|
-
* @param path The path to the nested property (e.g., ['profile', 'contact', 'email'])
|
|
59
|
-
* @param operator The filter operator (e.g., { eq: "test@example.com" })
|
|
60
|
-
* @param dialect Database dialect (postgresql or sqlite)
|
|
61
|
-
* @param columnSchema Optional schema of the JSON column for type inference
|
|
62
|
-
* @returns SQL condition
|
|
63
|
-
*/
|
|
64
|
-
public buildJsonbCondition(
|
|
65
|
-
column: PgColumn,
|
|
66
|
-
path: string[],
|
|
67
|
-
operator: FilterOperators<any>,
|
|
68
|
-
dialect: "postgresql" | "sqlite",
|
|
69
|
-
columnSchema?: any,
|
|
70
|
-
): SQL | undefined {
|
|
71
|
-
if (path.length === 0) {
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Check if operator is an array operator that needs JSONB (not text extraction)
|
|
76
|
-
const isArrayOperator =
|
|
77
|
-
operator.arrayContains !== undefined ||
|
|
78
|
-
operator.arrayContained !== undefined ||
|
|
79
|
-
operator.arrayOverlaps !== undefined;
|
|
80
|
-
|
|
81
|
-
let jsonValue: SQL;
|
|
82
|
-
|
|
83
|
-
if (dialect === "sqlite") {
|
|
84
|
-
// SQLite: json_extract(column, '$.path.to.field')
|
|
85
|
-
const pathStr = `$.${path.join(".")}`;
|
|
86
|
-
jsonValue = sql`json_extract(${column}, ${pathStr})`;
|
|
87
|
-
} else {
|
|
88
|
-
// PostgreSQL: Build the JSON path
|
|
89
|
-
let jsonPath = sql`${column}`;
|
|
90
|
-
|
|
91
|
-
// Navigate through all path elements except the last
|
|
92
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
93
|
-
jsonPath = sql`${jsonPath}->${path[i]}`;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// For the last element:
|
|
97
|
-
// - Use -> to keep as JSONB for array operators
|
|
98
|
-
// - Use ->> to extract as text for other operators
|
|
99
|
-
const lastPath = path[path.length - 1];
|
|
100
|
-
if (isArrayOperator) {
|
|
101
|
-
jsonValue = sql`${jsonPath}->${lastPath}`;
|
|
102
|
-
} else {
|
|
103
|
-
jsonValue = sql`${jsonPath}->>${lastPath}`;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Get field type for smart casting
|
|
108
|
-
const fieldType = columnSchema
|
|
109
|
-
? this.getFieldType(columnSchema, path)
|
|
110
|
-
: undefined;
|
|
111
|
-
|
|
112
|
-
// Apply the operator
|
|
113
|
-
return this.applyOperatorToJsonValue(
|
|
114
|
-
jsonValue,
|
|
115
|
-
operator,
|
|
116
|
-
dialect,
|
|
117
|
-
fieldType,
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Build JSONB array query conditions.
|
|
123
|
-
* Supports queries like: { addresses: { city: { eq: "Wonderland" } } }
|
|
124
|
-
* which translates to: EXISTS (SELECT 1 FROM jsonb_array_elements(addresses) elem WHERE elem->>'city' = 'Wonderland')
|
|
125
|
-
*
|
|
126
|
-
* @param dialect Database dialect (postgresql or sqlite)
|
|
127
|
-
* Note: SQLite array queries are not yet supported
|
|
128
|
-
*/
|
|
129
|
-
public buildJsonbArrayCondition(
|
|
130
|
-
column: PgColumn,
|
|
131
|
-
path: string[],
|
|
132
|
-
arrayPath: string,
|
|
133
|
-
operator: FilterOperators<any>,
|
|
134
|
-
dialect: "postgresql" | "sqlite",
|
|
135
|
-
): SQL | undefined {
|
|
136
|
-
if (dialect === "sqlite") {
|
|
137
|
-
throw new Error(
|
|
138
|
-
"Array queries in JSON columns are not yet supported for SQLite. " +
|
|
139
|
-
"Please use PostgreSQL for complex JSON array queries, or restructure your data.",
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (path.length === 0) {
|
|
144
|
-
return undefined;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Build the base JSONB path to the array
|
|
148
|
-
let jsonPath = sql`${column}`;
|
|
149
|
-
if (arrayPath) {
|
|
150
|
-
jsonPath = sql`${jsonPath}->${arrayPath}`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Build the condition for array elements
|
|
154
|
-
const lastPath = path[0];
|
|
155
|
-
const elemCondition = sql`elem->>${lastPath}`;
|
|
156
|
-
const condition = this.applyOperatorToJsonValue(
|
|
157
|
-
elemCondition,
|
|
158
|
-
operator,
|
|
159
|
-
dialect,
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
if (!condition) {
|
|
163
|
-
return undefined;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Wrap in EXISTS with jsonb_array_elements
|
|
167
|
-
return sql`EXISTS (SELECT 1 FROM jsonb_array_elements(${jsonPath}) AS elem WHERE ${condition})`;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Apply a filter operator to a JSONB value.
|
|
172
|
-
* @param dialect Database dialect for appropriate casting syntax
|
|
173
|
-
* @param fieldType Optional field type from schema for smart casting
|
|
174
|
-
*/
|
|
175
|
-
private applyOperatorToJsonValue(
|
|
176
|
-
jsonValue: SQL,
|
|
177
|
-
operator: FilterOperators<any>,
|
|
178
|
-
dialect: "postgresql" | "sqlite",
|
|
179
|
-
fieldType?: string,
|
|
180
|
-
): SQL | undefined {
|
|
181
|
-
// Helper to cast for numeric comparisons based on dialect and field type
|
|
182
|
-
const castForNumeric = (value: SQL): SQL => {
|
|
183
|
-
if (dialect === "sqlite") {
|
|
184
|
-
// Use INTEGER for int types, REAL for number types
|
|
185
|
-
if (fieldType === "integer" || fieldType === "int") {
|
|
186
|
-
return sql`CAST(${value} AS INTEGER)`;
|
|
187
|
-
}
|
|
188
|
-
// Default to REAL for numeric comparisons
|
|
189
|
-
return sql`CAST(${value} AS REAL)`;
|
|
190
|
-
}
|
|
191
|
-
// PostgreSQL
|
|
192
|
-
return sql`(${value})::numeric`;
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
if (typeof operator !== "object") {
|
|
196
|
-
// Direct value comparison
|
|
197
|
-
return sql`${jsonValue} = ${operator}`;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const conditions: SQL[] = [];
|
|
201
|
-
|
|
202
|
-
if (operator.eq !== undefined) {
|
|
203
|
-
conditions.push(sql`${jsonValue} = ${operator.eq}`);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (operator.ne !== undefined) {
|
|
207
|
-
conditions.push(sql`${jsonValue} != ${operator.ne}`);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (operator.gt !== undefined) {
|
|
211
|
-
// Cast to numeric for comparison
|
|
212
|
-
conditions.push(sql`${castForNumeric(jsonValue)} > ${operator.gt}`);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (operator.gte !== undefined) {
|
|
216
|
-
conditions.push(sql`${castForNumeric(jsonValue)} >= ${operator.gte}`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (operator.lt !== undefined) {
|
|
220
|
-
conditions.push(sql`${castForNumeric(jsonValue)} < ${operator.lt}`);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (operator.lte !== undefined) {
|
|
224
|
-
conditions.push(sql`${castForNumeric(jsonValue)} <= ${operator.lte}`);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (operator.like !== undefined) {
|
|
228
|
-
conditions.push(sql`${jsonValue} LIKE ${operator.like}`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (operator.ilike !== undefined) {
|
|
232
|
-
// SQLite: LIKE is case-insensitive by default, so use LIKE
|
|
233
|
-
// PostgreSQL: Use ILIKE
|
|
234
|
-
if (dialect === "sqlite") {
|
|
235
|
-
conditions.push(sql`${jsonValue} LIKE ${operator.ilike}`);
|
|
236
|
-
} else {
|
|
237
|
-
conditions.push(sql`${jsonValue} ILIKE ${operator.ilike}`);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (operator.notLike !== undefined) {
|
|
242
|
-
conditions.push(sql`${jsonValue} NOT LIKE ${operator.notLike}`);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (operator.notIlike !== undefined) {
|
|
246
|
-
// SQLite: LIKE is case-insensitive by default, so use NOT LIKE
|
|
247
|
-
// PostgreSQL: Use NOT ILIKE
|
|
248
|
-
if (dialect === "sqlite") {
|
|
249
|
-
conditions.push(sql`${jsonValue} NOT LIKE ${operator.notIlike}`);
|
|
250
|
-
} else {
|
|
251
|
-
conditions.push(sql`${jsonValue} NOT ILIKE ${operator.notIlike}`);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (operator.isNull !== undefined) {
|
|
256
|
-
conditions.push(sql`${jsonValue} IS NULL`);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (operator.isNotNull !== undefined) {
|
|
260
|
-
conditions.push(sql`${jsonValue} IS NOT NULL`);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (operator.inArray !== undefined && Array.isArray(operator.inArray)) {
|
|
264
|
-
conditions.push(
|
|
265
|
-
sql`${jsonValue} IN (${sql.join(
|
|
266
|
-
operator.inArray.map((v) => sql`${v}`),
|
|
267
|
-
sql`, `,
|
|
268
|
-
)})`,
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (
|
|
273
|
-
operator.notInArray !== undefined &&
|
|
274
|
-
Array.isArray(operator.notInArray)
|
|
275
|
-
) {
|
|
276
|
-
conditions.push(
|
|
277
|
-
sql`${jsonValue} NOT IN (${sql.join(
|
|
278
|
-
operator.notInArray.map((v) => sql`${v}`),
|
|
279
|
-
sql`, `,
|
|
280
|
-
)})`,
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Handle array operators for JSONB arrays
|
|
285
|
-
// When these operators are used, jsonValue will be a JSONB value (not text extracted)
|
|
286
|
-
if (operator.arrayContains !== undefined) {
|
|
287
|
-
if (dialect === "postgresql") {
|
|
288
|
-
// PostgreSQL @> operator: checks if left JSONB contains right JSONB
|
|
289
|
-
// JSON.stringify ensures the value is properly escaped as a JSON string
|
|
290
|
-
const jsonArray = JSON.stringify(
|
|
291
|
-
Array.isArray(operator.arrayContains)
|
|
292
|
-
? operator.arrayContains
|
|
293
|
-
: [operator.arrayContains],
|
|
294
|
-
);
|
|
295
|
-
// The value is safely parameterized via sql template, preventing SQL injection
|
|
296
|
-
conditions.push(sql`${jsonValue} @> ${jsonArray}::jsonb`);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (operator.arrayContained !== undefined) {
|
|
301
|
-
if (dialect === "postgresql") {
|
|
302
|
-
// PostgreSQL <@ operator: checks if left JSONB is contained in right JSONB
|
|
303
|
-
const jsonArray = JSON.stringify(
|
|
304
|
-
Array.isArray(operator.arrayContained)
|
|
305
|
-
? operator.arrayContained
|
|
306
|
-
: [operator.arrayContained],
|
|
307
|
-
);
|
|
308
|
-
conditions.push(sql`${jsonValue} <@ ${jsonArray}::jsonb`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (operator.arrayOverlaps !== undefined) {
|
|
313
|
-
if (dialect === "postgresql") {
|
|
314
|
-
// PostgreSQL ?| operator: checks if any of the array elements exist as top-level keys
|
|
315
|
-
// Note: For JSONB arrays, we need to use a different approach
|
|
316
|
-
// Convert the JSONB array to text array for the ?| operator
|
|
317
|
-
const values = Array.isArray(operator.arrayOverlaps)
|
|
318
|
-
? operator.arrayOverlaps
|
|
319
|
-
: [operator.arrayOverlaps];
|
|
320
|
-
|
|
321
|
-
// Build an OR condition to check if any value exists in the array
|
|
322
|
-
// This is safer than using ?| with dynamic values
|
|
323
|
-
const overlapConditions = values.map((val) => {
|
|
324
|
-
const jsonVal = JSON.stringify(val);
|
|
325
|
-
return sql`${jsonValue} @> ${jsonVal}::jsonb`;
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
if (overlapConditions.length > 0) {
|
|
329
|
-
conditions.push(sql`(${sql.join(overlapConditions, sql` OR `)})`);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (conditions.length === 0) {
|
|
335
|
-
return undefined;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (conditions.length === 1) {
|
|
339
|
-
return conditions[0];
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Multiple conditions - AND them together
|
|
343
|
-
return sql.join(conditions, sql` AND `);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Parse a nested query object and extract the path and operator.
|
|
348
|
-
* For example: { profile: { contact: { email: { eq: "test@example.com" } } } }
|
|
349
|
-
* Returns: { path: ['profile', 'contact', 'email'], operator: { eq: "test@example.com" } }
|
|
350
|
-
*/
|
|
351
|
-
public parseNestedQuery(
|
|
352
|
-
nestedQuery: any,
|
|
353
|
-
currentPath: string[] = [],
|
|
354
|
-
): Array<{ path: string[]; operator: FilterOperators<any> }> {
|
|
355
|
-
const results: Array<{ path: string[]; operator: FilterOperators<any> }> =
|
|
356
|
-
[];
|
|
357
|
-
|
|
358
|
-
for (const [key, value] of Object.entries(nestedQuery)) {
|
|
359
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
360
|
-
// Check if this is an operator object
|
|
361
|
-
const keys = Object.keys(value);
|
|
362
|
-
const hasOperators = keys.some((k) =>
|
|
363
|
-
[
|
|
364
|
-
"eq",
|
|
365
|
-
"ne",
|
|
366
|
-
"gt",
|
|
367
|
-
"gte",
|
|
368
|
-
"lt",
|
|
369
|
-
"lte",
|
|
370
|
-
"like",
|
|
371
|
-
"ilike",
|
|
372
|
-
"notLike",
|
|
373
|
-
"notIlike",
|
|
374
|
-
"isNull",
|
|
375
|
-
"isNotNull",
|
|
376
|
-
"inArray",
|
|
377
|
-
"notInArray",
|
|
378
|
-
"arrayContains",
|
|
379
|
-
"arrayContained",
|
|
380
|
-
"arrayOverlaps",
|
|
381
|
-
].includes(k),
|
|
382
|
-
);
|
|
383
|
-
|
|
384
|
-
if (hasOperators) {
|
|
385
|
-
// This is an operator, add to results
|
|
386
|
-
results.push({
|
|
387
|
-
path: [...currentPath, key],
|
|
388
|
-
operator: value as FilterOperators<any>,
|
|
389
|
-
});
|
|
390
|
-
} else {
|
|
391
|
-
// This is a nested object, recurse
|
|
392
|
-
const nestedResults = this.parseNestedQuery(value, [
|
|
393
|
-
...currentPath,
|
|
394
|
-
key,
|
|
395
|
-
]);
|
|
396
|
-
results.push(...nestedResults);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return results;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Determine if a property is a JSONB column based on the schema.
|
|
406
|
-
* A column is JSONB if it's defined as an object or array in the TypeBox schema.
|
|
407
|
-
*/
|
|
408
|
-
public isJsonbColumn(schema: TObject, columnName: string): boolean {
|
|
409
|
-
const property = schema.properties[columnName];
|
|
410
|
-
if (!property) {
|
|
411
|
-
return false;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Check if it's an object or array type
|
|
415
|
-
return (
|
|
416
|
-
(property as any).type === "object" || (property as any).type === "array"
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Check if an array property contains primitive types (string, number, boolean, etc.)
|
|
422
|
-
* rather than objects. Primitive arrays should use native Drizzle operators.
|
|
423
|
-
* @returns true if the array contains primitives, false if it contains objects
|
|
424
|
-
*/
|
|
425
|
-
public isPrimitiveArray(schema: TObject, columnName: string): boolean {
|
|
426
|
-
const property = schema.properties[columnName];
|
|
427
|
-
if (!property || (property as any).type !== "array") {
|
|
428
|
-
return false;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Check the items type
|
|
432
|
-
const items = (property as any).items;
|
|
433
|
-
if (!items) {
|
|
434
|
-
return false;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// If items is an object type, it's not a primitive array
|
|
438
|
-
// Primitive types are: string, number, integer, boolean, null
|
|
439
|
-
const itemType = items.type;
|
|
440
|
-
return (
|
|
441
|
-
itemType === "string" ||
|
|
442
|
-
itemType === "number" ||
|
|
443
|
-
itemType === "integer" ||
|
|
444
|
-
itemType === "boolean" ||
|
|
445
|
-
itemType === "null"
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Get the type of a field by navigating through a schema path.
|
|
451
|
-
* Used for smart type casting in SQL queries.
|
|
452
|
-
*
|
|
453
|
-
* @param columnSchema The schema of the JSON column (e.g., t.object({ age: t.integer() }))
|
|
454
|
-
* @param path The path to navigate (e.g., ['contact', 'email'])
|
|
455
|
-
* @returns The type string (e.g., 'integer', 'number', 'string') or undefined if not found
|
|
456
|
-
*/
|
|
457
|
-
private getFieldType(columnSchema: any, path: string[]): string | undefined {
|
|
458
|
-
let current = columnSchema;
|
|
459
|
-
|
|
460
|
-
for (const segment of path) {
|
|
461
|
-
// Navigate into object properties
|
|
462
|
-
if (current.type === "object" && current.properties) {
|
|
463
|
-
current = current.properties[segment];
|
|
464
|
-
if (!current) {
|
|
465
|
-
return undefined;
|
|
466
|
-
}
|
|
467
|
-
} else {
|
|
468
|
-
// Path segment doesn't exist or current is not an object
|
|
469
|
-
return undefined;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
return current.type;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Check if a nested path points to an array property.
|
|
478
|
-
*/
|
|
479
|
-
public isArrayProperty(schema: TObject, path: string[]): boolean {
|
|
480
|
-
if (path.length === 0) {
|
|
481
|
-
return false;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
let currentSchema: any = schema.properties[path[0]];
|
|
485
|
-
if (!currentSchema) {
|
|
486
|
-
return false;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// If first element is an array, return true
|
|
490
|
-
if (currentSchema.type === "array") {
|
|
491
|
-
return true;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// Navigate through nested objects
|
|
495
|
-
for (let i = 1; i < path.length; i++) {
|
|
496
|
-
if (currentSchema.type === "object" && currentSchema.properties) {
|
|
497
|
-
currentSchema = currentSchema.properties[path[i]];
|
|
498
|
-
if (!currentSchema) {
|
|
499
|
-
return false;
|
|
500
|
-
}
|
|
501
|
-
if (currentSchema.type === "array") {
|
|
502
|
-
return true;
|
|
503
|
-
}
|
|
504
|
-
} else {
|
|
505
|
-
return false;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
return false;
|
|
510
|
-
}
|
|
511
|
-
}
|