@ekodb/ekodb-client 0.1.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/src/schema.ts ADDED
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Schema management for collections
3
+ *
4
+ * This module provides types and utilities for defining and managing
5
+ * collection schemas with field types, constraints, and indexes.
6
+ */
7
+
8
+ /**
9
+ * Vector index algorithm
10
+ */
11
+ export enum VectorIndexAlgorithm {
12
+ /** Simple flat index (brute force) */
13
+ Flat = "flat",
14
+ /** Hierarchical Navigable Small World */
15
+ HNSW = "hnsw",
16
+ /** Inverted File Index (for future) */
17
+ IVF = "ivf",
18
+ }
19
+
20
+ /**
21
+ * Distance metric for vector similarity
22
+ */
23
+ export enum DistanceMetric {
24
+ Cosine = "cosine",
25
+ Euclidean = "euclidean",
26
+ DotProduct = "dotproduct",
27
+ }
28
+
29
+ /**
30
+ * Index configuration for a field
31
+ */
32
+ export type IndexConfig =
33
+ | {
34
+ type: "text";
35
+ language?: string;
36
+ analyzer?: string;
37
+ }
38
+ | {
39
+ type: "vector";
40
+ algorithm?: VectorIndexAlgorithm;
41
+ metric?: DistanceMetric;
42
+ m?: number;
43
+ ef_construction?: number;
44
+ }
45
+ | {
46
+ type: "btree";
47
+ }
48
+ | {
49
+ type: "hash";
50
+ };
51
+
52
+ /**
53
+ * Field type schema with constraints
54
+ *
55
+ * Valid field types (case-sensitive):
56
+ * - String, Integer, Float, Boolean, DateTime
57
+ * - Array, Object, Vector, Set
58
+ * - UUID, Decimal, Binary, Bytes, Duration, Number, Null
59
+ */
60
+ export interface FieldTypeSchema {
61
+ /** Field type - must be one of the valid types (case-sensitive, e.g., "String", "Integer", "Float") */
62
+ field_type: string;
63
+ /** Default value for the field */
64
+ default?: any;
65
+ /** Whether the field must be unique across records */
66
+ unique?: boolean;
67
+ /** Whether the field is required */
68
+ required?: boolean;
69
+ /** Allowed enum values */
70
+ enums?: any[];
71
+ /** Maximum value (for numbers/dates) */
72
+ max?: any;
73
+ /** Minimum value (for numbers/dates) */
74
+ min?: any;
75
+ /** Regex pattern for string validation */
76
+ regex?: string;
77
+ /** Index configuration */
78
+ index?: IndexConfig;
79
+ }
80
+
81
+ /**
82
+ * Collection schema
83
+ */
84
+ export interface Schema {
85
+ /** Field definitions */
86
+ fields: Record<string, FieldTypeSchema>;
87
+ /** Schema version */
88
+ version?: number;
89
+ /** Creation timestamp */
90
+ created_at?: string;
91
+ /** Last modification timestamp */
92
+ last_modified?: string;
93
+ /** Whether to bypass ripple replication */
94
+ bypass_ripple?: boolean;
95
+ }
96
+
97
+ /**
98
+ * Collection metadata with analytics
99
+ */
100
+ export interface CollectionMetadata {
101
+ /** Schema definition */
102
+ collection: Schema;
103
+ /** Analytics data (if available) */
104
+ analytics?: any;
105
+ }
106
+
107
+ /**
108
+ * Builder for constructing field type schemas
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * const field = new FieldTypeSchemaBuilder("string")
113
+ * .required()
114
+ * .unique()
115
+ * .pattern("^[a-z]+$")
116
+ * .build();
117
+ * ```
118
+ */
119
+ export class FieldTypeSchemaBuilder {
120
+ private schema: FieldTypeSchema;
121
+
122
+ constructor(fieldType: string) {
123
+ this.schema = { field_type: fieldType };
124
+ }
125
+
126
+ /**
127
+ * Set the field as required
128
+ */
129
+ required(): this {
130
+ this.schema.required = true;
131
+ return this;
132
+ }
133
+
134
+ /**
135
+ * Set the field as unique
136
+ */
137
+ unique(): this {
138
+ this.schema.unique = true;
139
+ return this;
140
+ }
141
+
142
+ /**
143
+ * Set a default value
144
+ */
145
+ defaultValue(value: any): this {
146
+ this.schema.default = value;
147
+ return this;
148
+ }
149
+
150
+ /**
151
+ * Set enum values
152
+ */
153
+ enums(values: any[]): this {
154
+ this.schema.enums = values;
155
+ return this;
156
+ }
157
+
158
+ /**
159
+ * Set min/max range
160
+ */
161
+ range(min?: any, max?: any): this {
162
+ this.schema.min = min;
163
+ this.schema.max = max;
164
+ return this;
165
+ }
166
+
167
+ /**
168
+ * Set regex pattern
169
+ */
170
+ pattern(regex: string): this {
171
+ this.schema.regex = regex;
172
+ return this;
173
+ }
174
+
175
+ /**
176
+ * Add a text index
177
+ */
178
+ textIndex(language: string = "english", analyzer?: string): this {
179
+ this.schema.index = {
180
+ type: "text",
181
+ language,
182
+ analyzer,
183
+ };
184
+ return this;
185
+ }
186
+
187
+ /**
188
+ * Add a vector index
189
+ */
190
+ vectorIndex(
191
+ algorithm: VectorIndexAlgorithm = VectorIndexAlgorithm.Flat,
192
+ metric: DistanceMetric = DistanceMetric.Cosine,
193
+ m: number = 16,
194
+ efConstruction: number = 200
195
+ ): this {
196
+ this.schema.index = {
197
+ type: "vector",
198
+ algorithm,
199
+ metric,
200
+ m,
201
+ ef_construction: efConstruction,
202
+ };
203
+ return this;
204
+ }
205
+
206
+ /**
207
+ * Add a B-tree index
208
+ */
209
+ btreeIndex(): this {
210
+ this.schema.index = { type: "btree" };
211
+ return this;
212
+ }
213
+
214
+ /**
215
+ * Add a hash index
216
+ */
217
+ hashIndex(): this {
218
+ this.schema.index = { type: "hash" };
219
+ return this;
220
+ }
221
+
222
+ /**
223
+ * Build the final FieldTypeSchema
224
+ */
225
+ build(): FieldTypeSchema {
226
+ return this.schema;
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Builder for constructing collection schemas
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * const schema = new SchemaBuilder()
236
+ * .addField("name", new FieldTypeSchemaBuilder("string").required())
237
+ * .addField("email", new FieldTypeSchemaBuilder("string").unique())
238
+ * .addField("age", new FieldTypeSchemaBuilder("number").range(0, 150))
239
+ * .build();
240
+ *
241
+ * await client.createCollection("users", schema);
242
+ * ```
243
+ */
244
+ export class SchemaBuilder {
245
+ private schema: Schema;
246
+
247
+ constructor() {
248
+ this.schema = {
249
+ fields: {},
250
+ version: 1,
251
+ bypass_ripple: true,
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Add a field to the schema
257
+ */
258
+ addField(name: string, field: FieldTypeSchema | FieldTypeSchemaBuilder): this {
259
+ this.schema.fields[name] = field instanceof FieldTypeSchemaBuilder ? field.build() : field;
260
+ return this;
261
+ }
262
+
263
+ /**
264
+ * Set bypass_ripple flag
265
+ */
266
+ bypassRipple(bypass: boolean): this {
267
+ this.schema.bypass_ripple = bypass;
268
+ return this;
269
+ }
270
+
271
+ /**
272
+ * Set schema version
273
+ */
274
+ version(version: number): this {
275
+ this.schema.version = version;
276
+ return this;
277
+ }
278
+
279
+ /**
280
+ * Build the final Schema
281
+ */
282
+ build(): Schema {
283
+ return this.schema;
284
+ }
285
+ }
package/src/search.ts ADDED
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Full-text and vector search support for ekoDB
3
+ *
4
+ * This module provides comprehensive search capabilities including:
5
+ * - Full-text search with fuzzy matching
6
+ * - Vector/semantic search
7
+ * - Hybrid search (text + vector)
8
+ * - Field weighting and boosting
9
+ */
10
+
11
+ export interface SearchQuery {
12
+ /** Search query string */
13
+ query: string;
14
+ /** Language for stemming (e.g., "english", "spanish", "french") */
15
+ language?: string;
16
+ /** Case-sensitive search */
17
+ case_sensitive?: boolean;
18
+ /** Enable fuzzy matching (typo tolerance) */
19
+ fuzzy?: boolean;
20
+ /** Minimum score threshold (0.0-1.0) */
21
+ min_score?: number;
22
+ /** Fields to search in (comma-separated or array) */
23
+ fields?: string | string[];
24
+ /** Field weights (format: "field1:2.0,field2:1.5" or object) */
25
+ weights?: string | Record<string, number>;
26
+ /** Enable stemming */
27
+ enable_stemming?: boolean;
28
+ /** Boost exact matches */
29
+ boost_exact?: boolean;
30
+ /** Maximum edit distance for fuzzy matching (0-5) */
31
+ max_edit_distance?: number;
32
+ /** Bypass ripple cache */
33
+ bypass_ripple?: boolean;
34
+ /** Bypass cache */
35
+ bypass_cache?: boolean;
36
+ /** Maximum number of results to return */
37
+ limit?: number;
38
+
39
+ // Vector search parameters
40
+ /** Query vector for semantic search */
41
+ vector?: number[];
42
+ /** Field containing vectors (default: "embedding") */
43
+ vector_field?: string;
44
+ /** Similarity metric: "cosine", "euclidean", "dotproduct" */
45
+ vector_metric?: string;
46
+ /** Number of vector results (k-nearest neighbors) */
47
+ vector_k?: number;
48
+ /** Minimum similarity threshold */
49
+ vector_threshold?: number;
50
+
51
+ // Hybrid search parameters
52
+ /** Weight for text search (0.0-1.0) */
53
+ text_weight?: number;
54
+ /** Weight for vector search (0.0-1.0) */
55
+ vector_weight?: number;
56
+ }
57
+
58
+ /**
59
+ * Search result with score and matched fields
60
+ */
61
+ export interface SearchResult {
62
+ /** The matched record */
63
+ record: any;
64
+ /** Relevance score */
65
+ score: number;
66
+ /** Fields that matched the search query */
67
+ matched_fields: string[];
68
+ }
69
+
70
+ /**
71
+ * Search response containing results and metadata
72
+ */
73
+ export interface SearchResponse {
74
+ /** Array of search results */
75
+ results: SearchResult[];
76
+ /** Total number of results found */
77
+ total: number;
78
+ /** Query execution time in milliseconds */
79
+ took_ms?: number;
80
+ }
81
+
82
+ /**
83
+ * Builder for constructing search queries with fluent API
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * const query = new SearchQueryBuilder("john")
88
+ * .fields(["name", "email"])
89
+ * .fuzzy(true)
90
+ * .minScore(0.5)
91
+ * .limit(10)
92
+ * .build();
93
+ *
94
+ * const results = await client.search("users", query);
95
+ * ```
96
+ */
97
+ export class SearchQueryBuilder {
98
+ private query: SearchQuery;
99
+
100
+ constructor(queryString: string) {
101
+ this.query = { query: queryString };
102
+ }
103
+
104
+ /**
105
+ * Set the language for stemming
106
+ */
107
+ language(language: string): this {
108
+ this.query.language = language;
109
+ return this;
110
+ }
111
+
112
+ /**
113
+ * Enable case-sensitive search
114
+ */
115
+ caseSensitive(enabled: boolean = true): this {
116
+ this.query.case_sensitive = enabled;
117
+ return this;
118
+ }
119
+
120
+ /**
121
+ * Enable fuzzy matching
122
+ */
123
+ fuzzy(enabled: boolean = true): this {
124
+ this.query.fuzzy = enabled;
125
+ return this;
126
+ }
127
+
128
+ /**
129
+ * Set minimum score threshold
130
+ */
131
+ minScore(score: number): this {
132
+ this.query.min_score = score;
133
+ return this;
134
+ }
135
+
136
+ /**
137
+ * Set fields to search in
138
+ */
139
+ fields(fields: string | string[]): this {
140
+ this.query.fields = fields;
141
+ return this;
142
+ }
143
+
144
+ /**
145
+ * Set field weights
146
+ */
147
+ weights(weights: string | Record<string, number>): this {
148
+ this.query.weights = weights;
149
+ return this;
150
+ }
151
+
152
+ /**
153
+ * Enable stemming
154
+ */
155
+ enableStemming(enabled: boolean = true): this {
156
+ this.query.enable_stemming = enabled;
157
+ return this;
158
+ }
159
+
160
+ /**
161
+ * Boost exact matches
162
+ */
163
+ boostExact(enabled: boolean = true): this {
164
+ this.query.boost_exact = enabled;
165
+ return this;
166
+ }
167
+
168
+ /**
169
+ * Set maximum edit distance for fuzzy matching
170
+ */
171
+ maxEditDistance(distance: number): this {
172
+ this.query.max_edit_distance = distance;
173
+ return this;
174
+ }
175
+
176
+ /**
177
+ * Set query vector for semantic search
178
+ */
179
+ vector(vector: number[]): this {
180
+ this.query.vector = vector;
181
+ return this;
182
+ }
183
+
184
+ /**
185
+ * Set vector field name
186
+ */
187
+ vectorField(field: string): this {
188
+ this.query.vector_field = field;
189
+ return this;
190
+ }
191
+
192
+ /**
193
+ * Set vector similarity metric
194
+ */
195
+ vectorMetric(metric: "cosine" | "euclidean" | "dotproduct"): this {
196
+ this.query.vector_metric = metric;
197
+ return this;
198
+ }
199
+
200
+ /**
201
+ * Set number of vector results (k-nearest neighbors)
202
+ */
203
+ vectorK(k: number): this {
204
+ this.query.vector_k = k;
205
+ return this;
206
+ }
207
+
208
+ /**
209
+ * Set minimum similarity threshold
210
+ */
211
+ vectorThreshold(threshold: number): this {
212
+ this.query.vector_threshold = threshold;
213
+ return this;
214
+ }
215
+
216
+ /**
217
+ * Set text search weight for hybrid search
218
+ */
219
+ textWeight(weight: number): this {
220
+ this.query.text_weight = weight;
221
+ return this;
222
+ }
223
+
224
+ /**
225
+ * Set vector search weight for hybrid search
226
+ */
227
+ vectorWeight(weight: number): this {
228
+ this.query.vector_weight = weight;
229
+ return this;
230
+ }
231
+
232
+ /**
233
+ * Bypass ripple cache
234
+ */
235
+ bypassRipple(bypass: boolean = true): this {
236
+ this.query.bypass_ripple = bypass;
237
+ return this;
238
+ }
239
+
240
+ /**
241
+ * Bypass cache
242
+ */
243
+ bypassCache(bypass: boolean = true): this {
244
+ this.query.bypass_cache = bypass;
245
+ return this;
246
+ }
247
+
248
+ /**
249
+ * Set maximum number of results to return
250
+ */
251
+ limit(limit: number): this {
252
+ this.query.limit = limit;
253
+ return this;
254
+ }
255
+
256
+ /**
257
+ * Build the final SearchQuery object
258
+ */
259
+ build(): SearchQuery {
260
+ // Normalize fields to comma-separated string if array
261
+ if (Array.isArray(this.query.fields)) {
262
+ this.query.fields = this.query.fields.join(",");
263
+ }
264
+
265
+ // Normalize weights to string format if object
266
+ if (this.query.weights && typeof this.query.weights === "object") {
267
+ const weightEntries = Object.entries(this.query.weights)
268
+ .map(([field, weight]) => `${field}:${weight}`)
269
+ .join(",");
270
+ this.query.weights = weightEntries;
271
+ }
272
+
273
+ return this.query;
274
+ }
275
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "declaration": true,
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "moduleResolution": "node",
14
+ "resolveJsonModule": true
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }