@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,137 @@
|
|
|
1
|
+
# Lookup
|
|
2
|
+
|
|
3
|
+
The `lookup` method performs a foreign key join, replacing a field in your documents with the referenced data from another table. It is available on both `Stream<T>` and `Datum<T>`.
|
|
4
|
+
|
|
5
|
+
## Parameters
|
|
6
|
+
|
|
7
|
+
| Parameter | Type | Description |
|
|
8
|
+
| ---------- | -------------- | ---------------------------------------------------- |
|
|
9
|
+
| `other` | `Selection<U>` | The table containing the referenced documents |
|
|
10
|
+
| `localKey` | `keyof T` | The field in the current document holding the foreign key |
|
|
11
|
+
| `otherKey` | `keyof U` | The field in the other table to match against |
|
|
12
|
+
|
|
13
|
+
## Behavior
|
|
14
|
+
|
|
15
|
+
The `lookup` method replaces the local key field with the populated data:
|
|
16
|
+
|
|
17
|
+
- If the local key contains a **single value**, the field becomes a **single document** from the other table.
|
|
18
|
+
- If the local key contains an **array of values**, the field becomes an **array of documents** from the other table.
|
|
19
|
+
- TypeScript automatically tracks the type transformation.
|
|
20
|
+
|
|
21
|
+
## One-to-One Relationship
|
|
22
|
+
|
|
23
|
+
Replace a single foreign key with the referenced document:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
27
|
+
|
|
28
|
+
interface Post {
|
|
29
|
+
_id: string;
|
|
30
|
+
title: string;
|
|
31
|
+
authorId: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface User {
|
|
35
|
+
_id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
email: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const schema = Schema.get("blog")!;
|
|
41
|
+
const db = schema.instance();
|
|
42
|
+
|
|
43
|
+
const postsWithAuthors = await db
|
|
44
|
+
.table<Post>("posts")
|
|
45
|
+
.lookup(db.table<User>("users"), "authorId", "_id");
|
|
46
|
+
|
|
47
|
+
// Result type: { _id: string, title: string, authorId: User }
|
|
48
|
+
for (const post of postsWithAuthors) {
|
|
49
|
+
console.log(`${post.title} by ${post.authorId.name}`);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## One-to-Many Relationship
|
|
54
|
+
|
|
55
|
+
Replace an array of foreign keys with an array of documents:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
interface Order {
|
|
59
|
+
_id: string;
|
|
60
|
+
customerId: string;
|
|
61
|
+
productIds: string[];
|
|
62
|
+
total: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface Product {
|
|
66
|
+
_id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
price: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const ordersWithProducts = await db
|
|
72
|
+
.table<Order>("orders")
|
|
73
|
+
.lookup(db.table<Product>("products"), "productIds", "_id");
|
|
74
|
+
|
|
75
|
+
// Result type: { ..., productIds: Product[] }
|
|
76
|
+
for (const order of ordersWithProducts) {
|
|
77
|
+
for (const product of order.productIds) {
|
|
78
|
+
console.log(` - ${product.name}: $${product.price}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Combine with Other Operations
|
|
84
|
+
|
|
85
|
+
Lookup works seamlessly with filters, sorting, and other stream operations:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Filter before lookup
|
|
89
|
+
const recentWithCustomers = await db
|
|
90
|
+
.table("orders")
|
|
91
|
+
.filter((order) => order.key("createdAt").gt(new Date("2024-01-01")))
|
|
92
|
+
.lookup(db.table("customers"), "customerId", "_id");
|
|
93
|
+
|
|
94
|
+
// Lookup on a single document
|
|
95
|
+
const post = await db
|
|
96
|
+
.table<Post>("posts")
|
|
97
|
+
.get("post-1")
|
|
98
|
+
.lookup(db.table<User>("users"), "authorId", "_id");
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Lookup vs Join
|
|
102
|
+
|
|
103
|
+
### Use `lookup` when:
|
|
104
|
+
|
|
105
|
+
- You have a straightforward foreign key relationship.
|
|
106
|
+
- You want the referenced data to replace the foreign key field.
|
|
107
|
+
- You are working with one-to-one or one-to-many relationships.
|
|
108
|
+
|
|
109
|
+
### Use `join` or `joinInner` when:
|
|
110
|
+
|
|
111
|
+
- You need custom join predicates beyond simple equality.
|
|
112
|
+
- You want to keep both original and joined data as separate fields.
|
|
113
|
+
- You need to transform the result into a custom structure.
|
|
114
|
+
|
|
115
|
+
### Comparison
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Lookup: replaces the field
|
|
119
|
+
const withLookup = await db
|
|
120
|
+
.table("orders")
|
|
121
|
+
.lookup(db.table("customers"), "customerId", "_id");
|
|
122
|
+
// order.customerId is now the full customer object
|
|
123
|
+
|
|
124
|
+
// Join: custom output structure
|
|
125
|
+
const withJoin = await db
|
|
126
|
+
.table("orders")
|
|
127
|
+
.joinInner(
|
|
128
|
+
db.table("customers"),
|
|
129
|
+
(order, customer) => order.key("customerId").eq(customer.key("_id")),
|
|
130
|
+
(order, customer) => ({
|
|
131
|
+
orderId: order.key("_id"),
|
|
132
|
+
orderTotal: order.key("total"),
|
|
133
|
+
customerName: customer.key("name"),
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
// Result is a custom object with selected fields from both tables
|
|
137
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Results
|
|
2
|
+
|
|
3
|
+
AQL operations return structured result values that communicate what happened during execution. This section covers the result types produced by write operations and change feeds.
|
|
4
|
+
|
|
5
|
+
## Result Types
|
|
6
|
+
|
|
7
|
+
- [2. Insert Results](./2.write_results.md) - Return values from insert operations
|
|
8
|
+
- [3. Index Definitions](./3.index_changes.md) - Index configuration in schema definitions
|
|
9
|
+
- [4. Table Definitions](./4.table_changes.md) - Table structure and field types
|
|
10
|
+
- [5. Schema and Instance Management](./5.database_changes.md) - Schema lifecycle and instance management
|
|
11
|
+
- [6. Change Events](./6.value_changes.md) - Structure of change feed notifications
|
|
12
|
+
|
|
13
|
+
## Common Patterns
|
|
14
|
+
|
|
15
|
+
All AQL operations follow a consistent pattern:
|
|
16
|
+
|
|
17
|
+
1. Build a query by chaining operations.
|
|
18
|
+
2. Execute the query with `run()` or `await` to get the result.
|
|
19
|
+
3. Process the structured result.
|
|
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
|
+
// Insert returns an array of generated IDs
|
|
28
|
+
const ids = await users.insert({ name: "Alice", email: "alice@example.com" });
|
|
29
|
+
console.log("New ID:", ids[0]);
|
|
30
|
+
|
|
31
|
+
// Update/delete return the number of affected documents
|
|
32
|
+
const count = await users.get("user-123").update({ status: "active" });
|
|
33
|
+
console.log(`Modified ${count} documents`);
|
|
34
|
+
|
|
35
|
+
// Delete returns the number of deleted documents
|
|
36
|
+
const deleted = await users.get("user-123").delete();
|
|
37
|
+
console.log(`Deleted ${deleted} documents`);
|
|
38
|
+
```
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Insert Results
|
|
2
|
+
|
|
3
|
+
The `insert` method on `Table<T>` returns a `Query<string[]>` that resolves to an array of the generated or provided primary keys for the inserted documents.
|
|
4
|
+
|
|
5
|
+
## Return Value
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// Insert returns string[] (the IDs of inserted documents)
|
|
9
|
+
const ids: string[] = await users.insert({
|
|
10
|
+
name: "Alice",
|
|
11
|
+
email: "alice@example.com",
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
### Single Document Insert
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
21
|
+
|
|
22
|
+
const schema = Schema.get("myapp")!;
|
|
23
|
+
const users = schema.instance().table("users");
|
|
24
|
+
|
|
25
|
+
const ids = await users.insert({
|
|
26
|
+
name: "John Doe",
|
|
27
|
+
email: "john@example.com",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
console.log("Inserted document ID:", ids[0]);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Multiple Document Insert
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const ids = await users.insert([
|
|
37
|
+
{ name: "Alice", email: "alice@example.com" },
|
|
38
|
+
{ name: "Bob", email: "bob@example.com" },
|
|
39
|
+
{ name: "Carol", email: "carol@example.com" },
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
console.log(`Inserted ${ids.length} documents`);
|
|
43
|
+
console.log("IDs:", ids);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Insert with Conflict Handling
|
|
47
|
+
|
|
48
|
+
Use the `conflict` option to control behavior when a document with the same primary key already exists:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// Update matching fields on conflict
|
|
52
|
+
const ids = await users.insert(
|
|
53
|
+
{ _id: "user-123", name: "Updated Name", lastSeen: new Date() },
|
|
54
|
+
{ conflict: "update" },
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Replace the entire document on conflict
|
|
58
|
+
const ids2 = await users.insert(
|
|
59
|
+
{ _id: "user-123", name: "Replaced", email: "new@example.com" },
|
|
60
|
+
{ conflict: "replace" },
|
|
61
|
+
);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Update and Delete Results
|
|
65
|
+
|
|
66
|
+
The `update`, `replace`, and `delete` methods return a `Query<number>` that resolves to the count of affected documents:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// Update returns the number of modified documents
|
|
70
|
+
const updated = await users.get("user-123").update({ status: "active" });
|
|
71
|
+
console.log(`Modified: ${updated}`);
|
|
72
|
+
|
|
73
|
+
// Replace returns the number of replaced documents
|
|
74
|
+
const replaced = await users.get("user-123").replace({
|
|
75
|
+
_id: "user-123",
|
|
76
|
+
name: "New",
|
|
77
|
+
email: "new@example.com",
|
|
78
|
+
});
|
|
79
|
+
console.log(`Replaced: ${replaced}`);
|
|
80
|
+
|
|
81
|
+
// Delete returns the number of deleted documents
|
|
82
|
+
const deleted = await users
|
|
83
|
+
.filter((u) => u.key("status").eq("inactive"))
|
|
84
|
+
.delete();
|
|
85
|
+
console.log(`Deleted: ${deleted}`);
|
|
86
|
+
```
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Index Definitions
|
|
2
|
+
|
|
3
|
+
In AQL, indexes are defined as part of the schema definition rather than created at runtime. The schema declaration specifies which fields to index and what type of index to use.
|
|
4
|
+
|
|
5
|
+
## `IndexDefinition`
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
interface IndexDefinition {
|
|
9
|
+
fields?: string[]; // Fields for compound indexes
|
|
10
|
+
multi?: boolean; // Multi-valued index
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Define Indexes
|
|
15
|
+
|
|
16
|
+
Indexes are declared in the `indexes` property of each table definition:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
20
|
+
|
|
21
|
+
const schema = new Schema("myapp", {
|
|
22
|
+
users: {
|
|
23
|
+
fields: { name: "string", email: "string", role: "string" },
|
|
24
|
+
indexes: {
|
|
25
|
+
email: {}, // Simple index (field name = index name)
|
|
26
|
+
role: {}, // Simple index
|
|
27
|
+
name_role: { fields: ["name", "role"] }, // Compound index
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Simple Indexes
|
|
34
|
+
|
|
35
|
+
When `fields` is omitted, the index name is used as the field name:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
indexes: {
|
|
39
|
+
email: {}, // Creates an index on the "email" field
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Compound Indexes
|
|
44
|
+
|
|
45
|
+
Provide the `fields` array for indexes spanning multiple fields:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
indexes: {
|
|
49
|
+
full_name: { fields: ["firstName", "lastName"] },
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Multi Indexes
|
|
54
|
+
|
|
55
|
+
Set `multi: true` for array fields where each element should be individually indexed:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
indexes: {
|
|
59
|
+
tags: { multi: true },
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Use Indexes in Queries
|
|
64
|
+
|
|
65
|
+
Once defined, indexes are available for `getAll()` and `between()` operations:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const db = schema.instance();
|
|
69
|
+
const users = db.table("users");
|
|
70
|
+
|
|
71
|
+
// Lookup by secondary index
|
|
72
|
+
const admins = await users.getAll("admin", "role");
|
|
73
|
+
|
|
74
|
+
// Range query on an indexed field
|
|
75
|
+
const recent = await users.between("createdAt", new Date("2024-01-01"), new Date());
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
For more details, see [Index Management](../2.operations/5.indexes.md).
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Table Definitions
|
|
2
|
+
|
|
3
|
+
In AQL, tables are defined as part of a schema. Each table definition specifies the fields and indexes for that table.
|
|
4
|
+
|
|
5
|
+
## `TableDefinition`
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
interface TableDefinition {
|
|
9
|
+
fields: Record<string, FieldType>;
|
|
10
|
+
indexes: Record<string, IndexDefinition>;
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### `FieldType`
|
|
15
|
+
|
|
16
|
+
Field types describe the structure of each field:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
type FieldType =
|
|
20
|
+
| string // Primitive type name (e.g., "string", "number")
|
|
21
|
+
| Array<StringFieldType> // Array of a given type
|
|
22
|
+
| { [subfield: string]: StringFieldType } // Nested object
|
|
23
|
+
| import("io-ts").Mixed; // io-ts codec at the field root
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
A field can be described either with the string-token vocabulary (primitives, arrays, nested records) or with a single [io-ts](https://github.com/gcanti/io-ts) codec at the root of the field. Codecs are not mixed inside string-tree records — use io-ts combinators (`t.type`, `t.array`, …) to nest within a codec.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import * as t from "io-ts";
|
|
30
|
+
|
|
31
|
+
const Order = {
|
|
32
|
+
fields: {
|
|
33
|
+
id: "string",
|
|
34
|
+
status: t.union([t.literal("open"), t.literal("closed")]),
|
|
35
|
+
meta: t.type({ source: t.string, retries: t.number }),
|
|
36
|
+
},
|
|
37
|
+
indexes: {},
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Define Tables in a Schema
|
|
42
|
+
|
|
43
|
+
Tables are declared in the `SchemaDefinition` passed to the `Schema` constructor:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
47
|
+
|
|
48
|
+
const schema = new Schema("myapp", {
|
|
49
|
+
users: {
|
|
50
|
+
fields: {
|
|
51
|
+
name: "string",
|
|
52
|
+
email: "string",
|
|
53
|
+
age: "number",
|
|
54
|
+
address: {
|
|
55
|
+
street: "string",
|
|
56
|
+
city: "string",
|
|
57
|
+
zip: "string",
|
|
58
|
+
},
|
|
59
|
+
tags: ["string"],
|
|
60
|
+
},
|
|
61
|
+
indexes: {
|
|
62
|
+
email: {},
|
|
63
|
+
age: {},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
posts: {
|
|
67
|
+
fields: {
|
|
68
|
+
title: "string",
|
|
69
|
+
content: "string",
|
|
70
|
+
authorId: "string",
|
|
71
|
+
publishedAt: "date",
|
|
72
|
+
},
|
|
73
|
+
indexes: {
|
|
74
|
+
authorId: {},
|
|
75
|
+
publishedAt: {},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Access Tables
|
|
82
|
+
|
|
83
|
+
After creating a schema instance, access tables by name:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
await schema.createInstance();
|
|
87
|
+
|
|
88
|
+
const users = schema.instance().table("users");
|
|
89
|
+
const posts = schema.instance().table("posts");
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
For type-safe access, provide a generic type parameter:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
interface User {
|
|
96
|
+
_id: string;
|
|
97
|
+
name: string;
|
|
98
|
+
email: string;
|
|
99
|
+
age: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const users = schema.instance().table<User>("users");
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
For more details on table operations, see [Table Operations](../2.operations/3.table_operations.md).
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Schema and Instance Management
|
|
2
|
+
|
|
3
|
+
This page covers the `Schema`, `SchemaInstance`, and related types that control database structure and lifecycle.
|
|
4
|
+
|
|
5
|
+
## Schema
|
|
6
|
+
|
|
7
|
+
The `Schema<T>` class defines the structure of a database and manages its instances.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
class Schema<T = any> extends StagedObject {
|
|
11
|
+
static get(id: string): Schema | undefined;
|
|
12
|
+
constructor(id: string, definition: SchemaDefinition);
|
|
13
|
+
instance(id?: InstanceId): SchemaInstance<T>;
|
|
14
|
+
createInstance(id?: string): Query<string>;
|
|
15
|
+
destroyInstance(id?: string): Query<void>;
|
|
16
|
+
listInstances(): Query<string[]>;
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### `SchemaDefinition`
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
interface SchemaDefinition {
|
|
24
|
+
[tableName: string]: TableDefinition;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### `InstanceId` and `CROSS_INSTANCE`
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const CROSS_INSTANCE: unique symbol;
|
|
32
|
+
type InstanceId = string | typeof CROSS_INSTANCE;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`schema.instance(CROSS_INSTANCE)` returns a query stage that operates across every instance of the schema. Adapters skip the per-instance filter on read/update/delete and reject inserts.
|
|
36
|
+
|
|
37
|
+
## Create and Manage Instances
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
41
|
+
|
|
42
|
+
// Define a schema
|
|
43
|
+
const schema = new Schema("myapp", {
|
|
44
|
+
users: {
|
|
45
|
+
fields: { name: "string" },
|
|
46
|
+
indexes: {},
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Create the default instance
|
|
51
|
+
const instanceId = await schema.createInstance();
|
|
52
|
+
|
|
53
|
+
// Create a named instance
|
|
54
|
+
await schema.createInstance("tenant-1");
|
|
55
|
+
|
|
56
|
+
// Access instances
|
|
57
|
+
const defaultDb = schema.instance();
|
|
58
|
+
const tenantDb = schema.instance("tenant-1");
|
|
59
|
+
|
|
60
|
+
// Destroy an instance
|
|
61
|
+
await schema.destroyInstance("tenant-1");
|
|
62
|
+
|
|
63
|
+
// List named instances
|
|
64
|
+
const ids = await schema.listInstances();
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Retrieve a Schema
|
|
68
|
+
|
|
69
|
+
Use `Schema.get()` to look up a previously defined schema anywhere in your application:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const schema = Schema.get("myapp");
|
|
73
|
+
|
|
74
|
+
if (schema) {
|
|
75
|
+
const db = schema.instance();
|
|
76
|
+
const users = db.table("users");
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## SchemaInstance
|
|
81
|
+
|
|
82
|
+
The `SchemaInstance<T>` class represents a single instance of a schema. Its primary method is `table()`, which returns a `Table` for the specified table name.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
class SchemaInstance<T> extends StagedObject {
|
|
86
|
+
table<TK extends keyof T>(id: TK): Table<T[TK]>;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
const db = schema.instance();
|
|
92
|
+
const users = db.table("users");
|
|
93
|
+
const posts = db.table("posts");
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For practical usage patterns, see [Schema Management](../2.operations/2.database_management.md).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Change Events
|
|
2
|
+
|
|
3
|
+
The `Changes<T>` interface represents a change event emitted by change feeds. It includes the type of change and the document values before and after the change.
|
|
4
|
+
|
|
5
|
+
## Interface Definition
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
interface Changes<T> {
|
|
9
|
+
changeType: "added" | "removed" | "modified";
|
|
10
|
+
oldValue?: T;
|
|
11
|
+
newValue?: T;
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Properties
|
|
16
|
+
|
|
17
|
+
| Property | Type | Description |
|
|
18
|
+
| ------------ | ---------------------------------------- | ---------------------------------------------------- |
|
|
19
|
+
| `changeType` | `"added" \| "removed" \| "modified"` | The kind of change that occurred. |
|
|
20
|
+
| `oldValue` | `T \| undefined` | The document before the change. Undefined for inserts.|
|
|
21
|
+
| `newValue` | `T \| undefined` | The document after the change. Undefined for deletes. |
|
|
22
|
+
|
|
23
|
+
## Determine the Change Type
|
|
24
|
+
|
|
25
|
+
The `changeType` field indicates what happened:
|
|
26
|
+
|
|
27
|
+
- **`"added"`** - A new document was inserted. `newValue` contains the document; `oldValue` is undefined.
|
|
28
|
+
- **`"modified"`** - An existing document was updated. Both `oldValue` and `newValue` are present.
|
|
29
|
+
- **`"removed"`** - A document was deleted. `oldValue` contains the deleted document; `newValue` is undefined.
|
|
30
|
+
|
|
31
|
+
## Process Change Events
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
35
|
+
|
|
36
|
+
const schema = Schema.get("myapp")!;
|
|
37
|
+
const users = schema.instance().table("users");
|
|
38
|
+
|
|
39
|
+
const changes = await users.changes();
|
|
40
|
+
|
|
41
|
+
for (const change of changes) {
|
|
42
|
+
switch (change.changeType) {
|
|
43
|
+
case "added":
|
|
44
|
+
console.log("New document:", change.newValue);
|
|
45
|
+
break;
|
|
46
|
+
case "modified":
|
|
47
|
+
console.log("Updated:", change.oldValue, "->", change.newValue);
|
|
48
|
+
break;
|
|
49
|
+
case "removed":
|
|
50
|
+
console.log("Deleted:", change.oldValue);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Monitor a Single Document
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const userChanges = await users.get("user-123").changes();
|
|
60
|
+
|
|
61
|
+
for (const change of userChanges) {
|
|
62
|
+
if (change.changeType === "modified") {
|
|
63
|
+
console.log("User updated:", change.newValue);
|
|
64
|
+
} else if (change.changeType === "removed") {
|
|
65
|
+
console.log("User deleted");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Use Cases
|
|
71
|
+
|
|
72
|
+
- **Real-time notifications** - Push updates to connected clients when data changes.
|
|
73
|
+
- **Audit logging** - Record every modification with before/after snapshots.
|
|
74
|
+
- **Cache invalidation** - Clear or refresh caches when underlying data is modified.
|
|
75
|
+
- **Event-driven workflows** - Trigger downstream processing based on data changes.
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Database Interface
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Database interface provides AQL (Antelope Query Language), a database-agnostic query API. Each module implements this generic interface with the database engine of its choice. AQL offers a chainable, type-safe query builder that works identically across all supported backends, so your application code remains portable regardless of the underlying database.
|
|
6
|
+
|
|
7
|
+
## Documentation Structure
|
|
8
|
+
|
|
9
|
+
- [**1. Query Types**](./1.query_types/1.index.md) - Detailed reference for each query type:
|
|
10
|
+
|
|
11
|
+
- [Table](./1.query_types/2.table.md) - Table-level operations (insert, get, getAll, between)
|
|
12
|
+
- [Selection](./1.query_types/3.selection.md) - Multi-document operations (update, replace, delete)
|
|
13
|
+
- [SingleSelection](./1.query_types/4.single_selection.md) - Single-document operations
|
|
14
|
+
- [Stream](./1.query_types/5.stream.md) - Transformation, filtering, aggregation, joins, and grouping
|
|
15
|
+
- [Change Feeds](./1.query_types/6.feed.md) - Real-time change notifications
|
|
16
|
+
- [Query](./1.query_types/7.query.md) - Base query execution and async iteration
|
|
17
|
+
- [Datum](./1.query_types/8.datum.md) - Single-value manipulation
|
|
18
|
+
- [ValueProxy](./1.query_types/9.valueproxy.md) - In-query value operations (arithmetic, string, date, array, object)
|
|
19
|
+
|
|
20
|
+
- [**2. Operations**](./2.operations/1.index.md) - Practical guides:
|
|
21
|
+
|
|
22
|
+
- [Schema Management](./2.operations/2.database_management.md) - Create and manage schema instances
|
|
23
|
+
- [Table Operations](./2.operations/3.table_operations.md) - Access and work with tables
|
|
24
|
+
- [CRUD Operations](./2.operations/4.crud.md) - Create, read, update, and delete documents
|
|
25
|
+
- [Index Management](./2.operations/5.indexes.md) - Define and use secondary indexes
|
|
26
|
+
- [Filtering and Querying](./2.operations/6.filtering.md) - Filter, compare, and transform data
|
|
27
|
+
- [Lookup](./2.operations/7.lookup.md) - Foreign key joins and data population
|
|
28
|
+
|
|
29
|
+
- [**3. Results**](./3.results/1.index.md) - Result types and handling:
|
|
30
|
+
- [Insert Results](./3.results/2.write_results.md) - Results from insert operations
|
|
31
|
+
- [Index Definitions](./3.results/3.index_changes.md) - Index configuration in schema definitions
|
|
32
|
+
- [Table Definitions](./3.results/4.table_changes.md) - Table structure and field types
|
|
33
|
+
- [Schema and Instance Management](./3.results/5.database_changes.md) - Schema lifecycle and instance management
|
|
34
|
+
- [Change Events](./3.results/6.value_changes.md) - Change feed event structure
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- Database-agnostic query interface
|
|
39
|
+
- Consistent API across different database implementations
|
|
40
|
+
- Type-safe operations using TypeScript generics
|
|
41
|
+
- Schema-based database structure definition
|
|
42
|
+
- Multi-instance support per schema
|
|
43
|
+
- Secondary index support
|
|
44
|
+
- Change feeds for real-time updates
|
|
45
|
+
- Async iteration with cursor support
|
|
46
|
+
- Chainable query builder with lazy evaluation
|
|
47
|
+
|
|
48
|
+
## Database Interoperability
|
|
49
|
+
|
|
50
|
+
AQL decouples your application logic from the database engine. The same query code works with any backend that implements the interface, such as MongoDB, RethinkDB, or other community-provided implementations. This reduces migration cost and keeps your code portable.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/interface-database",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "AntelopeJS interface for database-agnostic query operations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelopejs",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"main": "dist/index.js",
|
|
17
17
|
"types": "dist/index.d.ts",
|
|
18
18
|
"files": [
|
|
19
|
-
"dist"
|
|
19
|
+
"dist",
|
|
20
|
+
"docs",
|
|
21
|
+
"skills"
|
|
20
22
|
],
|
|
21
23
|
"exports": {
|
|
22
24
|
".": {
|
|
@@ -110,6 +112,9 @@
|
|
|
110
112
|
}
|
|
111
113
|
},
|
|
112
114
|
"antelopeJs": {
|
|
113
|
-
"test": "src/antelope.test.ts"
|
|
115
|
+
"test": "src/antelope.test.ts",
|
|
116
|
+
"skills": [
|
|
117
|
+
"./skills"
|
|
118
|
+
]
|
|
114
119
|
}
|
|
115
120
|
}
|