Skip to content

Commit e63ebda

Browse files
committed
feat: implement query interface and redirect docs
1 parent 32c698e commit e63ebda

21 files changed

Lines changed: 493 additions & 133 deletions

File tree

docs/_data/navigation.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
v0.7.0:
22
- title: Getting Started
33
url: /v0.7.0/getting-started.html
4+
- title: Querying
5+
url: /v0.7.0/querying.html

docs/_layouts/default.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ <h2 class="project-tagline">{{ site.description }}</h2>
112112
{% assign version = page.url | split: '/' %}
113113
{% assign current_version = version[1] %}
114114

115-
{% if current_version == 'v0.7.0' %}
115+
{% if current_version == 'v0.7.0' or current_version == 'v0.6.0' %}
116116
<nav class="sidebar">
117117
<h3>Docs {{ current_version }}</h3>
118118
<ul>
@@ -131,10 +131,11 @@ <h3>Docs {{ current_version }}</h3>
131131
</div>
132132

133133
<footer class="site-footer" style="text-align: center; padding: 2rem; border-top: 1px solid #eee; margin-top: 2rem;">
134-
<span class="site-footer-owner"><a href="https://github.com/EntglDb/EntglDb.NodeJs">EntglDb.NodeJs</a> is maintained by the EntglDb Team.</span>
134+
<span class="site-footer-owner"><a href="https://github.com/EntglDb/EntglDb.NodeJs">EntglDb.NodeJs</a> is maintained
135+
by the EntglDb Team.</span>
135136
<span class="site-footer-credits">This page was generated by <a href="https://pages.github.com">GitHub
136137
Pages</a>.</span>
137138
</footer>
138139
</body>
139140

140-
</html>
141+
</html>

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Welcome to the documentation for **EntglDb Node.js**.
88

99
## Active Versions
1010

