@xylex-group/athena 1.4.0 → 1.5.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 +101 -8
- package/dist/{errors-CB-eJQ7x.d.cts → errors-C4GJaFI_.d.cts} +16 -1
- package/dist/{errors-CB-eJQ7x.d.ts → errors-C4GJaFI_.d.ts} +16 -1
- package/dist/index.cjs +173 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +173 -4
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +965 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +234 -3
- package/dist/react.d.ts +234 -3
- package/dist/react.js +960 -2
- package/dist/react.js.map +1 -1
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# athena-js
|
|
2
2
|
|
|
3
|
-
current version: `1.
|
|
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
|
|
3
|
+
current version: `1.5.0`
|
|
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 plus Athena-native React hooks for client-side use.
|
|
5
5
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
@@ -13,7 +13,7 @@ pnpm add @xylex-group/athena
|
|
|
13
13
|
yarn add @xylex-group/athena
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
React peer dependency is optional — only needed if you use `
|
|
16
|
+
React peer dependency is optional — only needed if you use `@xylex-group/athena/react` hooks.
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
npm install react # React >=17 required for the hook
|
|
@@ -156,6 +156,8 @@ const { data } = await athena
|
|
|
156
156
|
.from("characters")
|
|
157
157
|
.select("id, name")
|
|
158
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
|
|
159
161
|
.neq("role", "guest") // column != value
|
|
160
162
|
.gt("level", 5) // column > value
|
|
161
163
|
.gte("score", 100) // column >= value
|
|
@@ -172,6 +174,8 @@ const { data } = await athena
|
|
|
172
174
|
.or("status.eq.active,status.eq.pending"); // OR expression
|
|
173
175
|
```
|
|
174
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.
|
|
178
|
+
|
|
175
179
|
Canonical style for reads is to call `.select(...)` first, then apply filters:
|
|
176
180
|
|
|
177
181
|
```ts
|
|
@@ -408,11 +412,102 @@ mutation.finally(() => …);
|
|
|
408
412
|
|
|
409
413
|
The request fires only once regardless of how many times you call `.then()` or await the object.
|
|
410
414
|
|
|
411
|
-
## React
|
|
415
|
+
## React hooks
|
|
412
416
|
|
|
413
417
|
```tsx
|
|
414
418
|
"use client";
|
|
415
419
|
|
|
420
|
+
import {
|
|
421
|
+
AthenaQueryClientProvider,
|
|
422
|
+
createAthenaQueryClient,
|
|
423
|
+
useAthenaGateway,
|
|
424
|
+
useMutation,
|
|
425
|
+
useQuery,
|
|
426
|
+
} from "@xylex-group/athena/react";
|
|
427
|
+
import { createClient } from "@xylex-group/athena";
|
|
428
|
+
|
|
429
|
+
const queryClient = createAthenaQueryClient({
|
|
430
|
+
cache: { mode: "none" }, // default: no persistent data cache, inflight dedupe only
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const athena = createClient(
|
|
434
|
+
process.env.NEXT_PUBLIC_ATHENA_URL!,
|
|
435
|
+
process.env.NEXT_PUBLIC_ATHENA_API_KEY!,
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
type Product = {
|
|
439
|
+
id: string;
|
|
440
|
+
name: string;
|
|
441
|
+
price: number;
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
type CreateProductInput = {
|
|
445
|
+
name: string;
|
|
446
|
+
price: number;
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
function ProductsInner() {
|
|
450
|
+
const products = useQuery<Product[]>({
|
|
451
|
+
queryKey: ["products"],
|
|
452
|
+
queryFn: () =>
|
|
453
|
+
athena.from("products").select("id,name,price").limit(50),
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
const createProduct = useMutation<CreateProductInput, Product>({
|
|
457
|
+
mutationFn: (input) =>
|
|
458
|
+
athena.from("products").insert(input).select("id,name,price").single(),
|
|
459
|
+
onSuccess: () => {
|
|
460
|
+
void products.refetch();
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
if (products.isLoading) return <div>Loading...</div>;
|
|
465
|
+
if (products.error) return <div>{products.error.message}</div>;
|
|
466
|
+
|
|
467
|
+
return (
|
|
468
|
+
<div>
|
|
469
|
+
<button
|
|
470
|
+
onClick={() => {
|
|
471
|
+
createProduct.mutate({ name: "New product", price: 99 });
|
|
472
|
+
}}
|
|
473
|
+
>
|
|
474
|
+
Add Product
|
|
475
|
+
</button>
|
|
476
|
+
{products.data?.map((product) => (
|
|
477
|
+
<div key={product.id}>
|
|
478
|
+
{product.name} - {product.price}
|
|
479
|
+
</div>
|
|
480
|
+
))}
|
|
481
|
+
</div>
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function Products() {
|
|
486
|
+
return (
|
|
487
|
+
<AthenaQueryClientProvider client={queryClient}>
|
|
488
|
+
<ProductsInner />
|
|
489
|
+
</AthenaQueryClientProvider>
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Available React APIs:
|
|
495
|
+
|
|
496
|
+
- `useAthenaGateway`: low-level gateway hook (`fetchGateway`, `insertGateway`, `updateGateway`, `deleteGateway`, `rpcGateway`) with request/response logging.
|
|
497
|
+
- `createAthenaQueryClient` + `AthenaQueryClientProvider`: Athena query runtime boundary for scoped state and subscriptions.
|
|
498
|
+
- `useQuery`: lightweight read lifecycle hook (`status`, `isFetching`, `refetch`, `reset`) with normalized Athena error/result handling.
|
|
499
|
+
- `useMutation`: lightweight write lifecycle hook (`mutate`, `mutateAsync`, `reset`) with manual refetch/invalidation flow.
|
|
500
|
+
|
|
501
|
+
By design this is not a cache-heavy React Query clone:
|
|
502
|
+
|
|
503
|
+
- No TanStack/React Query dependency.
|
|
504
|
+
- No persistent data cache by default (`cache.mode = "none"`).
|
|
505
|
+
- Inflight dedupe for identical query keys is enabled.
|
|
506
|
+
- Manual `refetch()` after mutations is the default invalidation strategy.
|
|
507
|
+
|
|
508
|
+
`useAthenaGateway` example:
|
|
509
|
+
|
|
510
|
+
```tsx
|
|
416
511
|
import { useAthenaGateway } from "@xylex-group/athena/react";
|
|
417
512
|
import { useEffect } from "react";
|
|
418
513
|
|
|
@@ -423,7 +518,7 @@ export function UsersPanel() {
|
|
|
423
518
|
});
|
|
424
519
|
|
|
425
520
|
useEffect(() => {
|
|
426
|
-
fetchGateway({
|
|
521
|
+
void fetchGateway({
|
|
427
522
|
table_name: "users",
|
|
428
523
|
columns: ["id", "email"],
|
|
429
524
|
limit: 25,
|
|
@@ -437,9 +532,7 @@ export function UsersPanel() {
|
|
|
437
532
|
}
|
|
438
533
|
```
|
|
439
534
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
Hook config options mirror the client options: `baseUrl`, `apiKey`, `headers`, `userId`, `organizationId`, `publishEvent`.
|
|
535
|
+
`useAthenaGateway` config options mirror the client options: `baseUrl`, `apiKey`, `headers`, `userId`, `organizationId`, `publishEvent`.
|
|
443
536
|
|
|
444
537
|
## User context headers
|
|
445
538
|
|
|
@@ -8,14 +8,29 @@ 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;
|
|
19
34
|
}
|
|
20
35
|
type AthenaSortDirection = 'ascending' | 'descending';
|
|
21
36
|
interface AthenaSortBy {
|
|
@@ -205,4 +220,4 @@ declare class AthenaGatewayError extends Error {
|
|
|
205
220
|
}
|
|
206
221
|
declare function isAthenaGatewayError(error: unknown): error is AthenaGatewayError;
|
|
207
222
|
|
|
208
|
-
export { type
|
|
223
|
+
export { type AthenaGatewayCallOptions as A, type BackendConfig as B, type AthenaConditionValue as a, type AthenaConditionCastType as b, type AthenaConditionArrayValue as c, type AthenaConditionOperator as d, type AthenaGatewayErrorDetails as e, type AthenaRpcCallOptions as f, type BackendType as g, Backend as h, AthenaGatewayError as i, isAthenaGatewayError as j, type AthenaRpcFilter as k, type AthenaRpcFilterOperator as l, type AthenaRpcOrder as m, type AthenaRpcPayload as n, type AthenaGatewayErrorCode as o, type AthenaGatewayHookConfig as p, type AthenaGatewayHookResult as q, type AthenaFetchPayload as r, type AthenaInsertPayload as s, type AthenaUpdatePayload as t, type AthenaDeletePayload as u, type AthenaGatewayResponse as v };
|
|
@@ -8,14 +8,29 @@ 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;
|
|
19
34
|
}
|
|
20
35
|
type AthenaSortDirection = 'ascending' | 'descending';
|
|
21
36
|
interface AthenaSortBy {
|
|
@@ -205,4 +220,4 @@ declare class AthenaGatewayError extends Error {
|
|
|
205
220
|
}
|
|
206
221
|
declare function isAthenaGatewayError(error: unknown): error is AthenaGatewayError;
|
|
207
222
|
|
|
208
|
-
export { type
|
|
223
|
+
export { type AthenaGatewayCallOptions as A, type BackendConfig as B, type AthenaConditionValue as a, type AthenaConditionCastType as b, type AthenaConditionArrayValue as c, type AthenaConditionOperator as d, type AthenaGatewayErrorDetails as e, type AthenaRpcCallOptions as f, type BackendType as g, Backend as h, AthenaGatewayError as i, isAthenaGatewayError as j, type AthenaRpcFilter as k, type AthenaRpcFilterOperator as l, type AthenaRpcOrder as m, type AthenaRpcPayload as n, type AthenaGatewayErrorCode as o, type AthenaGatewayHookConfig as p, type AthenaGatewayHookResult as q, type AthenaFetchPayload as r, type AthenaInsertPayload as s, type AthenaUpdatePayload as t, type AthenaDeletePayload as u, type AthenaGatewayResponse as v };
|
package/dist/index.cjs
CHANGED
|
@@ -368,6 +368,8 @@ function createAthenaGatewayClient(config = {}) {
|
|
|
368
368
|
|
|
369
369
|
// src/client.ts
|
|
370
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;
|
|
371
373
|
function formatResult(response) {
|
|
372
374
|
const result = {
|
|
373
375
|
data: response.data ?? null,
|
|
@@ -448,14 +450,149 @@ function stringifyFilterValue(value) {
|
|
|
448
450
|
}
|
|
449
451
|
return String(value);
|
|
450
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
|
+
}
|
|
451
570
|
function createFilterMethods(state, addCondition, self) {
|
|
452
571
|
return {
|
|
453
572
|
eq(column, value) {
|
|
454
|
-
|
|
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" });
|
|
455
586
|
return self;
|
|
456
587
|
},
|
|
457
588
|
match(filters) {
|
|
458
|
-
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
|
+
});
|
|
459
596
|
return self;
|
|
460
597
|
},
|
|
461
598
|
range(from, to) {
|
|
@@ -679,7 +816,7 @@ function createTableBuilder(tableName, client) {
|
|
|
679
816
|
const state = {
|
|
680
817
|
conditions: []
|
|
681
818
|
};
|
|
682
|
-
const addCondition = (operator, column, value) => {
|
|
819
|
+
const addCondition = (operator, column, value, hints) => {
|
|
683
820
|
const condition = { operator };
|
|
684
821
|
if (column) {
|
|
685
822
|
condition.column = column;
|
|
@@ -693,15 +830,47 @@ function createTableBuilder(tableName, client) {
|
|
|
693
830
|
condition.eq_value = value;
|
|
694
831
|
}
|
|
695
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
|
+
}
|
|
696
845
|
state.conditions.push(condition);
|
|
697
846
|
};
|
|
698
847
|
const builder = {};
|
|
699
848
|
const filterMethods = createFilterMethods(state, addCondition, builder);
|
|
700
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
|
+
}
|
|
701
870
|
const payload = {
|
|
702
871
|
table_name: tableName,
|
|
703
872
|
columns,
|
|
704
|
-
conditions
|
|
873
|
+
conditions,
|
|
705
874
|
limit: state.limit,
|
|
706
875
|
offset: state.offset,
|
|
707
876
|
current_page: state.currentPage,
|