mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-09-24 09:02:00 +00:00
feat(headerspec): add UTF-8 encoding/decoding support
* Implement round-trip encoding/decoding for UTF-8 values in header functions. * Update encodeHeaderValue and decodeHeaderValue to use base64 utility functions. * Add tests for UTF-8 header value handling. * Update Vite config to externalize base64 utility.
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@warkypublic/resolvespec-js": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: added headers and few fixes
|
||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+5
-368
@@ -1,368 +1,5 @@
|
|||||||
export declare interface APIError {
|
export * from './common';
|
||||||
code: string;
|
export * from './resolvespec';
|
||||||
message: string;
|
export * from './websocketspec';
|
||||||
details?: any;
|
export * from './headerspec';
|
||||||
detail?: string;
|
//# sourceMappingURL=index.d.ts.map
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface APIResponse<T = any> {
|
|
||||||
success: boolean;
|
|
||||||
data: T;
|
|
||||||
metadata?: Metadata;
|
|
||||||
error?: APIError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build HTTP headers from Options, matching Go's restheadspec handler conventions.
|
|
||||||
*
|
|
||||||
* Header mapping:
|
|
||||||
* - X-Select-Fields: comma-separated columns
|
|
||||||
* - X-Not-Select-Fields: comma-separated omit_columns
|
|
||||||
* - X-FieldFilter-{col}: exact match (eq)
|
|
||||||
* - X-SearchOp-{operator}-{col}: AND filter
|
|
||||||
* - X-SearchOr-{operator}-{col}: OR filter
|
|
||||||
* - X-Sort: +col (asc), -col (desc)
|
|
||||||
* - X-Limit, X-Offset: pagination
|
|
||||||
* - X-Cursor-Forward, X-Cursor-Backward: cursor pagination
|
|
||||||
* - X-Preload: RelationName:field1,field2 pipe-separated
|
|
||||||
* - X-Fetch-RowNumber: row number fetch
|
|
||||||
* - X-CQL-SEL-{col}: computed columns
|
|
||||||
* - X-Custom-SQL-W: custom operators (AND)
|
|
||||||
*/
|
|
||||||
export declare function buildHeaders(options: Options): Record<string, string>;
|
|
||||||
|
|
||||||
export declare interface ClientConfig {
|
|
||||||
baseUrl: string;
|
|
||||||
token?: string;
|
|
||||||
/** Custom HTTP headers. Token and HeaderSpec query options take precedence. */
|
|
||||||
headers?: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface Column {
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
is_nullable: boolean;
|
|
||||||
is_primary: boolean;
|
|
||||||
is_unique: boolean;
|
|
||||||
has_index: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface ComputedColumn {
|
|
||||||
name: string;
|
|
||||||
expression: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare type ConnectionState = 'connecting' | 'connected' | 'disconnecting' | 'disconnected' | 'reconnecting';
|
|
||||||
|
|
||||||
export declare interface CustomOperator {
|
|
||||||
name: string;
|
|
||||||
sql: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode a header value that may be base64 encoded with ZIP_ or __ prefix.
|
|
||||||
*/
|
|
||||||
export declare function decodeHeaderValue(value: string): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode a value with base64 and ZIP_ prefix for complex header values.
|
|
||||||
*/
|
|
||||||
export declare function encodeHeaderValue(value: string): string;
|
|
||||||
|
|
||||||
export declare interface FilterOption {
|
|
||||||
column: string;
|
|
||||||
operator: Operator | string;
|
|
||||||
value: any;
|
|
||||||
logic_operator?: 'AND' | 'OR';
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare function getHeaderSpecClient(config: ClientConfig): HeaderSpecClient;
|
|
||||||
|
|
||||||
export declare function getResolveSpecClient(config: ClientConfig): ResolveSpecClient;
|
|
||||||
|
|
||||||
export declare function getWebSocketClient(config: WebSocketClientConfig): WebSocketClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HeaderSpec REST client.
|
|
||||||
* Sends query options via HTTP headers instead of request body, matching the Go restheadspec handler.
|
|
||||||
*
|
|
||||||
* HTTP methods: GET=read, POST=create, PUT=update, DELETE=delete
|
|
||||||
*/
|
|
||||||
export declare class HeaderSpecClient {
|
|
||||||
private config;
|
|
||||||
constructor(config: ClientConfig);
|
|
||||||
private buildUrl;
|
|
||||||
private baseHeaders;
|
|
||||||
private fetchWithError;
|
|
||||||
read<T = any>(schema: string, entity: string, id?: string, options?: Options): Promise<APIResponse<T>>;
|
|
||||||
create<T = any>(schema: string, entity: string, data: any, options?: Options): Promise<APIResponse<T>>;
|
|
||||||
update<T = any>(schema: string, entity: string, id: string, data: any, options?: Options): Promise<APIResponse<T>>;
|
|
||||||
delete(schema: string, entity: string, id: string): Promise<APIResponse<void>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare type MessageType = 'request' | 'response' | 'notification' | 'subscription' | 'error' | 'ping' | 'pong';
|
|
||||||
|
|
||||||
export declare interface Metadata {
|
|
||||||
total: number;
|
|
||||||
count: number;
|
|
||||||
filtered: number;
|
|
||||||
limit: number;
|
|
||||||
offset: number;
|
|
||||||
row_number?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare type Operation = 'read' | 'create' | 'update' | 'delete';
|
|
||||||
|
|
||||||
export declare type Operator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'in' | 'contains' | 'startswith' | 'endswith' | 'between' | 'between_inclusive' | 'is_null' | 'is_not_null';
|
|
||||||
|
|
||||||
export declare interface Options {
|
|
||||||
preload?: PreloadOption[];
|
|
||||||
columns?: string[];
|
|
||||||
omit_columns?: string[];
|
|
||||||
filters?: FilterOption[];
|
|
||||||
sort?: SortOption[];
|
|
||||||
limit?: number;
|
|
||||||
offset?: number;
|
|
||||||
customOperators?: CustomOperator[];
|
|
||||||
computedColumns?: ComputedColumn[];
|
|
||||||
parameters?: Parameter[];
|
|
||||||
cursor_forward?: string;
|
|
||||||
cursor_backward?: string;
|
|
||||||
fetch_row_number?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface Parameter {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
sequence?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface PreloadOption {
|
|
||||||
relation: string;
|
|
||||||
table_name?: string;
|
|
||||||
columns?: string[];
|
|
||||||
omit_columns?: string[];
|
|
||||||
sort?: SortOption[];
|
|
||||||
filters?: FilterOption[];
|
|
||||||
where?: string;
|
|
||||||
limit?: number;
|
|
||||||
offset?: number;
|
|
||||||
updatable?: boolean;
|
|
||||||
computed_ql?: Record<string, string>;
|
|
||||||
recursive?: boolean;
|
|
||||||
primary_key?: string;
|
|
||||||
related_key?: string;
|
|
||||||
foreign_key?: string;
|
|
||||||
recursive_child_key?: string;
|
|
||||||
sql_joins?: string[];
|
|
||||||
join_aliases?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface RequestBody {
|
|
||||||
operation: Operation;
|
|
||||||
id?: number | string | string[];
|
|
||||||
data?: any | any[];
|
|
||||||
options?: Options;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare class ResolveSpecClient {
|
|
||||||
private config;
|
|
||||||
constructor(config: ClientConfig);
|
|
||||||
private buildUrl;
|
|
||||||
private baseHeaders;
|
|
||||||
private fetchWithError;
|
|
||||||
getMetadata(schema: string, entity: string): Promise<APIResponse<TableMetadata>>;
|
|
||||||
read<T = any>(schema: string, entity: string, id?: number | string | string[], options?: Options): Promise<APIResponse<T>>;
|
|
||||||
create<T = any>(schema: string, entity: string, data: any | any[], options?: Options): Promise<APIResponse<T>>;
|
|
||||||
update<T = any>(schema: string, entity: string, data: any | any[], id?: number | string | string[], options?: Options): Promise<APIResponse<T>>;
|
|
||||||
delete(schema: string, entity: string, id: number | string): Promise<APIResponse<void>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare type SortDirection = 'asc' | 'desc' | 'ASC' | 'DESC';
|
|
||||||
|
|
||||||
export declare interface SortOption {
|
|
||||||
column: string;
|
|
||||||
direction: SortDirection;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface Subscription {
|
|
||||||
id: string;
|
|
||||||
entity: string;
|
|
||||||
schema?: string;
|
|
||||||
options?: WSOptions;
|
|
||||||
callback?: (notification: WSNotificationMessage) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface SubscriptionOptions {
|
|
||||||
filters?: FilterOption[];
|
|
||||||
onNotification?: (notification: WSNotificationMessage) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface TableMetadata {
|
|
||||||
schema: string;
|
|
||||||
table: string;
|
|
||||||
columns: Column[];
|
|
||||||
relations: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare class WebSocketClient {
|
|
||||||
private ws;
|
|
||||||
private config;
|
|
||||||
private messageHandlers;
|
|
||||||
private subscriptions;
|
|
||||||
private eventListeners;
|
|
||||||
private state;
|
|
||||||
private reconnectAttempts;
|
|
||||||
private reconnectTimer;
|
|
||||||
private heartbeatTimer;
|
|
||||||
private isManualClose;
|
|
||||||
constructor(config: WebSocketClientConfig);
|
|
||||||
connect(): Promise<void>;
|
|
||||||
disconnect(): void;
|
|
||||||
request<T = any>(operation: WSOperation, entity: string, options?: {
|
|
||||||
schema?: string;
|
|
||||||
record_id?: string;
|
|
||||||
data?: any;
|
|
||||||
options?: WSOptions;
|
|
||||||
}): Promise<T>;
|
|
||||||
read<T = any>(entity: string, options?: {
|
|
||||||
schema?: string;
|
|
||||||
record_id?: string;
|
|
||||||
filters?: FilterOption[];
|
|
||||||
columns?: string[];
|
|
||||||
sort?: SortOption[];
|
|
||||||
preload?: PreloadOption[];
|
|
||||||
limit?: number;
|
|
||||||
offset?: number;
|
|
||||||
}): Promise<T>;
|
|
||||||
create<T = any>(entity: string, data: any, options?: {
|
|
||||||
schema?: string;
|
|
||||||
}): Promise<T>;
|
|
||||||
update<T = any>(entity: string, id: string, data: any, options?: {
|
|
||||||
schema?: string;
|
|
||||||
}): Promise<T>;
|
|
||||||
delete(entity: string, id: string, options?: {
|
|
||||||
schema?: string;
|
|
||||||
}): Promise<void>;
|
|
||||||
meta<T = any>(entity: string, options?: {
|
|
||||||
schema?: string;
|
|
||||||
}): Promise<T>;
|
|
||||||
subscribe(entity: string, callback: (notification: WSNotificationMessage) => void, options?: {
|
|
||||||
schema?: string;
|
|
||||||
filters?: FilterOption[];
|
|
||||||
}): Promise<string>;
|
|
||||||
unsubscribe(subscriptionId: string): Promise<void>;
|
|
||||||
getSubscriptions(): Subscription[];
|
|
||||||
getState(): ConnectionState;
|
|
||||||
isConnected(): boolean;
|
|
||||||
on<K extends keyof WebSocketClientEvents>(event: K, callback: WebSocketClientEvents[K]): void;
|
|
||||||
off<K extends keyof WebSocketClientEvents>(event: K): void;
|
|
||||||
private handleMessage;
|
|
||||||
private handleResponse;
|
|
||||||
private handleNotification;
|
|
||||||
private send;
|
|
||||||
private startHeartbeat;
|
|
||||||
private stopHeartbeat;
|
|
||||||
private setState;
|
|
||||||
private ensureConnected;
|
|
||||||
private emit;
|
|
||||||
private log;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface WebSocketClientConfig {
|
|
||||||
url: string;
|
|
||||||
reconnect?: boolean;
|
|
||||||
reconnectInterval?: number;
|
|
||||||
maxReconnectAttempts?: number;
|
|
||||||
heartbeatInterval?: number;
|
|
||||||
debug?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface WebSocketClientEvents {
|
|
||||||
connect: () => void;
|
|
||||||
disconnect: (event: CloseEvent) => void;
|
|
||||||
error: (error: Error) => void;
|
|
||||||
message: (message: WSMessage) => void;
|
|
||||||
stateChange: (state: ConnectionState) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface WSErrorInfo {
|
|
||||||
code: string;
|
|
||||||
message: string;
|
|
||||||
details?: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface WSMessage {
|
|
||||||
id?: string;
|
|
||||||
type: MessageType;
|
|
||||||
operation?: WSOperation;
|
|
||||||
schema?: string;
|
|
||||||
entity?: string;
|
|
||||||
record_id?: string;
|
|
||||||
data?: any;
|
|
||||||
options?: WSOptions;
|
|
||||||
subscription_id?: string;
|
|
||||||
success?: boolean;
|
|
||||||
error?: WSErrorInfo;
|
|
||||||
metadata?: Record<string, any>;
|
|
||||||
timestamp?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface WSNotificationMessage {
|
|
||||||
type: 'notification';
|
|
||||||
operation: WSOperation;
|
|
||||||
subscription_id: string;
|
|
||||||
schema?: string;
|
|
||||||
entity: string;
|
|
||||||
data: any;
|
|
||||||
timestamp: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare type WSOperation = 'read' | 'create' | 'update' | 'delete' | 'subscribe' | 'unsubscribe' | 'meta';
|
|
||||||
|
|
||||||
export declare interface WSOptions {
|
|
||||||
filters?: FilterOption[];
|
|
||||||
columns?: string[];
|
|
||||||
omit_columns?: string[];
|
|
||||||
preload?: PreloadOption[];
|
|
||||||
sort?: SortOption[];
|
|
||||||
limit?: number;
|
|
||||||
offset?: number;
|
|
||||||
parameters?: Parameter[];
|
|
||||||
cursor_forward?: string;
|
|
||||||
cursor_backward?: string;
|
|
||||||
fetch_row_number?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface WSRequestMessage {
|
|
||||||
id: string;
|
|
||||||
type: 'request';
|
|
||||||
operation: WSOperation;
|
|
||||||
schema?: string;
|
|
||||||
entity: string;
|
|
||||||
record_id?: string;
|
|
||||||
data?: any;
|
|
||||||
options?: WSOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface WSResponseMessage {
|
|
||||||
id: string;
|
|
||||||
type: 'response';
|
|
||||||
success: boolean;
|
|
||||||
data?: any;
|
|
||||||
error?: WSErrorInfo;
|
|
||||||
metadata?: Record<string, any>;
|
|
||||||
timestamp: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface WSSubscriptionMessage {
|
|
||||||
id: string;
|
|
||||||
type: 'subscription';
|
|
||||||
operation: 'subscribe' | 'unsubscribe';
|
|
||||||
schema?: string;
|
|
||||||
entity: string;
|
|
||||||
options?: WSOptions;
|
|
||||||
subscription_id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export { }
|
|
||||||
Vendored
+226
-278
@@ -1,72 +1,76 @@
|
|||||||
import { v4 as l } from "uuid";
|
import { v4 as e } from "uuid";
|
||||||
function u(...r) {
|
import { b64DecodeUnicode as t, b64EncodeUnicode as n } from "@warkypublic/artemis-kit/base64";
|
||||||
const e = {};
|
//#region src/common/http.ts
|
||||||
for (const t of r)
|
function r(...e) {
|
||||||
for (const [s, n] of Object.entries(t)) {
|
let t = {};
|
||||||
for (const i of Object.keys(e))
|
for (let n of e) for (let [e, r] of Object.entries(n)) {
|
||||||
i.toLowerCase() === s.toLowerCase() && delete e[i];
|
for (let n of Object.keys(t)) n.toLowerCase() === e.toLowerCase() && delete t[n];
|
||||||
Object.defineProperty(e, s, { value: n, enumerable: !0, configurable: !0, writable: !0 });
|
Object.defineProperty(t, e, {
|
||||||
|
value: r,
|
||||||
|
enumerable: !0,
|
||||||
|
configurable: !0,
|
||||||
|
writable: !0
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return e;
|
return t;
|
||||||
}
|
}
|
||||||
function m(r) {
|
function i(e) {
|
||||||
return u(
|
return r({ "Content-Type": "application/json" }, e.headers ?? {}, e.token ? { Authorization: `Bearer ${e.token}` } : {});
|
||||||
{ "Content-Type": "application/json" },
|
|
||||||
r.headers ?? {},
|
|
||||||
r.token ? { Authorization: `Bearer ${r.token}` } : {}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
function p(r) {
|
function a(e) {
|
||||||
const e = Object.entries(m(r)).map(([t, s]) => [t.toLowerCase(), s]).sort(([t], [s]) => t.localeCompare(s));
|
let t = Object.entries(i(e)).map(([e, t]) => [e.toLowerCase(), t]).sort(([e], [t]) => e.localeCompare(t));
|
||||||
return JSON.stringify([r.baseUrl, e]);
|
return JSON.stringify([e.baseUrl, t]);
|
||||||
}
|
}
|
||||||
const f = /* @__PURE__ */ new Map();
|
//#endregion
|
||||||
function v(r) {
|
//#region src/resolvespec/client.ts
|
||||||
const e = p(r);
|
var o = /* @__PURE__ */ new Map();
|
||||||
let t = f.get(e);
|
function s(e) {
|
||||||
return t || (t = new y(r), f.set(e, t)), t;
|
let t = a(e), n = o.get(t);
|
||||||
|
return n || (n = new c(e), o.set(t, n)), n;
|
||||||
}
|
}
|
||||||
class y {
|
var c = class {
|
||||||
constructor(e) {
|
constructor(e) {
|
||||||
this.config = { ...e, headers: { ...e.headers } };
|
this.config = {
|
||||||
|
...e,
|
||||||
|
headers: { ...e.headers }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
buildUrl(e, t, s) {
|
buildUrl(e, t, n) {
|
||||||
let n = `${this.config.baseUrl}/${e}/${t}`;
|
let r = `${this.config.baseUrl}/${e}/${t}`;
|
||||||
return s && (n += `/${s}`), n;
|
return n && (r += `/${n}`), r;
|
||||||
}
|
}
|
||||||
baseHeaders() {
|
baseHeaders() {
|
||||||
return m(this.config);
|
return i(this.config);
|
||||||
}
|
}
|
||||||
async fetchWithError(e, t) {
|
async fetchWithError(e, t) {
|
||||||
const s = await fetch(e, t), n = await s.json();
|
let n = await fetch(e, t), r = await n.json();
|
||||||
if (!s.ok)
|
if (!n.ok) throw Error(r.error?.message || "An error occurred");
|
||||||
throw new Error(n.error?.message || "An error occurred");
|
return r;
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
async getMetadata(e, t) {
|
async getMetadata(e, t) {
|
||||||
const s = this.buildUrl(e, t);
|
let n = this.buildUrl(e, t);
|
||||||
return this.fetchWithError(s, {
|
return this.fetchWithError(n, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: this.baseHeaders()
|
headers: this.baseHeaders()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async read(e, t, s, n) {
|
async read(e, t, n, r) {
|
||||||
const i = typeof s == "number" || typeof s == "string" ? String(s) : void 0, a = this.buildUrl(e, t, i), c = {
|
let i = typeof n == "number" || typeof n == "string" ? String(n) : void 0, a = this.buildUrl(e, t, i), o = {
|
||||||
operation: "read",
|
operation: "read",
|
||||||
id: Array.isArray(s) ? s : void 0,
|
id: Array.isArray(n) ? n : void 0,
|
||||||
options: n
|
options: r
|
||||||
};
|
};
|
||||||
return this.fetchWithError(a, {
|
return this.fetchWithError(a, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: this.baseHeaders(),
|
headers: this.baseHeaders(),
|
||||||
body: JSON.stringify(c)
|
body: JSON.stringify(o)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async create(e, t, s, n) {
|
async create(e, t, n, r) {
|
||||||
const i = this.buildUrl(e, t), a = {
|
let i = this.buildUrl(e, t), a = {
|
||||||
operation: "create",
|
operation: "create",
|
||||||
data: s,
|
data: n,
|
||||||
options: n
|
options: r
|
||||||
};
|
};
|
||||||
return this.fetchWithError(i, {
|
return this.fetchWithError(i, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -74,37 +78,33 @@ class y {
|
|||||||
body: JSON.stringify(a)
|
body: JSON.stringify(a)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async update(e, t, s, n, i) {
|
async update(e, t, n, r, i) {
|
||||||
const a = typeof n == "number" || typeof n == "string" ? String(n) : void 0, c = this.buildUrl(e, t, a), o = {
|
let a = typeof r == "number" || typeof r == "string" ? String(r) : void 0, o = this.buildUrl(e, t, a), s = {
|
||||||
operation: "update",
|
operation: "update",
|
||||||
id: Array.isArray(n) ? n : void 0,
|
id: Array.isArray(r) ? r : void 0,
|
||||||
data: s,
|
data: n,
|
||||||
options: i
|
options: i
|
||||||
};
|
};
|
||||||
return this.fetchWithError(c, {
|
return this.fetchWithError(o, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: this.baseHeaders(),
|
headers: this.baseHeaders(),
|
||||||
body: JSON.stringify(o)
|
body: JSON.stringify(s)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async delete(e, t, s) {
|
async delete(e, t, n) {
|
||||||
const n = this.buildUrl(e, t, String(s)), i = {
|
let r = this.buildUrl(e, t, String(n));
|
||||||
operation: "delete"
|
return this.fetchWithError(r, {
|
||||||
};
|
|
||||||
return this.fetchWithError(n, {
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: this.baseHeaders(),
|
headers: this.baseHeaders(),
|
||||||
body: JSON.stringify(i)
|
body: JSON.stringify({ operation: "delete" })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}, l = /* @__PURE__ */ new Map();
|
||||||
|
function u(e) {
|
||||||
|
let t = e.url, n = l.get(t);
|
||||||
|
return n || (n = new d(e), l.set(t, n)), n;
|
||||||
}
|
}
|
||||||
const b = /* @__PURE__ */ new Map();
|
var d = class {
|
||||||
function O(r) {
|
|
||||||
const e = r.url;
|
|
||||||
let t = b.get(e);
|
|
||||||
return t || (t = new S(r), b.set(e, t)), t;
|
|
||||||
}
|
|
||||||
class S {
|
|
||||||
constructor(e) {
|
constructor(e) {
|
||||||
this.ws = null, this.messageHandlers = /* @__PURE__ */ new Map(), this.subscriptions = /* @__PURE__ */ new Map(), this.eventListeners = {}, this.state = "disconnected", this.reconnectAttempts = 0, this.reconnectTimer = null, this.heartbeatTimer = null, this.isManualClose = !1, this.config = {
|
this.ws = null, this.messageHandlers = /* @__PURE__ */ new Map(), this.subscriptions = /* @__PURE__ */ new Map(), this.eventListeners = {}, this.state = "disconnected", this.reconnectAttempts = 0, this.reconnectTimer = null, this.heartbeatTimer = null, this.isManualClose = !1, this.config = {
|
||||||
url: e.url,
|
url: e.url,
|
||||||
@@ -124,44 +124,44 @@ class S {
|
|||||||
try {
|
try {
|
||||||
this.ws = new WebSocket(this.config.url), this.ws.onopen = () => {
|
this.ws = new WebSocket(this.config.url), this.ws.onopen = () => {
|
||||||
this.log("Connected to WebSocket server"), this.setState("connected"), this.reconnectAttempts = 0, this.startHeartbeat(), this.emit("connect"), e();
|
this.log("Connected to WebSocket server"), this.setState("connected"), this.reconnectAttempts = 0, this.startHeartbeat(), this.emit("connect"), e();
|
||||||
}, this.ws.onmessage = (s) => {
|
}, this.ws.onmessage = (e) => {
|
||||||
this.handleMessage(s.data);
|
this.handleMessage(e.data);
|
||||||
}, this.ws.onerror = (s) => {
|
}, this.ws.onerror = (e) => {
|
||||||
this.log("WebSocket error:", s);
|
this.log("WebSocket error:", e);
|
||||||
const n = new Error("WebSocket connection error");
|
let n = /* @__PURE__ */ Error("WebSocket connection error");
|
||||||
this.emit("error", n), t(n);
|
this.emit("error", n), t(n);
|
||||||
}, this.ws.onclose = (s) => {
|
}, this.ws.onclose = (e) => {
|
||||||
this.log("WebSocket closed:", s.code, s.reason), this.stopHeartbeat(), this.setState("disconnected"), this.emit("disconnect", s), this.config.reconnect && !this.isManualClose && this.reconnectAttempts < this.config.maxReconnectAttempts && (this.reconnectAttempts++, this.log(`Reconnection attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts}`), this.setState("reconnecting"), this.reconnectTimer = setTimeout(() => {
|
this.log("WebSocket closed:", e.code, e.reason), this.stopHeartbeat(), this.setState("disconnected"), this.emit("disconnect", e), this.config.reconnect && !this.isManualClose && this.reconnectAttempts < this.config.maxReconnectAttempts && (this.reconnectAttempts++, this.log(`Reconnection attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts}`), this.setState("reconnecting"), this.reconnectTimer = setTimeout(() => {
|
||||||
this.connect().catch((n) => {
|
this.connect().catch((e) => {
|
||||||
this.log("Reconnection failed:", n);
|
this.log("Reconnection failed:", e);
|
||||||
});
|
});
|
||||||
}, this.config.reconnectInterval));
|
}, this.config.reconnectInterval));
|
||||||
};
|
};
|
||||||
} catch (s) {
|
} catch (e) {
|
||||||
t(s);
|
t(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
disconnect() {
|
disconnect() {
|
||||||
this.isManualClose = !0, this.reconnectTimer && (clearTimeout(this.reconnectTimer), this.reconnectTimer = null), this.stopHeartbeat(), this.ws && (this.setState("disconnecting"), this.ws.close(), this.ws = null), this.setState("disconnected"), this.messageHandlers.clear();
|
this.isManualClose = !0, this.reconnectTimer &&= (clearTimeout(this.reconnectTimer), null), this.stopHeartbeat(), this.ws &&= (this.setState("disconnecting"), this.ws.close(), null), this.setState("disconnected"), this.messageHandlers.clear();
|
||||||
}
|
}
|
||||||
async request(e, t, s) {
|
async request(t, n, r) {
|
||||||
this.ensureConnected();
|
this.ensureConnected();
|
||||||
const n = l(), i = {
|
let i = e(), a = {
|
||||||
id: n,
|
id: i,
|
||||||
type: "request",
|
type: "request",
|
||||||
operation: e,
|
operation: t,
|
||||||
entity: t,
|
entity: n,
|
||||||
schema: s?.schema,
|
schema: r?.schema,
|
||||||
record_id: s?.record_id,
|
record_id: r?.record_id,
|
||||||
data: s?.data,
|
data: r?.data,
|
||||||
options: s?.options
|
options: r?.options
|
||||||
};
|
};
|
||||||
return new Promise((a, c) => {
|
return new Promise((e, t) => {
|
||||||
this.messageHandlers.set(n, (o) => {
|
this.messageHandlers.set(i, (n) => {
|
||||||
o.success ? a(o.data) : c(new Error(o.error?.message || "Request failed"));
|
n.success ? e(n.data) : t(Error(n.error?.message || "Request failed"));
|
||||||
}), this.send(i), setTimeout(() => {
|
}), this.send(a), setTimeout(() => {
|
||||||
this.messageHandlers.has(n) && (this.messageHandlers.delete(n), c(new Error("Request timeout")));
|
this.messageHandlers.has(i) && (this.messageHandlers.delete(i), t(/* @__PURE__ */ Error("Request timeout")));
|
||||||
}, 3e4);
|
}, 3e4);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -179,73 +179,68 @@ class S {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async create(e, t, s) {
|
async create(e, t, n) {
|
||||||
return this.request("create", e, {
|
return this.request("create", e, {
|
||||||
schema: s?.schema,
|
schema: n?.schema,
|
||||||
data: t
|
data: t
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async update(e, t, s, n) {
|
async update(e, t, n, r) {
|
||||||
return this.request("update", e, {
|
return this.request("update", e, {
|
||||||
schema: n?.schema,
|
schema: r?.schema,
|
||||||
record_id: t,
|
record_id: t,
|
||||||
data: s
|
data: n
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async delete(e, t, s) {
|
async delete(e, t, n) {
|
||||||
await this.request("delete", e, {
|
await this.request("delete", e, {
|
||||||
schema: s?.schema,
|
schema: n?.schema,
|
||||||
record_id: t
|
record_id: t
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async meta(e, t) {
|
async meta(e, t) {
|
||||||
return this.request("meta", e, {
|
return this.request("meta", e, { schema: t?.schema });
|
||||||
schema: t?.schema
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
async subscribe(e, t, s) {
|
async subscribe(t, n, r) {
|
||||||
this.ensureConnected();
|
this.ensureConnected();
|
||||||
const n = l(), i = {
|
let i = e(), a = {
|
||||||
id: n,
|
id: i,
|
||||||
type: "subscription",
|
type: "subscription",
|
||||||
operation: "subscribe",
|
operation: "subscribe",
|
||||||
entity: e,
|
entity: t,
|
||||||
schema: s?.schema,
|
schema: r?.schema,
|
||||||
options: {
|
options: { filters: r?.filters }
|
||||||
filters: s?.filters
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
return new Promise((a, c) => {
|
return new Promise((e, o) => {
|
||||||
this.messageHandlers.set(n, (o) => {
|
this.messageHandlers.set(i, (i) => {
|
||||||
if (o.success && o.data?.subscription_id) {
|
if (i.success && i.data?.subscription_id) {
|
||||||
const h = o.data.subscription_id;
|
let a = i.data.subscription_id;
|
||||||
this.subscriptions.set(h, {
|
this.subscriptions.set(a, {
|
||||||
id: h,
|
id: a,
|
||||||
entity: e,
|
entity: t,
|
||||||
schema: s?.schema,
|
schema: r?.schema,
|
||||||
options: { filters: s?.filters },
|
options: { filters: r?.filters },
|
||||||
callback: t
|
callback: n
|
||||||
}), this.log(`Subscribed to ${e} with ID: ${h}`), a(h);
|
}), this.log(`Subscribed to ${t} with ID: ${a}`), e(a);
|
||||||
} else
|
} else o(Error(i.error?.message || "Subscription failed"));
|
||||||
c(new Error(o.error?.message || "Subscription failed"));
|
}), this.send(a), setTimeout(() => {
|
||||||
}), this.send(i), setTimeout(() => {
|
this.messageHandlers.has(i) && (this.messageHandlers.delete(i), o(/* @__PURE__ */ Error("Subscription timeout")));
|
||||||
this.messageHandlers.has(n) && (this.messageHandlers.delete(n), c(new Error("Subscription timeout")));
|
|
||||||
}, 1e4);
|
}, 1e4);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async unsubscribe(e) {
|
async unsubscribe(t) {
|
||||||
this.ensureConnected();
|
this.ensureConnected();
|
||||||
const t = l(), s = {
|
let n = e(), r = {
|
||||||
id: t,
|
id: n,
|
||||||
type: "subscription",
|
type: "subscription",
|
||||||
operation: "unsubscribe",
|
operation: "unsubscribe",
|
||||||
subscription_id: e
|
subscription_id: t
|
||||||
};
|
};
|
||||||
return new Promise((n, i) => {
|
return new Promise((e, i) => {
|
||||||
this.messageHandlers.set(t, (a) => {
|
this.messageHandlers.set(n, (n) => {
|
||||||
a.success ? (this.subscriptions.delete(e), this.log(`Unsubscribed from ${e}`), n()) : i(new Error(a.error?.message || "Unsubscribe failed"));
|
n.success ? (this.subscriptions.delete(t), this.log(`Unsubscribed from ${t}`), e()) : i(Error(n.error?.message || "Unsubscribe failed"));
|
||||||
}), this.send(s), setTimeout(() => {
|
}), this.send(r), setTimeout(() => {
|
||||||
this.messageHandlers.has(t) && (this.messageHandlers.delete(t), i(new Error("Unsubscribe timeout")));
|
this.messageHandlers.has(n) && (this.messageHandlers.delete(n), i(/* @__PURE__ */ Error("Unsubscribe timeout")));
|
||||||
}, 1e4);
|
}, 1e4);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -264,10 +259,9 @@ class S {
|
|||||||
off(e) {
|
off(e) {
|
||||||
delete this.eventListeners[e];
|
delete this.eventListeners[e];
|
||||||
}
|
}
|
||||||
// Private methods
|
|
||||||
handleMessage(e) {
|
handleMessage(e) {
|
||||||
try {
|
try {
|
||||||
const t = JSON.parse(e);
|
let t = JSON.parse(e);
|
||||||
switch (this.log("Received message:", t), this.emit("message", t), t.type) {
|
switch (this.log("Received message:", t), this.emit("message", t), t.type) {
|
||||||
case "response":
|
case "response":
|
||||||
this.handleResponse(t);
|
this.handleResponse(t);
|
||||||
@@ -275,210 +269,164 @@ class S {
|
|||||||
case "notification":
|
case "notification":
|
||||||
this.handleNotification(t);
|
this.handleNotification(t);
|
||||||
break;
|
break;
|
||||||
case "pong":
|
case "pong": break;
|
||||||
break;
|
default: this.log("Unknown message type:", t.type);
|
||||||
default:
|
|
||||||
this.log("Unknown message type:", t.type);
|
|
||||||
}
|
}
|
||||||
} catch (t) {
|
} catch (e) {
|
||||||
this.log("Error parsing message:", t);
|
this.log("Error parsing message:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleResponse(e) {
|
handleResponse(e) {
|
||||||
const t = this.messageHandlers.get(e.id);
|
let t = this.messageHandlers.get(e.id);
|
||||||
t && (t(e), this.messageHandlers.delete(e.id));
|
t && (t(e), this.messageHandlers.delete(e.id));
|
||||||
}
|
}
|
||||||
handleNotification(e) {
|
handleNotification(e) {
|
||||||
const t = this.subscriptions.get(e.subscription_id);
|
let t = this.subscriptions.get(e.subscription_id);
|
||||||
t?.callback && t.callback(e);
|
t?.callback && t.callback(e);
|
||||||
}
|
}
|
||||||
send(e) {
|
send(e) {
|
||||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw Error("WebSocket is not connected");
|
||||||
throw new Error("WebSocket is not connected");
|
let t = JSON.stringify(e);
|
||||||
const t = JSON.stringify(e);
|
|
||||||
this.log("Sending message:", e), this.ws.send(t);
|
this.log("Sending message:", e), this.ws.send(t);
|
||||||
}
|
}
|
||||||
startHeartbeat() {
|
startHeartbeat() {
|
||||||
this.heartbeatTimer || (this.heartbeatTimer = setInterval(() => {
|
this.heartbeatTimer ||= setInterval(() => {
|
||||||
if (this.isConnected()) {
|
if (this.isConnected()) {
|
||||||
const e = {
|
let t = {
|
||||||
id: l(),
|
id: e(),
|
||||||
type: "ping"
|
type: "ping"
|
||||||
};
|
};
|
||||||
this.send(e);
|
this.send(t);
|
||||||
}
|
}
|
||||||
}, this.config.heartbeatInterval));
|
}, this.config.heartbeatInterval);
|
||||||
}
|
}
|
||||||
stopHeartbeat() {
|
stopHeartbeat() {
|
||||||
this.heartbeatTimer && (clearInterval(this.heartbeatTimer), this.heartbeatTimer = null);
|
this.heartbeatTimer &&= (clearInterval(this.heartbeatTimer), null);
|
||||||
}
|
}
|
||||||
setState(e) {
|
setState(e) {
|
||||||
this.state !== e && (this.state = e, this.emit("stateChange", e));
|
this.state !== e && (this.state = e, this.emit("stateChange", e));
|
||||||
}
|
}
|
||||||
ensureConnected() {
|
ensureConnected() {
|
||||||
if (!this.isConnected())
|
if (!this.isConnected()) throw Error("WebSocket is not connected. Call connect() first.");
|
||||||
throw new Error("WebSocket is not connected. Call connect() first.");
|
|
||||||
}
|
}
|
||||||
emit(e, ...t) {
|
emit(e, ...t) {
|
||||||
const s = this.eventListeners[e];
|
let n = this.eventListeners[e];
|
||||||
s && s(...t);
|
n && n(...t);
|
||||||
}
|
}
|
||||||
log(...e) {
|
log(...e) {
|
||||||
this.config.debug && console.log("[WebSocketClient]", ...e);
|
this.config.debug && console.log("[WebSocketClient]", ...e);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
//#endregion
|
||||||
|
//#region src/headerspec/client.ts
|
||||||
|
function f(e) {
|
||||||
|
return "ZIP_" + n(e);
|
||||||
}
|
}
|
||||||
function W(r) {
|
function p(e) {
|
||||||
return typeof btoa == "function" ? "ZIP_" + btoa(r) : "ZIP_" + Buffer.from(r, "utf-8").toString("base64");
|
let t = e;
|
||||||
|
return t.startsWith("ZIP_") ? (t = t.slice(4).replace(/[\n\r ]/g, ""), t = m(t)) : t.startsWith("__") && (t = t.slice(2).replace(/[\n\r ]/g, ""), t = m(t)), (t.startsWith("ZIP_") || t.startsWith("__")) && (t = p(t)), t;
|
||||||
}
|
}
|
||||||
function H(r) {
|
function m(e) {
|
||||||
let e = r;
|
return t(e);
|
||||||
return e.startsWith("ZIP_") ? (e = e.slice(4).replace(/[\n\r ]/g, ""), e = g(e)) : e.startsWith("__") && (e = e.slice(2).replace(/[\n\r ]/g, ""), e = g(e)), (e.startsWith("ZIP_") || e.startsWith("__")) && (e = H(e)), e;
|
|
||||||
}
|
}
|
||||||
function g(r) {
|
function h(e) {
|
||||||
return typeof atob == "function" ? atob(r) : Buffer.from(r, "base64").toString("utf-8");
|
let t = {};
|
||||||
}
|
if (e.columns?.length && (t["X-Select-Fields"] = e.columns.join(",")), e.omit_columns?.length && (t["X-Not-Select-Fields"] = e.omit_columns.join(",")), e.filters?.length) for (let n of e.filters) {
|
||||||
function d(r) {
|
let e = n.logic_operator ?? "AND", r = g(n.operator), i = _(n);
|
||||||
const e = {};
|
n.operator === "eq" && e === "AND" ? t[`X-FieldFilter-${n.column}`] = i : e === "OR" ? t[`X-SearchOr-${r}-${n.column}`] = i : t[`X-SearchOp-${r}-${n.column}`] = i;
|
||||||
if (r.columns?.length && (e["X-Select-Fields"] = r.columns.join(",")), r.omit_columns?.length && (e["X-Not-Select-Fields"] = r.omit_columns.join(",")), r.filters?.length)
|
|
||||||
for (const t of r.filters) {
|
|
||||||
const s = t.logic_operator ?? "AND", n = C(t.operator), i = E(t);
|
|
||||||
t.operator === "eq" && s === "AND" ? e[`X-FieldFilter-${t.column}`] = i : s === "OR" ? e[`X-SearchOr-${n}-${t.column}`] = i : e[`X-SearchOp-${n}-${t.column}`] = i;
|
|
||||||
}
|
}
|
||||||
if (r.sort?.length) {
|
if (e.sort?.length && (t["X-Sort"] = e.sort.map((e) => e.direction.toUpperCase() === "DESC" ? `-${e.column}` : `+${e.column}`).join(",")), e.limit !== void 0 && (t["X-Limit"] = String(e.limit)), e.offset !== void 0 && (t["X-Offset"] = String(e.offset)), e.cursor_forward && (t["X-Cursor-Forward"] = e.cursor_forward), e.cursor_backward && (t["X-Cursor-Backward"] = e.cursor_backward), e.preload?.length && (t["X-Preload"] = e.preload.map((e) => e.columns?.length ? `${e.relation}:${e.columns.join(",")}` : e.relation).join("|")), e.fetch_row_number && (t["X-Fetch-RowNumber"] = e.fetch_row_number), e.computedColumns?.length) for (let n of e.computedColumns) t[`X-CQL-SEL-${n.name}`] = n.expression;
|
||||||
const t = r.sort.map((s) => s.direction.toUpperCase() === "DESC" ? `-${s.column}` : `+${s.column}`);
|
return e.customOperators?.length && (t["X-Custom-SQL-W"] = e.customOperators.map((e) => e.sql).join(" AND ")), t;
|
||||||
e["X-Sort"] = t.join(",");
|
|
||||||
}
|
|
||||||
if (r.limit !== void 0 && (e["X-Limit"] = String(r.limit)), r.offset !== void 0 && (e["X-Offset"] = String(r.offset)), r.cursor_forward && (e["X-Cursor-Forward"] = r.cursor_forward), r.cursor_backward && (e["X-Cursor-Backward"] = r.cursor_backward), r.preload?.length) {
|
|
||||||
const t = r.preload.map((s) => s.columns?.length ? `${s.relation}:${s.columns.join(",")}` : s.relation);
|
|
||||||
e["X-Preload"] = t.join("|");
|
|
||||||
}
|
|
||||||
if (r.fetch_row_number && (e["X-Fetch-RowNumber"] = r.fetch_row_number), r.computedColumns?.length)
|
|
||||||
for (const t of r.computedColumns)
|
|
||||||
e[`X-CQL-SEL-${t.name}`] = t.expression;
|
|
||||||
if (r.customOperators?.length) {
|
|
||||||
const t = r.customOperators.map(
|
|
||||||
(s) => s.sql
|
|
||||||
);
|
|
||||||
e["X-Custom-SQL-W"] = t.join(" AND ");
|
|
||||||
}
|
|
||||||
return e;
|
|
||||||
}
|
}
|
||||||
function C(r) {
|
function g(e) {
|
||||||
switch (r) {
|
switch (e) {
|
||||||
case "eq":
|
case "eq": return "equals";
|
||||||
return "equals";
|
case "neq": return "notequals";
|
||||||
case "neq":
|
case "gt": return "greaterthan";
|
||||||
return "notequals";
|
case "gte": return "greaterthanorequal";
|
||||||
case "gt":
|
case "lt": return "lessthan";
|
||||||
return "greaterthan";
|
case "lte": return "lessthanorequal";
|
||||||
case "gte":
|
|
||||||
return "greaterthanorequal";
|
|
||||||
case "lt":
|
|
||||||
return "lessthan";
|
|
||||||
case "lte":
|
|
||||||
return "lessthanorequal";
|
|
||||||
case "like":
|
case "like":
|
||||||
case "ilike":
|
case "ilike":
|
||||||
case "contains":
|
case "contains": return "contains";
|
||||||
return "contains";
|
case "startswith": return "beginswith";
|
||||||
case "startswith":
|
case "endswith": return "endswith";
|
||||||
return "beginswith";
|
case "in": return "in";
|
||||||
case "endswith":
|
case "between": return "between";
|
||||||
return "endswith";
|
case "between_inclusive": return "betweeninclusive";
|
||||||
case "in":
|
case "is_null": return "empty";
|
||||||
return "in";
|
case "is_not_null": return "notempty";
|
||||||
case "between":
|
default: return e;
|
||||||
return "between";
|
|
||||||
case "between_inclusive":
|
|
||||||
return "betweeninclusive";
|
|
||||||
case "is_null":
|
|
||||||
return "empty";
|
|
||||||
case "is_not_null":
|
|
||||||
return "notempty";
|
|
||||||
default:
|
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function E(r) {
|
function _(e) {
|
||||||
return r.value === null || r.value === void 0 ? "" : Array.isArray(r.value) ? r.value.join(",") : String(r.value);
|
return e.value === null || e.value === void 0 ? "" : Array.isArray(e.value) ? e.value.join(",") : String(e.value);
|
||||||
}
|
}
|
||||||
const w = /* @__PURE__ */ new Map();
|
var v = /* @__PURE__ */ new Map();
|
||||||
function T(r) {
|
function y(e) {
|
||||||
const e = p(r);
|
let t = a(e), n = v.get(t);
|
||||||
let t = w.get(e);
|
return n || (n = new b(e), v.set(t, n)), n;
|
||||||
return t || (t = new _(r), w.set(e, t)), t;
|
|
||||||
}
|
}
|
||||||
class _ {
|
var b = class {
|
||||||
constructor(e) {
|
constructor(e) {
|
||||||
this.config = { ...e, headers: { ...e.headers } };
|
this.config = {
|
||||||
|
...e,
|
||||||
|
headers: { ...e.headers }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
buildUrl(e, t, s) {
|
buildUrl(e, t, n) {
|
||||||
let n = `${this.config.baseUrl}/${e}/${t}`;
|
let r = `${this.config.baseUrl}/${e}/${t}`;
|
||||||
return s && (n += `/${s}`), n;
|
return n && (r += `/${n}`), r;
|
||||||
}
|
}
|
||||||
baseHeaders() {
|
baseHeaders() {
|
||||||
return m(this.config);
|
return i(this.config);
|
||||||
}
|
}
|
||||||
async fetchWithError(e, t) {
|
async fetchWithError(e, t) {
|
||||||
const s = await fetch(e, t), n = await s.json();
|
let n = await fetch(e, t), r = await n.json();
|
||||||
if (!s.ok)
|
if (!n.ok) throw Error(r.error?.message || `${n.statusText} (${n.status})`);
|
||||||
throw new Error(
|
|
||||||
n.error?.message || `${s.statusText} (${s.status})`
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
data: n,
|
data: r,
|
||||||
success: !0,
|
success: !0,
|
||||||
error: n.error ? n.error : void 0,
|
error: r.error ? r.error : void 0,
|
||||||
metadata: {
|
metadata: {
|
||||||
count: s.headers.get("content-range") ? Number(s.headers.get("content-range")?.split("/")[1]) : 0,
|
count: n.headers.get("content-range") ? Number(n.headers.get("content-range")?.split("/")[1]) : 0,
|
||||||
total: s.headers.get("content-range") ? Number(s.headers.get("content-range")?.split("/")[1]) : 0,
|
total: n.headers.get("content-range") ? Number(n.headers.get("content-range")?.split("/")[1]) : 0,
|
||||||
filtered: s.headers.get("content-range") ? Number(s.headers.get("content-range")?.split("/")[1]) : 0,
|
filtered: n.headers.get("content-range") ? Number(n.headers.get("content-range")?.split("/")[1]) : 0,
|
||||||
offset: s.headers.get("content-range") ? Number(
|
offset: n.headers.get("content-range") ? Number(n.headers.get("content-range")?.split("/")[0].split("-")[0]) : 0,
|
||||||
s.headers.get("content-range")?.split("/")[0].split("-")[0]
|
limit: n.headers.get("x-limit") ? Number(n.headers.get("x-limit")) : 0
|
||||||
) : 0,
|
|
||||||
limit: s.headers.get("x-limit") ? Number(s.headers.get("x-limit")) : 0
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async read(e, t, s, n) {
|
async read(e, t, n, i) {
|
||||||
const i = this.buildUrl(e, t, s), a = n ? d(n) : {};
|
let a = this.buildUrl(e, t, n), o = i ? h(i) : {};
|
||||||
return this.fetchWithError(i, {
|
|
||||||
method: "GET",
|
|
||||||
headers: u(this.baseHeaders(), a)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async create(e, t, s, n) {
|
|
||||||
const i = this.buildUrl(e, t), a = n ? d(n) : {};
|
|
||||||
return this.fetchWithError(i, {
|
|
||||||
method: "POST",
|
|
||||||
headers: u(this.baseHeaders(), a),
|
|
||||||
body: JSON.stringify(s)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async update(e, t, s, n, i) {
|
|
||||||
const a = this.buildUrl(e, t, s), c = i ? d(i) : {};
|
|
||||||
return this.fetchWithError(a, {
|
return this.fetchWithError(a, {
|
||||||
method: "PUT",
|
method: "GET",
|
||||||
headers: u(this.baseHeaders(), c),
|
headers: r(this.baseHeaders(), o)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async create(e, t, n, i) {
|
||||||
|
let a = this.buildUrl(e, t), o = i ? h(i) : {};
|
||||||
|
return this.fetchWithError(a, {
|
||||||
|
method: "POST",
|
||||||
|
headers: r(this.baseHeaders(), o),
|
||||||
body: JSON.stringify(n)
|
body: JSON.stringify(n)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async delete(e, t, s) {
|
async update(e, t, n, i, a) {
|
||||||
const n = this.buildUrl(e, t, s);
|
let o = this.buildUrl(e, t, n), s = a ? h(a) : {};
|
||||||
return this.fetchWithError(n, {
|
return this.fetchWithError(o, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: r(this.baseHeaders(), s),
|
||||||
|
body: JSON.stringify(i)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async delete(e, t, n) {
|
||||||
|
let r = this.buildUrl(e, t, n);
|
||||||
|
return this.fetchWithError(r, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: this.baseHeaders()
|
headers: this.baseHeaders()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
export {
|
|
||||||
_ as HeaderSpecClient,
|
|
||||||
y as ResolveSpecClient,
|
|
||||||
S as WebSocketClient,
|
|
||||||
d as buildHeaders,
|
|
||||||
H as decodeHeaderValue,
|
|
||||||
W as encodeHeaderValue,
|
|
||||||
T as getHeaderSpecClient,
|
|
||||||
v as getResolveSpecClient,
|
|
||||||
O as getWebSocketClient
|
|
||||||
};
|
};
|
||||||
|
//#endregion
|
||||||
|
export { b as HeaderSpecClient, c as ResolveSpecClient, d as WebSocketClient, h as buildHeaders, p as decodeHeaderValue, f as encodeHeaderValue, y as getHeaderSpecClient, s as getResolveSpecClient, u as getWebSocketClient };
|
||||||
|
|||||||
+13
-11
@@ -38,20 +38,22 @@
|
|||||||
"author": "Hein (Warkanum) Puth",
|
"author": "Hein (Warkanum) Puth",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"uuid": "^13.0.0"
|
"@warkypublic/artemis-kit": "^1.0.10",
|
||||||
|
"uuid": "^14.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.29.8",
|
"@changesets/cli": "^3.0.3",
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@types/jsdom": "^27.0.0",
|
"@types/jsdom": "^30.0.0",
|
||||||
"eslint": "^10.0.0",
|
"@types/node": "^26.6.2",
|
||||||
"globals": "^17.3.0",
|
"eslint": "^10.11.0",
|
||||||
"jsdom": "^28.1.0",
|
"globals": "^17.12.0",
|
||||||
"typescript": "^5.9.3",
|
"jsdom": "^30.1.1",
|
||||||
"typescript-eslint": "^8.55.0",
|
"typescript": "^6.0.3",
|
||||||
"vite": "^7.3.1",
|
"typescript-eslint": "^8.70.1",
|
||||||
"vite-plugin-dts": "^4.5.4",
|
"vite": "^8.3.0",
|
||||||
"vitest": "^4.0.18"
|
"vite-plugin-dts": "^5.1.1",
|
||||||
|
"vitest": "^5.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
|||||||
Generated
+1283
-1293
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
|||||||
|
packages:
|
||||||
|
- '.'
|
||||||
|
|
||||||
|
allowBuilds:
|
||||||
|
esbuild: true
|
||||||
@@ -126,11 +126,22 @@ describe('encodeHeaderValue / decodeHeaderValue', () => {
|
|||||||
expect(decoded).toBe(original);
|
expect(decoded).toBe(original);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should round-trip UTF-8 values', () => {
|
||||||
|
const original = 'café ☕ 你好';
|
||||||
|
expect(decodeHeaderValue(encodeHeaderValue(original))).toBe(original);
|
||||||
|
});
|
||||||
|
|
||||||
it('should decode __ prefixed values', () => {
|
it('should decode __ prefixed values', () => {
|
||||||
const encoded = '__' + btoa('hello');
|
const encoded = '__' + btoa('hello');
|
||||||
expect(decodeHeaderValue(encoded)).toBe('hello');
|
expect(decodeHeaderValue(encoded)).toBe('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should decode UTF-8 values with the __ prefix', () => {
|
||||||
|
const bytes = new TextEncoder().encode('café ☕');
|
||||||
|
const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');
|
||||||
|
expect(decodeHeaderValue('__' + btoa(binary))).toBe('café ☕');
|
||||||
|
});
|
||||||
|
|
||||||
it('should return plain values as-is', () => {
|
it('should return plain values as-is', () => {
|
||||||
expect(decodeHeaderValue('plain')).toBe('plain');
|
expect(decodeHeaderValue('plain')).toBe('plain');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { clientCacheKey, clientHeaders, mergeHeaders } from '../common/http';
|
import { clientCacheKey, clientHeaders, mergeHeaders } from '../common/http';
|
||||||
|
import { b64DecodeUnicode, b64EncodeUnicode } from '@warkypublic/artemis-kit/base64';
|
||||||
import type {
|
import type {
|
||||||
APIResponse,
|
APIResponse,
|
||||||
ClientConfig,
|
ClientConfig,
|
||||||
@@ -13,10 +14,7 @@ import type {
|
|||||||
* 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") {
|
return "ZIP_" + b64EncodeUnicode(value);
|
||||||
return "ZIP_" + btoa(value);
|
|
||||||
}
|
|
||||||
return "ZIP_" + Buffer.from(value, "utf-8").toString("base64");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,10 +40,7 @@ export function decodeHeaderValue(value: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function decodeBase64(str: string): string {
|
function decodeBase64(str: string): string {
|
||||||
if (typeof atob === "function") {
|
return b64DecodeUnicode(str);
|
||||||
return atob(str);
|
|
||||||
}
|
|
||||||
return Buffer.from(str, "base64").toString("utf-8");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default defineConfig({
|
|||||||
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`,
|
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`,
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: ['uuid', 'semver'],
|
external: ['uuid', 'semver', '@warkypublic/artemis-kit/base64'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user