@ekodb/ekodb-client 0.3.0 → 0.5.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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.JoinBuilder = exports.DistanceMetric = exports.VectorIndexAlgorithm = exports.FieldTypeSchemaBuilder = exports.SchemaBuilder = exports.SearchQueryBuilder = exports.SortOrder = exports.QueryBuilder = exports.RateLimitError = exports.MergeStrategy = exports.SerializationFormat = exports.WebSocketClient = exports.EkoDBClient = void 0;
3
+ exports.ChatMessage = exports.Stage = exports.JoinBuilder = exports.DistanceMetric = exports.VectorIndexAlgorithm = exports.FieldTypeSchemaBuilder = exports.SchemaBuilder = exports.SearchQueryBuilder = exports.SortOrder = exports.QueryBuilder = exports.RateLimitError = exports.MergeStrategy = exports.SerializationFormat = 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; } });
@@ -19,3 +19,6 @@ Object.defineProperty(exports, "VectorIndexAlgorithm", { enumerable: true, get:
19
19
  Object.defineProperty(exports, "DistanceMetric", { enumerable: true, get: function () { return schema_1.DistanceMetric; } });
20
20
  var join_1 = require("./join");
21
21
  Object.defineProperty(exports, "JoinBuilder", { enumerable: true, get: function () { return join_1.JoinBuilder; } });
22
+ var functions_1 = require("./functions");
23
+ Object.defineProperty(exports, "Stage", { enumerable: true, get: function () { return functions_1.Stage; } });
24
+ Object.defineProperty(exports, "ChatMessage", { enumerable: true, get: function () { return functions_1.ChatMessage; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekodb/ekodb-client",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Official TypeScript/JavaScript client for ekoDB",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/client.ts CHANGED
@@ -6,6 +6,7 @@ import { encode, decode } from "@msgpack/msgpack";
6
6
  import { QueryBuilder, Query as QueryBuilderQuery } from "./query-builder";
7
7
  import { SearchQuery, SearchQueryBuilder, SearchResponse } from "./search";
8
8
  import { Schema, SchemaBuilder, CollectionMetadata } from "./schema";
9
+ import { Script, FunctionResult } from "./functions";
9
10
 
10
11
  export interface Record {
11
12
  [key: string]: any;
@@ -396,6 +397,14 @@ export class EkoDBClient {
396
397
  throw new RateLimitError(retryAfter);
397
398
  }
398
399
 
400
+ // Handle unauthorized (401) - try refreshing token
401
+ if (response.status === 401 && attempt === 0) {
402
+ console.log("Authentication failed, refreshing token...");
403
+ await this.refreshToken();
404
+ // Retry with new token
405
+ return this.makeRequest<T>(method, path, data, attempt + 1, forceJson);
406
+ }
407
+
399
408
  // Handle service unavailable (503)
400
409
  if (
401
410
  response.status === 503 &&
@@ -954,12 +963,242 @@ export class EkoDBClient {
954
963
  );
955
964
  }
956
965
 
966
+ // ========================================================================
967
+ // SCRIPTS API
968
+ // ========================================================================
969
+
970
+ /**
971
+ * Save a new script definition
972
+ */
973
+ async saveScript(script: Script): Promise<string> {
974
+ const result = await this.makeRequest<{ id: string }>(
975
+ "POST",
976
+ "/api/functions",
977
+ script,
978
+ );
979
+ return result.id;
980
+ }
981
+
982
+ /**
983
+ * Get a script by ID
984
+ */
985
+ async getScript(id: string): Promise<Script> {
986
+ return this.makeRequest<Script>("GET", `/api/functions/${id}`);
987
+ }
988
+
989
+ /**
990
+ * List all scripts, optionally filtered by tags
991
+ */
992
+ async listScripts(tags?: string[]): Promise<Script[]> {
993
+ const params = tags ? `?tags=${tags.join(",")}` : "";
994
+ return this.makeRequest<Script[]>("GET", `/api/functions${params}`);
995
+ }
996
+
997
+ /**
998
+ * Update an existing script by ID
999
+ */
1000
+ async updateScript(id: string, script: Script): Promise<void> {
1001
+ await this.makeRequest<void>("PUT", `/api/functions/${id}`, script);
1002
+ }
1003
+
1004
+ /**
1005
+ * Delete a script by ID
1006
+ */
1007
+ async deleteScript(id: string): Promise<void> {
1008
+ await this.makeRequest<void>("DELETE", `/api/functions/${id}`);
1009
+ }
1010
+
1011
+ /**
1012
+ * Call a saved script by ID or label
1013
+ */
1014
+ async callScript(
1015
+ idOrLabel: string,
1016
+ params?: { [key: string]: any },
1017
+ ): Promise<FunctionResult> {
1018
+ return this.makeRequest<FunctionResult>(
1019
+ "POST",
1020
+ `/api/functions/${idOrLabel}`,
1021
+ params || {},
1022
+ );
1023
+ }
1024
+
957
1025
  /**
958
1026
  * Create a WebSocket client
959
1027
  */
960
1028
  websocket(wsURL: string): WebSocketClient {
961
1029
  return new WebSocketClient(wsURL, this.token!);
962
1030
  }
1031
+
1032
+ // ========== RAG Helper Methods ==========
1033
+
1034
+ /**
1035
+ * Generate embeddings for text using ekoDB's native Functions
1036
+ *
1037
+ * This helper simplifies embedding generation by:
1038
+ * 1. Creating a temporary collection with the text
1039
+ * 2. Running a Script with FindAll + Embed Functions
1040
+ * 3. Extracting and returning the embedding vector
1041
+ * 4. Cleaning up temporary resources
1042
+ *
1043
+ * @param text - The text to generate embeddings for
1044
+ * @param model - The embedding model to use (e.g., "text-embedding-3-small")
1045
+ * @returns Array of floats representing the embedding vector
1046
+ *
1047
+ * @example
1048
+ * ```typescript
1049
+ * const embedding = await client.embed(
1050
+ * "Hello world",
1051
+ * "text-embedding-3-small"
1052
+ * );
1053
+ * console.log(`Generated ${embedding.length} dimensions`);
1054
+ * ```
1055
+ */
1056
+ async embed(text: string, model: string): Promise<number[]> {
1057
+ const tempCollection = `embed_temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1058
+
1059
+ try {
1060
+ // Insert temporary record with the text
1061
+ await this.insert(tempCollection, { text }, undefined);
1062
+
1063
+ // Create Script with FindAll + Embed Functions
1064
+ const tempLabel = `embed_script_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1065
+ const script: Script = {
1066
+ label: tempLabel,
1067
+ name: "Generate Embedding",
1068
+ description: "Temporary script for embedding generation",
1069
+ version: "1.0",
1070
+ parameters: {},
1071
+ functions: [
1072
+ {
1073
+ type: "FindAll",
1074
+ collection: tempCollection,
1075
+ },
1076
+ {
1077
+ type: "Embed",
1078
+ input_field: "text",
1079
+ output_field: "embedding",
1080
+ model: model,
1081
+ } as any,
1082
+ ],
1083
+ tags: [],
1084
+ };
1085
+
1086
+ // Save and execute the script
1087
+ const scriptId = await this.saveScript(script);
1088
+ const result = await this.callScript(scriptId, undefined);
1089
+
1090
+ // Clean up
1091
+ await this.deleteScript(scriptId).catch(() => {});
1092
+ await this.deleteCollection(tempCollection).catch(() => {});
1093
+
1094
+ // Extract embedding from result
1095
+ if (result.records && result.records.length > 0) {
1096
+ const record = result.records[0];
1097
+ if (record.embedding && Array.isArray(record.embedding)) {
1098
+ return record.embedding as number[];
1099
+ }
1100
+ }
1101
+
1102
+ throw new Error("Failed to extract embedding from result");
1103
+ } catch (error) {
1104
+ // Ensure cleanup even on error
1105
+ await this.deleteCollection(tempCollection).catch(() => {});
1106
+ throw error;
1107
+ }
1108
+ }
1109
+
1110
+ /**
1111
+ * Perform text search without embeddings
1112
+ *
1113
+ * Simplified text search with full-text matching, fuzzy search, and stemming.
1114
+ *
1115
+ * @param collection - Collection name to search
1116
+ * @param queryText - Search query text
1117
+ * @param limit - Maximum number of results to return
1118
+ * @returns Array of matching records
1119
+ *
1120
+ * @example
1121
+ * ```typescript
1122
+ * const results = await client.textSearch(
1123
+ * "documents",
1124
+ * "ownership system",
1125
+ * 10
1126
+ * );
1127
+ * ```
1128
+ */
1129
+ async textSearch(
1130
+ collection: string,
1131
+ queryText: string,
1132
+ limit: number,
1133
+ ): Promise<Record[]> {
1134
+ const searchQuery: SearchQuery = {
1135
+ query: queryText,
1136
+ limit,
1137
+ };
1138
+
1139
+ const response = await this.search(collection, searchQuery);
1140
+ return response.results.map((r) => r.record);
1141
+ }
1142
+
1143
+ /**
1144
+ * Perform hybrid search combining text and vector search
1145
+ *
1146
+ * Combines semantic similarity (vector search) with keyword matching (text search)
1147
+ * for more accurate and relevant results.
1148
+ *
1149
+ * @param collection - Collection name to search
1150
+ * @param queryText - Search query text
1151
+ * @param queryVector - Embedding vector for semantic search
1152
+ * @param limit - Maximum number of results to return
1153
+ * @returns Array of matching records
1154
+ *
1155
+ * @example
1156
+ * ```typescript
1157
+ * const embedding = await client.embed(query, "text-embedding-3-small");
1158
+ * const results = await client.hybridSearch(
1159
+ * "documents",
1160
+ * query,
1161
+ * embedding,
1162
+ * 5
1163
+ * );
1164
+ * ```
1165
+ */
1166
+ async hybridSearch(
1167
+ collection: string,
1168
+ queryText: string,
1169
+ queryVector: number[],
1170
+ limit: number,
1171
+ ): Promise<Record[]> {
1172
+ const searchQuery: SearchQuery = {
1173
+ query: queryText,
1174
+ vector: queryVector,
1175
+ limit,
1176
+ };
1177
+
1178
+ const response = await this.search(collection, searchQuery);
1179
+ return response.results.map((r) => r.record);
1180
+ }
1181
+
1182
+ /**
1183
+ * Find all records in a collection with a limit
1184
+ *
1185
+ * Simplified method to query all documents in a collection.
1186
+ *
1187
+ * @param collection - Collection name
1188
+ * @param limit - Maximum number of records to return
1189
+ * @returns Array of records
1190
+ *
1191
+ * @example
1192
+ * ```typescript
1193
+ * const allMessages = await client.findAll("messages", 1000);
1194
+ * console.log(`Found ${allMessages.length} messages`);
1195
+ * ```
1196
+ */
1197
+ async findAllWithLimit(collection: string, limit: number): Promise<Record[]> {
1198
+ const query = new QueryBuilder().limit(limit).build();
1199
+ const results = await this.find(collection, query);
1200
+ return results;
1201
+ }
963
1202
  }
964
1203
 
965
1204
  /**
@@ -0,0 +1,420 @@
1
+ /**
2
+ * Scripts API for ekoDB TypeScript client
3
+ */
4
+
5
+ export interface Script {
6
+ label: string;
7
+ name: string;
8
+ description?: string;
9
+ version: string;
10
+ parameters: { [key: string]: ParameterDefinition };
11
+ functions: FunctionStageConfig[];
12
+ tags: string[];
13
+ created_at?: string;
14
+ updated_at?: string;
15
+ }
16
+
17
+ export interface ParameterDefinition {
18
+ required: boolean;
19
+ default?: any;
20
+ description?: string;
21
+ param_type?: string;
22
+ }
23
+
24
+ // ParameterValue removed - use plain values instead
25
+
26
+ export type FunctionStageConfig =
27
+ | { type: "FindAll"; collection: string }
28
+ | {
29
+ type: "Query";
30
+ collection: string;
31
+ filter?: Record<string, any>;
32
+ sort?: SortFieldConfig[];
33
+ limit?: number;
34
+ skip?: number;
35
+ }
36
+ | { type: "Project"; fields: string[]; exclude: boolean }
37
+ | {
38
+ type: "Group";
39
+ by_fields: string[];
40
+ functions: GroupFunctionConfig[];
41
+ }
42
+ | { type: "Count"; output_field: string }
43
+ | { type: "Filter"; filter: Record<string, any> }
44
+ | { type: "Sort"; sort: SortFieldConfig[] }
45
+ | { type: "Limit"; limit: number }
46
+ | { type: "Skip"; skip: number }
47
+ | {
48
+ type: "Insert";
49
+ collection: string;
50
+ record: Record<string, any>;
51
+ bypass_ripple?: boolean;
52
+ ttl?: number;
53
+ }
54
+ | {
55
+ type: "Update";
56
+ collection: string;
57
+ filter: Record<string, any>;
58
+ updates: Record<string, any>;
59
+ bypass_ripple?: boolean;
60
+ ttl?: number;
61
+ }
62
+ | {
63
+ type: "UpdateById";
64
+ collection: string;
65
+ record_id: string;
66
+ updates: Record<string, any>;
67
+ bypass_ripple?: boolean;
68
+ ttl?: number;
69
+ }
70
+ | {
71
+ type: "Delete";
72
+ collection: string;
73
+ filter: Record<string, any>;
74
+ bypass_ripple?: boolean;
75
+ }
76
+ | {
77
+ type: "DeleteById";
78
+ collection: string;
79
+ record_id: string;
80
+ bypass_ripple?: boolean;
81
+ }
82
+ | {
83
+ type: "BatchInsert";
84
+ collection: string;
85
+ records: Record<string, any>[];
86
+ bypass_ripple?: boolean;
87
+ }
88
+ | {
89
+ type: "BatchDelete";
90
+ collection: string;
91
+ record_ids: string[];
92
+ bypass_ripple?: boolean;
93
+ }
94
+ | {
95
+ type: "HttpRequest";
96
+ url: string;
97
+ method?: string;
98
+ headers?: Record<string, string>;
99
+ body?: any;
100
+ }
101
+ | {
102
+ type: "VectorSearch";
103
+ collection: string;
104
+ query_vector: number[];
105
+ limit?: number;
106
+ threshold?: number;
107
+ }
108
+ | {
109
+ type: "TextSearch";
110
+ collection: string;
111
+ query_text: string;
112
+ fields?: string[];
113
+ limit?: number;
114
+ fuzzy?: boolean;
115
+ }
116
+ | {
117
+ type: "HybridSearch";
118
+ collection: string;
119
+ query_text: string;
120
+ query_vector?: number[];
121
+ limit?: number;
122
+ }
123
+ | {
124
+ type: "Chat";
125
+ messages: ChatMessage[];
126
+ model?: string;
127
+ temperature?: number;
128
+ max_tokens?: number;
129
+ }
130
+ | {
131
+ type: "Embed";
132
+ input_field: string;
133
+ output_field: string;
134
+ model?: string;
135
+ };
136
+
137
+ export interface ChatMessage {
138
+ role: string;
139
+ content: string;
140
+ }
141
+
142
+ export const ChatMessage = {
143
+ system: (content: string): ChatMessage => ({
144
+ role: "system",
145
+ content: content,
146
+ }),
147
+ user: (content: string): ChatMessage => ({
148
+ role: "user",
149
+ content: content,
150
+ }),
151
+ assistant: (content: string): ChatMessage => ({
152
+ role: "assistant",
153
+ content: content,
154
+ }),
155
+ };
156
+
157
+ export interface GroupFunctionConfig {
158
+ output_field: string;
159
+ operation:
160
+ | "Sum"
161
+ | "Average"
162
+ | "Count"
163
+ | "Min"
164
+ | "Max"
165
+ | "First"
166
+ | "Last"
167
+ | "Push";
168
+ input_field?: string;
169
+ }
170
+
171
+ export interface SortFieldConfig {
172
+ field: string;
173
+ ascending: boolean;
174
+ }
175
+
176
+ export interface FunctionResult {
177
+ records: Record<string, any>[];
178
+ stats: FunctionStats;
179
+ }
180
+
181
+ export interface FunctionStats {
182
+ input_count: number;
183
+ output_count: number;
184
+ execution_time_ms: number;
185
+ stages_executed: number;
186
+ stage_stats: StageStats[];
187
+ }
188
+
189
+ export interface StageStats {
190
+ stage: string;
191
+ input_count: number;
192
+ output_count: number;
193
+ execution_time_ms: number;
194
+ }
195
+
196
+ // Stage builder functions
197
+ export const Stage = {
198
+ findAll: (collection: string): FunctionStageConfig => ({
199
+ type: "FindAll",
200
+ collection,
201
+ }),
202
+
203
+ query: (
204
+ collection: string,
205
+ filter?: Record<string, any>,
206
+ sort?: SortFieldConfig[],
207
+ limit?: number,
208
+ skip?: number,
209
+ ): FunctionStageConfig => ({
210
+ type: "Query",
211
+ collection,
212
+ filter,
213
+ sort,
214
+ limit,
215
+ skip,
216
+ }),
217
+
218
+ project: (fields: string[], exclude = false): FunctionStageConfig => ({
219
+ type: "Project",
220
+ fields,
221
+ exclude,
222
+ }),
223
+
224
+ group: (
225
+ by_fields: string[],
226
+ functions: GroupFunctionConfig[],
227
+ ): FunctionStageConfig => ({
228
+ type: "Group",
229
+ by_fields,
230
+ functions,
231
+ }),
232
+
233
+ count: (output_field = "count"): FunctionStageConfig => ({
234
+ type: "Count",
235
+ output_field,
236
+ }),
237
+
238
+ insert: (
239
+ collection: string,
240
+ record: Record<string, any>,
241
+ bypassRipple = false,
242
+ ttl?: number,
243
+ ): FunctionStageConfig => ({
244
+ type: "Insert",
245
+ collection,
246
+ record,
247
+ bypass_ripple: bypassRipple,
248
+ ttl,
249
+ }),
250
+
251
+ update: (
252
+ collection: string,
253
+ filter: Record<string, any>,
254
+ updates: Record<string, any>,
255
+ bypassRipple = false,
256
+ ttl?: number,
257
+ ): FunctionStageConfig => ({
258
+ type: "Update",
259
+ collection,
260
+ filter,
261
+ updates,
262
+ bypass_ripple: bypassRipple,
263
+ ttl,
264
+ }),
265
+
266
+ updateById: (
267
+ collection: string,
268
+ record_id: string,
269
+ updates: Record<string, any>,
270
+ bypassRipple = false,
271
+ ttl?: number,
272
+ ): FunctionStageConfig => ({
273
+ type: "UpdateById",
274
+ collection,
275
+ record_id,
276
+ updates,
277
+ bypass_ripple: bypassRipple,
278
+ ttl,
279
+ }),
280
+
281
+ delete: (
282
+ collection: string,
283
+ filter: Record<string, any>,
284
+ bypassRipple = false,
285
+ ): FunctionStageConfig => ({
286
+ type: "Delete",
287
+ collection,
288
+ filter,
289
+ bypass_ripple: bypassRipple,
290
+ }),
291
+
292
+ deleteById: (
293
+ collection: string,
294
+ record_id: string,
295
+ bypassRipple = false,
296
+ ): FunctionStageConfig => ({
297
+ type: "DeleteById",
298
+ collection,
299
+ record_id,
300
+ bypass_ripple: bypassRipple,
301
+ }),
302
+
303
+ batchInsert: (
304
+ collection: string,
305
+ records: Record<string, any>[],
306
+ bypassRipple = false,
307
+ ): FunctionStageConfig => ({
308
+ type: "BatchInsert",
309
+ collection,
310
+ records,
311
+ bypass_ripple: bypassRipple,
312
+ }),
313
+
314
+ batchDelete: (
315
+ collection: string,
316
+ record_ids: string[],
317
+ bypassRipple = false,
318
+ ): FunctionStageConfig => ({
319
+ type: "BatchDelete",
320
+ collection,
321
+ record_ids,
322
+ bypass_ripple: bypassRipple,
323
+ }),
324
+
325
+ filter: (filter: Record<string, any>): FunctionStageConfig => ({
326
+ type: "Filter",
327
+ filter,
328
+ }),
329
+
330
+ sort: (sort: SortFieldConfig[]): FunctionStageConfig => ({
331
+ type: "Sort",
332
+ sort,
333
+ }),
334
+
335
+ limit: (limit: number): FunctionStageConfig => ({
336
+ type: "Limit",
337
+ limit,
338
+ }),
339
+
340
+ skip: (skip: number): FunctionStageConfig => ({
341
+ type: "Skip",
342
+ skip,
343
+ }),
344
+
345
+ httpRequest: (
346
+ url: string,
347
+ method = "GET",
348
+ headers?: Record<string, string>,
349
+ body?: any,
350
+ ): FunctionStageConfig => ({
351
+ type: "HttpRequest",
352
+ url,
353
+ method,
354
+ headers,
355
+ body,
356
+ }),
357
+
358
+ vectorSearch: (
359
+ collection: string,
360
+ query_vector: number[],
361
+ limit?: number,
362
+ threshold?: number,
363
+ ): FunctionStageConfig => ({
364
+ type: "VectorSearch",
365
+ collection,
366
+ query_vector,
367
+ limit,
368
+ threshold,
369
+ }),
370
+
371
+ textSearch: (
372
+ collection: string,
373
+ query_text: string,
374
+ options?: { fields?: string[]; limit?: number; fuzzy?: boolean },
375
+ ): FunctionStageConfig => ({
376
+ type: "TextSearch",
377
+ collection,
378
+ query_text,
379
+ fields: options?.fields,
380
+ limit: options?.limit,
381
+ fuzzy: options?.fuzzy,
382
+ }),
383
+
384
+ hybridSearch: (
385
+ collection: string,
386
+ query_text: string,
387
+ query_vector?: number[],
388
+ limit?: number,
389
+ ): FunctionStageConfig => ({
390
+ type: "HybridSearch",
391
+ collection,
392
+ query_text,
393
+ query_vector,
394
+ limit,
395
+ }),
396
+
397
+ chat: (
398
+ messages: ChatMessage[],
399
+ model?: string,
400
+ temperature?: number,
401
+ max_tokens?: number,
402
+ ): FunctionStageConfig => ({
403
+ type: "Chat",
404
+ messages,
405
+ model,
406
+ temperature,
407
+ max_tokens,
408
+ }),
409
+
410
+ embed: (
411
+ input_field: string,
412
+ output_field: string,
413
+ model?: string,
414
+ ): FunctionStageConfig => ({
415
+ type: "Embed",
416
+ input_field,
417
+ output_field,
418
+ model,
419
+ }),
420
+ };