create-feltdb 0.7.0 → 0.7.1
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/dist/package-versions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// One release train keeps generated applications installable. The repository
|
|
2
2
|
// validation script checks these values against every workspace manifest.
|
|
3
|
-
export const FELTDB_PACKAGE_VERSION = '0.7.
|
|
3
|
+
export const FELTDB_PACKAGE_VERSION = '0.7.1';
|
|
4
4
|
export const feltdbPackageRange = `^${FELTDB_PACKAGE_VERSION}`;
|
|
@@ -1175,18 +1175,27 @@ impl FeltDb {
|
|
|
1175
1175
|
}
|
|
1176
1176
|
}
|
|
1177
1177
|
|
|
1178
|
-
// Every precondition held.
|
|
1179
|
-
//
|
|
1180
|
-
// the authority validated version N above and writes N + 1 here,
|
|
1181
|
-
// inside the same lock, so the check and the advance cannot be
|
|
1182
|
-
// separated by another writer.
|
|
1178
|
+
// Every precondition held. The authority owns both ends of the
|
|
1179
|
+
// public document-version lifecycle inside this lock:
|
|
1183
1180
|
//
|
|
1184
|
-
//
|
|
1185
|
-
//
|
|
1186
|
-
//
|
|
1187
|
-
//
|
|
1181
|
+
// - a create protected by `requireAbsent` writes version 1;
|
|
1182
|
+
// - a write fenced by `expected_version = N` writes N + 1.
|
|
1183
|
+
//
|
|
1184
|
+
// An unconditional write remains verbatim, and a guard-only
|
|
1185
|
+
// precondition writes nothing. See
|
|
1188
1186
|
// docs/architecture/transaction-version-contract.md.
|
|
1189
|
-
let
|
|
1187
|
+
let advanced: Vec<AtomicMutation>;
|
|
1188
|
+
let creates: HashSet<(&str, &str)> = preconditions
|
|
1189
|
+
.iter()
|
|
1190
|
+
.filter(|condition| condition.expected_version.is_none())
|
|
1191
|
+
.map(|condition| (condition.capability.as_str(), condition.key.as_str()))
|
|
1192
|
+
.chain(
|
|
1193
|
+
record_preconditions
|
|
1194
|
+
.iter()
|
|
1195
|
+
.filter(|condition| condition.require_absent)
|
|
1196
|
+
.map(|condition| (condition.capability.as_str(), condition.key.as_str())),
|
|
1197
|
+
)
|
|
1198
|
+
.collect();
|
|
1190
1199
|
let fenced: HashMap<(&str, &str), u64> = record_preconditions
|
|
1191
1200
|
.iter()
|
|
1192
1201
|
.filter_map(|condition| {
|
|
@@ -1195,15 +1204,19 @@ impl FeltDb {
|
|
|
1195
1204
|
.map(|version| ((condition.capability.as_str(), condition.key.as_str()), version))
|
|
1196
1205
|
})
|
|
1197
1206
|
.collect();
|
|
1198
|
-
let mutations: &[AtomicMutation] = if fenced.is_empty() {
|
|
1207
|
+
let mutations: &[AtomicMutation] = if fenced.is_empty() && creates.is_empty() {
|
|
1199
1208
|
mutations
|
|
1200
1209
|
} else {
|
|
1201
1210
|
advanced = mutations
|
|
1202
1211
|
.iter()
|
|
1203
1212
|
.map(|mutation| {
|
|
1204
|
-
let
|
|
1205
|
-
|
|
1206
|
-
|
|
1213
|
+
let record = (mutation.capability.as_str(), mutation.key.as_str());
|
|
1214
|
+
let version = if creates.contains(&record) {
|
|
1215
|
+
Some(1)
|
|
1216
|
+
} else {
|
|
1217
|
+
fenced.get(&record).map(|expected| expected + 1)
|
|
1218
|
+
};
|
|
1219
|
+
let Some(version) = version else {
|
|
1207
1220
|
return mutation.clone();
|
|
1208
1221
|
};
|
|
1209
1222
|
// A delete has no record left to carry a version, so a
|
|
@@ -1212,12 +1225,9 @@ impl FeltDb {
|
|
|
1212
1225
|
return mutation.clone();
|
|
1213
1226
|
};
|
|
1214
1227
|
let mut fields = fields;
|
|
1215
|
-
// The
|
|
1216
|
-
//
|
|
1217
|
-
|
|
1218
|
-
// because a caller that computes a different next
|
|
1219
|
-
// version must not be able to install it.
|
|
1220
|
-
fields.insert("__version".to_string(), Value::from(expected + 1));
|
|
1228
|
+
// The caller cannot manufacture either the initial or
|
|
1229
|
+
// next authoritative version.
|
|
1230
|
+
fields.insert("__version".to_string(), Value::from(version));
|
|
1221
1231
|
AtomicMutation {
|
|
1222
1232
|
capability: mutation.capability.clone(),
|
|
1223
1233
|
key: mutation.key.clone(),
|
|
@@ -28,12 +28,9 @@
|
|
|
28
28
|
//! different numbers that happen to share a name, and fencing on the wrong one
|
|
29
29
|
//! would compare a caller's value against something the caller never sees.
|
|
30
30
|
//!
|
|
31
|
-
//!
|
|
32
|
-
//!
|
|
33
|
-
//!
|
|
34
|
-
//! other must advance `__version` in the value it writes. That is asserted
|
|
35
|
-
//! below rather than assumed, because a fence that silently stops fencing is
|
|
36
|
-
//! worse than no fence.
|
|
31
|
+
//! The authority owns conditional transaction versions: `requireAbsent`
|
|
32
|
+
//! creates version 1 and a version-fenced write of N stores N + 1. Both happen
|
|
33
|
+
//! inside the same lock as validation. Unconditional writes remain verbatim.
|
|
37
34
|
|
|
38
35
|
#[cfg(test)]
|
|
39
36
|
mod tests {
|
|
@@ -401,6 +398,7 @@ mod tests {
|
|
|
401
398
|
guarded(&db, "tx-new", &[absent("fresh")], &[set("cells", "fresh", json!({ "id": "fresh" }))])
|
|
402
399
|
.expect("absent record commits");
|
|
403
400
|
assert!(present(&db, "cells", "fresh"));
|
|
401
|
+
assert_eq!(version_of(&db, "cells", "fresh"), 1);
|
|
404
402
|
|
|
405
403
|
let error = guarded(
|
|
406
404
|
&db,
|
|
@@ -807,9 +805,7 @@ mod tests {
|
|
|
807
805
|
}
|
|
808
806
|
|
|
809
807
|
#[test]
|
|
810
|
-
fn
|
|
811
|
-
// Insert remains the client's: Collection.insert supplies __version 1,
|
|
812
|
-
// which is where record versioning begins.
|
|
808
|
+
fn a_transaction_create_gets_authority_version_one_and_can_be_fenced_immediately() {
|
|
813
809
|
let dir = TempDir::new().unwrap();
|
|
814
810
|
let db = db(&dir);
|
|
815
811
|
|
|
@@ -817,10 +813,19 @@ mod tests {
|
|
|
817
813
|
&db,
|
|
818
814
|
"tx-insert",
|
|
819
815
|
&[RecordPrecondition { require_absent: true, ..precondition("cells", "new") }],
|
|
820
|
-
&[set("cells", "new", json!({ "id": "new", "__version":
|
|
816
|
+
&[set("cells", "new", json!({ "id": "new", "__version": 999, "state": "created" }))],
|
|
821
817
|
)
|
|
822
818
|
.expect("commits");
|
|
823
|
-
assert_eq!(version_of(&db, "cells", "new"), 1, "the
|
|
819
|
+
assert_eq!(version_of(&db, "cells", "new"), 1, "the authority owns the initial version");
|
|
820
|
+
|
|
821
|
+
guarded(
|
|
822
|
+
&db,
|
|
823
|
+
"tx-update-new",
|
|
824
|
+
&[expect_version("cells", "new", 1)],
|
|
825
|
+
&[set("cells", "new", json!({ "id": "new", "state": "updated" }))],
|
|
826
|
+
)
|
|
827
|
+
.expect("the newly created version can be fenced immediately");
|
|
828
|
+
assert_eq!(version_of(&db, "cells", "new"), 2);
|
|
824
829
|
|
|
825
830
|
let missing = guarded(
|
|
826
831
|
&db,
|
|
@@ -834,6 +839,33 @@ mod tests {
|
|
|
834
839
|
);
|
|
835
840
|
}
|
|
836
841
|
|
|
842
|
+
#[test]
|
|
843
|
+
fn replayed_conditional_create_does_not_create_or_advance_twice() {
|
|
844
|
+
let dir = TempDir::new().unwrap();
|
|
845
|
+
let db = db(&dir);
|
|
846
|
+
let absent = RecordPrecondition { require_absent: true, ..precondition("cells", "new") };
|
|
847
|
+
|
|
848
|
+
let first = guarded(
|
|
849
|
+
&db,
|
|
850
|
+
"tx-create-once",
|
|
851
|
+
&[absent.clone()],
|
|
852
|
+
&[set("cells", "new", json!({ "id": "new", "delivery": 1 }))],
|
|
853
|
+
)
|
|
854
|
+
.expect("first delivery commits");
|
|
855
|
+
assert!(!first.duplicate);
|
|
856
|
+
|
|
857
|
+
let replay = guarded(
|
|
858
|
+
&db,
|
|
859
|
+
"tx-create-once",
|
|
860
|
+
&[absent],
|
|
861
|
+
&[set("cells", "new", json!({ "id": "new", "delivery": 2 }))],
|
|
862
|
+
)
|
|
863
|
+
.expect("replay returns the original transaction result");
|
|
864
|
+
assert!(replay.duplicate);
|
|
865
|
+
assert_eq!(version_of(&db, "cells", "new"), 1);
|
|
866
|
+
assert_eq!(field(&db, "cells", "new", "delivery"), 1);
|
|
867
|
+
}
|
|
868
|
+
|
|
837
869
|
#[test]
|
|
838
870
|
fn replay_of_a_fenced_transaction_does_not_advance_twice() {
|
|
839
871
|
let dir = TempDir::new().unwrap();
|
|
@@ -41,7 +41,7 @@ use feltdb::{
|
|
|
41
41
|
Grant, GrantSigner, GrantStore, Subject as GrantSubject,
|
|
42
42
|
},
|
|
43
43
|
cardinality_endpoint::{CardinalityContext, CardinalityDiagnosticResponse},
|
|
44
|
-
AtomicMutation,
|
|
44
|
+
AtomicMutation,
|
|
45
45
|
policy_evaluation::{Actor, PolicyContext, PolicyEvaluator, PolicySubject},
|
|
46
46
|
state_contract::{
|
|
47
47
|
begin_read, compare_schemas, execute_query as execute_state_query, execute_transaction,
|
|
@@ -9134,7 +9134,6 @@ async fn commit_transaction(
|
|
|
9134
9134
|
// Validate every operation before any of them is turned into a mutation, so
|
|
9135
9135
|
// a malformed request cannot commit a prefix of the transaction.
|
|
9136
9136
|
let mut mutations = Vec::with_capacity(request.operations.len());
|
|
9137
|
-
let mut preconditions = Vec::new();
|
|
9138
9137
|
let mut record_preconditions: Vec<RecordPrecondition> = Vec::new();
|
|
9139
9138
|
let mut seen = std::collections::HashSet::new();
|
|
9140
9139
|
|
|
@@ -9173,14 +9172,17 @@ async fn commit_transaction(
|
|
|
9173
9172
|
));
|
|
9174
9173
|
}
|
|
9175
9174
|
if operation.require_absent {
|
|
9176
|
-
//
|
|
9177
|
-
//
|
|
9178
|
-
//
|
|
9179
|
-
// the
|
|
9180
|
-
|
|
9175
|
+
// Use the public record-precondition path so a lost creation race
|
|
9176
|
+
// is both evaluated under the store lock and returned as a
|
|
9177
|
+
// structured presence conflict. The same condition tells the
|
|
9178
|
+
// authority to assign document version 1 to the created value.
|
|
9179
|
+
record_preconditions.push(RecordPrecondition {
|
|
9181
9180
|
capability: operation.collection.clone(),
|
|
9182
9181
|
key: key.clone(),
|
|
9182
|
+
require_absent: true,
|
|
9183
9183
|
expected_version: None,
|
|
9184
|
+
expected_epoch: None,
|
|
9185
|
+
expected_lease_id: None,
|
|
9184
9186
|
});
|
|
9185
9187
|
}
|
|
9186
9188
|
// A guard attached to a staged write. Previously declared by the
|
|
@@ -9216,7 +9218,7 @@ async fn commit_transaction(
|
|
|
9216
9218
|
&request.transaction_id,
|
|
9217
9219
|
None,
|
|
9218
9220
|
None,
|
|
9219
|
-
&
|
|
9221
|
+
&[],
|
|
9220
9222
|
&record_preconditions,
|
|
9221
9223
|
&mutations,
|
|
9222
9224
|
None,
|