@antelopejs/interface-database 0.1.3 → 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 +8 -3
- package/skills/database-interface/SKILL.md +67 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Query Types
|
|
2
|
+
|
|
3
|
+
AQL provides several query classes that form a hierarchy of capabilities. Each class encapsulates a different aspect of database interactions, from full table access down to individual value manipulation.
|
|
4
|
+
|
|
5
|
+
## Query Type Hierarchy
|
|
6
|
+
|
|
7
|
+
All query types inherit from `StagedObject`, which records operations as a pipeline of stages. The hierarchy is:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Query<T>
|
|
11
|
+
├── Datum<T>
|
|
12
|
+
│ └── SingleSelection<T>
|
|
13
|
+
└── Stream<T>
|
|
14
|
+
└── Selection<T>
|
|
15
|
+
└── Table<T>
|
|
16
|
+
|
|
17
|
+
ValueProxy<T> (used within query callbacks)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Common Features
|
|
21
|
+
|
|
22
|
+
All query types that extend `Query<T>` share these capabilities:
|
|
23
|
+
|
|
24
|
+
- **`run()`** - Execute the query and return a `Promise<T>` with the result.
|
|
25
|
+
- **`then()`** - `PromiseLike` support, so queries can be awaited directly.
|
|
26
|
+
- **`cursor()`** - Return an `AsyncGenerator` for streaming results one at a time.
|
|
27
|
+
- **`[Symbol.asyncIterator]()`** - Enable `for await...of` loops over query results.
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
31
|
+
|
|
32
|
+
// Execute a query explicitly
|
|
33
|
+
const result = await schema.instance().table("users").run();
|
|
34
|
+
|
|
35
|
+
// Await the query directly (uses PromiseLike)
|
|
36
|
+
const users = await schema.instance().table("users");
|
|
37
|
+
|
|
38
|
+
// Iterate with async generator
|
|
39
|
+
for await (const user of schema.instance().table("users")) {
|
|
40
|
+
console.log(user.name);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Available Query Types
|
|
45
|
+
|
|
46
|
+
- [**Table**](./2.table.md) - Represents a database table with insert, get, getAll, and between operations
|
|
47
|
+
- [**Selection**](./3.selection.md) - A set of documents supporting update, replace, and delete
|
|
48
|
+
- [**SingleSelection**](./4.single_selection.md) - A single document with update, replace, delete, and change feed support
|
|
49
|
+
- [**Stream**](./5.stream.md) - A sequence of results with map, filter, join, group, aggregate, and sort operations
|
|
50
|
+
- [**Change Feeds**](./6.feed.md) - Real-time change notifications via the `changes()` method
|
|
51
|
+
- [**Query**](./7.query.md) - The base class providing execution and async iteration
|
|
52
|
+
- [**Datum**](./8.datum.md) - A single value with field access, transformation, and lookup capabilities
|
|
53
|
+
- [**ValueProxy**](./9.valueproxy.md) - Proxy for in-query value operations (arithmetic, string, date, array, object)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Table
|
|
2
|
+
|
|
3
|
+
The `Table<T>` class represents a database table and provides methods for inserting documents and retrieving them by primary key, secondary index, or range. It extends `Selection<T>`, inheriting all multi-document operations and stream capabilities.
|
|
4
|
+
|
|
5
|
+
## Class Definition
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
class Table<T> extends Selection<T> {
|
|
9
|
+
insert(obj: DeepPartial<T> | DeepPartial<T>[], options?: InsertOptions): Query<string[]>;
|
|
10
|
+
get(key: string): SingleSelection<T>;
|
|
11
|
+
getAll(keys: string | number | boolean | (string | number | boolean)[], index?: string): Selection<T>;
|
|
12
|
+
between<TK extends keyof T>(index: TK, low: T[TK], high: T[TK]): Selection<T>;
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Access a Table
|
|
17
|
+
|
|
18
|
+
Tables are accessed through a `SchemaInstance`. Define a schema, create an instance, then call `table()`:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
22
|
+
|
|
23
|
+
const schema = new Schema("myapp", {
|
|
24
|
+
users: {
|
|
25
|
+
fields: { name: "string", email: "string" },
|
|
26
|
+
indexes: { email: {} },
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
await schema.createInstance();
|
|
31
|
+
const users = schema.instance().table("users");
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Insert Documents
|
|
35
|
+
|
|
36
|
+
The `insert` method adds one or more documents to the table and returns the generated IDs.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Insert a single document
|
|
40
|
+
const ids = await users.insert({
|
|
41
|
+
name: "Alice Johnson",
|
|
42
|
+
email: "alice@example.com",
|
|
43
|
+
});
|
|
44
|
+
console.log("Inserted ID:", ids[0]);
|
|
45
|
+
|
|
46
|
+
// Insert multiple documents
|
|
47
|
+
const batchIds = await users.insert([
|
|
48
|
+
{ name: "Bob", email: "bob@example.com" },
|
|
49
|
+
{ name: "Carol", email: "carol@example.com" },
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
// Insert with conflict handling
|
|
53
|
+
const upsertIds = await users.insert(
|
|
54
|
+
{ _id: "user-123", name: "Updated Name" },
|
|
55
|
+
{ conflict: "update" },
|
|
56
|
+
);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### `InsertOptions`
|
|
60
|
+
|
|
61
|
+
| Option | Type | Description |
|
|
62
|
+
| ---------- | ------------------------ | --------------------------------------------------- |
|
|
63
|
+
| `conflict` | `"update" \| "replace"` | Behavior when a document with the same ID exists. |
|
|
64
|
+
|
|
65
|
+
## Retrieve Documents
|
|
66
|
+
|
|
67
|
+
### `get(key)`
|
|
68
|
+
|
|
69
|
+
Retrieve a single document by its primary key. Returns a `SingleSelection<T>`.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const user = await users.get("user-123");
|
|
73
|
+
|
|
74
|
+
if (user) {
|
|
75
|
+
console.log("Found:", user.name);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `getAll(keys, index?)`
|
|
80
|
+
|
|
81
|
+
Retrieve multiple documents by key values. When `index` is provided, the lookup uses the specified secondary index instead of the primary key.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// By primary key
|
|
85
|
+
const selected = await users.getAll(["user-1", "user-2", "user-3"]);
|
|
86
|
+
|
|
87
|
+
// By secondary index
|
|
88
|
+
const admins = await users.getAll("admin", "role");
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `between(index, low, high)`
|
|
92
|
+
|
|
93
|
+
Retrieve documents where the specified index value falls within a range. The lower bound is inclusive, and the upper bound is exclusive.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const recent = await users.between(
|
|
97
|
+
"createdAt",
|
|
98
|
+
new Date("2024-01-01"),
|
|
99
|
+
new Date(),
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Inherited Methods
|
|
104
|
+
|
|
105
|
+
Since `Table` extends `Selection`, which extends `Stream`, it inherits:
|
|
106
|
+
|
|
107
|
+
- **Selection methods**: `update()`, `replace()`, `delete()` -- see [Selection](./3.selection.md)
|
|
108
|
+
- **Stream methods**: `map()`, `filter()`, `orderBy()`, `count()`, `join()`, and more -- see [Stream](./5.stream.md)
|
|
@@ -0,0 +1,94 @@
|
|
|
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("createdAt", new Date("2024-01-01"), new Date());
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Update Documents
|
|
34
|
+
|
|
35
|
+
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.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// Update with a partial object
|
|
39
|
+
const count = await users
|
|
40
|
+
.getAll("inactive", "status")
|
|
41
|
+
.update({ status: "active" });
|
|
42
|
+
|
|
43
|
+
console.log(`Activated ${count} users`);
|
|
44
|
+
|
|
45
|
+
// Update with a function for computed values
|
|
46
|
+
await users
|
|
47
|
+
.getAll("active", "status")
|
|
48
|
+
.update((user) => ({
|
|
49
|
+
loginCount: user.key("loginCount").add(1),
|
|
50
|
+
}));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Replace Documents
|
|
54
|
+
|
|
55
|
+
The `replace` method substitutes entire documents with the provided data. Unlike `update`, all fields not present in the replacement are removed.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const count = await users
|
|
59
|
+
.getAll("user-123")
|
|
60
|
+
.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
|
|
76
|
+
.getAll("inactive", "status")
|
|
77
|
+
.delete();
|
|
78
|
+
|
|
79
|
+
console.log(`Deleted ${count} inactive users`);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Inherited Methods
|
|
83
|
+
|
|
84
|
+
Since `Selection` extends `Stream`, it inherits all stream operations:
|
|
85
|
+
|
|
86
|
+
- Transformation: `map()`, `pluck()`, `without()`
|
|
87
|
+
- Filtering: `filter()`
|
|
88
|
+
- Aggregation: `count()`, `sum()`, `avg()`, `min()`, `max()`
|
|
89
|
+
- Joining: `join()`, `joinInner()`, `lookup()`
|
|
90
|
+
- Ordering: `orderBy()`, `slice()`, `nth()`
|
|
91
|
+
- Grouping: `group()`
|
|
92
|
+
- Deduplication: `distinct()`
|
|
93
|
+
|
|
94
|
+
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,237 @@
|
|
|
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")
|
|
87
|
+
.and(user.key("age").ge(18)),
|
|
88
|
+
);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `pluck()` and `without()`
|
|
92
|
+
|
|
93
|
+
Select or exclude specific fields from all documents.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Keep only specific fields
|
|
97
|
+
const slim = await users.pluck("_id", "name", "email");
|
|
98
|
+
|
|
99
|
+
// Remove sensitive fields
|
|
100
|
+
const safe = await users.without("password", "ssn");
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Combine Streams
|
|
104
|
+
|
|
105
|
+
### `union()`
|
|
106
|
+
|
|
107
|
+
Concatenate two streams without deduplication.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const allPremium = await users
|
|
111
|
+
.filter((u) => u.key("plan").eq("premium"))
|
|
112
|
+
.union(
|
|
113
|
+
users.filter((u) => u.key("plan").eq("enterprise")),
|
|
114
|
+
);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Join Data
|
|
118
|
+
|
|
119
|
+
### `join()` - Left Join
|
|
120
|
+
|
|
121
|
+
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.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const usersWithOrders = await users.join(
|
|
125
|
+
schema.instance().table("orders"),
|
|
126
|
+
(user, order) => order.key("userId").eq(user.key("_id")),
|
|
127
|
+
(user, order) => ({
|
|
128
|
+
userName: user.key("name"),
|
|
129
|
+
orderTotal: order.key("amount").default(0),
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### `joinInner()` - Inner Join
|
|
135
|
+
|
|
136
|
+
Perform an inner join. Only documents with matches on both sides appear in the result.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const matched = await users.joinInner(
|
|
140
|
+
schema.instance().table("orders"),
|
|
141
|
+
(user, order) => order.key("userId").eq(user.key("_id")),
|
|
142
|
+
(user, order) => ({
|
|
143
|
+
userName: user.key("name"),
|
|
144
|
+
amount: order.key("amount"),
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### `lookup()`
|
|
150
|
+
|
|
151
|
+
Replace a foreign key field with the referenced document from another table. See [Lookup](../2.operations/7.lookup.md) for detailed usage.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const postsWithAuthors = await schema
|
|
155
|
+
.instance()
|
|
156
|
+
.table("posts")
|
|
157
|
+
.lookup(schema.instance().table("users"), "authorId", "_id");
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Group Data
|
|
161
|
+
|
|
162
|
+
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.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const statsByRole = await users.group("role", (stream, role) => ({
|
|
166
|
+
role: role,
|
|
167
|
+
total: stream.count(),
|
|
168
|
+
avgAge: stream.avg("age"),
|
|
169
|
+
}));
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Order and Paginate
|
|
173
|
+
|
|
174
|
+
### `orderBy()`
|
|
175
|
+
|
|
176
|
+
Sort the stream by an index field.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const sorted = await users.orderBy("name", "asc");
|
|
180
|
+
const newest = await users.orderBy("createdAt", "desc");
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### `slice()`
|
|
184
|
+
|
|
185
|
+
Extract a subsection of the stream for pagination.
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
const page1 = await users.orderBy("createdAt").slice(0, 10);
|
|
189
|
+
const page2 = await users.orderBy("createdAt").slice(10, 10);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### `nth()`
|
|
193
|
+
|
|
194
|
+
Retrieve a single document by its position in the stream. Returns a `Datum<T | null>`.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const first = await users.orderBy("createdAt").nth(0);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Aggregate Data
|
|
201
|
+
|
|
202
|
+
Stream provides several aggregation methods that return a `Datum<number>`:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const totalUsers = await users.count();
|
|
206
|
+
const distinctEmails = await users.count("email");
|
|
207
|
+
const totalSales = await schema.instance().table("orders").sum("amount");
|
|
208
|
+
const avgOrder = await schema.instance().table("orders").avg("amount");
|
|
209
|
+
const maxOrder = await schema.instance().table("orders").max("amount");
|
|
210
|
+
const minOrder = await schema.instance().table("orders").min("amount");
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### `distinct()`
|
|
214
|
+
|
|
215
|
+
Retrieve deduplicated documents or distinct values of a specific field.
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// All distinct documents
|
|
219
|
+
const unique = await users.distinct();
|
|
220
|
+
|
|
221
|
+
// Distinct values of a field
|
|
222
|
+
const roles = await users.distinct("role");
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Change Feeds
|
|
226
|
+
|
|
227
|
+
The `changes` method subscribes to real-time change notifications on the stream.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const changes = await users.changes();
|
|
231
|
+
|
|
232
|
+
for (const change of changes) {
|
|
233
|
+
console.log(change.changeType, change.oldValue, change.newValue);
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
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.
|