@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.
@@ -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
+ ```
@@ -0,0 +1,102 @@
1
+ # Datum
2
+
3
+ The `Datum<T>` class represents a single value in the query system. It extends `Query<T>` and provides methods for accessing fields, applying transformations, and performing foreign key lookups on individual values.
4
+
5
+ ## Class Definition
6
+
7
+ ```typescript
8
+ class Datum<T> extends Query<T> {
9
+ cast<U>(): Datum<U>;
10
+ key<K extends keyof NonNullable<T>>(key: K, def?: unknown): Datum<NonNullable<T>[K]>;
11
+ default<U>(val: Value<U>): Datum<Exclude<T, undefined | null> | U>;
12
+ do<U>(mapper: (val: ValueProxy<T>) => U): Datum<ExtractType<U>>;
13
+ lookup<U>(other: Selection<U>, localKey: keyof T, otherKey: keyof U): Datum<...>;
14
+ pluck(...fields: string[]): Datum<Partial<T>>;
15
+ }
16
+ ```
17
+
18
+ ## Field Access
19
+
20
+ The `key` method retrieves a field from a document datum. You can chain `key` calls for nested field access. An optional default value can be provided for the case when the field is undefined.
21
+
22
+ ```typescript
23
+ import { Schema } from "@antelopejs/interface-database";
24
+
25
+ const schema = Schema.get("myapp")!;
26
+ const users = schema.instance().table("users");
27
+
28
+ // Access a field
29
+ const name = await users.get("user-123").key("name");
30
+
31
+ // Access a nested field
32
+ const city = await users.get("user-123").key("address").key("city");
33
+
34
+ // Provide a default value
35
+ const phone = await users.get("user-123").key("phone", "N/A");
36
+ ```
37
+
38
+ ## Default Values
39
+
40
+ The `default` method provides a fallback value when the datum is null or undefined.
41
+
42
+ ```typescript
43
+ const user = await users.get("maybe-missing").default({
44
+ _id: "guest",
45
+ name: "Guest User",
46
+ role: "visitor",
47
+ });
48
+ ```
49
+
50
+ ## Transform with `do()`
51
+
52
+ The `do` method applies a transformation function to the datum. The callback receives a `ValueProxy<T>` that provides operators for building expressions.
53
+
54
+ ```typescript
55
+ const formatted = await users.get("user-123").do((user) => ({
56
+ displayName: user.key("name"),
57
+ contact: user.key("email"),
58
+ hasPhone: user.key("phone").ne(undefined),
59
+ }));
60
+ ```
61
+
62
+ ## Foreign Key Lookup
63
+
64
+ The `lookup` method replaces a foreign key field with the referenced document from another table.
65
+
66
+ ```typescript
67
+ interface Post {
68
+ _id: string;
69
+ title: string;
70
+ authorId: string;
71
+ }
72
+
73
+ interface User {
74
+ _id: string;
75
+ name: string;
76
+ }
77
+
78
+ const post = await schema
79
+ .instance()
80
+ .table<Post>("posts")
81
+ .get("post-1")
82
+ .lookup(schema.instance().table<User>("users"), "authorId", "_id");
83
+
84
+ // post.authorId is now the full User object
85
+ console.log(post.authorId.name);
86
+ ```
87
+
88
+ ## Field Selection
89
+
90
+ The `pluck` method selects specific fields from a document datum, discarding the rest.
91
+
92
+ ```typescript
93
+ const profile = await users.get("user-123").pluck("name", "email", "avatar");
94
+ ```
95
+
96
+ ## Type Casting
97
+
98
+ The `cast` method changes the TypeScript type of the datum without performing any runtime conversion. Use this when you know the actual type differs from what the compiler infers.
99
+
100
+ ```typescript
101
+ const raw = users.get("user-123").cast<{ name: string; role: string }>();
102
+ ```
@@ -0,0 +1,230 @@
1
+ # ValueProxy
2
+
3
+ The `ValueProxy<T>` class acts as a proxy to a database value. Operations on a ValueProxy are not executed immediately but are recorded as query stages. ValueProxy objects appear inside callback functions for `map`, `filter`, `do`, `update`, `join`, and other query methods.
4
+
5
+ ## Class Definition
6
+
7
+ ValueProxy provides type-specific methods based on the underlying value type. The available operations are organized by category.
8
+
9
+ ## Create a ValueProxy
10
+
11
+ ValueProxy instances are created automatically when you use callback-based query methods. You can also create one from a constant value:
12
+
13
+ ```typescript
14
+ import { ValueProxy } from "@antelopejs/interface-database";
15
+
16
+ const constant = ValueProxy.constant(42);
17
+ const strConst = ValueProxy.constant("hello");
18
+ ```
19
+
20
+ ## General Operations
21
+
22
+ These methods are available on all ValueProxy types.
23
+
24
+ ### `default(value)`
25
+
26
+ Return the given value if the proxy is null.
27
+
28
+ ```typescript
29
+ const phone = user.key("phone").default("N/A");
30
+ ```
31
+
32
+ ### `eq(value)` / `ne(value)`
33
+
34
+ Equality and inequality comparison.
35
+
36
+ ```typescript
37
+ const isActive = user.key("status").eq("active");
38
+ const isNotAdmin = user.key("role").ne("admin");
39
+ ```
40
+
41
+ ### `and(value)` / `or(value)` / `not()`
42
+
43
+ Logical operators for combining boolean expressions.
44
+
45
+ ```typescript
46
+ const canAccess = user.key("active").and(user.key("verified"));
47
+ const isSpecial = user.key("role").eq("admin").or(user.key("plan").eq("premium"));
48
+ const isInactive = user.key("active").not();
49
+ ```
50
+
51
+ ### `cast<U>()`
52
+
53
+ Change the TypeScript type without runtime conversion.
54
+
55
+ ## Number Operations
56
+
57
+ Available when the proxy wraps a numeric value.
58
+
59
+ ### Arithmetic
60
+
61
+ ```typescript
62
+ const next = age.add(1);
63
+ const diff = total.sub(discount);
64
+ const doubled = price.mul(2);
65
+ const half = amount.div(2);
66
+ const remainder = count.mod(10);
67
+ ```
68
+
69
+ ### Rounding
70
+
71
+ ```typescript
72
+ const rounded = score.round();
73
+ const ceiling = value.ceil();
74
+ const floored = value.floor();
75
+ ```
76
+
77
+ ### Bitwise
78
+
79
+ ```typescript
80
+ const bitwiseAnd = flags.band(0xff);
81
+ const bitwiseOr = flags.bor(0x01);
82
+ const bitwiseXor = flags.bxor(mask);
83
+ const bitwiseNot = flags.bnot();
84
+ const shifted = flags.blshift(2);
85
+ const unshifted = flags.brshift(2);
86
+ const signPreserved = flags.brshiftPreserveSign(2);
87
+ ```
88
+
89
+ ## Comparison Operations
90
+
91
+ Available for `number`, `string`, and `Date` proxy values.
92
+
93
+ ```typescript
94
+ const isAdult = age.gt(17);
95
+ const isMinor = age.le(17);
96
+ const isRecent = date.ge(new Date("2024-01-01"));
97
+ const isBefore = date.lt(deadline);
98
+ ```
99
+
100
+ | Method | Description |
101
+ | -------- | ---------------------- |
102
+ | `gt(v)` | Greater than |
103
+ | `ge(v)` | Greater than or equal |
104
+ | `lt(v)` | Less than |
105
+ | `le(v)` | Less than or equal |
106
+
107
+ ## Date Operations
108
+
109
+ Available when the proxy wraps a `Date` value. All date component methods accept an optional `timezone` parameter.
110
+
111
+ ### Arithmetic
112
+
113
+ ```typescript
114
+ const threeDaysLater = created.add(3 * 24 * 60 * 60);
115
+ const elapsed = now.sub(created); // difference in seconds
116
+ ```
117
+
118
+ ### Components
119
+
120
+ ```typescript
121
+ const y = created.year();
122
+ const m = created.month();
123
+ const d = created.day();
124
+ const dow = created.dayofweek();
125
+ const doy = created.dayofyear();
126
+ const h = created.hours();
127
+ const min = created.minutes();
128
+ const sec = created.seconds();
129
+ const tod = created.timeofday(); // seconds since midnight
130
+ const epoch = created.epoch(); // seconds since UNIX epoch
131
+ ```
132
+
133
+ ### Range Check
134
+
135
+ ```typescript
136
+ const inRange = created.during(
137
+ ValueProxy.constant(new Date("2024-01-01")),
138
+ ValueProxy.constant(new Date("2025-01-01")),
139
+ );
140
+ ```
141
+
142
+ ## String Operations
143
+
144
+ Available when the proxy wraps a `string` value.
145
+
146
+ ```typescript
147
+ const upper = name.upcase();
148
+ const lower = name.downcase();
149
+ const parts = email.split("@");
150
+ const full = first.concat(last);
151
+ const len = name.strlen();
152
+ const matches = email.match("^.*@gmail\\.com$");
153
+ ```
154
+
155
+ | Method | Description |
156
+ | ------------------- | ------------------------------ |
157
+ | `upcase()` | Convert to uppercase |
158
+ | `downcase()` | Convert to lowercase |
159
+ | `split(sep, max?)` | Split by separator |
160
+ | `concat(other)` | Concatenate with another string|
161
+ | `strlen()` | Number of Unicode codepoints |
162
+ | `match(regex)` | Test against a regex pattern |
163
+
164
+ ## Array Operations
165
+
166
+ Available when the proxy wraps an array value.
167
+
168
+ ```typescript
169
+ const first = tags.index(0);
170
+ const hasAdmin = tags.includes("admin");
171
+ const sub = tags.slice(0, 3);
172
+ const upper = tags.map((t) => t.upcase());
173
+ const long = tags.filter((t) => t.strlen().gt(5));
174
+ const empty = tags.isempty();
175
+ const len = tags.count();
176
+ const total = scores.sum();
177
+ const average = scores.avg();
178
+ const lowest = scores.min();
179
+ const highest = scores.max();
180
+ ```
181
+
182
+ | Method | Description |
183
+ | ---------------- | ---------------------------------- |
184
+ | `index(n)` | Element at position `n` |
185
+ | `includes(val)` | Check if the array contains a value|
186
+ | `slice(s, e?)` | Extract a sub-array |
187
+ | `map(fn)` | Transform each element |
188
+ | `filter(fn)` | Keep elements matching a predicate |
189
+ | `isempty()` | Check if the array is empty |
190
+ | `count()` | Number of elements |
191
+ | `sum()` | Sum of numeric elements |
192
+ | `avg()` | Average of numeric elements |
193
+ | `min()` | Minimum of numeric elements |
194
+ | `max()` | Maximum of numeric elements |
195
+
196
+ ## Object Operations
197
+
198
+ Available when the proxy wraps a record/object value.
199
+
200
+ ```typescript
201
+ const email = user.key("email");
202
+ const emailOrDefault = user.key("email", "none@example.com");
203
+ const merged = user.merge({ lastSeen: new Date() });
204
+ const fieldNames = user.keys();
205
+ const fieldValues = user.values();
206
+ const hasRequired = user.hasfields("email", "name");
207
+ ```
208
+
209
+ | Method | Description |
210
+ | ------------------ | ------------------------------------ |
211
+ | `key(k, def?)` | Access a field, with optional default|
212
+ | `merge(other)` | Merge with another object |
213
+ | `keys()` | Array of field names |
214
+ | `values()` | Array of field values |
215
+ | `hasfields(...fs)` | Check if all specified fields exist |
216
+
217
+ ## Compose Expressions
218
+
219
+ ValueProxy methods are chainable, enabling complex expressions within query callbacks:
220
+
221
+ ```typescript
222
+ const result = await users.filter((user) =>
223
+ user.key("age").ge(18)
224
+ .and(user.key("status").eq("active"))
225
+ .and(
226
+ user.key("role").eq("admin")
227
+ .or(user.key("plan").eq("premium")),
228
+ ),
229
+ );
230
+ ```
@@ -0,0 +1,34 @@
1
+ # Operations
2
+
3
+ This section covers practical operations you can perform with the AQL interface. These guides walk through schema management, table access, document CRUD, index usage, filtering, and foreign key lookups.
4
+
5
+ ## Schema and Instance Management
6
+
7
+ AQL organizes data through schemas and instances:
8
+
9
+ - [2. Schema Management](./2.database_management.md) - Define schemas, create and destroy instances
10
+
11
+ ## Table Access
12
+
13
+ Tables live inside schema instances:
14
+
15
+ - [3. Table Operations](./3.table_operations.md) - Access tables and work with their contents
16
+
17
+ ## Document Operations
18
+
19
+ Documents are the fundamental unit of data in AQL:
20
+
21
+ - [4. CRUD Operations](./4.crud.md) - Insert, read, update, replace, and delete documents
22
+
23
+ ## Index Operations
24
+
25
+ Indexes accelerate queries by enabling efficient lookups:
26
+
27
+ - [5. Index Management](./5.indexes.md) - Define secondary indexes in schema definitions
28
+
29
+ ## Query Operations
30
+
31
+ Transform and filter data with the query builder:
32
+
33
+ - [6. Filtering and Querying](./6.filtering.md) - Filter, compare, and transform documents
34
+ - [7. Lookup](./7.lookup.md) - Foreign key joins and data population
@@ -0,0 +1,151 @@
1
+ # Schema Management
2
+
3
+ AQL organizes data through schemas and instances. A `Schema` defines the structure of your database (tables, fields, and indexes). Each schema can have multiple instances, and the underlying module implementation determines how instances map to physical storage.
4
+
5
+ ## Define a Schema
6
+
7
+ Create a schema by providing an ID and a definition of tables with their fields and indexes:
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" },
15
+ indexes: {
16
+ email: {},
17
+ role: {},
18
+ },
19
+ },
20
+ posts: {
21
+ fields: { title: "string", content: "string", authorId: "string" },
22
+ indexes: {
23
+ authorId: {},
24
+ },
25
+ },
26
+ });
27
+ ```
28
+
29
+ ### `SchemaDefinition`
30
+
31
+ A schema definition is a record mapping table names to their `TableDefinition`:
32
+
33
+ ```typescript
34
+ interface TableDefinition {
35
+ fields: Record<string, FieldType>;
36
+ indexes: Record<string, IndexDefinition>;
37
+ }
38
+
39
+ interface IndexDefinition {
40
+ fields?: string[]; // For compound indexes
41
+ multi?: boolean; // Multi-valued index
42
+ }
43
+ ```
44
+
45
+ ## Create an Instance
46
+
47
+ After defining a schema, create an instance to start working with data. The optional `id` parameter distinguishes multiple instances of the same schema.
48
+
49
+ ```typescript
50
+ // Create the default instance
51
+ await schema.createInstance();
52
+
53
+ // Create a named instance
54
+ await schema.createInstance("tenant-abc");
55
+ ```
56
+
57
+ ## Access an Instance
58
+
59
+ Retrieve a `SchemaInstance` to access tables:
60
+
61
+ ```typescript
62
+ // Default instance
63
+ const db = schema.instance();
64
+
65
+ // Named instance
66
+ const tenantDb = schema.instance("tenant-abc");
67
+ ```
68
+
69
+ To operate across every instance of the schema, pass the `CROSS_INSTANCE` sentinel:
70
+
71
+ ```typescript
72
+ import { CROSS_INSTANCE } from "@antelopejs/interface-database";
73
+
74
+ const everything = schema.instance(CROSS_INSTANCE).table("users");
75
+ ```
76
+
77
+ ## Destroy an Instance
78
+
79
+ Remove an instance and its data:
80
+
81
+ ```typescript
82
+ await schema.destroyInstance("tenant-abc");
83
+ ```
84
+
85
+ ## List Instances
86
+
87
+ List the IDs of named instances of a schema:
88
+
89
+ ```typescript
90
+ const ids = await schema.listInstances();
91
+ // ["tenant-abc", "tenant-xyz"]
92
+ ```
93
+
94
+ The default (unnamed) instance is not included.
95
+
96
+ ## Retrieve a Schema
97
+
98
+ Use the static `Schema.get()` method to look up a previously defined schema by its ID:
99
+
100
+ ```typescript
101
+ const schema = Schema.get("myapp");
102
+
103
+ if (schema) {
104
+ const users = schema.instance().table("users");
105
+ }
106
+ ```
107
+
108
+ ## Work with Tables
109
+
110
+ Once you have a `SchemaInstance`, access tables by name. The generic type parameter provides type safety for your documents:
111
+
112
+ ```typescript
113
+ interface User {
114
+ _id: string;
115
+ name: string;
116
+ email: string;
117
+ role: string;
118
+ }
119
+
120
+ interface Post {
121
+ _id: string;
122
+ title: string;
123
+ content: string;
124
+ authorId: string;
125
+ }
126
+
127
+ const db = schema.instance();
128
+ const users = db.table<User>("users");
129
+ const posts = db.table<Post>("posts");
130
+ ```
131
+
132
+ ## Multi-Tenant Pattern
133
+
134
+ Schemas with multiple instances support multi-tenant architectures. Each tenant gets an isolated instance of the same schema:
135
+
136
+ ```typescript
137
+ const schema = new Schema("saas", {
138
+ projects: {
139
+ fields: { name: "string" },
140
+ indexes: {},
141
+ },
142
+ });
143
+
144
+ // Create one instance per tenant
145
+ await schema.createInstance("tenant-1");
146
+ await schema.createInstance("tenant-2");
147
+
148
+ // Access tenant-specific data
149
+ const tenant1Projects = schema.instance("tenant-1").table("projects");
150
+ const tenant2Projects = schema.instance("tenant-2").table("projects");
151
+ ```