@antelopejs/interface-database 0.1.4 → 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 (71) 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/2.table.md +10 -4
  55. package/docs/1.query_types/3.selection.md +15 -17
  56. package/docs/1.query_types/5.stream.md +2 -5
  57. package/docs/1.query_types/6.feed.md +1 -1
  58. package/docs/1.query_types/9.valueproxy.md +43 -41
  59. package/docs/2.operations/2.database_management.md +2 -2
  60. package/docs/2.operations/3.table_operations.md +1 -1
  61. package/docs/2.operations/4.crud.md +1 -2
  62. package/docs/2.operations/5.indexes.md +4 -4
  63. package/docs/2.operations/6.filtering.md +23 -27
  64. package/docs/2.operations/7.lookup.md +13 -15
  65. package/docs/2.operations/8.atomic_mutations.md +71 -0
  66. package/docs/3.results/3.index_changes.md +9 -5
  67. package/docs/3.results/4.table_changes.md +3 -3
  68. package/docs/3.results/6.value_changes.md +5 -5
  69. package/docs/index.md +1 -0
  70. package/package.json +55 -40
  71. package/skills/database-interface/SKILL.md +41 -7
@@ -44,7 +44,10 @@ Logical operators for combining boolean expressions.
44
44
 
45
45
  ```typescript
46
46
  const canAccess = user.key("active").and(user.key("verified"));
47
- const isSpecial = user.key("role").eq("admin").or(user.key("plan").eq("premium"));
47
+ const isSpecial = user
48
+ .key("role")
49
+ .eq("admin")
50
+ .or(user.key("plan").eq("premium"));
48
51
  const isInactive = user.key("active").not();
49
52
  ```
50
53
 
@@ -97,12 +100,12 @@ const isRecent = date.ge(new Date("2024-01-01"));
97
100
  const isBefore = date.lt(deadline);
98
101
  ```
99
102
 
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 |
103
+ | Method | Description |
104
+ | ------- | --------------------- |
105
+ | `gt(v)` | Greater than |
106
+ | `ge(v)` | Greater than or equal |
107
+ | `lt(v)` | Less than |
108
+ | `le(v)` | Less than or equal |
106
109
 
107
110
  ## Date Operations
108
111
 
@@ -127,7 +130,7 @@ const h = created.hours();
127
130
  const min = created.minutes();
128
131
  const sec = created.seconds();
129
132
  const tod = created.timeofday(); // seconds since midnight
130
- const epoch = created.epoch(); // seconds since UNIX epoch
133
+ const epoch = created.epoch(); // seconds since UNIX epoch
131
134
  ```
132
135
 
133
136
  ### Range Check
@@ -152,14 +155,14 @@ const len = name.strlen();
152
155
  const matches = email.match("^.*@gmail\\.com$");
153
156
  ```
154
157
 
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 |
158
+ | Method | Description |
159
+ | ------------------ | ------------------------------- |
160
+ | `upcase()` | Convert to uppercase |
161
+ | `downcase()` | Convert to lowercase |
162
+ | `split(sep, max?)` | Split by separator |
163
+ | `concat(other)` | Concatenate with another string |
164
+ | `strlen()` | Number of Unicode codepoints |
165
+ | `match(regex)` | Test against a regex pattern |
163
166
 
164
167
  ## Array Operations
165
168
 
@@ -179,19 +182,19 @@ const lowest = scores.min();
179
182
  const highest = scores.max();
180
183
  ```
181
184
 
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 |
185
+ | Method | Description |
186
+ | --------------- | ----------------------------------- |
187
+ | `index(n)` | Element at position `n` |
188
+ | `includes(val)` | Check if the array contains a value |
189
+ | `slice(s, e?)` | Extract a sub-array |
190
+ | `map(fn)` | Transform each element |
191
+ | `filter(fn)` | Keep elements matching a predicate |
192
+ | `isempty()` | Check if the array is empty |
193
+ | `count()` | Number of elements |
194
+ | `sum()` | Sum of numeric elements |
195
+ | `avg()` | Average of numeric elements |
196
+ | `min()` | Minimum of numeric elements |
197
+ | `max()` | Maximum of numeric elements |
195
198
 
196
199
  ## Object Operations
197
200
 
@@ -206,13 +209,13 @@ const fieldValues = user.values();
206
209
  const hasRequired = user.hasfields("email", "name");
