@antelopejs/interface-database 0.1.3 → 0.1.5

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.
Files changed (79) hide show
  1. package/dist/antelope.test.js +2 -2
  2. package/dist/antelope.test.js.map +1 -1
  3. package/dist/atomic.d.ts +41 -0
  4. package/dist/atomic.js +135 -0
  5. package/dist/atomic.js.map +1 -0
  6. package/dist/common.js.map +1 -1
  7. package/dist/datum.d.ts +2 -2
  8. package/dist/datum.js.map +1 -1
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.js +15 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/query.js +1 -1
  13. package/dist/query.js.map +1 -1
  14. package/dist/schema.d.ts +1 -1
  15. package/dist/schema.js +1 -1
  16. package/dist/schema.js.map +1 -1
  17. package/dist/selection.d.ts +4 -1
  18. package/dist/selection.js +7 -0
  19. package/dist/selection.js.map +1 -1
  20. package/dist/stream.d.ts +1 -1
  21. package/dist/stream.js.map +1 -1
  22. package/dist/tests/aggregation_operations.test.js +1 -1
  23. package/dist/tests/aggregation_operations.test.js.map +1 -1
  24. package/dist/tests/atomic_mutation.test.d.ts +1 -0
  25. package/dist/tests/atomic_mutation.test.js +185 -0
  26. package/dist/tests/atomic_mutation.test.js.map +1 -0
  27. package/dist/tests/basic_operations.test.js +1 -1
  28. package/dist/tests/basic_operations.test.js.map +1 -1
  29. package/dist/tests/between_operations.test.js +1 -1
  30. package/dist/tests/between_operations.test.js.map +1 -1
  31. package/dist/tests/change_feeds.test.js +1 -1
  32. package/dist/tests/change_feeds.test.js.map +1 -1
  33. package/dist/tests/datasets/codec_fields.js.map +1 -1
  34. package/dist/tests/date_operations.test.js +1 -1
  35. package/dist/tests/date_operations.test.js.map +1 -1
  36. package/dist/tests/do_operations.test.js +1 -1
  37. package/dist/tests/do_operations.test.js.map +1 -1
  38. package/dist/tests/filters.test.js +1 -1
  39. package/dist/tests/filters.test.js.map +1 -1
  40. package/dist/tests/group_operations.test.js +1 -1
  41. package/dist/tests/group_operations.test.js.map +1 -1
  42. package/dist/tests/instance_modes.test.js +1 -1
  43. package/dist/tests/instance_modes.test.js.map +1 -1
  44. package/dist/tests/joins.test.js +2 -2
  45. package/dist/tests/joins.test.js.map +1 -1
  46. package/dist/tests/lookup_operations.test.js +2 -2
  47. package/dist/tests/lookup_operations.test.js.map +1 -1
  48. package/dist/tests/merge_operations.test.js +2 -2
  49. package/dist/tests/merge_operations.test.js.map +1 -1
  50. package/dist/tests/sorting.test.js +1 -1
  51. package/dist/tests/sorting.test.js.map +1 -1
  52. package/dist/tests/union_operations.test.js +2 -2
  53. package/dist/tests/union_operations.test.js.map +1 -1
  54. package/docs/1.query_types/1.index.md +53 -0
  55. package/docs/1.query_types/2.table.md +114 -0
  56. package/docs/1.query_types/3.selection.md +92 -0
  57. package/docs/1.query_types/4.single_selection.md +121 -0
  58. package/docs/1.query_types/5.stream.md +234 -0
  59. package/docs/1.query_types/6.feed.md +82 -0
  60. package/docs/1.query_types/7.query.md +82 -0
  61. package/docs/1.query_types/8.datum.md +102 -0
  62. package/docs/1.query_types/9.valueproxy.md +232 -0
  63. package/docs/2.operations/1.index.md +34 -0
  64. package/docs/2.operations/2.database_management.md +151 -0
  65. package/docs/2.operations/3.table_operations.md +117 -0
  66. package/docs/2.operations/4.crud.md +214 -0
  67. package/docs/2.operations/5.indexes.md +110 -0
  68. package/docs/2.operations/6.filtering.md +172 -0
  69. package/docs/2.operations/7.lookup.md +135 -0
  70. package/docs/2.operations/8.atomic_mutations.md +71 -0
  71. package/docs/3.results/1.index.md +38 -0
  72. package/docs/3.results/2.write_results.md +86 -0
  73. package/docs/3.results/3.index_changes.md +82 -0
  74. package/docs/3.results/4.table_changes.md +105 -0
  75. package/docs/3.results/5.database_changes.md +96 -0
  76. package/docs/3.results/6.value_changes.md +75 -0
  77. package/docs/index.md +51 -0
  78. package/package.json +62 -42
  79. package/skills/database-interface/SKILL.md +101 -0
