@ekodb/ekodb-client 0.24.0 → 0.26.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 +5 -2
- package/dist/client.d.ts +109 -2
- package/dist/client.js +160 -47
- package/dist/client.test.js +264 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +6 -1
- package/dist/websocket.test.js +66 -0
- package/package.json +2 -2
- package/src/client.test.ts +321 -0
- package/src/client.ts +254 -13
- package/src/index.ts +8 -0
- package/src/websocket.test.ts +90 -0
- package/tsconfig.json +2 -2
package/dist/client.test.js
CHANGED
|
@@ -1045,6 +1045,47 @@ function mockErrorResponse(status, message) {
|
|
|
1045
1045
|
(0, vitest_1.expect)(result.anthropic).toHaveLength(2);
|
|
1046
1046
|
(0, vitest_1.expect)(result.perplexity).toHaveLength(1);
|
|
1047
1047
|
});
|
|
1048
|
+
(0, vitest_1.it)("carries gemini and the per-provider status from the server", async () => {
|
|
1049
|
+
const client = createTestClient();
|
|
1050
|
+
mockTokenResponse();
|
|
1051
|
+
mockJsonResponse({
|
|
1052
|
+
openai: [],
|
|
1053
|
+
anthropic: ["claude-sonnet-4-5"],
|
|
1054
|
+
perplexity: ["sonar"],
|
|
1055
|
+
gemini: ["gemini-2.5-flash"],
|
|
1056
|
+
providers: {
|
|
1057
|
+
anthropic: { status: "ok", verified: true, model_count: 1 },
|
|
1058
|
+
gemini: { status: "ok", verified: true, model_count: 1 },
|
|
1059
|
+
openai: {
|
|
1060
|
+
status: "auth_failed",
|
|
1061
|
+
verified: true,
|
|
1062
|
+
http_status: 401,
|
|
1063
|
+
message: "Failed to fetch OpenAI models: 401 Unauthorized",
|
|
1064
|
+
},
|
|
1065
|
+
perplexity: {
|
|
1066
|
+
status: "ok",
|
|
1067
|
+
verified: false,
|
|
1068
|
+
message: "static model list; key not verified",
|
|
1069
|
+
},
|
|
1070
|
+
},
|
|
1071
|
+
});
|
|
1072
|
+
const result = await client.getChatModels();
|
|
1073
|
+
(0, vitest_1.expect)(result.gemini).toEqual(["gemini-2.5-flash"]);
|
|
1074
|
+
(0, vitest_1.expect)(result.providers?.openai.status).toBe("auth_failed");
|
|
1075
|
+
(0, vitest_1.expect)(result.providers?.openai.http_status).toBe(401);
|
|
1076
|
+
(0, vitest_1.expect)(result.providers?.openai.verified).toBe(true);
|
|
1077
|
+
(0, vitest_1.expect)(result.providers?.perplexity.verified).toBe(false);
|
|
1078
|
+
(0, vitest_1.expect)(result.providers?.anthropic.model_count).toBe(1);
|
|
1079
|
+
});
|
|
1080
|
+
(0, vitest_1.it)("tolerates a server that predates gemini and providers", async () => {
|
|
1081
|
+
const client = createTestClient();
|
|
1082
|
+
mockTokenResponse();
|
|
1083
|
+
mockJsonResponse({ openai: ["gpt-4o"], anthropic: [], perplexity: [] });
|
|
1084
|
+
const result = await client.getChatModels();
|
|
1085
|
+
(0, vitest_1.expect)(result.openai).toEqual(["gpt-4o"]);
|
|
1086
|
+
(0, vitest_1.expect)(result.gemini).toBeUndefined();
|
|
1087
|
+
(0, vitest_1.expect)(result.providers).toBeUndefined();
|
|
1088
|
+
});
|
|
1048
1089
|
(0, vitest_1.it)("gets models for specific provider", async () => {
|
|
1049
1090
|
const client = createTestClient();
|
|
1050
1091
|
mockTokenResponse();
|
|
@@ -1909,6 +1950,112 @@ function mockErrorResponse(status, message) {
|
|
|
1909
1950
|
(0, vitest_1.expect)(events[2].messageId).toBe("msg_1");
|
|
1910
1951
|
(0, vitest_1.expect)(events[2].executionTimeMs).toBe(42);
|
|
1911
1952
|
});
|
|
1953
|
+
(0, vitest_1.it)("treats a frame named error as an error even when its payload says message", async () => {
|
|
1954
|
+
const client = createTestClient();
|
|
1955
|
+
mockTokenResponse();
|
|
1956
|
+
const sseBody = 'event: token\ndata: {"token":"Hel"}\n\nevent: error\ndata: {"message":"boom"}\n\n';
|
|
1957
|
+
mockFetch.mockResolvedValueOnce({
|
|
1958
|
+
ok: true,
|
|
1959
|
+
status: 200,
|
|
1960
|
+
text: async () => sseBody,
|
|
1961
|
+
headers: new Headers({ "content-type": "text/event-stream" }),
|
|
1962
|
+
});
|
|
1963
|
+
const events = [];
|
|
1964
|
+
const stream = client.chatMessageStream("chat_123", {
|
|
1965
|
+
message: "Hello",
|
|
1966
|
+
});
|
|
1967
|
+
stream.on("event", (evt) => events.push(evt));
|
|
1968
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1969
|
+
(0, vitest_1.expect)(events).toEqual([
|
|
1970
|
+
{ type: "chunk", content: "Hel" },
|
|
1971
|
+
{ type: "error", error: "boom" },
|
|
1972
|
+
]);
|
|
1973
|
+
});
|
|
1974
|
+
(0, vitest_1.it)("stops reading after an error frame and emits nothing that follows it", async () => {
|
|
1975
|
+
const client = createTestClient();
|
|
1976
|
+
mockTokenResponse();
|
|
1977
|
+
const sseBody = 'event: error\ndata: {"message":"boom"}\n\nevent: token\ndata: {"token":"late"}\n\n';
|
|
1978
|
+
mockFetch.mockResolvedValueOnce({
|
|
1979
|
+
ok: true,
|
|
1980
|
+
status: 200,
|
|
1981
|
+
text: async () => sseBody,
|
|
1982
|
+
headers: new Headers({ "content-type": "text/event-stream" }),
|
|
1983
|
+
});
|
|
1984
|
+
const events = [];
|
|
1985
|
+
const stream = client.chatMessageStream("chat_123", {
|
|
1986
|
+
message: "Hello",
|
|
1987
|
+
});
|
|
1988
|
+
stream.on("event", (evt) => events.push(evt));
|
|
1989
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1990
|
+
(0, vitest_1.expect)(events).toEqual([{ type: "error", error: "boom" }]);
|
|
1991
|
+
});
|
|
1992
|
+
(0, vitest_1.it)("cancels the body reader after an error frame instead of waiting for the server to close", async () => {
|
|
1993
|
+
const client = createTestClient();
|
|
1994
|
+
mockTokenResponse();
|
|
1995
|
+
const encoder = new TextEncoder();
|
|
1996
|
+
const chunks = [
|
|
1997
|
+
encoder.encode('event: error\ndata: {"message":"boom"}\n\n'),
|
|
1998
|
+
encoder.encode('event: token\ndata: {"token":"late"}\n\n'),
|
|
1999
|
+
];
|
|
2000
|
+
const cancel = vitest_1.vi.fn(async () => { });
|
|
2001
|
+
let reads = 0;
|
|
2002
|
+
const reader = {
|
|
2003
|
+
read: vitest_1.vi.fn(async () => {
|
|
2004
|
+
// A server (or proxy) that does not close after the error frame: it
|
|
2005
|
+
// keeps sending frames. Bounded so a client that never stops fails
|
|
2006
|
+
// the assertions below instead of looping forever.
|
|
2007
|
+
if (reads >= 50)
|
|
2008
|
+
return { done: true, value: undefined };
|
|
2009
|
+
const value = chunks[Math.min(reads, chunks.length - 1)];
|
|
2010
|
+
reads += 1;
|
|
2011
|
+
return { done: false, value };
|
|
2012
|
+
}),
|
|
2013
|
+
cancel,
|
|
2014
|
+
};
|
|
2015
|
+
mockFetch.mockResolvedValueOnce({
|
|
2016
|
+
ok: true,
|
|
2017
|
+
status: 200,
|
|
2018
|
+
body: { getReader: () => reader },
|
|
2019
|
+
text: async () => "",
|
|
2020
|
+
headers: new Headers({ "content-type": "text/event-stream" }),
|
|
2021
|
+
});
|
|
2022
|
+
const events = [];
|
|
2023
|
+
const stream = client.chatMessageStream("chat_123", {
|
|
2024
|
+
message: "Hello",
|
|
2025
|
+
});
|
|
2026
|
+
stream.on("event", (evt) => events.push(evt));
|
|
2027
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2028
|
+
(0, vitest_1.expect)(events).toEqual([{ type: "error", error: "boom" }]);
|
|
2029
|
+
// One read delivered the error frame; the reader was cancelled rather
|
|
2030
|
+
// than read until the server closed.
|
|
2031
|
+
(0, vitest_1.expect)(reader.read).toHaveBeenCalledTimes(1);
|
|
2032
|
+
(0, vitest_1.expect)(cancel).toHaveBeenCalledTimes(1);
|
|
2033
|
+
});
|
|
2034
|
+
(0, vitest_1.it)("keeps the error text a string when the server sends a structured error", async () => {
|
|
2035
|
+
const client = createTestClient();
|
|
2036
|
+
mockTokenResponse();
|
|
2037
|
+
const sseBody = 'event: error\ndata: {"error":{"code":"upstream_down","status":503},"error_kind":"provider_unavailable","provider":"openai"}\n\n';
|
|
2038
|
+
mockFetch.mockResolvedValueOnce({
|
|
2039
|
+
ok: true,
|
|
2040
|
+
status: 200,
|
|
2041
|
+
text: async () => sseBody,
|
|
2042
|
+
headers: new Headers({ "content-type": "text/event-stream" }),
|
|
2043
|
+
});
|
|
2044
|
+
const events = [];
|
|
2045
|
+
const stream = client.chatMessageStream("chat_123", {
|
|
2046
|
+
message: "Hello",
|
|
2047
|
+
});
|
|
2048
|
+
stream.on("event", (evt) => events.push(evt));
|
|
2049
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2050
|
+
(0, vitest_1.expect)(events).toEqual([
|
|
2051
|
+
{
|
|
2052
|
+
type: "error",
|
|
2053
|
+
error: "Unknown error",
|
|
2054
|
+
errorKind: "provider_unavailable",
|
|
2055
|
+
provider: "openai",
|
|
2056
|
+
},
|
|
2057
|
+
]);
|
|
2058
|
+
});
|
|
1912
2059
|
(0, vitest_1.it)("emits error event on SSE error", async () => {
|
|
1913
2060
|
const client = createTestClient();
|
|
1914
2061
|
mockTokenResponse();
|
|
@@ -1928,6 +2075,36 @@ function mockErrorResponse(status, message) {
|
|
|
1928
2075
|
(0, vitest_1.expect)(events).toHaveLength(1);
|
|
1929
2076
|
(0, vitest_1.expect)(events[0]).toEqual({ type: "error", error: "LLM timeout" });
|
|
1930
2077
|
});
|
|
2078
|
+
(0, vitest_1.it)("carries the provider failure classification on an error event", async () => {
|
|
2079
|
+
// The deployment classifies a provider failure (`error_kind`, `provider`,
|
|
2080
|
+
// `provider_status`, `retry_after_secs` on the wire); the event carries
|
|
2081
|
+
// every one of them, in this shape's camelCase, so a consumer can act on
|
|
2082
|
+
// it without string-matching.
|
|
2083
|
+
const client = createTestClient();
|
|
2084
|
+
mockTokenResponse();
|
|
2085
|
+
const sseBody = 'data: {"error":"OpenAI API error: Incorrect API key provided","error_kind":"provider_auth_failed","provider":"openai","provider_status":401}\n';
|
|
2086
|
+
mockFetch.mockResolvedValueOnce({
|
|
2087
|
+
ok: true,
|
|
2088
|
+
status: 200,
|
|
2089
|
+
text: async () => sseBody,
|
|
2090
|
+
headers: new Headers({ "content-type": "text/event-stream" }),
|
|
2091
|
+
});
|
|
2092
|
+
const events = [];
|
|
2093
|
+
const stream = client.chatMessageStream("chat_123", {
|
|
2094
|
+
message: "Hello",
|
|
2095
|
+
});
|
|
2096
|
+
stream.on("event", (evt) => events.push(evt));
|
|
2097
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2098
|
+
(0, vitest_1.expect)(events).toEqual([
|
|
2099
|
+
{
|
|
2100
|
+
type: "error",
|
|
2101
|
+
error: "OpenAI API error: Incorrect API key provided",
|
|
2102
|
+
errorKind: "provider_auth_failed",
|
|
2103
|
+
provider: "openai",
|
|
2104
|
+
providerStatus: 401,
|
|
2105
|
+
},
|
|
2106
|
+
]);
|
|
2107
|
+
});
|
|
1931
2108
|
(0, vitest_1.it)("emits error event on non-200 HTTP response", async () => {
|
|
1932
2109
|
const client = createTestClient();
|
|
1933
2110
|
mockTokenResponse();
|
|
@@ -2671,3 +2848,90 @@ function mockErrorResponse(status, message) {
|
|
|
2671
2848
|
await (0, vitest_1.expect)(client.find("users")).rejects.toThrow(/timed out after 20ms/);
|
|
2672
2849
|
});
|
|
2673
2850
|
});
|
|
2851
|
+
// ============================================================================
|
|
2852
|
+
// Health Contract Tests
|
|
2853
|
+
// ============================================================================
|
|
2854
|
+
(0, vitest_1.describe)("health / healthStatus", () => {
|
|
2855
|
+
(0, vitest_1.it)("health() returns true for a reachable degraded server (bug fix)", async () => {
|
|
2856
|
+
const client = createTestClient();
|
|
2857
|
+
mockTokenResponse();
|
|
2858
|
+
mockJsonResponse({ status: "degraded", integrity_ok: false });
|
|
2859
|
+
(0, vitest_1.expect)(await client.health()).toBe(true);
|
|
2860
|
+
});
|
|
2861
|
+
(0, vitest_1.it)("health() returns false when unreachable", async () => {
|
|
2862
|
+
const client = createTestClient();
|
|
2863
|
+
mockTokenResponse();
|
|
2864
|
+
mockErrorResponse(503, "unavailable");
|
|
2865
|
+
(0, vitest_1.expect)(await client.health()).toBe(false);
|
|
2866
|
+
});
|
|
2867
|
+
(0, vitest_1.it)("healthStatus() surfaces degraded without erroring", async () => {
|
|
2868
|
+
const client = createTestClient();
|
|
2869
|
+
mockTokenResponse();
|
|
2870
|
+
mockJsonResponse({ status: "degraded", integrity_ok: false });
|
|
2871
|
+
const hs = await client.healthStatus();
|
|
2872
|
+
(0, vitest_1.expect)(hs.reachable).toBe(true);
|
|
2873
|
+
(0, vitest_1.expect)(hs.status).toBe("degraded");
|
|
2874
|
+
(0, vitest_1.expect)(hs.integrityOk).toBe(false);
|
|
2875
|
+
});
|
|
2876
|
+
(0, vitest_1.it)("healthStatus() unreachable -> reachable=false, status=unknown", async () => {
|
|
2877
|
+
const client = createTestClient();
|
|
2878
|
+
mockTokenResponse();
|
|
2879
|
+
mockErrorResponse(503, "unavailable");
|
|
2880
|
+
const hs = await client.healthStatus();
|
|
2881
|
+
(0, vitest_1.expect)(hs.reachable).toBe(false);
|
|
2882
|
+
(0, vitest_1.expect)(hs.status).toBe("unknown");
|
|
2883
|
+
});
|
|
2884
|
+
(0, vitest_1.it)("healthStatus() reads integrity from the admin nested shape", async () => {
|
|
2885
|
+
const client = createTestClient();
|
|
2886
|
+
mockTokenResponse();
|
|
2887
|
+
mockJsonResponse({
|
|
2888
|
+
status: "ok",
|
|
2889
|
+
integrity: { healthy: true, manifest_load_failed: [] },
|
|
2890
|
+
});
|
|
2891
|
+
const hs = await client.healthStatus();
|
|
2892
|
+
(0, vitest_1.expect)(hs.status).toBe("ok");
|
|
2893
|
+
(0, vitest_1.expect)(hs.integrityOk).toBe(true);
|
|
2894
|
+
});
|
|
2895
|
+
(0, vitest_1.it)("parseHealthStatus fails safe to degraded on a missing status", () => {
|
|
2896
|
+
const hs = (0, client_1.parseHealthStatus)({ integrity_ok: true });
|
|
2897
|
+
(0, vitest_1.expect)(hs.reachable).toBe(true);
|
|
2898
|
+
(0, vitest_1.expect)(hs.status).toBe("degraded");
|
|
2899
|
+
});
|
|
2900
|
+
(0, vitest_1.it)("parseHealthStatus yields an unknown snapshot for a non-object body", () => {
|
|
2901
|
+
const hs = (0, client_1.parseHealthStatus)("not json");
|
|
2902
|
+
(0, vitest_1.expect)(hs.reachable).toBe(false);
|
|
2903
|
+
(0, vitest_1.expect)(hs.status).toBe("unknown");
|
|
2904
|
+
});
|
|
2905
|
+
(0, vitest_1.it)("JSON serialization is a safe summary that excludes detail", () => {
|
|
2906
|
+
const hs = (0, client_1.parseHealthStatus)({
|
|
2907
|
+
status: "degraded",
|
|
2908
|
+
integrity: {
|
|
2909
|
+
healthy: false,
|
|
2910
|
+
manifest_load_failed: ["secret_collection"],
|
|
2911
|
+
},
|
|
2912
|
+
});
|
|
2913
|
+
const json = JSON.stringify(hs);
|
|
2914
|
+
(0, vitest_1.expect)(json).not.toContain("detail");
|
|
2915
|
+
(0, vitest_1.expect)(json).not.toContain("secret_collection");
|
|
2916
|
+
const parsed = JSON.parse(json);
|
|
2917
|
+
(0, vitest_1.expect)(parsed).toEqual({
|
|
2918
|
+
reachable: true,
|
|
2919
|
+
status: "degraded",
|
|
2920
|
+
integrity_ok: false,
|
|
2921
|
+
});
|
|
2922
|
+
// detail is still readable in-process
|
|
2923
|
+
(0, vitest_1.expect)(hs.detail).toBeDefined();
|
|
2924
|
+
});
|
|
2925
|
+
(0, vitest_1.it)("parseHealthStatus preserves a raw off-contract status (matches Go)", () => {
|
|
2926
|
+
(0, vitest_1.expect)((0, client_1.parseHealthStatus)({ status: "healthy" }).status).toBe("healthy");
|
|
2927
|
+
(0, vitest_1.expect)((0, client_1.parseHealthStatus)({ status: "ok" }).status).toBe("ok");
|
|
2928
|
+
(0, vitest_1.expect)((0, client_1.parseHealthStatus)({ status: "degraded" }).status).toBe("degraded");
|
|
2929
|
+
});
|
|
2930
|
+
(0, vitest_1.it)("parseHealthStatus fails safe to degraded on a non-string status", () => {
|
|
2931
|
+
(0, vitest_1.expect)((0, client_1.parseHealthStatus)({ status: { nested: 1 } }).status).toBe("degraded");
|
|
2932
|
+
});
|
|
2933
|
+
(0, vitest_1.it)("parseHealthStatus treats a non-object (array) body as unknown", () => {
|
|
2934
|
+
(0, vitest_1.expect)((0, client_1.parseHealthStatus)([1, 2, 3]).reachable).toBe(false);
|
|
2935
|
+
(0, vitest_1.expect)((0, client_1.parseHealthStatus)([1, 2, 3]).status).toBe("unknown");
|
|
2936
|
+
});
|
|
2937
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { EkoDBClient, WebSocketClient, EventStream, SerializationFormat, MergeStrategy, RateLimitError, SchemaCache, extractRecordId, } from "./client";
|
|
1
|
+
export { EkoDBClient, WebSocketClient, EventStream, SerializationFormat, MergeStrategy, RateLimitError, SchemaCache, extractRecordId, HealthStatus, parseHealthStatus, HealthOK, HealthDegraded, HealthUnknown, } from "./client";
|
|
2
|
+
export type { HealthState } from "./client";
|
|
2
3
|
export { QueryBuilder, SortOrder } from "./query-builder";
|
|
3
4
|
export { SearchQueryBuilder } from "./search";
|
|
4
5
|
export { SchemaBuilder, FieldTypeSchemaBuilder, VectorIndexAlgorithm, DistanceMetric, } from "./schema";
|
|
@@ -12,4 +13,4 @@ export type { Schema, FieldTypeSchema, IndexConfig, CollectionMetadata, } from "
|
|
|
12
13
|
export type { JoinConfig } from "./join";
|
|
13
14
|
export type { UserFunction, ParameterDefinition, FunctionStageConfig, GroupFunctionConfig, SortFieldConfig, FunctionResult, FunctionStats, StageStats, } from "./functions";
|
|
14
15
|
export type { MutationNotification, ChatStreamEvent, ClientToolDefinition, ChatSendOptions, SubscribeOptions, } from "./client";
|
|
15
|
-
export type { Record, Query, BatchOperationResult, ClientConfig, RateLimitInfo, CollectionConfig, ChatRequest, CreateChatSessionRequest, ChatMessageRequest, TokenUsage, ChatResponse, ChatSession, ChatSessionResponse, ListSessionsQuery, ListSessionsResponse, GetMessagesQuery, GetMessagesResponse, UpdateSessionRequest, MergeSessionsRequest, ChatModels, CompactChatRequest, CompactChatResponse, EmbedRequest, EmbedResponse, RawCompletionRequest, RawCompletionResponse, ToolChoice, ToolConfig, } from "./client";
|
|
16
|
+
export type { Record, Query, BatchOperationResult, ClientConfig, RateLimitInfo, CollectionConfig, ChatRequest, CreateChatSessionRequest, ChatMessageRequest, TokenUsage, ChatResponse, ChatSession, ChatSessionResponse, ListSessionsQuery, ListSessionsResponse, GetMessagesQuery, GetMessagesResponse, UpdateSessionRequest, MergeSessionsRequest, ChatModels, ChatProviderState, ChatProviderStatus, CompactChatRequest, CompactChatResponse, EmbedRequest, EmbedResponse, RawCompletionRequest, RawCompletionResponse, ToolChoice, ToolConfig, } from "./client";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Field = exports.getObjectValue = exports.getVectorValue = exports.getSetValue = exports.getArrayValue = exports.getBinaryValue = exports.getBytesValue = exports.getDurationValue = exports.getDecimalValue = exports.getUUIDValue = exports.getDateTimeValue = exports.extractRecord = exports.getValues = exports.getValue = exports.parameterRef = exports.ChatMessage = exports.Stage = exports.JoinBuilder = exports.DistanceMetric = exports.VectorIndexAlgorithm = exports.FieldTypeSchemaBuilder = exports.SchemaBuilder = exports.SearchQueryBuilder = exports.SortOrder = exports.QueryBuilder = exports.extractRecordId = exports.SchemaCache = exports.RateLimitError = exports.MergeStrategy = exports.SerializationFormat = exports.EventStream = exports.WebSocketClient = exports.EkoDBClient = void 0;
|
|
3
|
+
exports.Field = exports.getObjectValue = exports.getVectorValue = exports.getSetValue = exports.getArrayValue = exports.getBinaryValue = exports.getBytesValue = exports.getDurationValue = exports.getDecimalValue = exports.getUUIDValue = exports.getDateTimeValue = exports.extractRecord = exports.getValues = exports.getValue = exports.parameterRef = exports.ChatMessage = exports.Stage = exports.JoinBuilder = exports.DistanceMetric = exports.VectorIndexAlgorithm = exports.FieldTypeSchemaBuilder = exports.SchemaBuilder = exports.SearchQueryBuilder = exports.SortOrder = exports.QueryBuilder = exports.HealthUnknown = exports.HealthDegraded = exports.HealthOK = exports.parseHealthStatus = exports.HealthStatus = exports.extractRecordId = exports.SchemaCache = exports.RateLimitError = exports.MergeStrategy = exports.SerializationFormat = exports.EventStream = exports.WebSocketClient = exports.EkoDBClient = void 0;
|
|
4
4
|
var client_1 = require("./client");
|
|
5
5
|
Object.defineProperty(exports, "EkoDBClient", { enumerable: true, get: function () { return client_1.EkoDBClient; } });
|
|
6
6
|
Object.defineProperty(exports, "WebSocketClient", { enumerable: true, get: function () { return client_1.WebSocketClient; } });
|
|
@@ -10,6 +10,11 @@ Object.defineProperty(exports, "MergeStrategy", { enumerable: true, get: functio
|
|
|
10
10
|
Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return client_1.RateLimitError; } });
|
|
11
11
|
Object.defineProperty(exports, "SchemaCache", { enumerable: true, get: function () { return client_1.SchemaCache; } });
|
|
12
12
|
Object.defineProperty(exports, "extractRecordId", { enumerable: true, get: function () { return client_1.extractRecordId; } });
|
|
13
|
+
Object.defineProperty(exports, "HealthStatus", { enumerable: true, get: function () { return client_1.HealthStatus; } });
|
|
14
|
+
Object.defineProperty(exports, "parseHealthStatus", { enumerable: true, get: function () { return client_1.parseHealthStatus; } });
|
|
15
|
+
Object.defineProperty(exports, "HealthOK", { enumerable: true, get: function () { return client_1.HealthOK; } });
|
|
16
|
+
Object.defineProperty(exports, "HealthDegraded", { enumerable: true, get: function () { return client_1.HealthDegraded; } });
|
|
17
|
+
Object.defineProperty(exports, "HealthUnknown", { enumerable: true, get: function () { return client_1.HealthUnknown; } });
|
|
13
18
|
var query_builder_1 = require("./query-builder");
|
|
14
19
|
Object.defineProperty(exports, "QueryBuilder", { enumerable: true, get: function () { return query_builder_1.QueryBuilder; } });
|
|
15
20
|
Object.defineProperty(exports, "SortOrder", { enumerable: true, get: function () { return query_builder_1.SortOrder; } });
|
package/dist/websocket.test.js
CHANGED
|
@@ -300,6 +300,72 @@ function waitForMessage(ws) {
|
|
|
300
300
|
(0, vitest_1.expect)(stream.closed).toBe(true);
|
|
301
301
|
client.close();
|
|
302
302
|
});
|
|
303
|
+
// The WebSocket route carries the provider-failure classification like
|
|
304
|
+
// the SSE route does, in the event's camelCase shape.
|
|
305
|
+
(0, vitest_1.it)("carries the provider failure classification on a chat stream error", async () => {
|
|
306
|
+
const client = new client_1.WebSocketClient(`ws://localhost:${port}/api/ws`, "test-token");
|
|
307
|
+
const streamPromise = client.chatSend("chat-4", "test");
|
|
308
|
+
await new Promise((r) => wss.once("connection", r));
|
|
309
|
+
const ws = getLastConnection();
|
|
310
|
+
await waitForMessage(ws);
|
|
311
|
+
const stream = await streamPromise;
|
|
312
|
+
const events = [];
|
|
313
|
+
stream.on("event", (e) => events.push(e));
|
|
314
|
+
ws.send(JSON.stringify({
|
|
315
|
+
type: "ChatStreamError",
|
|
316
|
+
payload: {
|
|
317
|
+
chat_id: "chat-4",
|
|
318
|
+
error: "OpenAI API error 429 Too Many Requests",
|
|
319
|
+
error_kind: "provider_rate_limited",
|
|
320
|
+
provider: "openai",
|
|
321
|
+
provider_status: 429,
|
|
322
|
+
retry_after_secs: 7,
|
|
323
|
+
},
|
|
324
|
+
}));
|
|
325
|
+
await new Promise((r) => stream.on("close", r));
|
|
326
|
+
(0, vitest_1.expect)(events).toEqual([
|
|
327
|
+
{
|
|
328
|
+
type: "error",
|
|
329
|
+
error: "OpenAI API error 429 Too Many Requests",
|
|
330
|
+
errorKind: "provider_rate_limited",
|
|
331
|
+
provider: "openai",
|
|
332
|
+
providerStatus: 429,
|
|
333
|
+
retryAfterSecs: 7,
|
|
334
|
+
},
|
|
335
|
+
]);
|
|
336
|
+
client.close();
|
|
337
|
+
});
|
|
338
|
+
// A structured `error` value is still an error, with string text — the
|
|
339
|
+
// WebSocket route guards the shape exactly as the SSE route does.
|
|
340
|
+
(0, vitest_1.it)("keeps the error text a string when the payload's error is an object", async () => {
|
|
341
|
+
const client = new client_1.WebSocketClient(`ws://localhost:${port}/api/ws`, "test-token");
|
|
342
|
+
const streamPromise = client.chatSend("chat-5", "test");
|
|
343
|
+
await new Promise((r) => wss.once("connection", r));
|
|
344
|
+
const ws = getLastConnection();
|
|
345
|
+
await waitForMessage(ws);
|
|
346
|
+
const stream = await streamPromise;
|
|
347
|
+
const events = [];
|
|
348
|
+
stream.on("event", (e) => events.push(e));
|
|
349
|
+
ws.send(JSON.stringify({
|
|
350
|
+
type: "ChatStreamError",
|
|
351
|
+
payload: {
|
|
352
|
+
chat_id: "chat-5",
|
|
353
|
+
error: { code: "upstream_down", status: 503 },
|
|
354
|
+
error_kind: "provider_unavailable",
|
|
355
|
+
provider: "gemini",
|
|
356
|
+
},
|
|
357
|
+
}));
|
|
358
|
+
await new Promise((r) => stream.on("close", r));
|
|
359
|
+
(0, vitest_1.expect)(events).toEqual([
|
|
360
|
+
{
|
|
361
|
+
type: "error",
|
|
362
|
+
error: "Unknown error",
|
|
363
|
+
errorKind: "provider_unavailable",
|
|
364
|
+
provider: "gemini",
|
|
365
|
+
},
|
|
366
|
+
]);
|
|
367
|
+
client.close();
|
|
368
|
+
});
|
|
303
369
|
(0, vitest_1.it)("sends options with ChatSend", async () => {
|
|
304
370
|
const client = new client_1.WebSocketClient(`ws://localhost:${port}/api/ws`, "test-token");
|
|
305
371
|
const streamPromise = client.chatSend("chat-3", "Hello", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekodb/ekodb-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Official TypeScript/JavaScript client for ekoDB",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^24.13.2",
|
|
24
24
|
"@types/ws": "^8.18.1",
|
|
25
|
-
"typescript": "^
|
|
25
|
+
"typescript": "^6.0.3",
|
|
26
26
|
"vitest": "^4.0.18"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|