@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.
Files changed (92) hide show
  1. package/dist/agent-runtime.d.ts +64 -2
  2. package/dist/agent-runtime.d.ts.map +1 -1
  3. package/dist/agent-runtime.js +121 -11
  4. package/dist/agent-state.d.ts +48 -0
  5. package/dist/agent-state.d.ts.map +1 -1
  6. package/dist/agent-state.js +174 -12
  7. package/dist/agent.d.ts +10 -1
  8. package/dist/agent.d.ts.map +1 -1
  9. package/dist/agent.js +10 -1
  10. package/dist/build-identity.json +4 -4
  11. package/dist/cli/commands.js +27 -6
  12. package/dist/collection.d.ts +3 -1
  13. package/dist/collection.d.ts.map +1 -1
  14. package/dist/collection.js +30 -8
  15. package/dist/create/build-identity.json +4 -4
  16. package/dist/create/cli.js +1 -1
  17. package/dist/create/create.js +4 -6
  18. package/dist/create/package-versions.js +1 -1
  19. package/dist/create/server-source/Cargo.lock +6 -6
  20. package/dist/create/server-source/Cargo.toml +1 -1
  21. package/dist/create/server-source/crates/feltdb/src/lib.rs +126 -35
  22. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +162 -53
  23. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +137 -1
  24. package/dist/create/server-source/crates/feltdb/src/workload.rs +1427 -91
  25. package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
  26. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
  27. package/dist/create/server-source/crates/feltdb-server/README.md +6 -1
  28. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +9 -1
  29. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +85 -25
  30. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +4 -0
  31. package/dist/create/server-source/crates/feltdb-server/src/console_accounts.rs +1009 -0
  32. package/dist/create/server-source/crates/feltdb-server/src/control_authorization.rs +441 -0
  33. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +68 -6
  34. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +355 -23
  35. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +3 -0
  36. package/dist/create/server-source/crates/feltdb-server/src/main.rs +879 -84
  37. package/dist/create/server-source/crates/feltdb-server/src/redaction.rs +208 -0
  38. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +198 -19
  39. package/dist/create/server-source/crates/feltdb-server/tests/transaction_precondition_contract.rs +403 -0
  40. package/dist/db.d.ts +64 -9
  41. package/dist/db.d.ts.map +1 -1
  42. package/dist/db.js +153 -27
  43. package/dist/deployment.d.ts +19 -0
  44. package/dist/deployment.d.ts.map +1 -1
  45. package/dist/deployment.js +38 -2
  46. package/dist/embedded-query.d.ts.map +1 -1
  47. package/dist/embedded-query.js +5 -4
  48. package/dist/embedded-transaction.d.ts +8 -4
  49. package/dist/embedded-transaction.d.ts.map +1 -1
  50. package/dist/embedded-transaction.js +55 -19
  51. package/dist/error-codes.d.ts +31 -2
  52. package/dist/error-codes.d.ts.map +1 -1
  53. package/dist/error-codes.js +41 -2
  54. package/dist/feltdb.d.ts +7 -0
  55. package/dist/feltdb.d.ts.map +1 -1
  56. package/dist/file-db.d.ts +27 -0
  57. package/dist/file-db.d.ts.map +1 -1
  58. package/dist/file-db.js +145 -7
  59. package/dist/http-client.d.ts +11 -3
  60. package/dist/http-client.d.ts.map +1 -1
  61. package/dist/http-client.js +27 -14
  62. package/dist/http-db.d.ts +27 -1
  63. package/dist/http-db.d.ts.map +1 -1
  64. package/dist/http-db.js +252 -54
  65. package/dist/index.js +1 -1
  66. package/dist/local-state-lock.d.ts +34 -0
  67. package/dist/local-state-lock.d.ts.map +1 -0
  68. package/dist/local-state-lock.js +225 -0
  69. package/dist/reactive-graph.d.ts +0 -3
  70. package/dist/reactive-graph.d.ts.map +1 -1
  71. package/dist/reactive-graph.js +11 -1
  72. package/dist/runtime-registry.d.ts +7 -2
  73. package/dist/runtime-registry.d.ts.map +1 -1
  74. package/dist/runtime-registry.js +2 -2
  75. package/dist/studio/components/index.js +1 -1
  76. package/dist/studio/{components-BSGmHfif.js → components-CJmzntBi.js} +2 -1
  77. package/dist/studio/index.js +1 -1
  78. package/dist/studio-app/assets/{feltdb_wasm-3hDngl1h.js → feltdb_wasm-BjM1ylL3.js} +1 -1
  79. package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
  80. package/dist/studio-app/assets/index-1wd0uKua.js +30 -0
  81. package/dist/studio-app/index.html +1 -1
  82. package/dist/transaction.d.ts +27 -11
  83. package/dist/transaction.d.ts.map +1 -1
  84. package/dist/transaction.js +20 -14
  85. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  86. package/dist/wasm/package.json +1 -1
  87. package/dist/workload.d.ts +31 -3
  88. package/dist/workload.d.ts.map +1 -1
  89. package/dist/workload.js +28 -3
  90. package/package.json +1 -1
  91. package/dist/studio-app/assets/feltdb_wasm_bg-CFQT4gCy.wasm +0 -0
  92. 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.claims.remove(claim_id);
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());