@@ -0,0 +1,92 @@
1
+ # Selection
2
+
3
+ The `Selection<T>` class represents a set of documents and provides methods for bulk mutation operations. It extends `Stream<T>`, inheriting all transformation, filtering, and aggregation capabilities.
4
+
5
+ ## Class Definition
6
+
7
+ ```typescript
8
+ class Selection<T> extends Stream<T> {
9
+ update(document: DeepPartial<T>): Query<number>;
10
+ update<U>(document: (val: ValueProxy<T>) => U): Query<number>;
11
+ replace(document: DeepPartial<T>): Query<number>;
12
+ delete(): Query<number>;
13
+ }
14
+ ```
15
+
16
+ ## Create a Selection
17
+
18
+ Selections are produced by table methods such as `getAll()` and `between()`, or by chaining stream operations like `filter()`:
19
+
20
+ ```typescript
21
+ import { Schema } from "@antelopejs/interface-database";
22
+
23
+ const schema = Schema.get("myapp")!;
24
+ const users = schema.instance().table("users");
25
+
26
+ // Selection from getAll
27
+ const admins = users.getAll("admin", "role");
28
+
29
+ // Selection from between
30
+ const recentUsers = users.between(
31
+ "createdAt",
32
+ new Date("2024-01-01"),
33
+ new Date(),
34
+ );
35
+ ```
36
+
37
+ ## Update Documents
38
+
39
+ The `update` method modifies matching documents with the provided partial data. Fields not included in the update object remain unchanged. It returns the number of modified documents.
40
+
41
+ ```typescript
42
+ // Update with a partial object
43
+ const count = await users
44
+ .getAll("inactive", "status")
45
+ .update({ status: "active" });
46
+
47
+ console.log(`Activated ${count} users`);
48
+
49
+ // Update with a function for computed values
50
+ await users.getAll("active", "status").update((user) => ({
51
+ loginCount: user.key("loginCount").add(1),
52
+ }));
53
+ ```
54
+
55
+ ## Replace Documents
56
+
57
+ The `replace` method substitutes entire documents with the provided data. Unlike `update`, all fields not present in the replacement are removed.
58
+
59
+ ```typescript
60
+ const count = await users.getAll("user-123").replace({
61
+ _id: "user-123",
62
+ name: "New Name",
63
+ email: "new@example.com",
64
+ role: "user",
65
+ });
66
+
67
+ console.log(`Replaced ${count} documents`);
68
+ ```
69
+
70
+ ## Delete Documents
71
+
72
+ The `delete` method removes all documents in the selection and returns the number of deleted documents.
73
+
74
+ ```typescript
75
+ const count = await users.getAll("inactive", "status").delete();
76
+
77
+ console.log(`Deleted ${count} inactive users`);
78
+ ```
79
+
80
+ ## Inherited Methods
81
+
82
+ Since `Selection` extends `Stream`, it inherits all stream operations:
83
+
84
+ - Transformation: `map()`, `pluck()`, `without()`
85
+ - Filtering: `filter()`
86
+ - Aggregation: `count()`, `sum()`, `avg()`, `min()`, `max()`
87
+ - Joining: `join()`, `joinInner()`, `lookup()`
88
+ - Ordering: `orderBy()`, `slice()`, `nth()`
89
+ - Grouping: `group()`
90
+ - Deduplication: `distinct()`
91
+
92
+ See [Stream](./5.stream.md) for complete details on these methods.
@@ -0,0 +1,121 @@
1
+ # SingleSelection
2
+
3
+ The `SingleSelection<T>` class represents a reference to a single document in a table. It extends `Datum<T>` and provides methods for updating, replacing, deleting, and monitoring changes to an individual document.
4
+
5
+ ## Class Definition
6
+
7
+ ```typescript
8
+ class SingleSelection<T> extends Datum<T> {
9
+ update(document: DeepPartial<T>): Query<number>;
10
+ update<U>(document: (val: ValueProxy<T>) => U): Query<number>;
11
+ replace(document: DeepPartial<T>): Query<number>;
12
+ delete(): Query<number>;
13
+ changes(): Query<Changes<T>[]>;
14
+ }
15
+ ```
16
+
17
+ ## Create a SingleSelection
18
+
19
+ SingleSelection objects are produced by calling `get()` on a table:
20
+
21
+ ```typescript
22
+ import { Schema } from "@antelopejs/interface-database";
23
+
24
+ const schema = Schema.get("myapp")!;
25
+ const users = schema.instance().table("users");
26
+
27
+ // Get a document by primary key
28
+ const user = users.get("user-123");
29
+ ```
30
+
31
+ ## Read a Document
32
+
33
+ Since `SingleSelection` extends `Datum`, you can execute it directly to retrieve the document value:
34
+
35
+ ```typescript
36
+ const user = await users.get("user-123");
37
+
38
+ if (user) {
39
+ console.log("Found:", user.name);
40
+ }
41
+
42
+ // Access specific fields
43
+ const email = await users.get("user-123").key("email");
44
+ console.log("Email:", email);
45
+
46
+ // Transform with do()
47
+ const display = await users.get("user-123").do((u) => ({
48
+ label: u.key("name"),
49
+ contact: u.key("email"),
50
+ }));
51
+ ```
52
+
53
+ ## Update a Document
54
+
55
+ The `update` method modifies fields in the document. Unmentioned fields remain unchanged. Returns the number of modified documents.
56
+
57
+ ```typescript
58
+ // Update with a partial object
59
+ const count = await users.get("user-123").update({
60
+ status: "active",
61
+ lastLogin: new Date(),
62
+ });
63
+
64
+ // Update with a function for computed values
65
+ await users.get("user-123").update((user) => ({
66
+ loginCount: user.key("loginCount").add(1),
67
+ lastLogin: new Date(),
68
+ }));
69
+ ```
70
+
71
+ ## Replace a Document
72
+
73
+ The `replace` method substitutes the entire document with new data.
74
+
75
+ ```typescript
76
+ const count = await users.get("user-123").replace({
77
+ _id: "user-123",
78
+ name: "New Name",
79
+ email: "new@example.com",
80
+ role: "user",
81
+ });
82
+ ```
83
+
84
+ ## Delete a Document
85
+
86
+ The `delete` method removes the document from the table.
87
+
88
+ ```typescript
89
+ const count = await users.get("user-123").delete();
90
+
91
+ if (count === 1) {
92
+ console.log("User deleted successfully");
93
+ }
94
+ ```
95
+
96
+ ## Monitor Changes
97
+
98
+ The `changes` method returns change events for the document. Each event contains a `changeType` and the `oldValue`/`newValue` of the document.
99
+
100
+ ```typescript
101
+ const changes = await users.get("user-123").changes();
102
+
103
+ for (const change of changes) {
104
+ console.log("Type:", change.changeType); // "added" | "removed" | "modified"
105
+ console.log("Old:", change.oldValue);
106
+ console.log("New:", change.newValue);
107
+ }
108
+ ```
109
+
110
+ ## Inherited Methods
111
+
112
+ Since `SingleSelection` extends `Datum`, it inherits:
113
+
114
+ - **`key()`** - Access a field by name
115
+ - **`default()`** - Provide a fallback value for null
116
+ - **`do()`** - Apply a transformation function
117
+ - **`lookup()`** - Perform a foreign key lookup
118
+ - **`pluck()`** - Select specific fields
119
+ - **`cast()`** - Change the TypeScript type
120
+
121
+ See [Datum](./8.datum.md) for complete details.
@@ -0,0 +1,234 @@
1
+ # Stream
2
+
3
+ The `Stream<T>` class represents a sequence of documents that can be transformed, filtered, joined, grouped, and aggregated. It extends `Query<T[]>` and serves as the foundation for collection-based operations in AQL.
4
+
5
+ ## Class Definition
6
+
7
+ ```typescript
8
+ class Stream<T> extends Query<T[]> {
9
+ // Type casting
10
+ cast<U>(): Stream<U>;
11
+
12
+ // Field access
13
+ key<K extends keyof T>(key: K, def?: unknown): Stream<T[K]>;
14
+ default<U>(val: Value<U>): Stream<Exclude<T, undefined | null> | U>;
15
+
16
+ // Transformation
17
+ map<U>(mapper: (val: ValueProxy<T>) => U): Stream<ExtractType<U>>;
18
+ filter(predicate: (val: ValueProxy<T>) => ValueProxyOrValue<boolean>): this;
19
+ pluck(...fields: string[]): Stream<Partial<T>>;
20
+ without(...fields: string[]): Stream<Partial<T>>;
21
+
22
+ // Combining
23
+ union<U>(other: Stream<U>): Stream<T | U>;
24
+
25
+ // Joining
26
+ join<U, V>(right: Stream<U>, predicate, mapper): Stream<ExtractType<V>>;
27
+ joinInner<U, V>(right: Stream<U>, predicate, mapper): Stream<ExtractType<V>>;
28
+ lookup<U, TK extends keyof T>(right: Selection<U>, localKey: TK, otherKey: keyof U): Stream<...>;
29
+
30
+ // Grouping
31
+ group<U, K>(index: K, mapper: (stream: Stream<T>, group: ValueProxy) => U): Stream<ExtractType<U>>;
32
+
33
+ // Ordering and pagination
34
+ orderBy(index: string, direction?: "asc" | "desc"): this;
35
+ slice(offset: Value<number>, count?: Value<number>): this;
36
+ nth(n: Value<number>): Datum<T | null>;
37
+
38
+ // Aggregation
39
+ count(field?: keyof T): Datum<number>;
40
+ sum(field?: keyof T): Datum<number>;
41
+ avg(field?: keyof T): Datum<number>;
42
+ min(field?: keyof T): Datum<number>;
43
+ max(field?: keyof T): Datum<number>;
44
+ distinct(): Datum<T[]>;
45
+ distinct<TK extends keyof T>(field: TK): Stream<T[TK]>;
46
+
47
+ // Change feeds
48
+ changes(): Query<Changes<T>[]>;
49
+ }
50
+ ```
51
+
52
+ ## Field Access
53
+
54
+ Access a specific field from all documents in the stream with `key()`, or provide a default value for null fields with `default()`.
55
+
56
+ ```typescript
57
+ const users = schema.instance().table("users");
58
+
59
+ // Extract a single field
60
+ const emails = await users.key("email");
61
+
62
+ // Provide a default value
63
+ const phones = await users.key("phone").default("N/A");
64
+ ```
65
+
66
+ ## Transform Data
67
+
68
+ ### `map()`
69
+
70
+ Transform each document using a mapping function. The callback receives a `ValueProxy<T>` for constructing expressions.
71
+
72
+ ```typescript
73
+ const profiles = await users.map((user) => ({
74
+ id: user.key("_id"),
75
+ displayName: user.key("name"),
76
+ contact: user.key("email"),
77
+ }));
78
+ ```
79
+
80
+ ### `filter()`
81
+
82
+ Select documents matching a predicate. The callback returns a boolean expression.
83
+
84
+ ```typescript
85
+ const active = await users.filter((user) =>
86
+ user.key("status").eq("active").and(user.key("age").ge(18)),
87
+ );
88
+ ```
89
+
90
+ ### `pluck()` and `without()`
91
+
92
+ Select or exclude specific fields from all documents.
93
+
94
+ ```typescript
95
+ // Keep only specific fields
96
+ const slim = await users.pluck("_id", "name", "email");
97
+
98
+ // Remove sensitive fields
99
+ const safe = await users.without("password", "ssn");
100
+ ```
101
+
102
+ ## Combine Streams
103
+
104
+ ### `union()`
105
+
106
+ Concatenate two streams without deduplication.
107
+
108
+ ```typescript
109
+ const allPremium = await users
110
+ .filter((u) => u.key("plan").eq("premium"))
111
+ .union(users.filter((u) => u.key("plan").eq("enterprise")));
112
+ ```
113
+
114
+ ## Join Data
115
+
116
+ ### `join()` - Left Join
117
+
118
+ Perform a left join between two streams. Every document from the left stream appears in the result, with `null` for the right side when no match exists.
119
+
120
+ ```typescript
121
+ const usersWithOrders = await users.join(
122
+ schema.instance().table("orders"),
123
+ (user, order) => order.key("userId").eq(user.key("_id")),
124
+ (user, order) => ({
125
+ userName: user.key("name"),
126
+ orderTotal: order.key("amount").default(0),
127
+ }),
128
+ );
129
+ ```
130
+
131
+ ### `joinInner()` - Inner Join
132
+
133
+ Perform an inner join. Only documents with matches on both sides appear in the result.
134
+
135
+ ```typescript
136
+ const matched = await users.joinInner(
137
+ schema.instance().table("orders"),
138
+ (user, order) => order.key("userId").eq(user.key("_id")),
139
+ (user, order) => ({
140
+ userName: user.key("name"),
141
+ amount: order.key("amount"),
142
+ }),
143
+ );
144
+ ```
145
+
146
+ ### `lookup()`
147
+
148
+ Replace a foreign key field with the referenced document from another table. See [Lookup](../2.operations/7.lookup.md) for detailed usage.
149
+
150
+ ```typescript
151
+ const postsWithAuthors = await schema
152
+ .instance()
153
+ .table("posts")
154
+ .lookup(schema.instance().table("users"), "authorId", "_id");
155
+ ```
156
+
157
+ ## Group Data
158
+
159
+ The `group` method partitions the stream by an index value, then applies a mapping function to each group. The mapper receives the group's sub-stream and the group key.
160
+
161
+ ```typescript
162
+ const statsByRole = await users.group("role", (stream, role) => ({
163
+ role: role,
164
+ total: stream.count(),
165
+ avgAge: stream.avg("age"),
166
+ }));
167
+ ```
168
+
169
+ ## Order and Paginate
170
+
171
+ ### `orderBy()`
172
+
173
+ Sort the stream by an index field.
174
+
175
+ ```typescript
176
+ const sorted = await users.orderBy("name", "asc");
177
+ const newest = await users.orderBy("createdAt", "desc");
178
+ ```
179
+
180
+ ### `slice()`
181
+
182
+ Extract a subsection of the stream for pagination.
183
+
184
+ ```typescript
185
+ const page1 = await users.orderBy("createdAt").slice(0, 10);
186
+ const page2 = await users.orderBy("createdAt").slice(10, 10);
187
+ ```
188
+
189
+ ### `nth()`
190
+
191
+ Retrieve a single document by its position in the stream. Returns a `Datum<T | null>`.
192
+
193
+ ```typescript
194
+ const first = await users.orderBy("createdAt").nth(0);
195
+ ```
196
+
197
+ ## Aggregate Data
198
+
199
+ Stream provides several aggregation methods that return a `Datum<number>`:
200
+
201
+ ```typescript
202
+ const totalUsers = await users.count();
203
+ const distinctEmails = await users.count("email");
204
+ const totalSales = await schema.instance().table("orders").sum("amount");
205
+ const avgOrder = await schema.instance().table("orders").avg("amount");
206
+ const maxOrder = await schema.instance().table("orders").max("amount");
207
+ const minOrder = await schema.instance().table("orders").min("amount");
208
+ ```
209
+
210
+ ### `distinct()`
211
+
212
+ Retrieve deduplicated documents or distinct values of a specific field.
213
+
214
+ ```typescript
215
+ // All distinct documents
216
+ const unique = await users.distinct();
217
+
218
+ // Distinct values of a field
219
+ const roles = await users.distinct("role");
220
+ ```
221
+
222
+ ## Change Feeds
223
+
224
+ The `changes` method subscribes to real-time change notifications on the stream.
225
+
226
+ ```typescript
227
+ const changes = await users.changes();
228
+
229
+ for (const change of changes) {
230
+ console.log(change.changeType, change.oldValue, change.newValue);
231
+ }
232
+ ```
233
+
234
+ See [Change Feeds](./6.feed.md) for more details.
@@ -0,0 +1,82 @@
1
+ # Change Feeds
2
+
3
+ Change feeds provide real-time notifications when documents are inserted, updated, or deleted. Both `Stream<T>` and `SingleSelection<T>` expose a `changes()` method that returns change events.
4
+
5
+ ## The Changes Interface
6
+
7
+ ```typescript
8
+ interface Changes<T> {
9
+ changeType: "added" | "removed" | "modified";
10
+ oldValue?: T;
11
+ newValue?: T;
12
+ }
13
+ ```
14
+
15
+ | Property | Description |
16
+ | ------------ | ------------------------------------------------------------------ |
17
+ | `changeType` | The type of change: `"added"`, `"removed"`, or `"modified"`. |
18
+ | `oldValue` | The document value before the change. Undefined for new documents. |
19
+ | `newValue` | The document value after the change. Undefined for deletions. |
20
+
21
+ ## Create a Change Feed
22
+
23
+ Call `changes()` on any stream or single selection to subscribe to change events:
24
+
25
+ ```typescript
26
+ import { Schema } from "@antelopejs/interface-database";
27
+
28
+ const schema = Schema.get("myapp")!;
29
+ const users = schema.instance().table("users");
30
+
31
+ // Monitor all changes to a table
32
+ const allChanges = await users.changes();
33
+
34
+ // Monitor changes to a specific document
35
+ const userChanges = await users.get("user-123").changes();
36
+ ```
37
+
38
+ ## Process Change Events
39
+
40
+ Each change event includes a `changeType` field that indicates the nature of the change:
41
+
42
+ ```typescript
43
+ const changes = await users.changes();
44
+
45
+ for (const change of changes) {
46
+ switch (change.changeType) {
47
+ case "added":
48
+ console.log("New document:", change.newValue);
49
+ break;
50
+ case "modified":
51
+ console.log("Updated from:", change.oldValue);
52
+ console.log("Updated to:", change.newValue);
53
+ break;
54
+ case "removed":
55
+ console.log("Deleted document:", change.oldValue);
56
+ break;
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Use with Async Iteration
62
+
63
+ Since `changes()` returns a `Query`, you can use the cursor for streaming results:
64
+
65
+ ```typescript
66
+ const feed = users.changes();
67
+
68
+ for await (const batch of feed) {
69
+ for (const change of batch) {
70
+ console.log(`${change.changeType}:`, change.newValue ?? change.oldValue);
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## Use Cases
76
+
77
+ Change feeds are well suited for:
78
+
79
+ - **Real-time UI updates** - Push changes to connected clients as they happen.
80
+ - **Event-driven architectures** - React to data changes with downstream processing.
81
+ - **Audit logging** - Record every modification to a collection.
82
+ - **Cache invalidation** - Clear or update caches when the underlying data changes.
@@ -0,0 +1,82 @@
1
+ # Query
2
+
3
+ The `Query<T>` class is the base class for all query types in the AQL system. It implements `PromiseLike<T>`, so queries can be awaited directly, and provides cursor-based async iteration for streaming results.
4
+
5
+ ## Class Definition
6
+
7
+ ```typescript
8
+ class Query<T> extends StagedObject implements PromiseLike<T> {
9
+ run(): Promise<T>;
10
+ then<TResult1, TResult2>(
11
+ onfulfilled?: (value: T) => TResult1 | PromiseLike<TResult1>,
12
+ onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>,
13
+ ): PromiseLike<TResult1 | TResult2>;
14
+ cursor(): AsyncGenerator<T extends Array<infer T1> ? T1 : T, void, unknown>;
15
+ [Symbol.asyncIterator](): AsyncGenerator<...>;
16
+ }
17
+ ```
18
+
19
+ ## Execute a Query
20
+
21
+ ### `run()`
22
+
23
+ Execute the query pipeline and return the result as a promise.
24
+
25
+ ```typescript
26
+ import { Schema } from "@antelopejs/interface-database";
27
+
28
+ const schema = Schema.get("myapp")!;
29
+ const users = await schema.instance().table("users").run();
30
+ ```
31
+
32
+ ### Direct `await`
33
+
34
+ Since `Query` implements `PromiseLike`, you can await it directly without calling `run()`:
35
+
36
+ ```typescript
37
+ const users = await schema.instance().table("users");
38
+ ```
39
+
40
+ Both approaches are equivalent. Use whichever reads better in context.
41
+
42
+ ## Async Iteration
43
+
44
+ ### `cursor()`
45
+
46
+ Return an `AsyncGenerator` that yields results one at a time. This is useful for processing large result sets without loading everything into memory.
47
+
48
+ ```typescript
49
+ const cursor = schema.instance().table("users").cursor();
50
+
51
+ let result = await cursor.next();
52
+ while (!result.done) {
53
+ console.log("User:", result.value);
54
+ result = await cursor.next();
55
+ }
56
+ ```
57
+
58
+ ### `for await...of`
59
+
60
+ The `[Symbol.asyncIterator]()` method enables the `for await...of` syntax:
61
+
62
+ ```typescript
63
+ for await (const user of schema.instance().table("users")) {
64
+ console.log("Processing:", user.name);
65
+ }
66
+ ```
67
+
68
+ ## Query Composition
69
+
70
+ Queries use lazy evaluation. Operations are recorded as pipeline stages and only execute when `run()`, `then()`, or iteration begins. This means you can build queries incrementally:
71
+
72
+ ```typescript
73
+ const usersTable = schema.instance().table("users");
74
+
75
+ // Build different queries from the same base
76
+ const activeUsers = usersTable.filter((u) => u.key("status").eq("active"));
77
+ const inactiveUsers = usersTable.filter((u) => u.key("status").eq("inactive"));
78
+
79
+ // Execute them independently
80
+ const active = await activeUsers;
81
+ const inactive = await inactiveUsers;
82
+ ```