@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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BackendConfig, a as BackendType, A as AthenaConditionValue, b as AthenaConditionArrayValue, c as AthenaConditionOperator, d as AthenaGatewayCallOptions, e as AthenaGatewayErrorDetails, f as AthenaRpcCallOptions } from './errors-CB-eJQ7x.cjs';
2
- export { g as AthenaGatewayError, h as AthenaGatewayErrorCode, i as AthenaRpcFilter, j as AthenaRpcFilterOperator, k as AthenaRpcOrder, l as AthenaRpcPayload, m as Backend, n as isAthenaGatewayError } from './errors-CB-eJQ7x.cjs';
1
+ import { A as AthenaGatewayCallOptions, a as AthenaConditionValue, b as AthenaConditionCastType, c as AthenaConditionArrayValue, d as AthenaConditionOperator, e as AthenaGatewayErrorDetails, f as AthenaRpcCallOptions, B as BackendConfig, g as BackendType } from './errors-C4GJaFI_.cjs';
2
+ export { i as AthenaGatewayError, o as AthenaGatewayErrorCode, k as AthenaRpcFilter, l as AthenaRpcFilterOperator, m as AthenaRpcOrder, n as AthenaRpcPayload, h as Backend, j as isAthenaGatewayError } from './errors-C4GJaFI_.cjs';
3
3
 
