@ekodb/ekodb-client 0.19.0 → 0.21.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 +28 -1
- package/dist/client.d.ts +198 -17
- package/dist/client.js +653 -119
- package/dist/client.test.js +287 -0
- package/dist/functions.test.d.ts +1 -2
- package/dist/functions.test.js +1 -2
- package/dist/query-builder.d.ts +0 -4
- package/dist/query-builder.js +2 -14
- package/dist/query-builder.test.js +0 -5
- package/dist/utils.js +7 -1
- package/dist/utils.test.js +4 -0
- package/dist/websocket.test.js +339 -5
- package/package.json +1 -1
- package/src/client.test.ts +394 -1
- package/src/client.ts +821 -130
- package/src/functions.test.ts +1 -2
- package/src/query-builder.test.ts +0 -7
- package/src/query-builder.ts +2 -14
- package/src/utils.test.ts +5 -0
- package/src/utils.ts +9 -1
- package/src/websocket.test.ts +498 -5
package/dist/client.test.js
CHANGED
|
@@ -250,6 +250,12 @@ function mockErrorResponse(status, message) {
|
|
|
250
250
|
mockJsonResponse({ deleted: true });
|
|
251
251
|
await (0, vitest_1.expect)(client.kvDelete("my_key")).resolves.not.toThrow();
|
|
252
252
|
});
|
|
253
|
+
(0, vitest_1.it)("clears KV store", async () => {
|
|
254
|
+
const client = createTestClient();
|
|
255
|
+
mockTokenResponse();
|
|
256
|
+
mockJsonResponse({ message: "success" });
|
|
257
|
+
await (0, vitest_1.expect)(client.kvClear()).resolves.not.toThrow();
|
|
258
|
+
});
|
|
253
259
|
(0, vitest_1.it)("checks KV exists", async () => {
|
|
254
260
|
const client = createTestClient();
|
|
255
261
|
mockTokenResponse();
|
|
@@ -294,6 +300,13 @@ function mockErrorResponse(status, message) {
|
|
|
294
300
|
(0, vitest_1.expect)(result).toContain("users");
|
|
295
301
|
(0, vitest_1.expect)(result).toHaveLength(3);
|
|
296
302
|
});
|
|
303
|
+
(0, vitest_1.it)("lists user collections (excludes internal)", async () => {
|
|
304
|
+
const client = createTestClient();
|
|
305
|
+
mockTokenResponse();
|
|
306
|
+
mockJsonResponse({ collections: ["users", "posts"] });
|
|
307
|
+
const result = await client.listUserCollections();
|
|
308
|
+
(0, vitest_1.expect)(result).toEqual(["users", "posts"]);
|
|
309
|
+
});
|
|
297
310
|
(0, vitest_1.it)("deletes collection", async () => {
|
|
298
311
|
const client = createTestClient();
|
|
299
312
|
mockTokenResponse();
|
|
@@ -723,6 +736,64 @@ function mockErrorResponse(status, message) {
|
|
|
723
736
|
(0, vitest_1.expect)(result).toBeDefined();
|
|
724
737
|
});
|
|
725
738
|
});
|
|
739
|
+
(0, vitest_1.describe)("bypass_ripple on the transactional read path", () => {
|
|
740
|
+
(0, vitest_1.it)("findById sends bypass_ripple AND transaction_id together (query params)", async () => {
|
|
741
|
+
const client = createTestClient();
|
|
742
|
+
mockTokenResponse();
|
|
743
|
+
mockJsonResponse({ id: "user_123", name: "Alice" });
|
|
744
|
+
await client.findById("users", "user_123", {
|
|
745
|
+
bypassRipple: true,
|
|
746
|
+
transactionId: "tx_123",
|
|
747
|
+
});
|
|
748
|
+
const [url, init] = mockFetch.mock.calls[1];
|
|
749
|
+
(0, vitest_1.expect)(init.method).toBe("GET");
|
|
750
|
+
const parsed = new URL(url);
|
|
751
|
+
(0, vitest_1.expect)(parsed.searchParams.get("bypass_ripple")).toBe("true");
|
|
752
|
+
(0, vitest_1.expect)(parsed.searchParams.get("transaction_id")).toBe("tx_123");
|
|
753
|
+
});
|
|
754
|
+
(0, vitest_1.it)("findById sends bypass_ripple=false explicitly alongside transaction_id", async () => {
|
|
755
|
+
const client = createTestClient();
|
|
756
|
+
mockTokenResponse();
|
|
757
|
+
mockJsonResponse({ id: "user_123", name: "Alice" });
|
|
758
|
+
await client.findById("users", "user_123", {
|
|
759
|
+
bypassRipple: false,
|
|
760
|
+
transactionId: "tx_123",
|
|
761
|
+
});
|
|
762
|
+
const [url] = mockFetch.mock.calls[1];
|
|
763
|
+
const parsed = new URL(url);
|
|
764
|
+
(0, vitest_1.expect)(parsed.searchParams.get("bypass_ripple")).toBe("false");
|
|
765
|
+
(0, vitest_1.expect)(parsed.searchParams.get("transaction_id")).toBe("tx_123");
|
|
766
|
+
});
|
|
767
|
+
(0, vitest_1.it)("find sends bypass_ripple AND transaction_id together as query params", async () => {
|
|
768
|
+
const client = createTestClient();
|
|
769
|
+
mockTokenResponse();
|
|
770
|
+
mockJsonResponse([{ id: "user_1", name: "Alice" }]);
|
|
771
|
+
await client.find("users", { limit: 10 }, { bypassRipple: true, transactionId: "tx_123" });
|
|
772
|
+
const [url, init] = mockFetch.mock.calls[1];
|
|
773
|
+
(0, vitest_1.expect)(init.method).toBe("POST");
|
|
774
|
+
const parsed = new URL(url);
|
|
775
|
+
// bypass_ripple is a query param (like every other method), not in the body.
|
|
776
|
+
(0, vitest_1.expect)(parsed.searchParams.get("transaction_id")).toBe("tx_123");
|
|
777
|
+
(0, vitest_1.expect)(parsed.searchParams.get("bypass_ripple")).toBe("true");
|
|
778
|
+
const body = JSON.parse(init.body);
|
|
779
|
+
(0, vitest_1.expect)(body.bypass_ripple).toBeUndefined();
|
|
780
|
+
(0, vitest_1.expect)(body.limit).toBe(10);
|
|
781
|
+
});
|
|
782
|
+
(0, vitest_1.it)("find hoists bypass_ripple from the query object into the query string", async () => {
|
|
783
|
+
const client = createTestClient();
|
|
784
|
+
mockTokenResponse();
|
|
785
|
+
mockJsonResponse([]);
|
|
786
|
+
// A query object carrying bypass_ripple, as QueryBuilder.bypassRipple() builds.
|
|
787
|
+
await client.find("users", { limit: 5, bypass_ripple: true });
|
|
788
|
+
const [url, init] = mockFetch.mock.calls[1];
|
|
789
|
+
const parsed = new URL(url);
|
|
790
|
+
// Hoisted to the query string, removed from the body.
|
|
791
|
+
(0, vitest_1.expect)(parsed.searchParams.get("bypass_ripple")).toBe("true");
|
|
792
|
+
const body = JSON.parse(init.body);
|
|
793
|
+
(0, vitest_1.expect)(body.bypass_ripple).toBeUndefined();
|
|
794
|
+
(0, vitest_1.expect)(body.limit).toBe(5);
|
|
795
|
+
});
|
|
796
|
+
});
|
|
726
797
|
// ============================================================================
|
|
727
798
|
// Convenience Methods Tests
|
|
728
799
|
// ============================================================================
|
|
@@ -1340,6 +1411,16 @@ function mockErrorResponse(status, message) {
|
|
|
1340
1411
|
const dataCall = calls[1];
|
|
1341
1412
|
(0, vitest_1.expect)(dataCall[0]).toBe("http://localhost:8080/api/find/users/123");
|
|
1342
1413
|
});
|
|
1414
|
+
(0, vitest_1.it)("appends transaction_id when a transactionId is given", async () => {
|
|
1415
|
+
const client = createTestClient();
|
|
1416
|
+
mockTokenResponse();
|
|
1417
|
+
mockJsonResponse({ id: "123", name: "Alice" });
|
|
1418
|
+
await client.findByIdWithProjection("users", "123", ["name"], undefined, "txn-abc");
|
|
1419
|
+
const calls = global.fetch.mock.calls;
|
|
1420
|
+
const dataCall = calls[1];
|
|
1421
|
+
(0, vitest_1.expect)(dataCall[0]).toContain("transaction_id=txn-abc");
|
|
1422
|
+
(0, vitest_1.expect)(dataCall[0]).toContain("select_fields=name");
|
|
1423
|
+
});
|
|
1343
1424
|
});
|
|
1344
1425
|
// ============================================================================
|
|
1345
1426
|
// Goal CRUD Tests
|
|
@@ -1816,6 +1897,69 @@ function mockErrorResponse(status, message) {
|
|
|
1816
1897
|
(0, vitest_1.expect)(dataCall[1]?.method).toBe("POST");
|
|
1817
1898
|
(0, vitest_1.expect)(dataCall[1]?.headers?.Accept).toBe("text/event-stream");
|
|
1818
1899
|
});
|
|
1900
|
+
(0, vitest_1.it)("sends a resolved Bearer token, not a Promise (regression #124)", async () => {
|
|
1901
|
+
const client = createTestClient();
|
|
1902
|
+
mockTokenResponse();
|
|
1903
|
+
mockFetch.mockResolvedValueOnce({
|
|
1904
|
+
ok: true,
|
|
1905
|
+
status: 200,
|
|
1906
|
+
text: async () => 'data: {"token":"ok"}\n',
|
|
1907
|
+
headers: new Headers({ "content-type": "text/event-stream" }),
|
|
1908
|
+
});
|
|
1909
|
+
client.chatMessageStream("chat_789", { message: "Test" });
|
|
1910
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1911
|
+
const calls = global.fetch.mock.calls;
|
|
1912
|
+
const auth = calls[1][1]?.headers
|
|
1913
|
+
?.Authorization;
|
|
1914
|
+
(0, vitest_1.expect)(auth).toBe("Bearer test-jwt-token");
|
|
1915
|
+
(0, vitest_1.expect)(auth).not.toContain("[object Promise]");
|
|
1916
|
+
});
|
|
1917
|
+
(0, vitest_1.it)("streams SSE events incrementally from response.body, reassembling split lines (regression #125)", async () => {
|
|
1918
|
+
const client = createTestClient();
|
|
1919
|
+
mockTokenResponse();
|
|
1920
|
+
// A data line is deliberately split across chunk boundaries to exercise the
|
|
1921
|
+
// incremental buffer (the old code buffered the whole body via text()).
|
|
1922
|
+
const enc = new TextEncoder();
|
|
1923
|
+
const chunks = [
|
|
1924
|
+
'data: {"token":"He"}\nda',
|
|
1925
|
+
'ta: {"token":"llo"}\n',
|
|
1926
|
+
'data: {"content":"Hello","message_id":"m1","execution_time_ms":1}\n',
|
|
1927
|
+
];
|
|
1928
|
+
const body = new ReadableStream({
|
|
1929
|
+
start(controller) {
|
|
1930
|
+
for (const c of chunks)
|
|
1931
|
+
controller.enqueue(enc.encode(c));
|
|
1932
|
+
controller.close();
|
|
1933
|
+
},
|
|
1934
|
+
});
|
|
1935
|
+
mockFetch.mockResolvedValueOnce({
|
|
1936
|
+
ok: true,
|
|
1937
|
+
status: 200,
|
|
1938
|
+
body,
|
|
1939
|
+
text: async () => chunks.join(""),
|
|
1940
|
+
headers: new Headers({ "content-type": "text/event-stream" }),
|
|
1941
|
+
});
|
|
1942
|
+
const events = [];
|
|
1943
|
+
const stream = client.chatMessageStream("chat_s", { message: "Hi" });
|
|
1944
|
+
stream.on("event", (evt) => events.push(evt));
|
|
1945
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1946
|
+
(0, vitest_1.expect)(events.filter((e) => e.type === "chunk").map((e) => e.content)).toEqual(["He", "llo"]);
|
|
1947
|
+
(0, vitest_1.expect)(events[events.length - 1].type).toBe("end");
|
|
1948
|
+
(0, vitest_1.expect)(events[events.length - 1].messageId).toBe("m1");
|
|
1949
|
+
});
|
|
1950
|
+
});
|
|
1951
|
+
(0, vitest_1.describe)("EkoDBClient retry backoff", () => {
|
|
1952
|
+
(0, vitest_1.it)("backoffSeconds grows, caps at 5s, and jitters within [d/2, d] (#126)", () => {
|
|
1953
|
+
const client = createTestClient();
|
|
1954
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
1955
|
+
const d = Math.min(0.2 * 2 ** attempt, 5);
|
|
1956
|
+
for (let i = 0; i < 50; i++) {
|
|
1957
|
+
const v = client.backoffSeconds(attempt);
|
|
1958
|
+
(0, vitest_1.expect)(v).toBeGreaterThanOrEqual(d / 2);
|
|
1959
|
+
(0, vitest_1.expect)(v).toBeLessThanOrEqual(d);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
});
|
|
1819
1963
|
});
|
|
1820
1964
|
// ============================================================================
|
|
1821
1965
|
// Schedule CRUD Tests
|
|
@@ -2259,3 +2403,146 @@ function mockErrorResponse(status, message) {
|
|
|
2259
2403
|
(0, vitest_1.expect)(errors[0]).toContain("401");
|
|
2260
2404
|
});
|
|
2261
2405
|
});
|
|
2406
|
+
(0, vitest_1.describe)("baseURL normalization", () => {
|
|
2407
|
+
(0, vitest_1.it)("strips a trailing slash so request URLs have no double slash", async () => {
|
|
2408
|
+
mockTokenResponse();
|
|
2409
|
+
const client = new client_1.EkoDBClient({
|
|
2410
|
+
baseURL: "http://localhost:8080/",
|
|
2411
|
+
apiKey: "test-api-key",
|
|
2412
|
+
format: client_1.SerializationFormat.Json,
|
|
2413
|
+
shouldRetry: false,
|
|
2414
|
+
});
|
|
2415
|
+
await client.init();
|
|
2416
|
+
const url = mockFetch.mock.calls[0][0];
|
|
2417
|
+
(0, vitest_1.expect)(url).toBe("http://localhost:8080/api/auth/token");
|
|
2418
|
+
(0, vitest_1.expect)(url).not.toContain("//api");
|
|
2419
|
+
});
|
|
2420
|
+
(0, vitest_1.it)("leaves a URL without a trailing slash unchanged", async () => {
|
|
2421
|
+
mockTokenResponse();
|
|
2422
|
+
const client = new client_1.EkoDBClient({
|
|
2423
|
+
baseURL: "http://localhost:8080",
|
|
2424
|
+
apiKey: "test-api-key",
|
|
2425
|
+
format: client_1.SerializationFormat.Json,
|
|
2426
|
+
shouldRetry: false,
|
|
2427
|
+
});
|
|
2428
|
+
await client.init();
|
|
2429
|
+
const url = mockFetch.mock.calls[0][0];
|
|
2430
|
+
(0, vitest_1.expect)(url).toBe("http://localhost:8080/api/auth/token");
|
|
2431
|
+
});
|
|
2432
|
+
});
|
|
2433
|
+
(0, vitest_1.describe)("extractRecordId", () => {
|
|
2434
|
+
(0, vitest_1.it)("returns a plain string id", () => {
|
|
2435
|
+
(0, vitest_1.expect)((0, client_1.extractRecordId)({ id: "abc" })).toBe("abc");
|
|
2436
|
+
});
|
|
2437
|
+
(0, vitest_1.it)("unwraps a genuine typed wrapper id", () => {
|
|
2438
|
+
(0, vitest_1.expect)((0, client_1.extractRecordId)({ id: { type: "String", value: "abc" } })).toBe("abc");
|
|
2439
|
+
});
|
|
2440
|
+
(0, vitest_1.it)("stringifies a wrapped numeric id", () => {
|
|
2441
|
+
(0, vitest_1.expect)((0, client_1.extractRecordId)({ id: { type: "Integer", value: 123 } })).toBe("123");
|
|
2442
|
+
});
|
|
2443
|
+
(0, vitest_1.it)("does not treat a user object with a value key (no type) as the id", () => {
|
|
2444
|
+
// Regression for #134: { value: 1, currency: "USD" } is a user object,
|
|
2445
|
+
// not a typed wrapper, so it must not be unwrapped into the id.
|
|
2446
|
+
(0, vitest_1.expect)((0, client_1.extractRecordId)({ id: { value: 1, currency: "USD" } })).toBeUndefined();
|
|
2447
|
+
});
|
|
2448
|
+
(0, vitest_1.it)("prefers an alias candidate over id", () => {
|
|
2449
|
+
(0, vitest_1.expect)((0, client_1.extractRecordId)({ users_id: "u1", id: "x" }, ["users_id"])).toBe("u1");
|
|
2450
|
+
});
|
|
2451
|
+
(0, vitest_1.it)("ignores a non-wrapper alias object and falls back to id", () => {
|
|
2452
|
+
(0, vitest_1.expect)((0, client_1.extractRecordId)({ users_id: { value: 7, label: "lvl" }, id: "real" }, [
|
|
2453
|
+
"users_id",
|
|
2454
|
+
])).toBe("real");
|
|
2455
|
+
});
|
|
2456
|
+
(0, vitest_1.it)("falls back to _id", () => {
|
|
2457
|
+
(0, vitest_1.expect)((0, client_1.extractRecordId)({ _id: "underscore" })).toBe("underscore");
|
|
2458
|
+
});
|
|
2459
|
+
});
|
|
2460
|
+
// ============================================================================
|
|
2461
|
+
// URL Path Segment Encoding Tests
|
|
2462
|
+
//
|
|
2463
|
+
// Every caller-supplied path segment (collection, id, function label, chat
|
|
2464
|
+
// model/provider, session/message ids, etc.) must be percent-encoded so a
|
|
2465
|
+
// reserved char (`/`, space, `#`, `?`) can't break the URL. This matches the
|
|
2466
|
+
// Rust and Go clients. Query parameters are NOT path segments and go through
|
|
2467
|
+
// URLSearchParams, so they are out of scope here.
|
|
2468
|
+
// ============================================================================
|
|
2469
|
+
(0, vitest_1.describe)("EkoDBClient URL path segment encoding", () => {
|
|
2470
|
+
(0, vitest_1.it)("findById encodes a reserved-char id (a/b -> a%2Fb)", async () => {
|
|
2471
|
+
const client = createTestClient();
|
|
2472
|
+
mockTokenResponse();
|
|
2473
|
+
mockJsonResponse({ id: "a/b", name: "Alice" });
|
|
2474
|
+
await client.findById("users", "a/b");
|
|
2475
|
+
const [url] = mockFetch.mock.calls[1];
|
|
2476
|
+
(0, vitest_1.expect)(url).toContain("/api/find/users/a%2Fb");
|
|
2477
|
+
(0, vitest_1.expect)(url).not.toContain("/api/find/users/a/b");
|
|
2478
|
+
});
|
|
2479
|
+
(0, vitest_1.it)("findById encodes a reserved-char collection", async () => {
|
|
2480
|
+
const client = createTestClient();
|
|
2481
|
+
mockTokenResponse();
|
|
2482
|
+
mockJsonResponse({ id: "user_123" });
|
|
2483
|
+
await client.findById("my coll", "user_123");
|
|
2484
|
+
const [url] = mockFetch.mock.calls[1];
|
|
2485
|
+
(0, vitest_1.expect)(url).toContain("/api/find/my%20coll/user_123");
|
|
2486
|
+
});
|
|
2487
|
+
(0, vitest_1.it)("findById leaves a normal id unchanged (/api/find/users/123)", async () => {
|
|
2488
|
+
const client = createTestClient();
|
|
2489
|
+
mockTokenResponse();
|
|
2490
|
+
mockJsonResponse({ id: "123", name: "Alice" });
|
|
2491
|
+
await client.findById("users", "123");
|
|
2492
|
+
const [url] = mockFetch.mock.calls[1];
|
|
2493
|
+
(0, vitest_1.expect)(url).toContain("/api/find/users/123");
|
|
2494
|
+
(0, vitest_1.expect)(url).not.toContain("%2F");
|
|
2495
|
+
(0, vitest_1.expect)(url).not.toContain("%20");
|
|
2496
|
+
});
|
|
2497
|
+
(0, vitest_1.it)("callFunction encodes a label containing a slash (anthropic/claude)", async () => {
|
|
2498
|
+
const client = createTestClient();
|
|
2499
|
+
mockTokenResponse();
|
|
2500
|
+
mockJsonResponse({ result: { ok: true } });
|
|
2501
|
+
await client.callFunction("anthropic/claude", {});
|
|
2502
|
+
const [url] = mockFetch.mock.calls[1];
|
|
2503
|
+
(0, vitest_1.expect)(url).toContain("/api/functions/anthropic%2Fclaude");
|
|
2504
|
+
(0, vitest_1.expect)(url).not.toContain("/api/functions/anthropic/claude");
|
|
2505
|
+
});
|
|
2506
|
+
(0, vitest_1.it)("getUserFunction encodes a label containing reserved chars", async () => {
|
|
2507
|
+
const client = createTestClient();
|
|
2508
|
+
mockTokenResponse();
|
|
2509
|
+
mockJsonResponse({ label: "items get/by id" });
|
|
2510
|
+
await client.getUserFunction("items get/by id");
|
|
2511
|
+
const [url] = mockFetch.mock.calls[1];
|
|
2512
|
+
(0, vitest_1.expect)(url).toContain("/api/functions/items%20get%2Fby%20id");
|
|
2513
|
+
});
|
|
2514
|
+
(0, vitest_1.it)("getChatModel encodes a provider containing a slash (anthropic/claude)", async () => {
|
|
2515
|
+
const client = createTestClient();
|
|
2516
|
+
mockTokenResponse();
|
|
2517
|
+
mockJsonResponse(["claude-3"]);
|
|
2518
|
+
await client.getChatModel("anthropic/claude");
|
|
2519
|
+
const [url] = mockFetch.mock.calls[1];
|
|
2520
|
+
(0, vitest_1.expect)(url).toContain("/api/chat_models/anthropic%2Fclaude");
|
|
2521
|
+
(0, vitest_1.expect)(url).not.toContain("/api/chat_models/anthropic/claude");
|
|
2522
|
+
});
|
|
2523
|
+
(0, vitest_1.it)("deleteCollection encodes a reserved-char collection", async () => {
|
|
2524
|
+
const client = createTestClient();
|
|
2525
|
+
mockTokenResponse();
|
|
2526
|
+
mockJsonResponse({ status: "deleted" });
|
|
2527
|
+
await client.deleteCollection("a/b");
|
|
2528
|
+
const [url] = mockFetch.mock.calls[1];
|
|
2529
|
+
(0, vitest_1.expect)(url).toContain("/api/collections/a%2Fb");
|
|
2530
|
+
});
|
|
2531
|
+
(0, vitest_1.it)("getChatSessionMessages encodes the session id path segment", async () => {
|
|
2532
|
+
const client = createTestClient();
|
|
2533
|
+
mockTokenResponse();
|
|
2534
|
+
mockJsonResponse({ messages: [], total: 0 });
|
|
2535
|
+
await client.getChatSessionMessages("sess/1");
|
|
2536
|
+
const [url] = mockFetch.mock.calls[1];
|
|
2537
|
+
(0, vitest_1.expect)(url).toContain("/api/chat/sess%2F1/messages");
|
|
2538
|
+
(0, vitest_1.expect)(url).not.toContain("/api/chat/sess/1/messages");
|
|
2539
|
+
});
|
|
2540
|
+
(0, vitest_1.it)("chatMessage encodes the session id and leaves a normal one unchanged", async () => {
|
|
2541
|
+
const client = createTestClient();
|
|
2542
|
+
mockTokenResponse();
|
|
2543
|
+
mockJsonResponse({ message_id: "m1", content: "hi" });
|
|
2544
|
+
await client.chatMessage("sess#1", { message: "hi" });
|
|
2545
|
+
const [url] = mockFetch.mock.calls[1];
|
|
2546
|
+
(0, vitest_1.expect)(url).toContain("/api/chat/sess%231/messages");
|
|
2547
|
+
});
|
|
2548
|
+
});
|
package/dist/functions.test.d.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These tests cover the pure-data construction helpers and the structural
|
|
5
5
|
* parameter placeholder. They don't hit a running ekoDB — server-side
|
|
6
|
-
* behavior is covered by the
|
|
7
|
-
* `ekodb/ekodb_server/tests/function_parameters_tests.rs`.
|
|
6
|
+
* behavior is covered by the server-side integration tests.
|
|
8
7
|
*/
|
|
9
8
|
export {};
|
package/dist/functions.test.js
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* These tests cover the pure-data construction helpers and the structural
|
|
6
6
|
* parameter placeholder. They don't hit a running ekoDB — server-side
|
|
7
|
-
* behavior is covered by the
|
|
8
|
-
* `ekodb/ekodb_server/tests/function_parameters_tests.rs`.
|
|
7
|
+
* behavior is covered by the server-side integration tests.
|
|
9
8
|
*/
|
|
10
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
10
|
const vitest_1 = require("vitest");
|
package/dist/query-builder.d.ts
CHANGED
package/dist/query-builder.js
CHANGED
|
@@ -189,20 +189,8 @@ class QueryBuilder {
|
|
|
189
189
|
});
|
|
190
190
|
return this;
|
|
191
191
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
*/
|
|
195
|
-
regex(field, pattern) {
|
|
196
|
-
this.filters.push({
|
|
197
|
-
type: "Condition",
|
|
198
|
-
content: {
|
|
199
|
-
field,
|
|
200
|
-
operator: "Regex",
|
|
201
|
-
value: pattern,
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
return this;
|
|
205
|
-
}
|
|
192
|
+
// Note: regex filtering is pending server-side support. The server has no
|
|
193
|
+
// Regex filter operator; use contains/startsWith/endsWith instead.
|
|
206
194
|
// ========================================================================
|
|
207
195
|
// Logical Operators
|
|
208
196
|
// ========================================================================
|
|
@@ -95,10 +95,6 @@ const query_builder_1 = require("./query-builder");
|
|
|
95
95
|
const query = new query_builder_1.QueryBuilder().endsWith("filename", ".pdf").build();
|
|
96
96
|
(0, vitest_1.expect)(query.filter.content.operator).toBe("EndsWith");
|
|
97
97
|
});
|
|
98
|
-
(0, vitest_1.it)("builds regex filter", () => {
|
|
99
|
-
const query = new query_builder_1.QueryBuilder().regex("phone", "^\\+1").build();
|
|
100
|
-
(0, vitest_1.expect)(query.filter.content.operator).toBe("Regex");
|
|
101
|
-
});
|
|
102
98
|
});
|
|
103
99
|
// ============================================================================
|
|
104
100
|
// Logical Operators Tests
|
|
@@ -368,7 +364,6 @@ const query_builder_1 = require("./query-builder");
|
|
|
368
364
|
(0, vitest_1.expect)(qb.contains("i", "j")).toBe(qb);
|
|
369
365
|
(0, vitest_1.expect)(qb.startsWith("k", "l")).toBe(qb);
|
|
370
366
|
(0, vitest_1.expect)(qb.endsWith("m", "n")).toBe(qb);
|
|
371
|
-
(0, vitest_1.expect)(qb.regex("o", "p")).toBe(qb);
|
|
372
367
|
(0, vitest_1.expect)(qb.sortAsc("q")).toBe(qb);
|
|
373
368
|
(0, vitest_1.expect)(qb.sortDesc("r")).toBe(qb);
|
|
374
369
|
(0, vitest_1.expect)(qb.limit(1)).toBe(qb);
|
package/dist/utils.js
CHANGED
|
@@ -33,7 +33,13 @@ exports.extractRecord = extractRecord;
|
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
35
|
function getValue(field) {
|
|
36
|
-
|
|
36
|
+
// Only unwrap a genuine typed wrapper — one carrying BOTH a "type"
|
|
37
|
+
// discriminator and a "value". A user object that merely has a "value" key
|
|
38
|
+
// (e.g. { value: 1, currency: "USD" }) must pass through untouched.
|
|
39
|
+
if (field &&
|
|
40
|
+
typeof field === "object" &&
|
|
41
|
+
"type" in field &&
|
|
42
|
+
"value" in field) {
|
|
37
43
|
return field.value;
|
|
38
44
|
}
|
|
39
45
|
return field;
|
package/dist/utils.test.js
CHANGED
|
@@ -29,6 +29,10 @@ const utils_1 = require("./utils");
|
|
|
29
29
|
const field = { type: "Null", value: null };
|
|
30
30
|
(0, vitest_1.expect)((0, utils_1.getValue)(field)).toBeNull();
|
|
31
31
|
});
|
|
32
|
+
(0, vitest_1.it)("passes through a user object that has a value key but no type (regression #134)", () => {
|
|
33
|
+
const field = { value: 1, currency: "USD" };
|
|
34
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(field)).toEqual(field);
|
|
35
|
+
});
|
|
32
36
|
(0, vitest_1.it)("returns plain string as-is", () => {
|
|
33
37
|
(0, vitest_1.expect)((0, utils_1.getValue)("plain string")).toBe("plain string");
|
|
34
38
|
});
|