@antelopejs/interface-database 0.1.2 → 0.1.4
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/docs/1.query_types/1.index.md +53 -0
- package/docs/1.query_types/2.table.md +108 -0
- package/docs/1.query_types/3.selection.md +94 -0
- package/docs/1.query_types/4.single_selection.md +121 -0
- package/docs/1.query_types/5.stream.md +237 -0
- package/docs/1.query_types/6.feed.md +82 -0
- package/docs/1.query_types/7.query.md +82 -0
- package/docs/1.query_types/8.datum.md +102 -0
- package/docs/1.query_types/9.valueproxy.md +230 -0
- package/docs/2.operations/1.index.md +34 -0
- package/docs/2.operations/2.database_management.md +151 -0
- package/docs/2.operations/3.table_operations.md +117 -0
- package/docs/2.operations/4.crud.md +215 -0
- package/docs/2.operations/5.indexes.md +110 -0
- package/docs/2.operations/6.filtering.md +176 -0
- package/docs/2.operations/7.lookup.md +137 -0
- package/docs/3.results/1.index.md +38 -0
- package/docs/3.results/2.write_results.md +86 -0
- package/docs/3.results/3.index_changes.md +78 -0
- package/docs/3.results/4.table_changes.md +105 -0
- package/docs/3.results/5.database_changes.md +96 -0
- package/docs/3.results/6.value_changes.md +75 -0
- package/docs/index.md +50 -0
- package/package.json +12 -6
- package/skills/database-interface/SKILL.md +67 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Table Operations
|
|
2
|
+
|
|
3
|
+
Tables are the primary containers for documents in AQL. They are accessed through a `SchemaInstance` and provide methods for inserting, retrieving, and querying documents.
|
|
4
|
+
|
|
5
|
+
## Access a Table
|
|
6
|
+
|
|
7
|
+
Retrieve a table reference from a schema instance:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
11
|
+
|
|
12
|
+
const schema = Schema.get("myapp")!;
|
|
13
|
+
const db = schema.instance();
|
|
14
|
+
const users = db.table("users");
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Type-Safe Tables
|
|
18
|
+
|
|
19
|
+
Provide a generic type parameter to get type-safe operations:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
interface User {
|
|
23
|
+
_id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
email: string;
|
|
26
|
+
status: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const users = db.table<User>("users");
|
|
30
|
+
|
|
31
|
+
// TypeScript now validates field names and types
|
|
32
|
+
const user = await users.get("user-123"); // user is User | null
|
|
33
|
+
const emails = await users.key("email"); // string[]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Basic Operations
|
|
37
|
+
|
|
38
|
+
### Retrieve All Documents
|
|
39
|
+
|
|
40
|
+
A table reference doubles as a stream of all its documents:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const allUsers = await users;
|
|
44
|
+
console.log(`Total users: ${allUsers.length}`);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Retrieve by Primary Key
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const user = await users.get("user-123");
|
|
51
|
+
|
|
52
|
+
if (user) {
|
|
53
|
+
console.log("Found:", user.name);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Retrieve by Secondary Index
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const admins = await users.getAll("admin", "role");
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Retrieve by Range
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const recent = await users.between(
|
|
67
|
+
"createdAt",
|
|
68
|
+
new Date("2024-01-01"),
|
|
69
|
+
new Date(),
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Chain Operations
|
|
74
|
+
|
|
75
|
+
Since `Table` extends `Selection` and `Stream`, you can chain any stream operation directly:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// Filter and sort
|
|
79
|
+
const activeByName = await users
|
|
80
|
+
.filter((u) => u.key("status").eq("active"))
|
|
81
|
+
.orderBy("name");
|
|
82
|
+
|
|
83
|
+
// Paginate
|
|
84
|
+
const page = await users.orderBy("createdAt").slice(0, 10);
|
|
85
|
+
|
|
86
|
+
// Aggregate
|
|
87
|
+
const count = await users.count();
|
|
88
|
+
const avgAge = await users.avg("age");
|
|
89
|
+
|
|
90
|
+
// Transform
|
|
91
|
+
const names = await users.map((u) => u.key("name"));
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Insert Documents
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// Single document
|
|
98
|
+
const ids = await users.insert({
|
|
99
|
+
name: "Alice",
|
|
100
|
+
email: "alice@example.com",
|
|
101
|
+
status: "active",
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Multiple documents
|
|
105
|
+
const batchIds = await users.insert([
|
|
106
|
+
{ name: "Bob", email: "bob@example.com", status: "active" },
|
|
107
|
+
{ name: "Carol", email: "carol@example.com", status: "active" },
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
// Upsert (update on conflict)
|
|
111
|
+
await users.insert(
|
|
112
|
+
{ _id: "user-123", name: "Updated" },
|
|
113
|
+
{ conflict: "update" },
|
|
114
|
+
);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
For complete CRUD documentation, see [CRUD Operations](./4.crud.md).
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# CRUD Operations
|
|
2
|
+
|
|
3
|
+
CRUD operations (Create, Read, Update, Delete) form the foundation of document management in AQL. All examples assume a schema is already defined and an instance is available.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
7
|
+
|
|
8
|
+
const schema = Schema.get("myapp")!;
|
|
9
|
+
const users = schema.instance().table("users");
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Create Documents
|
|
13
|
+
|
|
14
|
+
### Insert a Single Document
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
const ids = await users.insert({
|
|
18
|
+
name: "Alice Johnson",
|
|
19
|
+
email: "alice@example.com",
|
|
20
|
+
status: "active",
|
|
21
|
+
createdAt: new Date(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
console.log("Inserted ID:", ids[0]);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Insert Multiple Documents
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
const ids = await users.insert([
|
|
31
|
+
{ name: "Bob", email: "bob@example.com", status: "active" },
|
|
32
|
+
{ name: "Carol", email: "carol@example.com", status: "active" },
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
console.log("Inserted IDs:", ids);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Upsert (Insert or Update)
|
|
39
|
+
|
|
40
|
+
Use the `conflict` option to handle documents that already exist:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// Update existing fields, keep unmentioned fields
|
|
44
|
+
await users.insert(
|
|
45
|
+
{ _id: "user-123", name: "Updated Name", lastSeen: new Date() },
|
|
46
|
+
{ conflict: "update" },
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Replace the entire document
|
|
50
|
+
await users.insert(
|
|
51
|
+
{ _id: "user-123", name: "Replaced", email: "new@example.com" },
|
|
52
|
+
{ conflict: "replace" },
|
|
53
|
+
);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Read Documents
|
|
57
|
+
|
|
58
|
+
### Get by Primary Key
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const user = await users.get("user-123");
|
|
62
|
+
|
|
63
|
+
if (user) {
|
|
64
|
+
console.log("Found:", user.name);
|
|
65
|
+
} else {
|
|
66
|
+
console.log("User not found");
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Get by Secondary Index
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const admins = await users.getAll("admin", "role");
|
|
74
|
+
console.log(`Found ${admins.length} admins`);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Get by Range
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const recent = await users.between(
|
|
81
|
+
"createdAt",
|
|
82
|
+
new Date("2024-01-01"),
|
|
83
|
+
new Date(),
|
|
84
|
+
);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Filter Documents
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// Filter with a predicate function
|
|
91
|
+
const active = await users.filter((user) =>
|
|
92
|
+
user.key("status").eq("active")
|
|
93
|
+
.and(user.key("age").ge(18)),
|
|
94
|
+
);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Select Specific Fields
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// Keep only listed fields
|
|
101
|
+
const profiles = await users.pluck("_id", "name", "avatar");
|
|
102
|
+
|
|
103
|
+
// Remove sensitive fields
|
|
104
|
+
const safe = await users.without("password", "ssn");
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Order and Paginate
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const page1 = await users.orderBy("createdAt", "desc").slice(0, 10);
|
|
111
|
+
const page2 = await users.orderBy("createdAt", "desc").slice(10, 10);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Stream Results
|
|
115
|
+
|
|
116
|
+
For large result sets, use async iteration to process documents one at a time:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
for await (const user of users) {
|
|
120
|
+
console.log("Processing:", user.name);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Update Documents
|
|
125
|
+
|
|
126
|
+
### Update a Single Document
|
|
127
|
+
|
|
128
|
+
The `update` method modifies specified fields while preserving all other fields.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const count = await users.get("user-123").update({
|
|
132
|
+
status: "active",
|
|
133
|
+
lastLogin: new Date(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (count === 1) {
|
|
137
|
+
console.log("User updated");
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Update with Computed Values
|
|
142
|
+
|
|
143
|
+
Pass a function to compute new values based on current ones:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
await users.get("user-123").update((user) => ({
|
|
147
|
+
loginCount: user.key("loginCount").add(1),
|
|
148
|
+
lastLogin: new Date(),
|
|
149
|
+
}));
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Update Multiple Documents
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const count = await users
|
|
156
|
+
.getAll("inactive", "status")
|
|
157
|
+
.update({ status: "active" });
|
|
158
|
+
|
|
159
|
+
console.log(`Activated ${count} users`);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Replace Documents
|
|
163
|
+
|
|
164
|
+
The `replace` method substitutes the entire document. Fields not included in the replacement are removed.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const count = await users.get("user-123").replace({
|
|
168
|
+
_id: "user-123",
|
|
169
|
+
name: "Alice Updated",
|
|
170
|
+
email: "alice.new@example.com",
|
|
171
|
+
status: "active",
|
|
172
|
+
updatedAt: new Date(),
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Delete Documents
|
|
177
|
+
|
|
178
|
+
### Delete a Single Document
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
const count = await users.get("user-123").delete();
|
|
182
|
+
|
|
183
|
+
if (count === 1) {
|
|
184
|
+
console.log("User deleted");
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Delete Multiple Documents
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
const count = await users
|
|
192
|
+
.filter((user) => user.key("status").eq("inactive"))
|
|
193
|
+
.delete();
|
|
194
|
+
|
|
195
|
+
console.log(`Deleted ${count} inactive users`);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Batch Operations
|
|
199
|
+
|
|
200
|
+
For large-scale inserts, batch documents to manage memory and throughput:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const documents = Array.from({ length: 10000 }, (_, i) => ({
|
|
204
|
+
index: i,
|
|
205
|
+
value: `Item ${i}`,
|
|
206
|
+
createdAt: new Date(),
|
|
207
|
+
}));
|
|
208
|
+
|
|
209
|
+
const batchSize = 1000;
|
|
210
|
+
for (let i = 0; i < documents.length; i += batchSize) {
|
|
211
|
+
const batch = documents.slice(i, i + batchSize);
|
|
212
|
+
await schema.instance().table("items").insert(batch);
|
|
213
|
+
console.log(`Inserted batch ${Math.floor(i / batchSize) + 1}`);
|
|
214
|
+
}
|
|
215
|
+
```
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Index Management
|
|
2
|
+
|
|
3
|
+
Indexes improve query performance by enabling the database to locate documents quickly based on indexed fields. In AQL, indexes are defined as part of the schema definition.
|
|
4
|
+
|
|
5
|
+
## Define Indexes in a Schema
|
|
6
|
+
|
|
7
|
+
Indexes are declared in the `indexes` property of each table definition:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
11
|
+
|
|
12
|
+
const schema = new Schema("myapp", {
|
|
13
|
+
users: {
|
|
14
|
+
fields: { name: "string", email: "string", role: "string", age: "number" },
|
|
15
|
+
indexes: {
|
|
16
|
+
email: {}, // Simple index on the "email" field
|
|
17
|
+
role: {}, // Simple index on the "role" field
|
|
18
|
+
name_age: { fields: ["name", "age"] }, // Compound index
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### `IndexDefinition`
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
interface IndexDefinition {
|
|
28
|
+
fields?: string[]; // Fields for compound indexes (omit for single-field)
|
|
29
|
+
multi?: boolean; // Whether this is a multi-valued index
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
When `fields` is omitted, the index name is used as the field name. For compound indexes, provide the `fields` array explicitly.
|
|
34
|
+
|
|
35
|
+
## Use Indexes for Queries
|
|
36
|
+
|
|
37
|
+
### `getAll()` with an Index
|
|
38
|
+
|
|
39
|
+
Retrieve documents by index value:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const db = schema.instance();
|
|
43
|
+
const users = db.table("users");
|
|
44
|
+
|
|
45
|
+
// Query by the "role" index
|
|
46
|
+
const admins = await users.getAll("admin", "role");
|
|
47
|
+
|
|
48
|
+
// Query multiple values
|
|
49
|
+
const targets = await users.getAll(["admin", "moderator"], "role");
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### `between()` with an Index
|
|
53
|
+
|
|
54
|
+
Retrieve documents where the index value falls within a range:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const results = await users.between("age", 18, 65);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `orderBy()` with an Index
|
|
61
|
+
|
|
62
|
+
Sort results using an indexed field for better performance:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const sorted = await users.orderBy("email", "asc");
|
|
66
|
+
const newest = await users.orderBy("createdAt", "desc");
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Compound Indexes
|
|
70
|
+
|
|
71
|
+
Compound indexes span multiple fields. Define them with the `fields` array:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const schema = new Schema("blog", {
|
|
75
|
+
posts: {
|
|
76
|
+
fields: {
|
|
77
|
+
category: "string",
|
|
78
|
+
publishedAt: "date",
|
|
79
|
+
title: "string",
|
|
80
|
+
},
|
|
81
|
+
indexes: {
|
|
82
|
+
category_date: { fields: ["category", "publishedAt"] },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Compound indexes support prefix-based lookups. You can query by the first field alone or by both fields together.
|
|
89
|
+
|
|
90
|
+
## Multi Indexes
|
|
91
|
+
|
|
92
|
+
Set `multi: true` for fields that contain arrays. The database creates an index entry for each element in the array, enabling lookups by any single element:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
const schema = new Schema("content", {
|
|
96
|
+
articles: {
|
|
97
|
+
fields: { tags: ["string"] },
|
|
98
|
+
indexes: {
|
|
99
|
+
tags: { multi: true },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Index Design Guidelines
|
|
106
|
+
|
|
107
|
+
- **Index fields used in `getAll()` and `between()` calls** for best performance.
|
|
108
|
+
- **Compound indexes** are useful when you frequently filter or sort by multiple fields together.
|
|
109
|
+
- **Avoid over-indexing.** Each index adds storage overhead and slows down writes.
|
|
110
|
+
- **Filter early** in your query chain to reduce the number of documents processed.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Filtering and Querying
|
|
2
|
+
|
|
3
|
+
This guide covers techniques for selecting, comparing, and transforming documents using AQL's filter and ValueProxy capabilities.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
7
|
+
|
|
8
|
+
const schema = Schema.get("myapp")!;
|
|
9
|
+
const users = schema.instance().table("users");
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Basic Filtering
|
|
13
|
+
|
|
14
|
+
Use the `filter` method with a predicate function. The callback receives a `ValueProxy<T>` representing each document.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
const active = await users.filter((user) =>
|
|
18
|
+
user.key("status").eq("active"),
|
|
19
|
+
);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Comparison Operators
|
|
23
|
+
|
|
24
|
+
### Equality and Inequality
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
const admins = await users.filter((u) => u.key("role").eq("admin"));
|
|
28
|
+
const nonAdmins = await users.filter((u) => u.key("role").ne("admin"));
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Numeric Comparisons
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
const adults = await users.filter((u) => u.key("age").ge(18));
|
|
35
|
+
const seniors = await users.filter((u) => u.key("age").gt(65));
|
|
36
|
+
const young = await users.filter((u) => u.key("age").lt(18));
|
|
37
|
+
const discounted = await users.filter((u) => u.key("age").le(25));
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Logical Operators
|
|
41
|
+
|
|
42
|
+
Combine conditions with `and()`, `or()`, and `not()`:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// AND
|
|
46
|
+
const verified = await users.filter((u) =>
|
|
47
|
+
u.key("status").eq("active").and(u.key("emailVerified").eq(true)),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// OR
|
|
51
|
+
const important = await users.filter((u) =>
|
|
52
|
+
u.key("role").eq("admin").or(u.key("plan").eq("premium")),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// NOT
|
|
56
|
+
const standard = await users.filter((u) =>
|
|
57
|
+
u.key("plan").eq("premium").not(),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Complex combinations
|
|
61
|
+
const target = await users.filter((u) =>
|
|
62
|
+
u.key("status").eq("active")
|
|
63
|
+
.and(
|
|
64
|
+
u.key("plan").eq("premium")
|
|
65
|
+
.or(u.key("totalPurchases").gt(500)),
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## String Operations
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Case-insensitive match
|
|
74
|
+
const bobs = await users.filter((u) =>
|
|
75
|
+
u.key("name").downcase().eq("bob"),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Regex matching
|
|
79
|
+
const gmailUsers = await users.filter((u) =>
|
|
80
|
+
u.key("email").match("@gmail\\.com$"),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// String concatenation in filter
|
|
84
|
+
const fullNameMatch = await users.filter((u) =>
|
|
85
|
+
u.key("firstName").concat(u.key("lastName")).eq("JohnSmith"),
|
|
86
|
+
);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Date Filtering
|
|
90
|
+
|
|
91
|
+
### Date Comparisons
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const newUsers = await users.filter((u) =>
|
|
95
|
+
u.key("createdAt").gt(new Date("2024-01-01")),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const q1Users = await users.filter((u) =>
|
|
99
|
+
u.key("createdAt").ge(new Date("2024-01-01"))
|
|
100
|
+
.and(u.key("createdAt").lt(new Date("2024-04-01"))),
|
|
101
|
+
);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Date Components
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const thisYear = await users.filter((u) =>
|
|
108
|
+
u.key("createdAt").year().eq(2024),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const morningLogins = await users.filter((u) =>
|
|
112
|
+
u.key("lastLogin").hours().lt(12),
|
|
113
|
+
);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Array Operations
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// Check array contents
|
|
120
|
+
const jsPosts = await schema.instance().table("posts").filter((post) =>
|
|
121
|
+
post.key("tags").includes("javascript"),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Check array length
|
|
125
|
+
const wellTagged = await schema.instance().table("posts").filter((post) =>
|
|
126
|
+
post.key("tags").count().gt(3),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Check if empty
|
|
130
|
+
const untagged = await schema.instance().table("posts").filter((post) =>
|
|
131
|
+
post.key("tags").isempty(),
|
|
132
|
+
);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Default Values in Filters
|
|
136
|
+
|
|
137
|
+
Use `default()` to handle potentially null fields:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const activeUsers = await users.filter((u) =>
|
|
141
|
+
u.key("active").default(false).eq(true),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const frequentVisitors = await users.filter((u) =>
|
|
145
|
+
u.key("visits").default(0).gt(10),
|
|
146
|
+
);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Ordering and Pagination
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// Sort ascending
|
|
153
|
+
const byName = await users.orderBy("name", "asc");
|
|
154
|
+
|
|
155
|
+
// Sort descending
|
|
156
|
+
const newest = await users.orderBy("createdAt", "desc");
|
|
157
|
+
|
|
158
|
+
// Paginate
|
|
159
|
+
const page1 = await users.orderBy("createdAt").slice(0, 10);
|
|
160
|
+
const page2 = await users.orderBy("createdAt").slice(10, 10);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Performance Tips
|
|
164
|
+
|
|
165
|
+
1. **Use `getAll()` and `between()` with indexes** instead of `filter()` when possible. Index-based lookups are significantly faster than full scans.
|
|
166
|
+
2. **Filter early** in the query chain to reduce the dataset before applying transformations or aggregations.
|
|
167
|
+
3. **Limit result sets** with `slice()` to avoid transferring unnecessary data.
|
|
168
|
+
4. **Avoid complex computations** inside filter predicates when simpler alternatives exist.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// Slower: full table scan with filter
|
|
172
|
+
const slow = await users.filter((u) => u.key("email").eq("user@example.com"));
|
|
173
|
+
|
|
174
|
+
// Faster: index-based lookup
|
|
175
|
+
const fast = await users.getAll("user@example.com", "email");
|
|
176
|
+
```
|