207
210
  ```
208
211
 
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 |
212
+ | Method | Description |
213
+ | ------------------ | ------------------------------------- |
214
+ | `key(k, def?)` | Access a field, with optional default |
215
+ | `merge(other)` | Merge with another object |
216
+ | `keys()` | Array of field names |
217
+ | `values()` | Array of field values |
218
+ | `hasfields(...fs)` | Check if all specified fields exist |
216
219
 
217
220
  ## Compose Expressions
218
221
 
@@ -220,11 +223,10 @@ ValueProxy methods are chainable, enabling complex expressions within query call
220
223
 
221
224
  ```typescript
222
225
  const result = await users.filter((user) =>
223
- user.key("age").ge(18)
226
+ user
227
+ .key("age")
228
+ .ge(18)
224
229
  .and(user.key("status").eq("active"))
225
- .and(
226
- user.key("role").eq("admin")
227
- .or(user.key("plan").eq("premium")),
228
- ),
230
+ .and(user.key("role").eq("admin").or(user.key("plan").eq("premium"))),
229
231
  );
230
232
  ```
@@ -37,8 +37,8 @@ interface TableDefinition {
37
37
  }
38
38
 
39
39
  interface IndexDefinition {
40
- fields?: string[]; // For compound indexes
41
- multi?: boolean; // Multi-valued index
40
+ fields?: string[]; // For compound indexes
41
+ multi?: boolean; // Multi-valued index
42
42
  }
43
43
  ```
44
44
 
@@ -30,7 +30,7 @@ const users = db.table<User>("users");
30
30
 
31
31
  // TypeScript now validates field names and types
32
32
  const user = await users.get("user-123"); // user is User | null
33
- const emails = await users.key("email"); // string[]
33
+ const emails = await users.key("email"); // string[]
34
34
  ```
35
35
 
36
36
  ## Basic Operations
@@ -89,8 +89,7 @@ const recent = await users.between(
89
89
  ```typescript
90
90
  // Filter with a predicate function
91
91
  const active = await users.filter((user) =>
92
- user.key("status").eq("active")
93
- .and(user.key("age").ge(18)),
92
+ user.key("status").eq("active").and(user.key("age").ge(18)),
94
93
  );
95
94
  ```
96
95
 
@@ -13,8 +13,8 @@ const schema = new Schema("myapp", {
13
13
  users: {
14
14
  fields: { name: "string", email: "string", role: "string", age: "number" },
15
15
  indexes: {
16
- email: {}, // Simple index on the "email" field
17
- role: {}, // Simple index on the "role" field
16
+ email: {}, // Simple index on the "email" field
17
+ role: {}, // Simple index on the "role" field
18
18
  name_age: { fields: ["name", "age"] }, // Compound index
19
19
  },
20
20
  },
@@ -25,8 +25,8 @@ const schema = new Schema("myapp", {
25
25
 
26
26
  ```typescript
27
27
  interface IndexDefinition {
28
- fields?: string[]; // Fields for compound indexes (omit for single-field)
29
- multi?: boolean; // Whether this is a multi-valued index
28
+ fields?: string[]; // Fields for compound indexes (omit for single-field)
29
+ multi?: boolean; // Whether this is a multi-valued index
30
30
  }
31
31
  ```
32
32
 
@@ -14,9 +14,7 @@ const users = schema.instance().table("users");
14
14
  Use the `filter` method with a predicate function. The callback receives a `ValueProxy<T>` representing each document.
15
15
 
16
16
  ```typescript
17
- const active = await users.filter((user) =>
18
- user.key("status").eq("active"),
19
- );
17
+ const active = await users.filter((user) => user.key("status").eq("active"));
20
18
  ```
21
19
 
22
20
  ## Comparison Operators
@@ -53,17 +51,14 @@ const important = await users.filter((u) =>
53
51
  );
54
52
 
55
53
  // NOT
56
- const standard = await users.filter((u) =>
57
- u.key("plan").eq("premium").not(),
58
- );
54
+ const standard = await users.filter((u) => u.key("plan").eq("premium").not());
59
55
 
60
56
  // Complex combinations
61
57
  const target = await users.filter((u) =>
62
- u.key("status").eq("active")
63
- .and(
64
- u.key("plan").eq("premium")
65
- .or(u.key("totalPurchases").gt(500)),
66
- ),
58
+ u
59
+ .key("status")
60
+ .eq("active")
61
+ .and(u.key("plan").eq("premium").or(u.key("totalPurchases").gt(500))),
67
62
  );
68
63
  ```
69
64
 