11-
* [**v0.7.0 (Latest)**](v0.7.0/getting-started.html) - Brotli Compression, Protocol v4.
11+
* [**v0.7.0 (Latest)**](https://www.entgldb.com/docs/v0.7.0) - *Hosted on EntglDb.com*
1212

1313
## Links
1414

docs/v0.7.0/getting-started.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,8 @@ EntglDb now supports Brotli compression for network synchronization. This is aut
5252

5353
### Security
5454
Secure Handshake using ECDH is available.
55+
56+
## Next Steps
57+
58+
- [Querying](querying.html)
59+

docs/v0.7.0/index.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
layout: default
3+
title: Documentation Moved
4+
---
5+
6+
# Documentation Moved
7+
8+
The documentation for **EntglDb.NodeJs v0.7.0** is now hosted centrally.
9+
10+
👉 [**View Documentation on EntglDb.com**](https://www.entgldb.com/docs/v0.7.0)

docs/v0.7.0/querying.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
layout: default
3+
title: Querying
4+
---
5+
6+
# Querying
7+
8+
EntglDb Node.js provides a MongoDB-like query syntax for local collections, allowing you to filter documents efficiently.
9+
10+
## Basic Querying
11+
12+
You can query documents using the `find` method on a `PeerCollection`.
13+
14+
```typescript
15+
const users = await peerStore.getCollection('users');
16+
17+
// Precise match
18+
const fabio = await users.find({ firstName: 'Fabio' });
19+
20+
// Comparisons using operators
21+
const adults = await users.find({ age: { $gte: 18 } });
22+
23+
// Logical Operators (Implicit AND)
24+
const activeAdmins = await users.find({
25+
isActive: true,
26+
role: 'Admin'
27+
});
28+
29+
// Explicit Logical Operators
30+
const complex = await users.find({
31+
$or: [
32+
{ role: 'Admin' },
33+
{ role: 'Moderator' }
34+
]
35+
});
36+
```
37+
38+
## Serialization Consistency
39+
40+
The query engine respects the `namingStrategy` configured for the `json-serialization` adapter.
41+
42+
```typescript
43+
// If translation logic is enabled (e.g. camelToSnake)
44+
// The following query:
45+
await users.find({ firstName: 'Fabio' });
46+
47+
// Will effectively query the underlying JSON field:
48+
// json_extract(data, '$.first_name')
49+
```
50+
51+
## Supported Operators
52+
53+
- `$eq` (Equal) - Default if value is direct
54+
- `$ne` (Not Equal)
55+
- `$gt` (Greater Than)
56+
- `$lt` (Less Than)
57+
- `$gte` (Greater Than or Equal)
58+
- `$lte` (Less Than or Equal)
59+
- `$and` (AND)
60+
- `$or` (OR)

packages/core/src/collection.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,24 @@ export class PeerCollection<T = any> {
6161
* Find documents matching a predicate
6262
* Note: This is a simple client-side filter. For production, implement server-side queries.
6363
*/
64-
async find(predicate: (doc: T) => boolean): Promise<T[]> {
65-
// This is a naive implementation that loads all docs
66-
// In production, implement proper query translation to SQL
67-
const results: T[] = [];
64+
/**
65+
* Find documents matching a query object
66+
* @param query The query object (e.g. { name: "Fabio" })
67+
* @param options Query options including naming strategy
68+
*/
69+
async find(query: any, options?: { namingStrategy?: (prop: string) => string }): Promise<T[]> {
70+
const { ObjectToQueryNodeTranslator } = require('./query/translator');
71+
const queryNode = ObjectToQueryNodeTranslator.translate(query, options);
72+
73+
// If no query, return all? or support empty query?
74+
// If queryNode is null (empty query), we might want to scan all.
75+
// For now let's pass null to store and let it decide (scan all).
6876

69-
// We'd need to add a method to IPeerStore to scan a collection
70-
// For now, this is a placeholder
71-
throw new Error('find() not yet implemented - needs store.scanCollection()');
77+
const docs = await this.store.findDocuments(this.name, queryNode);
78+
79+
return docs.map(doc => {
80+
const jsonData = new TextDecoder().decode(doc.data);
81+
return JSON.parse(jsonData) as T;
82+
});
7283
}
7384
}

packages/core/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,11 @@ export { IPeerStore } from './storage/interface';
1111
// Sync and conflict resolution
1212
export * from './sync';
1313

14+
// Re-export protocol types for convenience
1415
// Re-export protocol types for convenience
1516
export type { HLCTimestamp, Document, OplogEntry } from '@entgldb/protocol';
17+
18+
// Query exports
19+
export * from './query/query-node';
20+
export * from './query/translator';
21+
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
export abstract class QueryNode {
3+
abstract type: string;
4+
}
5+
6+
export class And extends QueryNode {
7+
readonly type = 'And';
8+
constructor(public left: QueryNode, public right: QueryNode) { super(); }
9+
}
10+
11+
export class Or extends QueryNode {
12+
readonly type = 'Or';
13+
constructor(public left: QueryNode, public right: QueryNode) { super(); }
14+
}
15+
16+
export class Eq extends QueryNode {
17+
readonly type = 'Eq';
18+
constructor(public field: string, public value: any) { super(); }
19+
}
20+
21+
export class Neq extends QueryNode {
22+
readonly type = 'Neq';
23+
constructor(public field: string, public value: any) { super(); }
24+
}
25+
26+
export class Gt extends QueryNode {
27+
readonly type = 'Gt';
28+
constructor(public field: string, public value: any) { super(); }
29+
}
30+
31+
export class Gte extends QueryNode {
32+
readonly type = 'Gte';
33+
constructor(public field: string, public value: any) { super(); }
34+
}
35+
36+
export class Lt extends QueryNode {
37+
readonly type = 'Lt';
38+
constructor(public field: string, public value: any) { super(); }
39+
}
40+
41+
export class Lte extends QueryNode {
42+
readonly type = 'Lte';
43+
constructor(public field: string, public value: any) { super(); }
44+
}
45+
46+
export class Contains extends QueryNode {
47+
readonly type = 'Contains';
48+
constructor(public field: string, public value: string) { super(); }
49+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { QueryNode, And, Or, Eq, Gt, Gte, Lt, Lte, Neq, Contains } from './query-node';
2+
3+
export interface QueryOptions {
4+
/**
5+
* Function to transform property names (e.g. camelCase to snake_case)
6+
*/
7+
namingStrategy?: (prop: string) => string;
8+
}
9+
10+
/**
11+
* Translates a MongoDB-like query object to a QueryNode tree.
12+
* Example: { name: "Fabio", age: { $gt: 18 } }
13+
*/
14+
export class ObjectToQueryNodeTranslator {
15+
16+
static translate(query: any, options?: QueryOptions): QueryNode | null {
17+
if (!query || Object.keys(query).length === 0) return null;
18+
19+
const nodes: QueryNode[] = [];
20+
21+
for (const key of Object.keys(query)) {
22+
const value = query[key];
23+
const fieldName = options?.namingStrategy ? options.namingStrategy(key) : key;
24+
25+
if (key === '$or') {
26+
if (Array.isArray(value)) {
27+
const subNodes = value.map(v => this.translate(v, options)).filter(n => n !== null) as QueryNode[];
28+
if (subNodes.length > 0) {
29+
let combined = subNodes[0];
30+
for (let i = 1; i < subNodes.length; i++) {
31+
combined = new Or(combined, subNodes[i]);
32+
}
33+
nodes.push(combined);
34+
}
35+
}
36+
continue;
37+
}
38+
39+
if (key === '$and') {
40+
if (Array.isArray(value)) {
41+
const subNodes = value.map(v => this.translate(v, options)).filter(n => n !== null) as QueryNode[];
42+
subNodes.forEach(n => nodes.push(n));
43+
}
44+
continue;
45+
}
46+
47+
// Primitive value check (e.g. { field: "value" } -> implies Eq)
48+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
49+
nodes.push(new Eq(fieldName, value));
50+
continue;
51+
}
52+
53+
// Operator check (e.g. { field: { $gt: 10 } })
54+
for (const op of Object.keys(value)) {
55+
const opVal = value[op];
56+
switch (op) {
57+
case '$eq': nodes.push(new Eq(fieldName, opVal)); break;
58+
case '$neq': nodes.push(new Neq(fieldName, opVal)); break;
59+
case '$gt': nodes.push(new Gt(fieldName, opVal)); break;
60+
case '$gte': nodes.push(new Gte(fieldName, opVal)); break;
61+
case '$lt': nodes.push(new Lt(fieldName, opVal)); break;
62+
case '$lte': nodes.push(new Lte(fieldName, opVal)); break;
63+
case '$contains': nodes.push(new Contains(fieldName, opVal)); break;
64+
default:
65+
// If it's not a known operator, maybe it's just a nested object Eq?
66+
// For now let's assume valid operators or fallback to Eq?
67+
// Actually in mongo {a: {b:1}} is exact match.
68+
// EntglDb is flat mostly. Let's throw or ignore.
69+
console.warn(`Unknown operator ${op} for field ${fieldName}`);
70+
break;
71+
}
72+
}
73+
}
74+
75+
if (nodes.length === 0) return null;
76+
if (nodes.length === 1) return nodes[0];
77+
78+
// Combine all implicit ANDs
79+
let result = nodes[0];
80+
for (let i = 1; i < nodes.length; i++) {
81+
result = new And(result, nodes[i]);
82+
}
83+
return result;
84+
}
85+
}

0 commit comments

Comments
 (0)