@feltdb/core 0.13.0 → 0.15.0
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/agent-runtime.d.ts +64 -2
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +121 -11
- package/dist/agent-state.d.ts +48 -0
- package/dist/agent-state.d.ts.map +1 -1
- package/dist/agent-state.js +174 -12
- package/dist/agent.d.ts +10 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +10 -1
- package/dist/build-identity.json +4 -4
- package/dist/cli/commands.js +27 -6
- package/dist/collection.d.ts +3 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +30 -8
- package/dist/create/build-identity.json +4 -4
- package/dist/create/cli.js +1 -1
- package/dist/create/create.js +4 -6
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +6 -6
- package/dist/create/server-source/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb/src/lib.rs +126 -35
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +162 -53
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +137 -1
- package/dist/create/server-source/crates/feltdb/src/workload.rs +1427 -91
- package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb-server/README.md +6 -1
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +9 -1
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +85 -25
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +4 -0
- package/dist/create/server-source/crates/feltdb-server/src/console_accounts.rs +1009 -0
- package/dist/create/server-source/crates/feltdb-server/src/control_authorization.rs +441 -0
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +68 -6
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +355 -23
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +3 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +879 -84
- package/dist/create/server-source/crates/feltdb-server/src/redaction.rs +208 -0
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +198 -19
- package/dist/create/server-source/crates/feltdb-server/tests/transaction_precondition_contract.rs +403 -0
- package/dist/db.d.ts +64 -9
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +153 -27
- package/dist/deployment.d.ts +19 -0
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +38 -2
- package/dist/embedded-query.d.ts.map +1 -1
- package/dist/embedded-query.js +5 -4
- package/dist/embedded-transaction.d.ts +8 -4
- package/dist/embedded-transaction.d.ts.map +1 -1
- package/dist/embedded-transaction.js +55 -19
- package/dist/error-codes.d.ts +31 -2
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +41 -2
- package/dist/feltdb.d.ts +7 -0
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.d.ts +27 -0
- package/dist/file-db.d.ts.map +1 -1
- package/dist/file-db.js +145 -7
- package/dist/http-client.d.ts +11 -3
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +27 -14
- package/dist/http-db.d.ts +27 -1
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +252 -54
- package/dist/index.js +1 -1
- package/dist/local-state-lock.d.ts +34 -0
- package/dist/local-state-lock.d.ts.map +1 -0
- package/dist/local-state-lock.js +225 -0
- package/dist/reactive-graph.d.ts +0 -3
- package/dist/reactive-graph.d.ts.map +1 -1
- package/dist/reactive-graph.js +11 -1
- package/dist/runtime-registry.d.ts +7 -2
- package/dist/runtime-registry.d.ts.map +1 -1
- package/dist/runtime-registry.js +2 -2
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-BSGmHfif.js → components-CJmzntBi.js} +2 -1
- package/dist/studio/index.js +1 -1
- package/dist/studio-app/assets/{feltdb_wasm-3hDngl1h.js → feltdb_wasm-BjM1ylL3.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
- package/dist/studio-app/assets/index-1wd0uKua.js +30 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/transaction.d.ts +27 -11
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +20 -14
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/wasm/package.json +1 -1
- package/dist/workload.d.ts +31 -3
- package/dist/workload.d.ts.map +1 -1
- package/dist/workload.js +28 -3
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-CFQT4gCy.wasm +0 -0
- package/dist/studio-app/assets/index-Dql1hS5m.js +0 -30
|
@@ -491,11 +491,51 @@ impl WorkerMeshStore {
|
|
|
491
491
|
/// no longer own. Recovery itself — `WorkerLost`, the attempt budget, the
|
|
492
492
|
/// decision — is `WorkloadStore::recover_expired`. Worker liveness is not
|
|
493
493
|
/// judged here; that is `detect_failures`.
|
|
494
|
+
/// Time out work past a deadline ([`WorkloadStore::enforce_deadlines`]) and
|
|
495
|
+
/// release the claims the committed workloads no longer hold, as
|
|
496
|
+
/// [`Self::recover_expired_work`] does for expired leases.
|
|
497
|
+
pub fn enforce_deadlines(
|
|
498
|
+
&mut self,
|
|
499
|
+
now: i64,
|
|
500
|
+
workloads: &mut WorkloadStore,
|
|
501
|
+
) -> Result<Vec<String>, WorkerMeshError> {
|
|
502
|
+
// The claims this node's projection knows, listed before the store is
|
|
503
|
+
// re-read: a claim another writer already ended must still be released
|
|
504
|
+
// here, so the list cannot come from the refreshed projection.
|
|
505
|
+
let claimed: Vec<(String, String, String)> = workloads
|
|
506
|
+
.workloads
|
|
507
|
+
.values()
|
|
508
|
+
.filter_map(|w| {
|
|
509
|
+
w.current_claim.as_ref().map(|claim| {
|
|
510
|
+
(
|
|
511
|
+
w.workload_id.clone(),
|
|
512
|
+
claim.worker_id.clone(),
|
|
513
|
+
claim.claim_id.clone(),
|
|
514
|
+
)
|
|
515
|
+
})
|
|
516
|
+
})
|
|
517
|
+
.collect();
|
|
518
|
+
let expired = workloads.enforce_deadlines(now)?;
|
|
519
|
+
for (workload_id, worker_id, claim_id) in claimed {
|
|
520
|
+
let still_current = workloads
|
|
521
|
+
.workloads
|
|
522
|
+
.get(&workload_id)
|
|
523
|
+
.and_then(|w| w.current_claim.as_ref())
|
|
524
|
+
.is_some_and(|claim| claim.claim_id == claim_id);
|
|
525
|
+
// Releasing is idempotent: a claim the worker no longer holds is a no-op.
|
|
526
|
+
if !still_current && self.workers.contains_key(&worker_id) {
|
|
527
|
+
self.release_claim(&worker_id, &claim_id)?;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
Ok(expired)
|
|
531
|
+
}
|
|
494
532
|
pub fn recover_expired_work(
|
|
495
533
|
&mut self,
|
|
496
534
|
now: i64,
|
|
497
535
|
workloads: &mut WorkloadStore,
|
|
498
536
|
) -> Result<Vec<String>, WorkerMeshError> {
|
|
537
|
+
// Listed before `recover_expired` re-reads the store, for the same
|
|
538
|
+
// reason as in `enforce_deadlines`.
|
|
499
539
|
let expired: Vec<(String, String, String)> = workloads
|
|
500
540
|
.workloads
|
|
501
541
|
.values()
|
|
@@ -645,7 +685,10 @@ impl WorkerMeshStore {
|
|
|
645
685
|
.workers
|
|
646
686
|
.get_mut(worker_id)
|
|
647
687
|
.ok_or_else(|| WorkerMeshError::new("WORKER_NOT_FOUND", "worker not found"))?;
|
|
648
|
-
worker.
|
|
688
|
+
// Releasing is idempotent: only a claim the worker still holds frees capacity.
|
|
689
|
+
if !worker.claims.remove(claim_id) {
|
|
690
|
+
return Ok(());
|
|
691
|
+
}
|
|
649
692
|
worker.capacity.active = worker.capacity.active.saturating_sub(1);
|
|
650
693
|
if worker.lifecycle == WorkerLifecycle::Draining && worker.claims.is_empty() {
|
|
651
694
|
worker.lifecycle = WorkerLifecycle::Offline
|
|
@@ -894,6 +937,99 @@ mod tests {
|
|
|
894
937
|
let _ = fs::remove_file(w);
|
|
895
938
|
}
|
|
896
939
|
#[test]
|
|
940
|
+
fn releasing_a_claim_twice_frees_capacity_once() {
|
|
941
|
+
let (m, wpath, mut mesh, mut workloads, grant) = setup("release-twice");
|
|
942
|
+
let worker = mesh
|
|
943
|
+
.register(WorkerRegistration {
|
|
944
|
+
tenant_id: "t".into(),
|
|
945
|
+
application_id: "a".into(),
|
|
946
|
+
name: "edge".into(),
|
|
947
|
+
runtime: WorkerRuntimeKind::Customer,
|
|
948
|
+
version: "1".into(),
|
|
949
|
+
capabilities: ["documents.extract".into()].into(),
|
|
950
|
+
workload_kinds: BTreeSet::new(),
|
|
951
|
+
resource_profile: Default::default(),
|
|
952
|
+
labels: BTreeMap::new(),
|
|
953
|
+
capacity: WorkerCapacity {
|
|
954
|
+
concurrency: 2,
|
|
955
|
+
active: 0,
|
|
956
|
+
},
|
|
957
|
+
registered_by: Subject::Service("registrar".into()),
|
|
958
|
+
authorization_grant: grant.clone(),
|
|
959
|
+
registered_at: 10,
|
|
960
|
+
})
|
|
961
|
+
.unwrap();
|
|
962
|
+
mesh.heartbeat(
|
|
963
|
+
&worker.worker_id,
|
|
964
|
+
WorkerHeartbeat {
|
|
965
|
+
worker_id: worker.worker_id.clone(),
|
|
966
|
+
version: "1".into(),
|
|
967
|
+
capability_version: 1,
|
|
968
|
+
capacity: WorkerCapacity {
|
|
969
|
+
concurrency: 2,
|
|
970
|
+
active: 0,
|
|
971
|
+
},
|
|
972
|
+
health: WorkerHealth::Healthy,
|
|
973
|
+
timestamp: 11,
|
|
974
|
+
running_claims: BTreeSet::new(),
|
|
975
|
+
},
|
|
976
|
+
2,
|
|
977
|
+
)
|
|
978
|
+
.unwrap();
|
|
979
|
+
let subject = grant.grant.subject.clone();
|
|
980
|
+
let create = |key: &str| CreateWorkload {
|
|
981
|
+
tenant_id: "t".into(),
|
|
982
|
+
application_id: "a".into(),
|
|
983
|
+
environment: "production".into(),
|
|
984
|
+
definition_id: "extract".into(),
|
|
985
|
+
definition_revision: "r".into(),
|
|
986
|
+
trigger: "manual".into(),
|
|
987
|
+
input: serde_json::json!({"file": key}),
|
|
988
|
+
requested_by: subject.clone(),
|
|
989
|
+
session_id: None,
|
|
990
|
+
authorization_snapshot: grant.clone(),
|
|
991
|
+
capability_snapshot: ["documents.extract".into()].into(),
|
|
992
|
+
connection_scopes: BTreeSet::new(),
|
|
993
|
+
priority: 0,
|
|
994
|
+
idempotency_key: key.into(),
|
|
995
|
+
retry_policy: WorkloadRetryPolicy::default(),
|
|
996
|
+
timeouts: WorkloadTimeouts::default(),
|
|
997
|
+
dependencies: vec![],
|
|
998
|
+
not_before: None,
|
|
999
|
+
created_at: 11,
|
|
1000
|
+
};
|
|
1001
|
+
let mut claims = vec![];
|
|
1002
|
+
for key in ["first", "second"] {
|
|
1003
|
+
let work = workloads.create(create(key)).unwrap();
|
|
1004
|
+
mesh.place_and_claim(
|
|
1005
|
+
&mut workloads,
|
|
1006
|
+
&work.workload_id,
|
|
1007
|
+
&worker.worker_id,
|
|
1008
|
+
12,
|
|
1009
|
+
10_000,
|
|
1010
|
+
)
|
|
1011
|
+
.unwrap();
|
|
1012
|
+
claims.push(
|
|
1013
|
+
workloads
|
|
1014
|
+
.get(&work.workload_id)
|
|
1015
|
+
.unwrap()
|
|
1016
|
+
.current_claim
|
|
1017
|
+
.clone()
|
|
1018
|
+
.unwrap()
|
|
1019
|
+
.claim_id,
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
assert_eq!(mesh.workers[&worker.worker_id].capacity.active, 2);
|
|
1023
|
+
// A completion retried after a lost response releases the same claim again.
|
|
1024
|
+
mesh.release_claim(&worker.worker_id, &claims[0]).unwrap();
|
|
1025
|
+
mesh.release_claim(&worker.worker_id, &claims[0]).unwrap();
|
|
1026
|
+
let state = &mesh.workers[&worker.worker_id];
|
|
1027
|
+
assert_eq!(state.claims.len(), 1);
|
|
1028
|
+
assert_eq!(state.capacity.active, 1, "the second claim is still held");
|
|
1029
|
+
let _ = fs::remove_file(m);
|
|
1030
|
+
let _ = fs::remove_file(wpath);
|
|
1031
|
+
}
|
|
1032
|
+
#[test]
|
|
897
1033
|
fn a_claim_recovered_by_another_writer_is_still_released() {
|
|
898
1034
|
let (m, wpath, mut mesh, mut workloads, grant) = setup("release-raced");
|
|
899
1035
|
let worker = register(&mut mesh, grant.clone());
|