@ekodb/ekodb-client 0.15.2 → 0.17.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.
@@ -0,0 +1,327 @@
1
+ "use strict";
2
+ /**
3
+ * Unit tests for the stored-function builder helpers (Stage + parameterRef).
4
+ *
5
+ * These tests cover the pure-data construction helpers and the structural
6
+ * parameter placeholder. They don't hit a running ekoDB — server-side
7
+ * behavior is covered by the Rust integration tests in
8
+ * `ekodb/ekodb_server/tests/function_parameters_tests.rs`.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ const vitest_1 = require("vitest");
12
+ const functions_1 = require("./functions");
13
+ (0, vitest_1.describe)("parameterRef", () => {
14
+ (0, vitest_1.it)("produces the structural placeholder shape ekoDB's resolver expects", () => {
15
+ (0, vitest_1.expect)((0, functions_1.parameterRef)("record")).toEqual({
16
+ type: "Parameter",
17
+ name: "record",
18
+ });
19
+ });
20
+ (0, vitest_1.it)("preserves an arbitrary parameter name verbatim", () => {
21
+ (0, vitest_1.expect)((0, functions_1.parameterRef)("user_id").name).toBe("user_id");
22
+ });
23
+ });
24
+ (0, vitest_1.describe)("Stage.param", () => {
25
+ (0, vitest_1.it)("is an alias for parameterRef(name)", () => {
26
+ (0, vitest_1.expect)(functions_1.Stage.param("x")).toEqual((0, functions_1.parameterRef)("x"));
27
+ });
28
+ });
29
+ (0, vitest_1.describe)("Stage.insert with a structural parameter placeholder", () => {
30
+ (0, vitest_1.it)("embeds the whole-record placeholder into Insert.record", () => {
31
+ const stage = functions_1.Stage.insert("users", functions_1.Stage.param("record"));
32
+ (0, vitest_1.expect)(stage.type).toBe("Insert");
33
+ (0, vitest_1.expect)(stage.collection).toBe("users");
34
+ (0, vitest_1.expect)(stage.record).toEqual({ type: "Parameter", name: "record" });
35
+ });
36
+ (0, vitest_1.it)("also accepts per-field placeholders mixed with literal values", () => {
37
+ const stage = functions_1.Stage.insert("items", {
38
+ label: "{{label}}",
39
+ parent_id: functions_1.Stage.param("parent_id"),
40
+ kind: "item",
41
+ tags: functions_1.Stage.param("tags"),
42
+ });
43
+ (0, vitest_1.expect)(stage.record.label).toBe("{{label}}");
44
+ (0, vitest_1.expect)(stage.record.parent_id).toEqual({
45
+ type: "Parameter",
46
+ name: "parent_id",
47
+ });
48
+ (0, vitest_1.expect)(stage.record.kind).toBe("item");
49
+ (0, vitest_1.expect)(stage.record.tags).toEqual({ type: "Parameter", name: "tags" });
50
+ });
51
+ });
52
+ (0, vitest_1.describe)("Stage.updateById with a structural parameter placeholder", () => {
53
+ (0, vitest_1.it)("embeds the whole-updates placeholder into UpdateById.updates", () => {
54
+ const stage = functions_1.Stage.updateById("items", "{{id}}", functions_1.Stage.param("updates"));
55
+ (0, vitest_1.expect)(stage.type).toBe("UpdateById");
56
+ (0, vitest_1.expect)(stage.collection).toBe("items");
57
+ (0, vitest_1.expect)(stage.record_id).toBe("{{id}}");
58
+ (0, vitest_1.expect)(stage.updates).toEqual({ type: "Parameter", name: "updates" });
59
+ });
60
+ (0, vitest_1.it)("accepts per-field placeholders for partial fine-grained updates", () => {
61
+ const stage = functions_1.Stage.updateById("items", "{{id}}", {
62
+ label: "{{label}}",
63
+ updated_at: "{{updated_at}}",
64
+ });
65
+ (0, vitest_1.expect)(stage.updates.label).toBe("{{label}}");
66
+ (0, vitest_1.expect)(stage.updates.updated_at).toBe("{{updated_at}}");
67
+ });
68
+ });
69
+ (0, vitest_1.describe)("Stage.update (filter-based) with structural updates", () => {
70
+ (0, vitest_1.it)("accepts a Parameter placeholder in both filter values and the updates body", () => {
71
+ const stage = functions_1.Stage.update("items", {
72
+ type: "Condition",
73
+ content: {
74
+ field: "id",
75
+ operator: "Eq",
76
+ value: functions_1.Stage.param("id"),
77
+ },
78
+ }, functions_1.Stage.param("updates"));
79
+ (0, vitest_1.expect)(stage.type).toBe("Update");
80
+ (0, vitest_1.expect)(stage.filter).toEqual({
81
+ type: "Condition",
82
+ content: {
83
+ field: "id",
84
+ operator: "Eq",
85
+ value: { type: "Parameter", name: "id" },
86
+ },
87
+ });
88
+ (0, vitest_1.expect)(stage.updates).toEqual({ type: "Parameter", name: "updates" });
89
+ });
90
+ });
91
+ (0, vitest_1.describe)("Stage.batchInsert with structural placeholders inside each record", () => {
92
+ (0, vitest_1.it)("lets callers template each record's record-body from params", () => {
93
+ const stage = functions_1.Stage.batchInsert("audit_log", [
94
+ { actor: functions_1.Stage.param("user_id"), at: "{{now}}", message: "created" },
95
+ { actor: functions_1.Stage.param("user_id"), at: "{{now}}", message: "initialized" },
96
+ ]);
97
+ (0, vitest_1.expect)(stage.type).toBe("BatchInsert");
98
+ (0, vitest_1.expect)(stage.records).toHaveLength(2);
99
+ (0, vitest_1.expect)(stage.records[0].actor).toEqual({
100
+ type: "Parameter",
101
+ name: "user_id",
102
+ });
103
+ (0, vitest_1.expect)(stage.records[0].at).toBe("{{now}}");
104
+ (0, vitest_1.expect)(stage.records[1].message).toBe("initialized");
105
+ });
106
+ });
107
+ (0, vitest_1.describe)("JSON serialization round-trip (what actually goes on the wire)", () => {
108
+ (0, vitest_1.it)("serializes a structural placeholder exactly as ekoDB expects", () => {
109
+ const stage = functions_1.Stage.insert("users", functions_1.Stage.param("record"));
110
+ const wire = JSON.parse(JSON.stringify(stage));
111
+ (0, vitest_1.expect)(stage.ttl).toBeUndefined();
112
+ (0, vitest_1.expect)("ttl" in wire).toBe(false);
113
+ (0, vitest_1.expect)(wire).toEqual({
114
+ type: "Insert",
115
+ collection: "users",
116
+ record: { type: "Parameter", name: "record" },
117
+ bypass_ripple: false,
118
+ });
119
+ });
120
+ });
121
+ // ============================================================================
122
+ // Crypto primitives: BcryptHash, BcryptVerify, RandomToken (ekoDB >= 0.41.0)
123
+ // ============================================================================
124
+ (0, vitest_1.describe)("Stage.bcryptHash", () => {
125
+ (0, vitest_1.it)("produces a BcryptHash stage with text placeholder + explicit cost", () => {
126
+ const stage = functions_1.Stage.bcryptHash("{{password}}", "password_hash", 12);
127
+ (0, vitest_1.expect)(stage.type).toBe("BcryptHash");
128
+ (0, vitest_1.expect)(stage.plain).toBe("{{password}}");
129
+ (0, vitest_1.expect)(stage.cost).toBe(12);
130
+ (0, vitest_1.expect)(stage.output_field).toBe("password_hash");
131
+ });
132
+ (0, vitest_1.it)("leaves cost undefined when the caller omits it", () => {
133
+ const stage = functions_1.Stage.bcryptHash("{{password}}", "pw_hash");
134
+ (0, vitest_1.expect)(stage.cost).toBeUndefined();
135
+ });
136
+ });
137
+ (0, vitest_1.describe)("Stage.bcryptVerify", () => {
138
+ (0, vitest_1.it)("produces a BcryptVerify stage wiring hash_field and output_field", () => {
139
+ const stage = functions_1.Stage.bcryptVerify("{{password}}", "password_hash", "valid");
140
+ (0, vitest_1.expect)(stage.type).toBe("BcryptVerify");
141
+ (0, vitest_1.expect)(stage.plain).toBe("{{password}}");
142
+ (0, vitest_1.expect)(stage.hash_field).toBe("password_hash");
143
+ (0, vitest_1.expect)(stage.output_field).toBe("valid");
144
+ });
145
+ });
146
+ (0, vitest_1.describe)("Stage.randomToken", () => {
147
+ (0, vitest_1.it)("produces a RandomToken stage with explicit encoding", () => {
148
+ const stage = functions_1.Stage.randomToken(32, "session_token", "hex");
149
+ (0, vitest_1.expect)(stage.type).toBe("RandomToken");
150
+ (0, vitest_1.expect)(stage.bytes).toBe(32);
151
+ (0, vitest_1.expect)(stage.encoding).toBe("hex");
152
+ (0, vitest_1.expect)(stage.output_field).toBe("session_token");
153
+ });
154
+ (0, vitest_1.it)("leaves encoding undefined by default (server treats as hex)", () => {
155
+ const stage = functions_1.Stage.randomToken(16, "token");
156
+ (0, vitest_1.expect)(stage.encoding).toBeUndefined();
157
+ });
158
+ (0, vitest_1.it)("accepts base64 and base64url encodings", () => {
159
+ const a = functions_1.Stage.randomToken(16, "t", "base64");
160
+ const b = functions_1.Stage.randomToken(16, "t", "base64url");
161
+ (0, vitest_1.expect)(a.encoding).toBe("base64");
162
+ (0, vitest_1.expect)(b.encoding).toBe("base64url");
163
+ });
164
+ });
165
+ (0, vitest_1.describe)("Crypto stages JSON wire format", () => {
166
+ (0, vitest_1.it)("BcryptHash round-trips through JSON unchanged", () => {
167
+ const stage = functions_1.Stage.bcryptHash("{{password}}", "password_hash", 12);
168
+ const wire = JSON.parse(JSON.stringify(stage));
169
+ (0, vitest_1.expect)(wire).toEqual({
170
+ type: "BcryptHash",
171
+ plain: "{{password}}",
172
+ cost: 12,
173
+ output_field: "password_hash",
174
+ });
175
+ });
176
+ (0, vitest_1.it)("BcryptVerify round-trips through JSON unchanged", () => {
177
+ const stage = functions_1.Stage.bcryptVerify("{{password}}", "password_hash", "valid");
178
+ const wire = JSON.parse(JSON.stringify(stage));
179
+ (0, vitest_1.expect)(wire).toEqual({
180
+ type: "BcryptVerify",
181
+ plain: "{{password}}",
182
+ hash_field: "password_hash",
183
+ output_field: "valid",
184
+ });
185
+ });
186
+ (0, vitest_1.it)("RandomToken round-trips through JSON unchanged", () => {
187
+ const stage = functions_1.Stage.randomToken(32, "token", "hex");
188
+ const wire = JSON.parse(JSON.stringify(stage));
189
+ (0, vitest_1.expect)(wire).toEqual({
190
+ type: "RandomToken",
191
+ bytes: 32,
192
+ encoding: "hex",
193
+ output_field: "token",
194
+ });
195
+ });
196
+ });
197
+ // ============================================================================
198
+ // Error Handling & Control Flow: TryCatch, Parallel, Sleep (ekoDB >= 0.42.0)
199
+ // ============================================================================
200
+ (0, vitest_1.describe)("Stage.tryCatch", () => {
201
+ (0, vitest_1.it)("produces a TryCatch stage with try/catch function lists", () => {
202
+ const stage = functions_1.Stage.tryCatch([functions_1.Stage.httpRequest("https://api.example.com/data")], [functions_1.Stage.insert("fallback_log", { error: "{{error}}" })], "api_error");
203
+ (0, vitest_1.expect)(stage.type).toBe("TryCatch");
204
+ (0, vitest_1.expect)(stage.try_functions).toHaveLength(1);
205
+ (0, vitest_1.expect)(stage.catch_functions).toHaveLength(1);
206
+ (0, vitest_1.expect)(stage.output_error_field).toBe("api_error");
207
+ });
208
+ (0, vitest_1.it)("leaves output_error_field undefined when omitted", () => {
209
+ const stage = functions_1.Stage.tryCatch([functions_1.Stage.findAll("users")], [functions_1.Stage.insert("errors", { msg: "failed" })]);
210
+ (0, vitest_1.expect)(stage.output_error_field).toBeUndefined();
211
+ });
212
+ });
213
+ (0, vitest_1.describe)("Stage.parallel", () => {
214
+ (0, vitest_1.it)("produces a Parallel stage with functions and wait_for_all", () => {
215
+ const stage = functions_1.Stage.parallel([functions_1.Stage.findAll("users"), functions_1.Stage.findAll("orders")], true);
216
+ (0, vitest_1.expect)(stage.type).toBe("Parallel");
217
+ (0, vitest_1.expect)(stage.functions).toHaveLength(2);
218
+ (0, vitest_1.expect)(stage.wait_for_all).toBe(true);
219
+ });
220
+ (0, vitest_1.it)("defaults wait_for_all to true", () => {
221
+ const stage = functions_1.Stage.parallel([functions_1.Stage.findAll("users")]);
222
+ (0, vitest_1.expect)(stage.wait_for_all).toBe(true);
223
+ });
224
+ (0, vitest_1.it)("accepts wait_for_all = false for race semantics", () => {
225
+ const stage = functions_1.Stage.parallel([functions_1.Stage.findAll("users"), functions_1.Stage.findAll("cache")], false);
226
+ (0, vitest_1.expect)(stage.wait_for_all).toBe(false);
227
+ });
228
+ });
229
+ (0, vitest_1.describe)("Stage.sleep", () => {
230
+ (0, vitest_1.it)("produces a Sleep stage with numeric duration", () => {
231
+ const stage = functions_1.Stage.sleep(1000);
232
+ (0, vitest_1.expect)(stage.type).toBe("Sleep");
233
+ (0, vitest_1.expect)(stage.duration_ms).toBe(1000);
234
+ });
235
+ (0, vitest_1.it)("accepts a text placeholder for parameter substitution", () => {
236
+ const stage = functions_1.Stage.sleep("{{delay}}");
237
+ (0, vitest_1.expect)(stage.duration_ms).toBe("{{delay}}");
238
+ });
239
+ });
240
+ // ============================================================================
241
+ // Response Formatting: Return (ekoDB >= 0.42.0)
242
+ // ============================================================================
243
+ (0, vitest_1.describe)("Stage.returnResponse", () => {
244
+ (0, vitest_1.it)("produces a Return stage with fields and status_code", () => {
245
+ const stage = functions_1.Stage.returnResponse({ message: "ok", user_id: "{{id}}" }, 201);
246
+ (0, vitest_1.expect)(stage.type).toBe("Return");
247
+ (0, vitest_1.expect)(stage.fields).toEqual({ message: "ok", user_id: "{{id}}" });
248
+ (0, vitest_1.expect)(stage.status_code).toBe(201);
249
+ });
250
+ (0, vitest_1.it)("leaves status_code undefined when omitted (server defaults to 200)", () => {
251
+ const stage = functions_1.Stage.returnResponse({ success: true });
252
+ (0, vitest_1.expect)(stage.status_code).toBeUndefined();
253
+ });
254
+ });
255
+ // ============================================================================
256
+ // Data Validation: Validate (ekoDB >= 0.42.0)
257
+ // ============================================================================
258
+ (0, vitest_1.describe)("Stage.validate", () => {
259
+ (0, vitest_1.it)("produces a Validate stage with schema, data_field, and on_error", () => {
260
+ const schema = {
261
+ type: "object",
262
+ required: ["name", "email"],
263
+ properties: {
264
+ name: { type: "string" },
265
+ email: { type: "string", format: "email" },
266
+ },
267
+ };
268
+ const stage = functions_1.Stage.validate(schema, "{{input}}", [
269
+ functions_1.Stage.returnResponse({ error: "validation failed" }, 400),
270
+ ]);
271
+ (0, vitest_1.expect)(stage.type).toBe("Validate");
272
+ (0, vitest_1.expect)(stage.schema).toEqual(schema);
273
+ (0, vitest_1.expect)(stage.data_field).toBe("{{input}}");
274
+ (0, vitest_1.expect)(stage.on_error).toHaveLength(1);
275
+ });
276
+ (0, vitest_1.it)("leaves on_error undefined when omitted", () => {
277
+ const stage = functions_1.Stage.validate({ type: "object" }, "record");
278
+ (0, vitest_1.expect)(stage.on_error).toBeUndefined();
279
+ });
280
+ });
281
+ // ============================================================================
282
+ // New stages JSON wire format
283
+ // ============================================================================
284
+ (0, vitest_1.describe)("New stages JSON wire format", () => {
285
+ (0, vitest_1.it)("TryCatch round-trips through JSON unchanged", () => {
286
+ const stage = functions_1.Stage.tryCatch([functions_1.Stage.findAll("users")], [functions_1.Stage.insert("errors", { msg: "failed" })], "err");
287
+ const wire = JSON.parse(JSON.stringify(stage));
288
+ (0, vitest_1.expect)(wire.type).toBe("TryCatch");
289
+ (0, vitest_1.expect)(wire.try_functions).toHaveLength(1);
290
+ (0, vitest_1.expect)(wire.catch_functions).toHaveLength(1);
291
+ (0, vitest_1.expect)(wire.output_error_field).toBe("err");
292
+ });
293
+ (0, vitest_1.it)("Parallel round-trips through JSON unchanged", () => {
294
+ const stage = functions_1.Stage.parallel([functions_1.Stage.findAll("a"), functions_1.Stage.findAll("b")], false);
295
+ const wire = JSON.parse(JSON.stringify(stage));
296
+ (0, vitest_1.expect)(wire).toEqual({
297
+ type: "Parallel",
298
+ functions: [
299
+ { type: "FindAll", collection: "a" },
300
+ { type: "FindAll", collection: "b" },
301
+ ],
302
+ wait_for_all: false,
303
+ });
304
+ });
305
+ (0, vitest_1.it)("Sleep round-trips through JSON unchanged", () => {
306
+ const wire = JSON.parse(JSON.stringify(functions_1.Stage.sleep(500)));
307
+ (0, vitest_1.expect)(wire).toEqual({ type: "Sleep", duration_ms: 500 });
308
+ });
309
+ (0, vitest_1.it)("Return round-trips through JSON unchanged", () => {
310
+ const stage = functions_1.Stage.returnResponse({ ok: true }, 201);
311
+ const wire = JSON.parse(JSON.stringify(stage));
312
+ (0, vitest_1.expect)(wire).toEqual({
313
+ type: "Return",
314
+ fields: { ok: true },
315
+ status_code: 201,
316
+ });
317
+ });
318
+ (0, vitest_1.it)("Validate round-trips through JSON unchanged", () => {
319
+ const stage = functions_1.Stage.validate({ type: "object" }, "data");
320
+ const wire = JSON.parse(JSON.stringify(stage));
321
+ (0, vitest_1.expect)(wire).toEqual({
322
+ type: "Validate",
323
+ schema: { type: "object" },
324
+ data_field: "data",
325
+ });
326
+ });
327
+ });
package/dist/index.d.ts CHANGED
@@ -3,12 +3,13 @@ export { QueryBuilder, SortOrder } from "./query-builder";
3
3
  export { SearchQueryBuilder } from "./search";
4
4
  export { SchemaBuilder, FieldTypeSchemaBuilder, VectorIndexAlgorithm, DistanceMetric, } from "./schema";
5
5
  export { JoinBuilder } from "./join";
6
- export { Stage, ChatMessage } from "./functions";
6
+ export { Stage, ChatMessage, parameterRef } from "./functions";
7
+ export type { ParameterRef } from "./functions";
7
8
  export { getValue, getValues, extractRecord, getDateTimeValue, getUUIDValue, getDecimalValue, getDurationValue, getBytesValue, getBinaryValue, getArrayValue, getSetValue, getVectorValue, getObjectValue, Field, } from "./utils";
8
9
  export type { WrappedFieldValue } from "./utils";
9
10
  export type { SearchQuery, SearchResult, SearchResponse } from "./search";
10
11
  export type { Schema, FieldTypeSchema, IndexConfig, CollectionMetadata, } from "./schema";
11
12
  export type { JoinConfig } from "./join";
12
- export type { Script, ParameterDefinition, FunctionStageConfig, GroupFunctionConfig, SortFieldConfig, FunctionResult, FunctionStats, StageStats, } from "./functions";
13
+ export type { UserFunction, ParameterDefinition, FunctionStageConfig, GroupFunctionConfig, SortFieldConfig, FunctionResult, FunctionStats, StageStats, } from "./functions";
13
14
  export type { MutationNotification, ChatStreamEvent, ClientToolDefinition, ChatSendOptions, SubscribeOptions, } from "./client";
14
- export type { Record, Query, BatchOperationResult, ClientConfig, RateLimitInfo, CollectionConfig, ChatRequest, CreateChatSessionRequest, ChatMessageRequest, TokenUsage, ChatResponse, ChatSession, ChatSessionResponse, ListSessionsQuery, ListSessionsResponse, GetMessagesQuery, GetMessagesResponse, UpdateSessionRequest, MergeSessionsRequest, ChatModels, EmbedRequest, EmbedResponse, RawCompletionRequest, RawCompletionResponse, UserFunction, ToolChoice, ToolConfig, } 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, 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.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.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; } });
@@ -25,6 +25,7 @@ Object.defineProperty(exports, "JoinBuilder", { enumerable: true, get: function
25
25
  var functions_1 = require("./functions");
26
26
  Object.defineProperty(exports, "Stage", { enumerable: true, get: function () { return functions_1.Stage; } });
27
27
  Object.defineProperty(exports, "ChatMessage", { enumerable: true, get: function () { return functions_1.ChatMessage; } });
28
+ Object.defineProperty(exports, "parameterRef", { enumerable: true, get: function () { return functions_1.parameterRef; } });
28
29
  var utils_1 = require("./utils");
29
30
  Object.defineProperty(exports, "getValue", { enumerable: true, get: function () { return utils_1.getValue; } });
30
31
  Object.defineProperty(exports, "getValues", { enumerable: true, get: function () { return utils_1.getValues; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekodb/ekodb-client",
3
- "version": "0.15.2",
3
+ "version": "0.17.0",
4
4
  "description": "Official TypeScript/JavaScript client for ekoDB",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -800,7 +800,7 @@ describe("EkoDBClient scripts advanced", () => {
800
800
  parameters: {},
801
801
  functions: [],
802
802
  };
803
- const result = await client.saveScript(script);
803
+ const result = await client.saveFunction(script);
804
804
 
805
805
  expect(result).toBeDefined();
806
806
  });
@@ -811,7 +811,7 @@ describe("EkoDBClient scripts advanced", () => {
811
811
  mockTokenResponse();
812
812
  mockJsonResponse({ id: "func_123", label: "my_function" });
813
813
 
814
- const result = await client.getScript("func_123");
814
+ const result = await client.getFunction("func_123");
815
815
 
816
816
  expect(result).toBeDefined();
817
817
  });
@@ -829,7 +829,7 @@ describe("EkoDBClient scripts advanced", () => {
829
829
  functions: [],
830
830
  };
831
831
  await expect(
832
- client.updateScript("func_123", script),
832
+ client.updateFunction("func_123", script),
833
833
  ).resolves.not.toThrow();
834
834
  });
835
835
  });
@@ -2674,3 +2674,243 @@ describe("EkoDBClient auth token management", () => {
2674
2674
  expect(mockFetch).toHaveBeenCalledTimes(2);
2675
2675
  });
2676
2676
  });
2677
+
2678
+ // ============================================================================
2679
+ // agent_id Tests
2680
+ // ============================================================================
2681
+
2682
+ describe("agent_id on chat types", () => {
2683
+ it("CreateChatSessionRequest includes agent_id", () => {
2684
+ const req: any = {
2685
+ collections: [{ collection_name: "docs" }],
2686
+ llm_provider: "openai",
2687
+ agent_id: "my-agent",
2688
+ };
2689
+ expect(req.agent_id).toBe("my-agent");
2690
+ });
2691
+
2692
+ it("CreateChatSessionRequest omits agent_id when undefined", () => {
2693
+ const req: any = {
2694
+ collections: [],
2695
+ llm_provider: "openai",
2696
+ };
2697
+ expect(req.agent_id).toBeUndefined();
2698
+ });
2699
+
2700
+ it("ChatSession includes agent_id", () => {
2701
+ const session: any = {
2702
+ chat_id: "c1",
2703
+ created_at: "2026-01-01",
2704
+ updated_at: "2026-01-01",
2705
+ llm_provider: "openai",
2706
+ llm_model: "gpt-4",
2707
+ collections: [],
2708
+ agent_id: "bot-1",
2709
+ message_count: 0,
2710
+ };
2711
+ expect(session.agent_id).toBe("bot-1");
2712
+ });
2713
+
2714
+ it("ChatSession allows missing agent_id", () => {
2715
+ const session: any = {
2716
+ chat_id: "c1",
2717
+ created_at: "2026-01-01",
2718
+ updated_at: "2026-01-01",
2719
+ llm_provider: "openai",
2720
+ llm_model: "gpt-4",
2721
+ collections: [],
2722
+ message_count: 0,
2723
+ };
2724
+ expect(session.agent_id).toBeUndefined();
2725
+ });
2726
+ });
2727
+
2728
+ // ============================================================================
2729
+ // client_tools / confirm_tools / exclude_tools Tests
2730
+ // ============================================================================
2731
+
2732
+ describe("ChatMessageRequest tool fields", () => {
2733
+ it("includes client_tools, confirm_tools, exclude_tools", () => {
2734
+ const req: any = {
2735
+ message: "hello",
2736
+ client_tools: [
2737
+ {
2738
+ name: "weather",
2739
+ description: "Get weather",
2740
+ parameters: { type: "object" },
2741
+ },
2742
+ ],
2743
+ confirm_tools: ["shell_exec"],
2744
+ exclude_tools: ["file_delete"],
2745
+ };
2746
+ expect(req.client_tools).toHaveLength(1);
2747
+ expect(req.client_tools[0].name).toBe("weather");
2748
+ expect(req.confirm_tools).toEqual(["shell_exec"]);
2749
+ expect(req.exclude_tools).toEqual(["file_delete"]);
2750
+ });
2751
+
2752
+ it("tool fields are optional", () => {
2753
+ const req: any = { message: "hi" };
2754
+ expect(req.client_tools).toBeUndefined();
2755
+ expect(req.confirm_tools).toBeUndefined();
2756
+ expect(req.exclude_tools).toBeUndefined();
2757
+ });
2758
+
2759
+ it("ClientToolDefinition has correct shape", () => {
2760
+ const tool: any = {
2761
+ name: "calc",
2762
+ description: "Calculator",
2763
+ parameters: { type: "object", properties: {} },
2764
+ };
2765
+ expect(tool.name).toBe("calc");
2766
+ expect(tool.description).toBe("Calculator");
2767
+ expect(tool.parameters.type).toBe("object");
2768
+ });
2769
+ });
2770
+
2771
+ // ============================================================================
2772
+ // submitChatToolResult Tests
2773
+ // ============================================================================
2774
+
2775
+ describe("submitChatToolResult", () => {
2776
+ it("sends tool result to correct endpoint", async () => {
2777
+ const client = createTestClient();
2778
+ mockTokenResponse();
2779
+ mockJsonResponse({});
2780
+
2781
+ await client.submitChatToolResult("chat-123", "call-456", true, {
2782
+ temp: "72F",
2783
+ });
2784
+
2785
+ expect(mockFetch).toHaveBeenCalledTimes(2);
2786
+ const call = mockFetch.mock.calls[1];
2787
+ expect(call[0]).toContain("/api/chat/chat-123/tool-result");
2788
+ const body = JSON.parse(call[1].body);
2789
+ expect(body.call_id).toBe("call-456");
2790
+ expect(body.success).toBe(true);
2791
+ expect(body.result.temp).toBe("72F");
2792
+ });
2793
+
2794
+ it("sends error result", async () => {
2795
+ const client = createTestClient();
2796
+ mockTokenResponse();
2797
+ mockJsonResponse({});
2798
+
2799
+ await client.submitChatToolResult(
2800
+ "chat-123",
2801
+ "call-456",
2802
+ false,
2803
+ undefined,
2804
+ "tool crashed",
2805
+ );
2806
+
2807
+ const call = mockFetch.mock.calls[1];
2808
+ const body = JSON.parse(call[1].body);
2809
+ expect(body.success).toBe(false);
2810
+ expect(body.error).toBe("tool crashed");
2811
+ expect(body.result).toBeUndefined();
2812
+ });
2813
+ });
2814
+
2815
+ // ============================================================================
2816
+ // subscribeSSE Tests
2817
+ // ============================================================================
2818
+
2819
+ describe("subscribeSSE", () => {
2820
+ /** Create a mock ReadableStream from a string */
2821
+ function mockReadableStream(data: string) {
2822
+ const encoder = new TextEncoder();
2823
+ const bytes = encoder.encode(data);
2824
+ let sent = false;
2825
+ return {
2826
+ getReader: () => ({
2827
+ read: async () => {
2828
+ if (!sent) {
2829
+ sent = true;
2830
+ return { value: bytes, done: false };
2831
+ }
2832
+ return { value: undefined, done: true };
2833
+ },
2834
+ }),
2835
+ };
2836
+ }
2837
+
2838
+ it("parses mutation events from SSE stream", async () => {
2839
+ const client = createTestClient();
2840
+ mockTokenResponse();
2841
+
2842
+ const sseBody =
2843
+ "event: subscribed\ndata: {}\n\n" +
2844
+ 'event: mutation\ndata: {"collection":"orders","event":"insert","record_ids":["r1"],"timestamp":"t1"}\n\n' +
2845
+ 'event: mutation\ndata: {"collection":"orders","event":"update","record_ids":["r2"],"timestamp":"t2"}\n\n';
2846
+
2847
+ mockFetch.mockResolvedValueOnce({
2848
+ ok: true,
2849
+ status: 200,
2850
+ body: mockReadableStream(sseBody),
2851
+ headers: new Headers({ "content-type": "text/event-stream" }),
2852
+ });
2853
+
2854
+ const stream = client.subscribeSSE("orders");
2855
+ const events: any[] = [];
2856
+ await new Promise<void>((resolve) => {
2857
+ stream.on("event", (e: any) => events.push(e));
2858
+ stream.on("close", resolve);
2859
+ setTimeout(resolve, 100);
2860
+ });
2861
+
2862
+ expect(events).toHaveLength(2);
2863
+ expect(events[0].event).toBe("insert");
2864
+ expect(events[0].recordIds).toEqual(["r1"]);
2865
+ expect(events[1].event).toBe("update");
2866
+ expect(events[1].recordIds).toEqual(["r2"]);
2867
+ });
2868
+
2869
+ it("passes filter params in URL", async () => {
2870
+ const client = createTestClient();
2871
+ mockTokenResponse();
2872
+
2873
+ mockFetch.mockResolvedValueOnce({
2874
+ ok: true,
2875
+ status: 200,
2876
+ body: mockReadableStream(""),
2877
+ headers: new Headers({ "content-type": "text/event-stream" }),
2878
+ });
2879
+
2880
+ client.subscribeSSE("orders", {
2881
+ filterField: "status",
2882
+ filterValue: "active",
2883
+ });
2884
+
2885
+ // Wait for async fetch
2886
+ await new Promise((r) => setTimeout(r, 50));
2887
+
2888
+ const call = mockFetch.mock.calls[1];
2889
+ expect(call[0]).toContain("/api/subscribe/orders");
2890
+ expect(call[0]).toContain("filter_field=status");
2891
+ expect(call[0]).toContain("filter_value=active");
2892
+ });
2893
+
2894
+ it("emits error on HTTP failure", async () => {
2895
+ const client = createTestClient();
2896
+ mockTokenResponse();
2897
+
2898
+ mockFetch.mockResolvedValueOnce({
2899
+ ok: false,
2900
+ status: 401,
2901
+ text: async () => "Unauthorized",
2902
+ headers: new Headers(),
2903
+ });
2904
+
2905
+ const stream = client.subscribeSSE("orders");
2906
+ const errors: string[] = [];
2907
+ await new Promise<void>((resolve) => {
2908
+ stream.on("error", (e: string) => errors.push(e));
2909
+ stream.on("close", resolve);
2910
+ setTimeout(resolve, 100);
2911
+ });
2912
+
2913
+ expect(errors).toHaveLength(1);
2914
+ expect(errors[0]).toContain("401");
2915
+ });
2916
+ });