4
4
  interface AthenaResult<T> {
5
5
  data: T | null;
@@ -25,6 +25,8 @@ interface OrderOptions {
25
25
  /** Shared filter chain - supports eq, limit, etc. in any order relative to select/update */
26
26
  interface FilterChain<Self> {
27
27
  eq(column: string, value: AthenaConditionValue): Self;
28
+ eqCast(column: string, value: AthenaConditionValue, cast: AthenaConditionCastType): Self;
29
+ eqUuid(column: string, value: string): Self;
28
30
  match(filters: Record<string, AthenaConditionValue>): Self;
29
31
  range(from: number, to: number): Self;
30
32
  limit(count: number): Self;
@@ -244,4 +246,4 @@ declare function assertInt(value: unknown, label?: string, options?: IntCoercion
244
246
  */
245
247
  declare function withRetry<T>(config: RetryConfig, fn: () => Promise<T>): Promise<T>;
246
248
 
247
- export { AthenaClient, type AthenaErrorKind, AthenaGatewayCallOptions, AthenaGatewayErrorDetails, type AthenaOperationContext, type AthenaResult, AthenaRpcCallOptions, type AthenaSdkClient, BackendConfig, BackendType, type IntCoercionOptions, type NormalizedAthenaError, type RequireAffectedOptions, type RetryBackoffStrategy, type RetryConfig, type RpcOrderOptions, type RpcQueryBuilder, type TableQueryBuilder, type UnwrapOneOptions, type UnwrapOptions, assertInt, coerceInt, createClient, isOk, normalizeAthenaError, requireAffected, requireSuccess, unwrap, unwrapOne, unwrapRows, withRetry };
249
+ export { AthenaClient, AthenaConditionCastType, type AthenaErrorKind, AthenaGatewayCallOptions, AthenaGatewayErrorDetails, type AthenaOperationContext, type AthenaResult, AthenaRpcCallOptions, type AthenaSdkClient, BackendConfig, BackendType, type IntCoercionOptions, type NormalizedAthenaError, type RequireAffectedOptions, type RetryBackoffStrategy, type RetryConfig, type RpcOrderOptions, type RpcQueryBuilder, type TableQueryBuilder, type UnwrapOneOptions, type UnwrapOptions, assertInt, coerceInt, createClient, isOk, normalizeAthenaError, requireAffected, requireSuccess, unwrap, unwrapOne, unwrapRows, withRetry };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BackendConfig, a as BackendType, A as AthenaConditionValue, b as AthenaConditionArrayValue, c as AthenaConditionOperator, d as AthenaGatewayCallOptions, e as AthenaGatewayErrorDetails, f as AthenaRpcCallOptions } from './errors-CB-eJQ7x.js';
2
- export { g as AthenaGatewayError, h as AthenaGatewayErrorCode, i as AthenaRpcFilter, j as AthenaRpcFilterOperator, k as AthenaRpcOrder, l as AthenaRpcPayload, m as Backend, n as isAthenaGatewayError } from './errors-CB-eJQ7x.js';
1
+ import { A as AthenaGatewayCallOptions, a as AthenaConditionValue, b as AthenaConditionCastType, c as AthenaConditionArrayValue, d as AthenaConditionOperator, e as AthenaGatewayErrorDetails, f as AthenaRpcCallOptions, B as BackendConfig, g as BackendType } from './errors-C4GJaFI_.js';
2
+ export { i as AthenaGatewayError, o as AthenaGatewayErrorCode, k as AthenaRpcFilter, l as AthenaRpcFilterOperator, m as AthenaRpcOrder, n as AthenaRpcPayload, h as Backend, j as isAthenaGatewayError } from './errors-C4GJaFI_.js';
3
3
 
4
4
  interface AthenaResult<T> {
5
5
  data: T | null;
@@ -25,6 +25,8 @@ interface OrderOptions {
25
25
  /** Shared filter chain - supports eq, limit, etc. in any order relative to select/update */
26
26
  interface FilterChain<Self> {
27
27
  eq(column: string, value: AthenaConditionValue): Self;
28
+ eqCast(column: string, value: AthenaConditionValue, cast: AthenaConditionCastType): Self;
29
+ eqUuid(column: string, value: string): Self;
28
30
  match(filters: Record<string, AthenaConditionValue>): Self;
29
31
  range(from: number, to: number): Self;
30
32
  limit(count: number): Self;
@@ -244,4 +246,4 @@ declare function assertInt(value: unknown, label?: string, options?: IntCoercion
244
246
  */
245
247
  declare function withRetry<T>(config: RetryConfig, fn: () => Promise<T>): Promise<T>;
246
248
 
247
- export { AthenaClient, type AthenaErrorKind, AthenaGatewayCallOptions, AthenaGatewayErrorDetails, type AthenaOperationContext, type AthenaResult, AthenaRpcCallOptions, type AthenaSdkClient, BackendConfig, BackendType, type IntCoercionOptions, type NormalizedAthenaError, type RequireAffectedOptions, type RetryBackoffStrategy, type RetryConfig, type RpcOrderOptions, type RpcQueryBuilder, type TableQueryBuilder, type UnwrapOneOptions, type UnwrapOptions, assertInt, coerceInt, createClient, isOk, normalizeAthenaError, requireAffected, requireSuccess, unwrap, unwrapOne, unwrapRows, withRetry };
249
+ export { AthenaClient, AthenaConditionCastType, type AthenaErrorKind, AthenaGatewayCallOptions, AthenaGatewayErrorDetails, type AthenaOperationContext, type AthenaResult, AthenaRpcCallOptions, type AthenaSdkClient, BackendConfig, BackendType, type IntCoercionOptions, type NormalizedAthenaError, type RequireAffectedOptions, type RetryBackoffStrategy, type RetryConfig, type RpcOrderOptions, type RpcQueryBuilder, type TableQueryBuilder, type UnwrapOneOptions, type UnwrapOptions, assertInt, coerceInt, createClient, isOk, normalizeAthenaError, requireAffected, requireSuccess, unwrap, unwrapOne, unwrapRows, withRetry };
package/dist/index.js CHANGED
@@ -366,6 +366,8 @@ function createAthenaGatewayClient(config = {}) {
366
366
 
367
367
  // src/client.ts
368
368
  var DEFAULT_COLUMNS = "*";
369
+ 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;
370
+ var SAFE_CAST_PATTERN = /^[a-z_][a-z0-9_]*(?:\[\])?$/i;
369
371
  function formatResult(response) {
370
372
  const result = {
371
373
  data: response.data ?? null,
@@ -446,14 +448,149 @@ function stringifyFilterValue(value) {
446
448
  }
447
449
  return String(value);
448
450
  }
451
+ function isUuidString(value) {
452
+ return UUID_PATTERN.test(value.trim());
453
+ }
454
+ function isUuidIdentifierColumn(column) {
455
+ return column === "id" || /(?:^|_)uuid(?:_|$)/i.test(column) || /_id$/i.test(column);
456
+ }
457
+ function shouldUseUuidTextComparison(column, value) {
458
+ return typeof value === "string" && isUuidString(value) && isUuidIdentifierColumn(column);
459
+ }
460
+ function normalizeCast(cast) {
461
+ const normalized = cast.trim().toLowerCase();
462
+ if (!SAFE_CAST_PATTERN.test(normalized)) {
463
+ throw new Error(`Invalid cast type "${cast}"`);
464
+ }
465
+ return normalized;
466
+ }
467
+ function escapeSqlStringLiteral(value) {
468
+ return value.replace(/'/g, "''");
469
+ }
470
+ function quoteIdentifier(identifier) {
471
+ return `"${identifier.replace(/"/g, '""')}"`;
472
+ }
473
+ function quoteQualifiedIdentifier(identifier) {
474
+ return identifier.split(".").map((segment) => quoteIdentifier(segment)).join(".");
475
+ }
476
+ function toSqlLiteral(value) {
477
+ if (value === null) return "NULL";
478
+ if (typeof value === "number") return Number.isFinite(value) ? String(value) : "NULL";
479
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
480
+ return `'${escapeSqlStringLiteral(value)}'`;
481
+ }
482
+ function withCast(expression, cast) {
483
+ if (!cast) return expression;
484
+ return `${expression}::${normalizeCast(cast)}`;
485
+ }
486
+ function buildSelectColumnsClause(columns) {
487
+ if (Array.isArray(columns)) {
488
+ return columns.map((column) => quoteQualifiedIdentifier(column)).join(", ");
489
+ }
490
+ return columns;
491
+ }
492
+ function conditionToSqlClause(condition) {
493
+ if (!condition.column) return null;
494
+ const column = withCast(quoteQualifiedIdentifier(condition.column), condition.column_cast);
495
+ const value = condition.value;
496
+ const sqlOperator = {
497
+ eq: "=",
498
+ neq: "!=",
499
+ gt: ">",
500
+ gte: ">=",
501
+ lt: "<",
502
+ lte: "<=",
503
+ like: "LIKE",
504
+ ilike: "ILIKE"
505
+ };
506
+ switch (condition.operator) {
507
+ case "eq":
508
+ case "neq":
509
+ case "gt":
510
+ case "gte":
511
+ case "lt":
512
+ case "lte":
513
+ case "like":
514
+ case "ilike": {
515
+ if (Array.isArray(value) || value === void 0) return null;
516
+ const rhs = withCast(toSqlLiteral(value), condition.value_cast);
517
+ return `${column} ${sqlOperator[condition.operator]} ${rhs}`;
518
+ }
519
+ case "is": {
520
+ if (value === null) return `${column} IS NULL`;
521
+ if (value === true) return `${column} IS TRUE`;
522
+ if (value === false) return `${column} IS FALSE`;
523
+ return null;
524
+ }
525
+ case "in": {
526
+ if (!Array.isArray(value)) return null;
527
+ if (value.length === 0) return "FALSE";
528
+ const values = value.map((item) => withCast(toSqlLiteral(item), condition.value_cast));
529
+ return `${column} IN (${values.join(", ")})`;
530
+ }
531
+ default:
532
+ return null;
533
+ }
534
+ }
535
+ function buildTypedSelectQuery(input) {
536
+ const whereClauses = [];
537
+ for (const condition of input.conditions) {
538
+ const clause = conditionToSqlClause(condition);
539
+ if (!clause) return null;
540
+ whereClauses.push(clause);
541
+ }
542
+ let limit = input.limit;
543
+ let offset = input.offset;
544
+ if (limit === void 0 && input.pageSize !== void 0) {
545
+ limit = input.pageSize;
546
+ }
547
+ if (offset === void 0 && input.pageSize !== void 0 && input.currentPage !== void 0 && input.currentPage > 0) {
548
+ offset = (input.currentPage - 1) * input.pageSize;
549
+ }
550
+ const sqlParts = [
551
+ `SELECT ${buildSelectColumnsClause(input.columns)} FROM ${quoteQualifiedIdentifier(input.tableName)}`
552
+ ];
553
+ if (whereClauses.length > 0) {
554
+ sqlParts.push(`WHERE ${whereClauses.join(" AND ")}`);
555
+ }
556
+ if (input.order?.field) {
557
+ const direction = input.order.direction === "descending" ? "DESC" : "ASC";
558
+ sqlParts.push(`ORDER BY ${quoteQualifiedIdentifier(input.order.field)} ${direction}`);
559
+ }
560
+ if (limit !== void 0) {
561
+ sqlParts.push(`LIMIT ${Math.max(0, Math.trunc(limit))}`);
562
+ }
563
+ if (offset !== void 0) {
564
+ sqlParts.push(`OFFSET ${Math.max(0, Math.trunc(offset))}`);
565
+ }
566
+ return `${sqlParts.join(" ")};`;
567
+ }
449
568
  function createFilterMethods(state, addCondition, self) {
450
569
  return {
451
570
  eq(column, value) {
452
- addCondition("eq", column, value);
571
+ if (shouldUseUuidTextComparison(column, value)) {
572
+ addCondition("eq", column, value, { columnCast: "text" });
573
+ } else {
574
+ addCondition("eq", column, value);
575
+ }
576
+ return self;
577
+ },
578
+ eqCast(column, value, cast) {
579
+ addCondition("eq", column, value, { valueCast: cast });
580
+ return self;
581
+ },
582
+ eqUuid(column, value) {
583
+ addCondition("eq", column, value, { valueCast: "uuid" });
453
584
  return self;
454
585
  },
455
586
  match(filters) {
456
- Object.entries(filters).forEach(([column, value]) => addCondition("eq", column, value));
587
+ Object.entries(filters).forEach(([column, value]) => {
588
+ if (shouldUseUuidTextComparison(column, value)) {
589
+ addCondition("eq", column, value, { columnCast: "text" });
590
+ } else {
591
+ addCondition("eq", column, value);
592
+ }
593
+ });
457
594
  return self;
458
595
  },
459
596
  range(from, to) {
@@ -677,7 +814,7 @@ function createTableBuilder(tableName, client) {
677
814
  const state = {
678
815
  conditions: []
679
816
  };
680
- const addCondition = (operator, column, value) => {
817
+ const addCondition = (operator, column, value, hints) => {
681
818
  const condition = { operator };
682
819
  if (column) {
683
820
  condition.column = column;
@@ -691,15 +828,47 @@ function createTableBuilder(tableName, client) {
691
828
  condition.eq_value = value;
692
829
  }
693
830
  }
831
+ if (hints?.valueCast) {
832
+ condition.value_cast = hints.valueCast;
833
+ if (operator === "eq") {
834
+ condition.eq_value_cast = hints.valueCast;
835
+ }
836
+ }
837
+ if (hints?.columnCast) {
838
+ condition.column_cast = hints.columnCast;
839
+ if (operator === "eq") {
840
+ condition.eq_column_cast = hints.columnCast;
841
+ }
842
+ }
694
843
  state.conditions.push(condition);
695
844
  };
696
845
  const builder = {};
697
846
  const filterMethods = createFilterMethods(state, addCondition, builder);
698
847
  const runSelect = async (columns = DEFAULT_COLUMNS, options) => {
848
+ const conditions = state.conditions.length ? state.conditions.map((condition) => ({ ...condition })) : void 0;
849
+ const hasTypedEqualityComparison = conditions?.some(
850
+ (condition) => condition.operator === "eq" && (condition.value_cast !== void 0 || condition.column_cast !== void 0)
851
+ ) ?? false;
852
+ if (hasTypedEqualityComparison && !options?.head && !options?.count && conditions) {
853
+ const query = buildTypedSelectQuery({
854
+ tableName,
855
+ columns,
856
+ conditions,
857
+ limit: state.limit,
858
+ offset: state.offset,
859
+ currentPage: state.currentPage,
860
+ pageSize: state.pageSize,
861
+ order: state.order
862
+ });
863
+ if (query) {
864
+ const queryResponse = await client.queryGateway({ query }, options);
865
+ return formatResult(queryResponse);
866
+ }
867
+ }
699
868
  const payload = {
700
869
  table_name: tableName,
701
870
  columns,
702
- conditions: state.conditions.length ? [...state.conditions] : void 0,
871
+ conditions,
703
872
  limit: state.limit,
704
873
  offset: state.offset,
705
874
  current_page: state.currentPage,