@@ -71,9 +66,7 @@ const target = await users.filter((u) =>
71
66
 
72
67
  ```typescript
73
68
  // Case-insensitive match
74
- const bobs = await users.filter((u) =>
75
- u.key("name").downcase().eq("bob"),
76
- );
69
+ const bobs = await users.filter((u) => u.key("name").downcase().eq("bob"));
77
70
 
78
71
  // Regex matching
79
72
  const gmailUsers = await users.filter((u) =>
@@ -96,7 +89,9 @@ const newUsers = await users.filter((u) =>
96
89
  );
97
90
 
98
91
  const q1Users = await users.filter((u) =>
99
- u.key("createdAt").ge(new Date("2024-01-01"))
92
+ u
93
+ .key("createdAt")
94
+ .ge(new Date("2024-01-01"))
100
95
  .and(u.key("createdAt").lt(new Date("2024-04-01"))),
101
96
  );
102
97
  ```
@@ -104,9 +99,7 @@ const q1Users = await users.filter((u) =>
104
99
  ### Date Components
105
100
 
106
101
  ```typescript
107
- const thisYear = await users.filter((u) =>
108
- u.key("createdAt").year().eq(2024),
109
- );
102
+ const thisYear = await users.filter((u) => u.key("createdAt").year().eq(2024));
110
103
 
111
104
  const morningLogins = await users.filter((u) =>
112
105
  u.key("lastLogin").hours().lt(12),
@@ -117,19 +110,22 @@ const morningLogins = await users.filter((u) =>
117
110
 
118
111
  ```typescript
119
112
  // Check array contents
120
- const jsPosts = await schema.instance().table("posts").filter((post) =>
121
- post.key("tags").includes("javascript"),
122
- );
113
+ const jsPosts = await schema
114
+ .instance()
115
+ .table("posts")
116
+ .filter((post) => post.key("tags").includes("javascript"));
123
117
 
124
118
  // Check array length
125
- const wellTagged = await schema.instance().table("posts").filter((post) =>
126
- post.key("tags").count().gt(3),
127
- );
119
+ const wellTagged = await schema
120
+ .instance()
121
+ .table("posts")
122
+ .filter((post) => post.key("tags").count().gt(3));
128
123
 
129
124
  // Check if empty
130
- const untagged = await schema.instance().table("posts").filter((post) =>
131
- post.key("tags").isempty(),
132
- );
125
+ const untagged = await schema
126
+ .instance()
127
+ .table("posts")
128
+ .filter((post) => post.key("tags").isempty());
133
129
  ```
134
130
 
135
131
  ## Default Values in Filters
@@ -4,11 +4,11 @@ The `lookup` method performs a foreign key join, replacing a field in your docum
4
4
 
5
5
  ## Parameters
6
6
 
7
- | Parameter | Type | Description |
8
- | ---------- | -------------- | ---------------------------------------------------- |
9
- | `other` | `Selection<U>` | The table containing the referenced documents |
7
+ | Parameter | Type | Description |
8
+ | ---------- | -------------- | --------------------------------------------------------- |
9
+ | `other` | `Selection<U>` | The table containing the referenced documents |
10
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 |
11
+ | `otherKey` | `keyof U` | The field in the other table to match against |
12
12
 
13
13
  ## Behavior
14
14
 
@@ -122,16 +122,14 @@ const withLookup = await db
122
122
  // order.customerId is now the full customer object
123
123
 
124
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
- );
125
+ const withJoin = await db.table("orders").joinInner(
126
+ db.table("customers"),
127
+ (order, customer) => order.key("customerId").eq(customer.key("_id")),
128
+ (order, customer) => ({
129
+ orderId: order.key("_id"),
130
+ orderTotal: order.key("total"),
131
+ customerName: customer.key("name"),
132
+ }),
133
+ );
136
134
  // Result is a custom object with selected fields from both tables
137
135
  ```
@@ -0,0 +1,71 @@
1
+ # Atomic single-record mutations
2
+
3
+ `Table.atomicMutation(id, request)` checks one immutable, instance-scoped record identity and one condition inside the database mutation. It returns `Query<AtomicMutationOutcome>`. It does not provide multi-record transactions, a lock service, receipts, or external-effect coordination.
4
+
5
+ Generic `filter(...).update(...)` and `filter(...).delete()` do **not** promise this guarantee. Use a direct `schema.instance(instanceId).table(tableId)` with one literal string identity. The default instance is supported; `CROSS_INSTANCE`, filtered selections, expression keys, cursors, and upserts are not.
6
+
7
+ ## Compare a revision
8
+
9
+ Public types are exported from `@antelopejs/interface-database` and `@antelopejs/interface-database/atomic`. An update checks the current revision and installs the patch and next revision together. A delete checks the revision inside the deletion.
10
+
11
+ ```ts
12
+ const outcome = await table.atomicMutation(recordId, {
13
+ type: "update",
14
+ revisionField: "revision",
15
+ expectedRevision: observed.revision,
16
+ nextRevision: freshRevision,
17
+ patch: { title: "Updated title" },
18
+ });
19
+
20
+ const deletion = await table.atomicMutation(recordId, {
21
+ type: "delete",
22
+ revisionField: "revision",
23
+ expectedRevision: observed.revision,
24
+ });
25
+ ```
26
+
27
+ Revision tokens are nonempty strings. Each update must install a changed token that the caller guarantees has never been used for that identity. All writers of protected state must advance the revision; otherwise a successful comparison does not prove that the observed state is current. Initialize new records with fresh revisions and use a new identity for each incarnation, including recreation after deletion.
28
+
29
+ `expectedRevision: { kind: "missing" }` matches an **absent revision field on an existing record**. It does not match a stored `null`, an empty string, or a missing record. An update can use this expectation to bootstrap legacy data atomically; a delete can use it when the domain permits deleting an unversioned record. A backend that cannot distinguish absence from null must reject this expectation rather than weaken it. In PostgreSQL, declared SQL columns collapse this distinction; an overflow JSON field can preserve it. Review mapper serialization before choosing revision storage.
30
+
31
+ ## Patch semantics
32
+
33
+ The patch replaces each supplied top-level field. Nested objects and arrays replace their entire previous values; they do not merge recursively. Unspecified fields remain unchanged, and `null` stores null rather than removing the field. There is no unset operation.
34
+
35
+ Patches contain only constant strings, booleans, finite numbers, null, valid Dates, dense arrays, and plain objects. Undefined, functions, query proxies, cycles, and operator/path field names are rejected. The patch cannot contain `id`, `_id`, the revision field, or adapter-owned scope fields. Adapters must preserve their normal Date storage representation without interpreting objects that resemble query stages as executable expressions.
36
+
37
+ ## Delete by one observed scalar
38
+
39
+ `deleteIfEqual` provides a narrower condition for domains where one field equality is sufficient, such as deletion of an expired record whose observed timestamp remains unchanged.
40
+
41
+ ```ts
42
+ const outcome = await table.atomicMutation(recordId, {
43
+ type: "deleteIfEqual",
44
+ field: "lastSeenAt",
45
+ expectedValue: observed.lastSeenAt,
46
+ });
47
+ ```
48
+
49
+ `expectedValue` accepts a string, finite number, boolean, or valid Date. Equality is exact and type-sensitive, with Dates compared by their stored instant; scalar equality must not match an array containing that scalar. Missing fields never match. Null, objects, arrays, identity fields, and scope fields are not supported.
50
+
51
+ This variant does not check or advance a revision and does not prevent ABA: a value can change and later return to its observed value. The domain must prove that equality alone authorizes deletion. It is not a substitute for revision checks on state transitions.
52
+
53
+ ## Handle all outcomes
54
+
55
+ | Outcome | Meaning |
56
+ | ------------- | -------------------------------------------------------------------------------------------------------------------- |
57
+ | `applied` | The adapter received definitive acknowledgement that the one mutation applied. |
58
+ | `not-applied` | The adapter received definitive acknowledgement that the target was missing or its scope or condition did not match. |
59
+ | `unknown` | The adapter cannot determine whether the mutation applied, including acknowledgement loss. |
60
+
61
+ Definitive validation and unsupported-operation errors throw. Neither an exception nor `unknown` means mismatch. The interface rejects invalid adapter results, including `undefined`, zero, and generic modified counts, with `AtomicMutationUnsupportedError`; adapters must reject unsupported stages explicitly. There is no fallback to generic filtered writes and no separate capability probe. Use an adapter whose implementation has been verified before relying on this API.
62
+
63
+ The interface and adapter must not automatically retry an uncertain mutation. Do not repeat external effects or infer failure from a later mismatch. Domain-specific recovery can reconcile durable evidence and receipts, but a fresh read alone cannot prove whether an earlier operation or external effect occurred. Any deliberate replay requires a domain proof and does not turn `not-applied` into evidence about the original attempt.
64
+
65
+ ## Adapter contract
66
+
67
+ The canonical terminal stage is `{ stage: "atomicMutation", options: undefined, args: [id, request] }`, immediately after `schema`, `instance`, and `table`. `AtomicMutation<T>` is the union of `AtomicUpdate<T>`, `AtomicDelete<T>`, and `AtomicDeleteIfEqual<T>`.
68
+
69
+ Adapters can reuse `ValidateAtomicMutation(id, request, protectedFields)` and `ValidateAtomicMutationTable(prefixStages)`. They must additionally validate the terminal envelope and schema-specific restrictions. Scope, immutable identity, and the revision or equality condition must participate in the target-local native mutation, not only an earlier selection or identity subquery. Backend tests must prove contention, scope isolation, missing/null distinction, top-level replacement, and acknowledgement-loss behavior.
70
+
71
+ This API remains unpublished during adapter and consumer verification. Package metadata alone does not establish capability; do not infer support from the unchanged development package version.
@@ -6,8 +6,8 @@ In AQL, indexes are defined as part of the schema definition rather than created
6
6
 
7
7
  ```typescript
8
8
  interface IndexDefinition {
9
- fields?: string[]; // Fields for compound indexes
10
- multi?: boolean; // Multi-valued index
9
+ fields?: string[]; // Fields for compound indexes
10
+ multi?: boolean; // Multi-valued index
11
11
  }
12
12
  ```
13
13
 
@@ -22,8 +22,8 @@ const schema = new Schema("myapp", {
22
22
  users: {
23
23
  fields: { name: "string", email: "string", role: "string" },
24
24
  indexes: {
25
- email: {}, // Simple index (field name = index name)
26
- role: {}, // Simple index
25
+ email: {}, // Simple index (field name = index name)
26
+ role: {}, // Simple index
27
27
  name_role: { fields: ["name", "role"] }, // Compound index
28
28
  },
29
29
  },
@@ -72,7 +72,11 @@ const users = db.table("users");
72
72
  const admins = await users.getAll("admin", "role");
73
73
 
74
74
  // Range query on an indexed field
75
- const recent = await users.between("createdAt", new Date("2024-01-01"), new Date());
75
+ const recent = await users.between(
76
+ "createdAt",
77
+ new Date("2024-01-01"),
78
+ new Date(),
79
+ );
76
80
  ```
77
81
 
78
82
  For more details, see [Index Management](../2.operations/5.indexes.md).
@@ -17,10 +17,10 @@ Field types describe the structure of each field:
17
17
 
18
18
  ```typescript
19
19
  type FieldType =
20
- | string // Primitive type name (e.g., "string", "number")
21
- | Array<StringFieldType> // Array of a given type
20
+ | string // Primitive type name (e.g., "string", "number")
21
+ | Array<StringFieldType> // Array of a given type
22
22
  | { [subfield: string]: StringFieldType } // Nested object
23
- | import("io-ts").Mixed; // io-ts codec at the field root
23
+ | import("io-ts").Mixed; // io-ts codec at the field root
24
24
  ```
25
25
 
26
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.
@@ -14,11 +14,11 @@ interface Changes<T> {
14
14
 
15
15
  ## Properties
16
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. |
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
22
 
23
23
  ## Determine the Change Type
24
24
 
package/docs/index.md CHANGED
@@ -25,6 +25,7 @@ The Database interface provides AQL (Antelope Query Language), a database-agnost
25
25
  - [Index Management](./2.operations/5.indexes.md) - Define and use secondary indexes
26
26
  - [Filtering and Querying](./2.operations/6.filtering.md) - Filter, compare, and transform data
27
27
  - [Lookup](./2.operations/7.lookup.md) - Foreign key joins and data population
28
+ - [Atomic Mutations](./2.operations/8.atomic_mutations.md) - Conditional single-record mutations and outcomes
28
29
 
29
30
  - [**3. Results**](./3.results/1.index.md) - Result types and handling:
30
31
  - [Insert Results](./3.results/2.write_results.md) - Results from insert operations
package/package.json CHANGED
@@ -1,30 +1,61 @@
1
1
  {
2
2
  "name": "@antelopejs/interface-database",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "AntelopeJS interface for database-agnostic query operations",
5
5
  "keywords": [
6
6
  "antelopejs",
7
- "interface",
8
- "database"
7
+ "database",
8
+ "interface"
9
9
  ],
10
+ "license": "Apache-2.0",
10
11
  "repository": {
11
12
  "type": "git",
12
13
  "url": "git+https://github.com/AntelopeJS/interface-database.git"
13
14
  },
14
- "license": "Apache-2.0",
15
- "packageManager": "pnpm@10.6.5",
16
- "main": "dist/index.js",
17
- "types": "dist/index.d.ts",
18
15
  "files": [
19
16
  "dist",
20
17
  "docs",
21
18
  "skills"
22
19
  ],
20
+ "main": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "typesVersions": {
23
+ "*": {
24
+ "atomic": [
25
+ "dist/atomic.d.ts"
26
+ ],
27
+ "common": [
28
+ "dist/common.d.ts"
29
+ ],
30
+ "datum": [
31
+ "dist/datum.d.ts"
32
+ ],
33
+ "query": [
34
+ "dist/query.d.ts"
35
+ ],
36
+ "schema": [
37
+ "dist/schema.d.ts"
38
+ ],
39
+ "selection": [
40
+ "dist/selection.d.ts"
41
+ ],
42
+ "stream": [
43
+ "dist/stream.d.ts"
44
+ ],
45
+ "valueproxy": [
46
+ "dist/valueproxy.d.ts"
47
+ ]
48
+ }
49
+ },
23
50
  "exports": {
24
51
  ".": {
25
52
  "types": "./dist/index.d.ts",
26
53
  "default": "./dist/index.js"
27
54
  },
55
+ "./atomic": {
56
+ "types": "./dist/atomic.d.ts",
57
+ "default": "./dist/atomic.js"
58
+ },
28
59
  "./common": {
29
60
  "types": "./dist/common.d.ts",
30
61
  "default": "./dist/common.js"
@@ -55,26 +86,37 @@
55
86
  },
56
87
  "./package.json": "./package.json"
57
88
  },
89
+ "publishConfig": {
90
+ "access": "public",
91
+ "provenance": true
92
+ },
58
93
  "scripts": {
59
94
  "build": "rimraf dist && tsc",
60
- "format": "biome format --write .",
61
- "lint:fix": "biome check --write .",
62
- "lint": "biome check .",
95
+ "format": "oxfmt .",
96
+ "lint:fix": "oxlint --fix",
97
+ "lint": "oxlint",
63
98
  "prepack": "pnpm run build",
64
99
  "release": "pnpm run lint && pnpm run prepack && release-it",
65
- "test": "pnpm run build && ajs module test ."
100
+ "test": "pnpm run build && ajs module test .",
101
+ "format:check": "oxfmt --check .",
102
+ "knip": "knip"
66
103
  },
67
104
  "devDependencies": {
68
105
  "@antelopejs/interface-core": ">=0.0.3 <1.0.0",
69
- "@biomejs/biome": "2.3.2",
106
+ "@antelopejs/tooling-configs": "^0.0.4",
70
107
  "@types/chai": "^4.3.20",
71
108
  "@types/mocha": "^10.0.10",
72
109
  "@types/node": "^22.19.15",
73
110
  "@types/semver": "^7.7.1",
74
111
  "chai": "^4.5.0",
112
+ "eslint-plugin-perfectionist": "^5.11.0",
75
113
  "fp-ts": "^2.16.0",
76
114
  "io-ts": "^2.2.0",
115
+ "knip": "^6.34.0",
77
116
  "mongodb-memory-server-core": "^11.0.1",
117
+ "oxfmt": "^0.66.0",
118
+ "oxlint": "1.81.0",
119
+ "oxlint-tsgolint": "^7.0.2001",
78
120
  "release-it": "^19.2.4",
79
121
  "release-it-changelogen": "^0.1.0",
80
122
  "rimraf": "^6.1.3",
@@ -83,34 +125,7 @@
83
125
  "peerDependencies": {
84
126
  "@antelopejs/interface-core": ">=0.0.3 <1.0.0"
85
127
  },
86
- "publishConfig": {
87
- "access": "public"
88
- },
89
- "typesVersions": {
90
- "*": {
91
- "common": [
92
- "dist/common.d.ts"
93
- ],
94
- "datum": [
95
- "dist/datum.d.ts"
96
- ],
97
- "query": [
98
- "dist/query.d.ts"
99
- ],
100
- "schema": [
101
- "dist/schema.d.ts"
102
- ],
103
- "selection": [
104
- "dist/selection.d.ts"
105
- ],
106
- "stream": [
107
- "dist/stream.d.ts"
108
- ],
109
- "valueproxy": [
110
- "dist/valueproxy.d.ts"
111
- ]
112
- }
113
- },
128
+ "packageManager": "pnpm@10.6.5",
114
129
  "antelopeJs": {
115
130
  "test": "src/antelope.test.ts",
116
131
  "skills": [