@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.
@@ -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
+ ```