@xylex-group/athena 1.3.0 → 1.4.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/README.md +77 -10
- package/dist/{errors-DHmpYG46.d.cts → errors-DQerAaXC.d.cts} +26 -1
- package/dist/{errors-DHmpYG46.d.ts → errors-DQerAaXC.d.ts} +26 -1
- package/dist/index.cjs +214 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +214 -5
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +6 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +6 -1
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# athena-js
|
|
2
2
|
|
|
3
|
-
current version: `1.
|
|
3
|
+
current version: `1.4.1`
|
|
4
4
|
`@xylex-group/athena` is a database driver and API gateway SDK that lets you interact with SQL backends over HTTP through a fluent builder API. It ships a typed query builder for Node.js / server environments and a React hook for client-side use.
|
|
5
5
|
|
|
6
6
|
## Install
|
|
@@ -153,10 +153,12 @@ Filters accumulate on the builder and are sent together when the query executes.
|
|
|
153
153
|
|
|
154
154
|
```ts
|
|
155
155
|
const { data } = await athena
|
|
156
|
-
.from("characters")
|
|
157
|
-
.select("id, name")
|
|
158
|
-
.eq("active", true) // column = value
|
|
159
|
-
.
|
|
156
|
+
.from("characters")
|
|
157
|
+
.select("id, name")
|
|
158
|
+
.eq("active", true) // column = value
|
|
159
|
+
.eqUuid("session_id", "550e8400-e29b-41d4-a716-446655440000") // explicit UUID cast
|
|
160
|
+
.eqCast("session_id", "550e8400-e29b-41d4-a716-446655440000", "uuid") // explicit cast type
|
|
161
|
+
.neq("role", "guest") // column != value
|
|
160
162
|
.gt("level", 5) // column > value
|
|
161
163
|
.gte("score", 100) // column >= value
|
|
162
164
|
.lt("age", 30) // column < value
|
|
@@ -169,8 +171,10 @@ const { data } = await athena
|
|
|
169
171
|
.containedBy("tags", ["hero", "villain"]) // array is subset of value
|
|
170
172
|
.match({ role: "admin", active: true }) // multiple eq filters at once
|
|
171
173
|
.not("role", "eq", "banned") // NOT col op val
|
|
172
|
-
.or("status.eq.active,status.eq.pending"); // OR expression
|
|
173
|
-
```
|
|
174
|
+
.or("status.eq.active,status.eq.pending"); // OR expression
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`eq()` now auto-detects UUID-like values on identifier columns (for example `id`, `*_id`, `*uuid*`) and uses a safe typed comparison path so UUID columns no longer require app-side manual `::uuid` / `::text` casts.
|
|
174
178
|
|
|
175
179
|
Canonical style for reads is to call `.select(...)` first, then apply filters:
|
|
176
180
|
|
|
@@ -183,14 +187,73 @@ const { data } = await athena
|
|
|
183
187
|
|
|
184
188
|
### Pagination
|
|
185
189
|
|
|
190
|
+
Two styles, pick whichever matches your UI / backend. Both live on the shared `FilterChain`, so they work before or after `.select()`.
|
|
191
|
+
|
|
186
192
|
```ts
|
|
187
|
-
//
|
|
193
|
+
// 1. offset / limit — contiguous windows
|
|
188
194
|
const { data } = await athena.from("users").select().limit(25).offset(50);
|
|
189
195
|
|
|
190
|
-
// range shorthand
|
|
191
|
-
const { data } = await athena.from("users").select().range(0, 24);
|
|
196
|
+
// range shorthand: offset = from, limit = to - from + 1
|
|
197
|
+
const { data: firstTwentyFive } = await athena.from("users").select().range(0, 24);
|
|
198
|
+
|
|
199
|
+
// 2. page based — maps to current_page / page_size / total_pages
|
|
200
|
+
const { data: page2 } = await athena
|
|
201
|
+
.from("orders")
|
|
202
|
+
.select("id, total")
|
|
203
|
+
.currentPage(2)
|
|
204
|
+
.pageSize(25);
|
|
205
|
+
|
|
206
|
+
// .totalPages() is an optional hint some backends use in the response envelope
|
|
207
|
+
const { data: hinted } = await athena
|
|
208
|
+
.from("orders")
|
|
209
|
+
.select("id, total")
|
|
210
|
+
.currentPage(1)
|
|
211
|
+
.pageSize(25)
|
|
212
|
+
.totalPages(10);
|
|
192
213
|
```
|
|
193
214
|
|
|
215
|
+
| Method | Body field |
|
|
216
|
+
|--------|------------|
|
|
217
|
+
| `.limit(n)` | `limit` |
|
|
218
|
+
| `.offset(n)` | `offset` |
|
|
219
|
+
| `.range(from, to)` | `offset` + `limit` |
|
|
220
|
+
| `.currentPage(n)` | `current_page` |
|
|
221
|
+
| `.pageSize(n)` | `page_size` |
|
|
222
|
+
| `.totalPages(n)` | `total_pages` |
|
|
223
|
+
|
|
224
|
+
### Ordering
|
|
225
|
+
|
|
226
|
+
`.order(column, { ascending? })` is available on the table builder, select chain, update chain, and delete — before or after the operation terminator. It serializes to `sort_by: { field, direction }` on the gateway payload and defaults to ascending.
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
// descending + limit
|
|
230
|
+
// SELECT * FROM rsf_messages WHERE room_id = $1 ORDER BY created_at DESC LIMIT 100
|
|
231
|
+
const { data } = await athena
|
|
232
|
+
.from("rsf_messages")
|
|
233
|
+
.eq("room_id", roomId)
|
|
234
|
+
.select("*", { stripNulls: false })
|
|
235
|
+
.order("created_at", { ascending: false })
|
|
236
|
+
.limit(100);
|
|
237
|
+
|
|
238
|
+
// ascending (default) + page-based pagination
|
|
239
|
+
const { data: page } = await athena
|
|
240
|
+
.from("orders")
|
|
241
|
+
.select("id, total, created_at")
|
|
242
|
+
.order("created_at")
|
|
243
|
+
.currentPage(1)
|
|
244
|
+
.pageSize(25);
|
|
245
|
+
|
|
246
|
+
// combine with .single() to grab the newest / oldest row
|
|
247
|
+
const { data: latest } = await athena
|
|
248
|
+
.from("messages")
|
|
249
|
+
.eq("room_id", roomId)
|
|
250
|
+
.select("*")
|
|
251
|
+
.order("created_at", { ascending: false })
|
|
252
|
+
.single();
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Only the last `.order()` wins — the SDK does not support multi-column ordering on the table builder. Use `.rpc()` or `.query()` for that.
|
|
256
|
+
|
|
194
257
|
### Single row
|
|
195
258
|
|
|
196
259
|
```ts
|
|
@@ -417,6 +480,10 @@ const athena = createClient(
|
|
|
417
480
|
|
|
418
481
|
Per-call headers are merged with the client-level headers, with per-call values winning on conflict.
|
|
419
482
|
|
|
483
|
+
The SDK also sends a standard identification header on every request:
|
|
484
|
+
|
|
485
|
+
- `X-Athena-Sdk: xylex-group/athena <version>`
|
|
486
|
+
|
|
420
487
|
## TypeScript
|
|
421
488
|
|
|
422
489
|
The package is written in TypeScript and ships declaration files. Pass a row type to `.from()` for fully-typed builder methods and results:
|
|
@@ -8,14 +8,34 @@ type AthenaGatewayEndpointPath = '/gateway/fetch' | '/gateway/insert' | '/gatewa
|
|
|
8
8
|
type AthenaCountOption = 'exact' | 'planned' | 'estimated';
|
|
9
9
|
type AthenaConditionValue = string | number | boolean | null;
|
|
10
10
|
type AthenaConditionArrayValue = Array<AthenaConditionValue>;
|
|
11
|
+
type AthenaConditionCastType = string;
|
|
11
12
|
type AthenaConditionOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'is' | 'in' | 'contains' | 'containedBy' | 'not' | 'or';
|
|
12
13
|
interface AthenaGatewayCondition {
|
|
13
14
|
column?: string;
|
|
14
15
|
operator: AthenaConditionOperator;
|
|
15
16
|
value?: AthenaConditionValue | AthenaConditionArrayValue | string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional explicit cast for `value` (for example `"uuid"`).
|
|
19
|
+
* Older gateways ignore unknown fields; newer gateways may use this hint.
|
|
20
|
+
*/
|
|
21
|
+
value_cast?: AthenaConditionCastType;
|
|
22
|
+
/**
|
|
23
|
+
* Optional explicit cast for `column` (for example `"text"`).
|
|
24
|
+
* Used by SDK SQL fallback for typed comparisons.
|
|
25
|
+
*/
|
|
26
|
+
column_cast?: AthenaConditionCastType;
|
|
16
27
|
/** Back-compat shape expected by older gateway implementations */
|
|
17
28
|
eq_column?: string;
|
|
18
29
|
eq_value?: AthenaConditionValue | AthenaConditionArrayValue | string;
|
|
30
|
+
/** Optional cast hint aligned with legacy eq_* fields */
|
|
31
|
+
eq_value_cast?: AthenaConditionCastType;
|
|
32
|
+
/** Optional cast hint aligned with legacy eq_* fields */
|
|
33
|
+
eq_column_cast?: AthenaConditionCastType;
|
|
34
|
+
}
|
|
35
|
+
type AthenaSortDirection = 'ascending' | 'descending';
|
|
36
|
+
interface AthenaSortBy {
|
|
37
|
+
field: string;
|
|
38
|
+
direction: AthenaSortDirection;
|
|
19
39
|
}
|
|
20
40
|
interface AthenaFetchPayload {
|
|
21
41
|
view_name?: string;
|
|
@@ -33,6 +53,7 @@ interface AthenaFetchPayload {
|
|
|
33
53
|
aggregation_column?: string;
|
|
34
54
|
aggregation_strategy?: 'cumulative_sum';
|
|
35
55
|
aggregation_dedup?: boolean;
|
|
56
|
+
sort_by?: AthenaSortBy;
|
|
36
57
|
}
|
|
37
58
|
interface AthenaInsertPayload {
|
|
38
59
|
table_name: string;
|
|
@@ -49,6 +70,10 @@ interface AthenaDeletePayload {
|
|
|
49
70
|
resource_id?: string;
|
|
50
71
|
columns?: string[] | string;
|
|
51
72
|
conditions?: AthenaGatewayCondition[];
|
|
73
|
+
sort_by?: AthenaSortBy;
|
|
74
|
+
current_page?: number;
|
|
75
|
+
page_size?: number;
|
|
76
|
+
total_pages?: number;
|
|
52
77
|
}
|
|
53
78
|
interface AthenaUpdatePayload extends AthenaFetchPayload {
|
|
54
79
|
set?: Record<string, unknown>;
|
|
@@ -195,4 +220,4 @@ declare class AthenaGatewayError extends Error {
|
|
|
195
220
|
}
|
|
196
221
|
declare function isAthenaGatewayError(error: unknown): error is AthenaGatewayError;
|
|
197
222
|
|
|
198
|
-
export { type AthenaConditionValue as A, type BackendConfig as B, type BackendType as a, type
|
|
223
|
+
export { type AthenaConditionValue as A, type BackendConfig as B, type BackendType as a, type AthenaConditionCastType as b, type AthenaConditionArrayValue as c, type AthenaConditionOperator as d, type AthenaGatewayCallOptions as e, type AthenaGatewayErrorDetails as f, type AthenaRpcCallOptions as g, AthenaGatewayError as h, type AthenaGatewayErrorCode as i, type AthenaRpcFilter as j, type AthenaRpcFilterOperator as k, type AthenaRpcOrder as l, type AthenaRpcPayload as m, Backend as n, isAthenaGatewayError as o, type AthenaGatewayHookConfig as p, type AthenaGatewayHookResult as q, type AthenaDeletePayload as r, type AthenaFetchPayload as s, type AthenaGatewayResponse as t, type AthenaInsertPayload as u, type AthenaUpdatePayload as v };
|
|
@@ -8,14 +8,34 @@ type AthenaGatewayEndpointPath = '/gateway/fetch' | '/gateway/insert' | '/gatewa
|
|
|
8
8
|
type AthenaCountOption = 'exact' | 'planned' | 'estimated';
|
|
9
9
|
type AthenaConditionValue = string | number | boolean | null;
|
|
10
10
|
type AthenaConditionArrayValue = Array<AthenaConditionValue>;
|
|
11
|
+
type AthenaConditionCastType = string;
|
|
11
12
|
type AthenaConditionOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'is' | 'in' | 'contains' | 'containedBy' | 'not' | 'or';
|
|
12
13
|
interface AthenaGatewayCondition {
|
|
13
14
|
column?: string;
|
|
14
15
|
operator: AthenaConditionOperator;
|
|
15
16
|
value?: AthenaConditionValue | AthenaConditionArrayValue | string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional explicit cast for `value` (for example `"uuid"`).
|
|
19
|
+
* Older gateways ignore unknown fields; newer gateways may use this hint.
|
|
20
|
+
*/
|
|
21
|
+
value_cast?: AthenaConditionCastType;
|
|
22
|
+
/**
|
|
23
|
+
* Optional explicit cast for `column` (for example `"text"`).
|
|
24
|
+
* Used by SDK SQL fallback for typed comparisons.
|
|
25
|
+
*/
|
|
26
|
+
column_cast?: AthenaConditionCastType;
|
|
16
27
|
/** Back-compat shape expected by older gateway implementations */
|
|
17
28
|
eq_column?: string;
|
|
18
29
|
eq_value?: AthenaConditionValue | AthenaConditionArrayValue | string;
|
|
30
|
+
/** Optional cast hint aligned with legacy eq_* fields */
|
|
31
|
+
eq_value_cast?: AthenaConditionCastType;
|
|
32
|
+
/** Optional cast hint aligned with legacy eq_* fields */
|
|
33
|
+
eq_column_cast?: AthenaConditionCastType;
|
|
34
|
+
}
|
|
35
|
+
type AthenaSortDirection = 'ascending' | 'descending';
|
|
36
|
+
interface AthenaSortBy {
|
|
37
|
+
field: string;
|
|
38
|
+
direction: AthenaSortDirection;
|
|
19
39
|
}
|
|
20
40
|
interface AthenaFetchPayload {
|
|
21
41
|
view_name?: string;
|
|
@@ -33,6 +53,7 @@ interface AthenaFetchPayload {
|
|
|
33
53
|
aggregation_column?: string;
|
|
34
54
|
aggregation_strategy?: 'cumulative_sum';
|
|
35
55
|
aggregation_dedup?: boolean;
|
|
56
|
+
sort_by?: AthenaSortBy;
|
|
36
57
|
}
|
|
37
58
|
interface AthenaInsertPayload {
|
|
38
59
|
table_name: string;
|
|
@@ -49,6 +70,10 @@ interface AthenaDeletePayload {
|
|
|
49
70
|
resource_id?: string;
|
|
50
71
|
columns?: string[] | string;
|
|
51
72
|
conditions?: AthenaGatewayCondition[];
|
|
73
|
+
sort_by?: AthenaSortBy;
|
|
74
|
+
current_page?: number;
|
|
75
|
+
page_size?: number;
|
|
76
|
+
total_pages?: number;
|
|
52
77
|
}
|
|
53
78
|
interface AthenaUpdatePayload extends AthenaFetchPayload {
|
|
54
79
|
set?: Record<string, unknown>;
|
|
@@ -195,4 +220,4 @@ declare class AthenaGatewayError extends Error {
|
|
|
195
220
|
}
|
|
196
221
|
declare function isAthenaGatewayError(error: unknown): error is AthenaGatewayError;
|
|
197
222
|
|
|
198
|
-
export { type AthenaConditionValue as A, type BackendConfig as B, type BackendType as a, type
|
|
223
|
+
export { type AthenaConditionValue as A, type BackendConfig as B, type BackendType as a, type AthenaConditionCastType as b, type AthenaConditionArrayValue as c, type AthenaConditionOperator as d, type AthenaGatewayCallOptions as e, type AthenaGatewayErrorDetails as f, type AthenaRpcCallOptions as g, AthenaGatewayError as h, type AthenaGatewayErrorCode as i, type AthenaRpcFilter as j, type AthenaRpcFilterOperator as k, type AthenaRpcOrder as l, type AthenaRpcPayload as m, Backend as n, isAthenaGatewayError as o, type AthenaGatewayHookConfig as p, type AthenaGatewayHookResult as q, type AthenaDeletePayload as r, type AthenaFetchPayload as s, type AthenaGatewayResponse as t, type AthenaInsertPayload as u, type AthenaUpdatePayload as v };
|
package/dist/index.cjs
CHANGED
|
@@ -63,6 +63,10 @@ function isAthenaGatewayError(error) {
|
|
|
63
63
|
// src/gateway/client.ts
|
|
64
64
|
var DEFAULT_BASE_URL = "https://athena-db.com";
|
|
65
65
|
var DEFAULT_CLIENT = "railway_direct";
|
|
66
|
+
var FALLBACK_SDK_VERSION = "1.3.0";
|
|
67
|
+
var SDK_NAME = "xylex-group/athena";
|
|
68
|
+
var SDK_VERSION = typeof process !== "undefined" && process?.env?.npm_package_version ? process.env.npm_package_version : FALLBACK_SDK_VERSION;
|
|
69
|
+
var SDK_HEADER_VALUE = `${SDK_NAME} ${SDK_VERSION}`;
|
|
66
70
|
function parseResponseBody(rawText, contentType) {
|
|
67
71
|
if (!rawText) {
|
|
68
72
|
return { parsed: null, parseFailed: false };
|
|
@@ -201,7 +205,8 @@ function buildHeaders(config, options) {
|
|
|
201
205
|
const finalApiKey = options?.apiKey ?? config.apiKey;
|
|
202
206
|
const finalPublishEvent = options?.publishEvent ?? config.publishEvent;
|
|
203
207
|
const headers = {
|
|
204
|
-
"Content-Type": "application/json"
|
|
208
|
+
"Content-Type": "application/json",
|
|
209
|
+
"X-Athena-Sdk": SDK_HEADER_VALUE
|
|
205
210
|
};
|
|
206
211
|
if (options?.userId ?? config.userId) {
|
|
207
212
|
headers["X-User-Id"] = options?.userId ?? config.userId ?? "";
|
|
@@ -363,6 +368,8 @@ function createAthenaGatewayClient(config = {}) {
|
|
|
363
368
|
|
|
364
369
|
// src/client.ts
|
|
365
370
|
var DEFAULT_COLUMNS = "*";
|
|
371
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
372
|
+
var SAFE_CAST_PATTERN = /^[a-z_][a-z0-9_]*(?:\[\])?$/i;
|
|
366
373
|
function formatResult(response) {
|
|
367
374
|
const result = {
|
|
368
375
|
data: response.data ?? null,
|
|
@@ -443,14 +450,149 @@ function stringifyFilterValue(value) {
|
|
|
443
450
|
}
|
|
444
451
|
return String(value);
|
|
445
452
|
}
|
|
453
|
+
function isUuidString(value) {
|
|
454
|
+
return UUID_PATTERN.test(value.trim());
|
|
455
|
+
}
|
|
456
|
+
function isUuidIdentifierColumn(column) {
|
|
457
|
+
return column === "id" || /(?:^|_)uuid(?:_|$)/i.test(column) || /_id$/i.test(column);
|
|
458
|
+
}
|
|
459
|
+
function shouldUseUuidTextComparison(column, value) {
|
|
460
|
+
return typeof value === "string" && isUuidString(value) && isUuidIdentifierColumn(column);
|
|
461
|
+
}
|
|
462
|
+
function normalizeCast(cast) {
|
|
463
|
+
const normalized = cast.trim().toLowerCase();
|
|
464
|
+
if (!SAFE_CAST_PATTERN.test(normalized)) {
|
|
465
|
+
throw new Error(`Invalid cast type "${cast}"`);
|
|
466
|
+
}
|
|
467
|
+
return normalized;
|
|
468
|
+
}
|
|
469
|
+
function escapeSqlStringLiteral(value) {
|
|
470
|
+
return value.replace(/'/g, "''");
|
|
471
|
+
}
|
|
472
|
+
function quoteIdentifier(identifier) {
|
|
473
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
474
|
+
}
|
|
475
|
+
function quoteQualifiedIdentifier(identifier) {
|
|
476
|
+
return identifier.split(".").map((segment) => quoteIdentifier(segment)).join(".");
|
|
477
|
+
}
|
|
478
|
+
function toSqlLiteral(value) {
|
|
479
|
+
if (value === null) return "NULL";
|
|
480
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "NULL";
|
|
481
|
+
if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
|
|
482
|
+
return `'${escapeSqlStringLiteral(value)}'`;
|
|
483
|
+
}
|
|
484
|
+
function withCast(expression, cast) {
|
|
485
|
+
if (!cast) return expression;
|
|
486
|
+
return `${expression}::${normalizeCast(cast)}`;
|
|
487
|
+
}
|
|
488
|
+
function buildSelectColumnsClause(columns) {
|
|
489
|
+
if (Array.isArray(columns)) {
|
|
490
|
+
return columns.map((column) => quoteQualifiedIdentifier(column)).join(", ");
|
|
491
|
+
}
|
|
492
|
+
return columns;
|
|
493
|
+
}
|
|
494
|
+
function conditionToSqlClause(condition) {
|
|
495
|
+
if (!condition.column) return null;
|
|
496
|
+
const column = withCast(quoteQualifiedIdentifier(condition.column), condition.column_cast);
|
|
497
|
+
const value = condition.value;
|
|
498
|
+
const sqlOperator = {
|
|
499
|
+
eq: "=",
|
|
500
|
+
neq: "!=",
|
|
501
|
+
gt: ">",
|
|
502
|
+
gte: ">=",
|
|
503
|
+
lt: "<",
|
|
504
|
+
lte: "<=",
|
|
505
|
+
like: "LIKE",
|
|
506
|
+
ilike: "ILIKE"
|
|
507
|
+
};
|
|
508
|
+
switch (condition.operator) {
|
|
509
|
+
case "eq":
|
|
510
|
+
case "neq":
|
|
511
|
+
case "gt":
|
|
512
|
+
case "gte":
|
|
513
|
+
case "lt":
|
|
514
|
+
case "lte":
|
|
515
|
+
case "like":
|
|
516
|
+
case "ilike": {
|
|
517
|
+
if (Array.isArray(value) || value === void 0) return null;
|
|
518
|
+
const rhs = withCast(toSqlLiteral(value), condition.value_cast);
|
|
519
|
+
return `${column} ${sqlOperator[condition.operator]} ${rhs}`;
|
|
520
|
+
}
|
|
521
|
+
case "is": {
|
|
522
|
+
if (value === null) return `${column} IS NULL`;
|
|
523
|
+
if (value === true) return `${column} IS TRUE`;
|
|
524
|
+
if (value === false) return `${column} IS FALSE`;
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
case "in": {
|
|
528
|
+
if (!Array.isArray(value)) return null;
|
|
529
|
+
if (value.length === 0) return "FALSE";
|
|
530
|
+
const values = value.map((item) => withCast(toSqlLiteral(item), condition.value_cast));
|
|
531
|
+
return `${column} IN (${values.join(", ")})`;
|
|
532
|
+
}
|
|
533
|
+
default:
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function buildTypedSelectQuery(input) {
|
|
538
|
+
const whereClauses = [];
|
|
539
|
+
for (const condition of input.conditions) {
|
|
540
|
+
const clause = conditionToSqlClause(condition);
|
|
541
|
+
if (!clause) return null;
|
|
542
|
+
whereClauses.push(clause);
|
|
543
|
+
}
|
|
544
|
+
let limit = input.limit;
|
|
545
|
+
let offset = input.offset;
|
|
546
|
+
if (limit === void 0 && input.pageSize !== void 0) {
|
|
547
|
+
limit = input.pageSize;
|
|
548
|
+
}
|
|
549
|
+
if (offset === void 0 && input.pageSize !== void 0 && input.currentPage !== void 0 && input.currentPage > 0) {
|
|
550
|
+
offset = (input.currentPage - 1) * input.pageSize;
|
|
551
|
+
}
|
|
552
|
+
const sqlParts = [
|
|
553
|
+
`SELECT ${buildSelectColumnsClause(input.columns)} FROM ${quoteQualifiedIdentifier(input.tableName)}`
|
|
554
|
+
];
|
|
555
|
+
if (whereClauses.length > 0) {
|
|
556
|
+
sqlParts.push(`WHERE ${whereClauses.join(" AND ")}`);
|
|
557
|
+
}
|
|
558
|
+
if (input.order?.field) {
|
|
559
|
+
const direction = input.order.direction === "descending" ? "DESC" : "ASC";
|
|
560
|
+
sqlParts.push(`ORDER BY ${quoteQualifiedIdentifier(input.order.field)} ${direction}`);
|
|
561
|
+
}
|
|
562
|
+
if (limit !== void 0) {
|
|
563
|
+
sqlParts.push(`LIMIT ${Math.max(0, Math.trunc(limit))}`);
|
|
564
|
+
}
|
|
565
|
+
if (offset !== void 0) {
|
|
566
|
+
sqlParts.push(`OFFSET ${Math.max(0, Math.trunc(offset))}`);
|
|
567
|
+
}
|
|
568
|
+
return `${sqlParts.join(" ")};`;
|
|
569
|
+
}
|
|
446
570
|
function createFilterMethods(state, addCondition, self) {
|
|
447
571
|
return {
|
|
448
572
|
eq(column, value) {
|
|
449
|
-
|
|
573
|
+
if (shouldUseUuidTextComparison(column, value)) {
|
|
574
|
+
addCondition("eq", column, value, { columnCast: "text" });
|
|
575
|
+
} else {
|
|
576
|
+
addCondition("eq", column, value);
|
|
577
|
+
}
|
|
578
|
+
return self;
|
|
579
|
+
},
|
|
580
|
+
eqCast(column, value, cast) {
|
|
581
|
+
addCondition("eq", column, value, { valueCast: cast });
|
|
582
|
+
return self;
|
|
583
|
+
},
|
|
584
|
+
eqUuid(column, value) {
|
|
585
|
+
addCondition("eq", column, value, { valueCast: "uuid" });
|
|
450
586
|
return self;
|
|
451
587
|
},
|
|
452
588
|
match(filters) {
|
|
453
|
-
Object.entries(filters).forEach(([column, value]) =>
|
|
589
|
+
Object.entries(filters).forEach(([column, value]) => {
|
|
590
|
+
if (shouldUseUuidTextComparison(column, value)) {
|
|
591
|
+
addCondition("eq", column, value, { columnCast: "text" });
|
|
592
|
+
} else {
|
|
593
|
+
addCondition("eq", column, value);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
454
596
|
return self;
|
|
455
597
|
},
|
|
456
598
|
range(from, to) {
|
|
@@ -466,6 +608,25 @@ function createFilterMethods(state, addCondition, self) {
|
|
|
466
608
|
state.offset = count;
|
|
467
609
|
return self;
|
|
468
610
|
},
|
|
611
|
+
currentPage(value) {
|
|
612
|
+
state.currentPage = value;
|
|
613
|
+
return self;
|
|
614
|
+
},
|
|
615
|
+
pageSize(value) {
|
|
616
|
+
state.pageSize = value;
|
|
617
|
+
return self;
|
|
618
|
+
},
|
|
619
|
+
totalPages(value) {
|
|
620
|
+
state.totalPages = value;
|
|
621
|
+
return self;
|
|
622
|
+
},
|
|
623
|
+
order(column, options) {
|
|
624
|
+
state.order = {
|
|
625
|
+
field: column,
|
|
626
|
+
direction: options?.ascending === false ? "descending" : "ascending"
|
|
627
|
+
};
|
|
628
|
+
return self;
|
|
629
|
+
},
|
|
469
630
|
gt(column, value) {
|
|
470
631
|
addCondition("gt", column, value);
|
|
471
632
|
return self;
|
|
@@ -655,7 +816,7 @@ function createTableBuilder(tableName, client) {
|
|
|
655
816
|
const state = {
|
|
656
817
|
conditions: []
|
|
657
818
|
};
|
|
658
|
-
const addCondition = (operator, column, value) => {
|
|
819
|
+
const addCondition = (operator, column, value, hints) => {
|
|
659
820
|
const condition = { operator };
|
|
660
821
|
if (column) {
|
|
661
822
|
condition.column = column;
|
|
@@ -669,17 +830,53 @@ function createTableBuilder(tableName, client) {
|
|
|
669
830
|
condition.eq_value = value;
|
|
670
831
|
}
|
|
671
832
|
}
|
|
833
|
+
if (hints?.valueCast) {
|
|
834
|
+
condition.value_cast = hints.valueCast;
|
|
835
|
+
if (operator === "eq") {
|
|
836
|
+
condition.eq_value_cast = hints.valueCast;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (hints?.columnCast) {
|
|
840
|
+
condition.column_cast = hints.columnCast;
|
|
841
|
+
if (operator === "eq") {
|
|
842
|
+
condition.eq_column_cast = hints.columnCast;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
672
845
|
state.conditions.push(condition);
|
|
673
846
|
};
|
|
674
847
|
const builder = {};
|
|
675
848
|
const filterMethods = createFilterMethods(state, addCondition, builder);
|
|
676
849
|
const runSelect = async (columns = DEFAULT_COLUMNS, options) => {
|
|
850
|
+
const conditions = state.conditions.length ? state.conditions.map((condition) => ({ ...condition })) : void 0;
|
|
851
|
+
const hasTypedEqualityComparison = conditions?.some(
|
|
852
|
+
(condition) => condition.operator === "eq" && (condition.value_cast !== void 0 || condition.column_cast !== void 0)
|
|
853
|
+
) ?? false;
|
|
854
|
+
if (hasTypedEqualityComparison && !options?.head && !options?.count && conditions) {
|
|
855
|
+
const query = buildTypedSelectQuery({
|
|
856
|
+
tableName,
|
|
857
|
+
columns,
|
|
858
|
+
conditions,
|
|
859
|
+
limit: state.limit,
|
|
860
|
+
offset: state.offset,
|
|
861
|
+
currentPage: state.currentPage,
|
|
862
|
+
pageSize: state.pageSize,
|
|
863
|
+
order: state.order
|
|
864
|
+
});
|
|
865
|
+
if (query) {
|
|
866
|
+
const queryResponse = await client.queryGateway({ query }, options);
|
|
867
|
+
return formatResult(queryResponse);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
677
870
|
const payload = {
|
|
678
871
|
table_name: tableName,
|
|
679
872
|
columns,
|
|
680
|
-
conditions
|
|
873
|
+
conditions,
|
|
681
874
|
limit: state.limit,
|
|
682
875
|
offset: state.offset,
|
|
876
|
+
current_page: state.currentPage,
|
|
877
|
+
page_size: state.pageSize,
|
|
878
|
+
total_pages: state.totalPages,
|
|
879
|
+
sort_by: state.order,
|
|
683
880
|
strip_nulls: options?.stripNulls ?? true,
|
|
684
881
|
count: options?.count,
|
|
685
882
|
head: options?.head
|
|
@@ -715,6 +912,10 @@ function createTableBuilder(tableName, client) {
|
|
|
715
912
|
state.conditions = [];
|
|
716
913
|
state.limit = void 0;
|
|
717
914
|
state.offset = void 0;
|
|
915
|
+
state.order = void 0;
|
|
916
|
+
state.currentPage = void 0;
|
|
917
|
+
state.pageSize = void 0;
|
|
918
|
+
state.totalPages = void 0;
|
|
718
919
|
return builder;
|
|
719
920
|
},
|
|
720
921
|
select(columns = DEFAULT_COLUMNS, options) {
|
|
@@ -806,6 +1007,10 @@ function createTableBuilder(tableName, client) {
|
|
|
806
1007
|
conditions: filters,
|
|
807
1008
|
strip_nulls: mergedOptions?.stripNulls ?? true
|
|
808
1009
|
};
|
|
1010
|
+
if (state.order) payload.sort_by = state.order;
|
|
1011
|
+
if (state.currentPage !== void 0) payload.current_page = state.currentPage;
|
|
1012
|
+
if (state.pageSize !== void 0) payload.page_size = state.pageSize;
|
|
1013
|
+
if (state.totalPages !== void 0) payload.total_pages = state.totalPages;
|
|
809
1014
|
if (columns) payload.columns = columns;
|
|
810
1015
|
const response = await client.updateGateway(payload, mergedOptions);
|
|
811
1016
|
return formatResult(response);
|
|
@@ -829,6 +1034,10 @@ function createTableBuilder(tableName, client) {
|
|
|
829
1034
|
resource_id: resourceId,
|
|
830
1035
|
conditions: filters
|
|
831
1036
|
};
|
|
1037
|
+
if (state.order) payload.sort_by = state.order;
|
|
1038
|
+
if (state.currentPage !== void 0) payload.current_page = state.currentPage;
|
|
1039
|
+
if (state.pageSize !== void 0) payload.page_size = state.pageSize;
|
|
1040
|
+
if (state.totalPages !== void 0) payload.total_pages = state.totalPages;
|
|
832
1041
|
if (columns) payload.columns = columns;
|
|
833
1042
|
const response = await client.deleteGateway(payload, mergedOptions);
|
|
834
1043
|
return formatResult(response);
|