mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-02-16 13:26:12 +00:00
refactor(headerspec): improve code formatting and consistency
This commit is contained in:
@@ -1,21 +1,21 @@
|
|||||||
import type {
|
import type {
|
||||||
ClientConfig,
|
|
||||||
APIResponse,
|
APIResponse,
|
||||||
Options,
|
ClientConfig,
|
||||||
FilterOption,
|
|
||||||
SortOption,
|
|
||||||
PreloadOption,
|
|
||||||
CustomOperator,
|
CustomOperator,
|
||||||
} from '../common/types';
|
FilterOption,
|
||||||
|
Options,
|
||||||
|
PreloadOption,
|
||||||
|
SortOption,
|
||||||
|
} from "../common/types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a value with base64 and ZIP_ prefix for complex header values.
|
* Encode a value with base64 and ZIP_ prefix for complex header values.
|
||||||
*/
|
*/
|
||||||
export function encodeHeaderValue(value: string): string {
|
export function encodeHeaderValue(value: string): string {
|
||||||
if (typeof btoa === 'function') {
|
if (typeof btoa === "function") {
|
||||||
return 'ZIP_' + btoa(value);
|
return "ZIP_" + btoa(value);
|
||||||
}
|
}
|
||||||
return 'ZIP_' + Buffer.from(value, 'utf-8').toString('base64');
|
return "ZIP_" + Buffer.from(value, "utf-8").toString("base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,16 +24,16 @@ export function encodeHeaderValue(value: string): string {
|
|||||||
export function decodeHeaderValue(value: string): string {
|
export function decodeHeaderValue(value: string): string {
|
||||||
let code = value;
|
let code = value;
|
||||||
|
|
||||||
if (code.startsWith('ZIP_')) {
|
if (code.startsWith("ZIP_")) {
|
||||||
code = code.slice(4).replace(/[\n\r ]/g, '');
|
code = code.slice(4).replace(/[\n\r ]/g, "");
|
||||||
code = decodeBase64(code);
|
code = decodeBase64(code);
|
||||||
} else if (code.startsWith('__')) {
|
} else if (code.startsWith("__")) {
|
||||||
code = code.slice(2).replace(/[\n\r ]/g, '');
|
code = code.slice(2).replace(/[\n\r ]/g, "");
|
||||||
code = decodeBase64(code);
|
code = decodeBase64(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle nested encoding
|
// Handle nested encoding
|
||||||
if (code.startsWith('ZIP_') || code.startsWith('__')) {
|
if (code.startsWith("ZIP_") || code.startsWith("__")) {
|
||||||
code = decodeHeaderValue(code);
|
code = decodeHeaderValue(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,10 +41,10 @@ export function decodeHeaderValue(value: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function decodeBase64(str: string): string {
|
function decodeBase64(str: string): string {
|
||||||
if (typeof atob === 'function') {
|
if (typeof atob === "function") {
|
||||||
return atob(str);
|
return atob(str);
|
||||||
}
|
}
|
||||||
return Buffer.from(str, 'base64').toString('utf-8');
|
return Buffer.from(str, "base64").toString("utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,24 +69,24 @@ export function buildHeaders(options: Options): Record<string, string> {
|
|||||||
|
|
||||||
// Column selection
|
// Column selection
|
||||||
if (options.columns?.length) {
|
if (options.columns?.length) {
|
||||||
headers['X-Select-Fields'] = options.columns.join(',');
|
headers["X-Select-Fields"] = options.columns.join(",");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.omit_columns?.length) {
|
if (options.omit_columns?.length) {
|
||||||
headers['X-Not-Select-Fields'] = options.omit_columns.join(',');
|
headers["X-Not-Select-Fields"] = options.omit_columns.join(",");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filters
|
// Filters
|
||||||
if (options.filters?.length) {
|
if (options.filters?.length) {
|
||||||
for (const filter of options.filters) {
|
for (const filter of options.filters) {
|
||||||
const logicOp = filter.logic_operator ?? 'AND';
|
const logicOp = filter.logic_operator ?? "AND";
|
||||||
const op = mapOperatorToHeaderOp(filter.operator);
|
const op = mapOperatorToHeaderOp(filter.operator);
|
||||||
const valueStr = formatFilterValue(filter);
|
const valueStr = formatFilterValue(filter);
|
||||||
|
|
||||||
if (filter.operator === 'eq' && logicOp === 'AND') {
|
if (filter.operator === "eq" && logicOp === "AND") {
|
||||||
// Simple field filter shorthand
|
// Simple field filter shorthand
|
||||||
headers[`X-FieldFilter-${filter.column}`] = valueStr;
|
headers[`X-FieldFilter-${filter.column}`] = valueStr;
|
||||||
} else if (logicOp === 'OR') {
|
} else if (logicOp === "OR") {
|
||||||
headers[`X-SearchOr-${op}-${filter.column}`] = valueStr;
|
headers[`X-SearchOr-${op}-${filter.column}`] = valueStr;
|
||||||
} else {
|
} else {
|
||||||
headers[`X-SearchOp-${op}-${filter.column}`] = valueStr;
|
headers[`X-SearchOp-${op}-${filter.column}`] = valueStr;
|
||||||
@@ -98,41 +98,41 @@ export function buildHeaders(options: Options): Record<string, string> {
|
|||||||
if (options.sort?.length) {
|
if (options.sort?.length) {
|
||||||
const sortParts = options.sort.map((s: SortOption) => {
|
const sortParts = options.sort.map((s: SortOption) => {
|
||||||
const dir = s.direction.toUpperCase();
|
const dir = s.direction.toUpperCase();
|
||||||
return dir === 'DESC' ? `-${s.column}` : `+${s.column}`;
|
return dir === "DESC" ? `-${s.column}` : `+${s.column}`;
|
||||||
});
|
});
|
||||||
headers['X-Sort'] = sortParts.join(',');
|
headers["X-Sort"] = sortParts.join(",");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pagination
|
// Pagination
|
||||||
if (options.limit !== undefined) {
|
if (options.limit !== undefined) {
|
||||||
headers['X-Limit'] = String(options.limit);
|
headers["X-Limit"] = String(options.limit);
|
||||||
}
|
}
|
||||||
if (options.offset !== undefined) {
|
if (options.offset !== undefined) {
|
||||||
headers['X-Offset'] = String(options.offset);
|
headers["X-Offset"] = String(options.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor pagination
|
// Cursor pagination
|
||||||
if (options.cursor_forward) {
|
if (options.cursor_forward) {
|
||||||
headers['X-Cursor-Forward'] = options.cursor_forward;
|
headers["X-Cursor-Forward"] = options.cursor_forward;
|
||||||
}
|
}
|
||||||
if (options.cursor_backward) {
|
if (options.cursor_backward) {
|
||||||
headers['X-Cursor-Backward'] = options.cursor_backward;
|
headers["X-Cursor-Backward"] = options.cursor_backward;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preload
|
// Preload
|
||||||
if (options.preload?.length) {
|
if (options.preload?.length) {
|
||||||
const parts = options.preload.map((p: PreloadOption) => {
|
const parts = options.preload.map((p: PreloadOption) => {
|
||||||
if (p.columns?.length) {
|
if (p.columns?.length) {
|
||||||
return `${p.relation}:${p.columns.join(',')}`;
|
return `${p.relation}:${p.columns.join(",")}`;
|
||||||
}
|
}
|
||||||
return p.relation;
|
return p.relation;
|
||||||
});
|
});
|
||||||
headers['X-Preload'] = parts.join('|');
|
headers["X-Preload"] = parts.join("|");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch row number
|
// Fetch row number
|
||||||
if (options.fetch_row_number) {
|
if (options.fetch_row_number) {
|
||||||
headers['X-Fetch-RowNumber'] = options.fetch_row_number;
|
headers["X-Fetch-RowNumber"] = options.fetch_row_number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computed columns
|
// Computed columns
|
||||||
@@ -144,8 +144,10 @@ export function buildHeaders(options: Options): Record<string, string> {
|
|||||||
|
|
||||||
// Custom operators -> X-Custom-SQL-W
|
// Custom operators -> X-Custom-SQL-W
|
||||||
if (options.customOperators?.length) {
|
if (options.customOperators?.length) {
|
||||||
const sqlParts = options.customOperators.map((co: CustomOperator) => co.sql);
|
const sqlParts = options.customOperators.map(
|
||||||
headers['X-Custom-SQL-W'] = sqlParts.join(' AND ');
|
(co: CustomOperator) => co.sql,
|
||||||
|
);
|
||||||
|
headers["X-Custom-SQL-W"] = sqlParts.join(" AND ");
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
@@ -153,32 +155,47 @@ export function buildHeaders(options: Options): Record<string, string> {
|
|||||||
|
|
||||||
function mapOperatorToHeaderOp(operator: string): string {
|
function mapOperatorToHeaderOp(operator: string): string {
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
case 'eq': return 'equals';
|
case "eq":
|
||||||
case 'neq': return 'notequals';
|
return "equals";
|
||||||
case 'gt': return 'greaterthan';
|
case "neq":
|
||||||
case 'gte': return 'greaterthanorequal';
|
return "notequals";
|
||||||
case 'lt': return 'lessthan';
|
case "gt":
|
||||||
case 'lte': return 'lessthanorequal';
|
return "greaterthan";
|
||||||
case 'like':
|
case "gte":
|
||||||
case 'ilike':
|
return "greaterthanorequal";
|
||||||
case 'contains': return 'contains';
|
case "lt":
|
||||||
case 'startswith': return 'beginswith';
|
return "lessthan";
|
||||||
case 'endswith': return 'endswith';
|
case "lte":
|
||||||
case 'in': return 'in';
|
return "lessthanorequal";
|
||||||
case 'between': return 'between';
|
case "like":
|
||||||
case 'between_inclusive': return 'betweeninclusive';
|
case "ilike":
|
||||||
case 'is_null': return 'empty';
|
case "contains":
|
||||||
case 'is_not_null': return 'notempty';
|
return "contains";
|
||||||
default: return operator;
|
case "startswith":
|
||||||
|
return "beginswith";
|
||||||
|
case "endswith":
|
||||||
|
return "endswith";
|
||||||
|
case "in":
|
||||||
|
return "in";
|
||||||
|
case "between":
|
||||||
|
return "between";
|
||||||
|
case "between_inclusive":
|
||||||
|
return "betweeninclusive";
|
||||||
|
case "is_null":
|
||||||
|
return "empty";
|
||||||
|
case "is_not_null":
|
||||||
|
return "notempty";
|
||||||
|
default:
|
||||||
|
return operator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatFilterValue(filter: FilterOption): string {
|
function formatFilterValue(filter: FilterOption): string {
|
||||||
if (filter.value === null || filter.value === undefined) {
|
if (filter.value === null || filter.value === undefined) {
|
||||||
return '';
|
return "";
|
||||||
}
|
}
|
||||||
if (Array.isArray(filter.value)) {
|
if (Array.isArray(filter.value)) {
|
||||||
return filter.value.join(',');
|
return filter.value.join(",");
|
||||||
}
|
}
|
||||||
return String(filter.value);
|
return String(filter.value);
|
||||||
}
|
}
|
||||||
@@ -218,35 +235,67 @@ export class HeaderSpecClient {
|
|||||||
|
|
||||||
private baseHeaders(): Record<string, string> {
|
private baseHeaders(): Record<string, string> {
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
};
|
};
|
||||||
if (this.config.token) {
|
if (this.config.token) {
|
||||||
headers['Authorization'] = `Bearer ${this.config.token}`;
|
headers["Authorization"] = `Bearer ${this.config.token}`;
|
||||||
}
|
}
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchWithError<T>(url: string, init: RequestInit): Promise<APIResponse<T>> {
|
private async fetchWithError<T>(
|
||||||
|
url: string,
|
||||||
|
init: RequestInit,
|
||||||
|
): Promise<APIResponse<T>> {
|
||||||
const response = await fetch(url, init);
|
const response = await fetch(url, init);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(data.error?.message || 'An error occurred');
|
throw new Error(
|
||||||
|
data.error?.message ||
|
||||||
|
`${response.statusText} ` + `(${response.status})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return {
|
||||||
|
data: data,
|
||||||
|
success: true,
|
||||||
|
error: data.error ? data.error : undefined,
|
||||||
|
metadata: {
|
||||||
|
count: response.headers.get("content-range")
|
||||||
|
? Number(response.headers.get("content-range")?.split("/")[1])
|
||||||
|
: 0,
|
||||||
|
total: response.headers.get("content-range")
|
||||||
|
? Number(response.headers.get("content-range")?.split("/")[1])
|
||||||
|
: 0,
|
||||||
|
filtered: response.headers.get("content-range")
|
||||||
|
? Number(response.headers.get("content-range")?.split("/")[1])
|
||||||
|
: 0,
|
||||||
|
offset: response.headers.get("content-range")
|
||||||
|
? Number(
|
||||||
|
response.headers
|
||||||
|
.get("content-range")
|
||||||
|
?.split("/")[0]
|
||||||
|
.split("-")[0],
|
||||||
|
)
|
||||||
|
: 0,
|
||||||
|
limit: response.headers.get("x-limit")
|
||||||
|
? Number(response.headers.get("x-limit"))
|
||||||
|
: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async read<T = any>(
|
async read<T = any>(
|
||||||
schema: string,
|
schema: string,
|
||||||
entity: string,
|
entity: string,
|
||||||
id?: string,
|
id?: string,
|
||||||
options?: Options
|
options?: Options,
|
||||||
): Promise<APIResponse<T>> {
|
): Promise<APIResponse<T>> {
|
||||||
const url = this.buildUrl(schema, entity, id);
|
const url = this.buildUrl(schema, entity, id);
|
||||||
const optHeaders = options ? buildHeaders(options) : {};
|
const optHeaders = options ? buildHeaders(options) : {};
|
||||||
return this.fetchWithError<T>(url, {
|
return this.fetchWithError<T>(url, {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: { ...this.baseHeaders(), ...optHeaders },
|
headers: { ...this.baseHeaders(), ...optHeaders },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -255,12 +304,12 @@ export class HeaderSpecClient {
|
|||||||
schema: string,
|
schema: string,
|
||||||
entity: string,
|
entity: string,
|
||||||
data: any,
|
data: any,
|
||||||
options?: Options
|
options?: Options,
|
||||||
): Promise<APIResponse<T>> {
|
): Promise<APIResponse<T>> {
|
||||||
const url = this.buildUrl(schema, entity);
|
const url = this.buildUrl(schema, entity);
|
||||||
const optHeaders = options ? buildHeaders(options) : {};
|
const optHeaders = options ? buildHeaders(options) : {};
|
||||||
return this.fetchWithError<T>(url, {
|
return this.fetchWithError<T>(url, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: { ...this.baseHeaders(), ...optHeaders },
|
headers: { ...this.baseHeaders(), ...optHeaders },
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
@@ -271,12 +320,12 @@ export class HeaderSpecClient {
|
|||||||
entity: string,
|
entity: string,
|
||||||
id: string,
|
id: string,
|
||||||
data: any,
|
data: any,
|
||||||
options?: Options
|
options?: Options,
|
||||||
): Promise<APIResponse<T>> {
|
): Promise<APIResponse<T>> {
|
||||||
const url = this.buildUrl(schema, entity, id);
|
const url = this.buildUrl(schema, entity, id);
|
||||||
const optHeaders = options ? buildHeaders(options) : {};
|
const optHeaders = options ? buildHeaders(options) : {};
|
||||||
return this.fetchWithError<T>(url, {
|
return this.fetchWithError<T>(url, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
headers: { ...this.baseHeaders(), ...optHeaders },
|
headers: { ...this.baseHeaders(), ...optHeaders },
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
@@ -285,11 +334,11 @@ export class HeaderSpecClient {
|
|||||||
async delete(
|
async delete(
|
||||||
schema: string,
|
schema: string,
|
||||||
entity: string,
|
entity: string,
|
||||||
id: string
|
id: string,
|
||||||
): Promise<APIResponse<void>> {
|
): Promise<APIResponse<void>> {
|
||||||
const url = this.buildUrl(schema, entity, id);
|
const url = this.buildUrl(schema, entity, id);
|
||||||
return this.fetchWithError<void>(url, {
|
return this.fetchWithError<void>(url, {
|
||||||
method: 'DELETE',
|
method: "DELETE",
|
||||||
headers: this.baseHeaders(),
|
headers: this.baseHeaders(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user