@feltdb/core 0.7.4 → 0.8.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 (106) hide show
  1. package/README.md +25 -0
  2. package/dist/application-development.d.ts +3 -1
  3. package/dist/application-development.d.ts.map +1 -1
  4. package/dist/application-development.js +16 -0
  5. package/dist/application-manifest.d.ts +1 -0
  6. package/dist/application-manifest.d.ts.map +1 -1
  7. package/dist/canonical-application.d.ts +1 -0
  8. package/dist/canonical-application.d.ts.map +1 -1
  9. package/dist/canonical-application.js +3 -2
  10. package/dist/cli/cli.js +1 -1
  11. package/dist/cli/commands.js +225 -42
  12. package/dist/cli/index.js +1 -1
  13. package/dist/cli/managed-environment.js +6 -0
  14. package/dist/cli/managed-project.js +6 -0
  15. package/dist/cli/proposal-client.js +12 -3
  16. package/dist/cli/publish-engine.js +7 -4
  17. package/dist/cli/source-sync.js +47 -3
  18. package/dist/create/cli.js +10 -1
  19. package/dist/create/create.js +10 -12
  20. package/dist/create/managed-account.js +71 -7
  21. package/dist/create/package-versions.js +1 -1
  22. package/dist/create/server-source/crates/feltdb/src/application.rs +10 -0
  23. package/dist/create/server-source/crates/feltdb/src/lib.rs +20 -2
  24. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +12 -24
  25. package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +11 -3
  26. package/dist/create/server-source/crates/feltdb-server/src/authenticated_principal.rs +0 -1
  27. package/dist/create/server-source/crates/feltdb-server/src/certification_harness.rs +23 -22
  28. package/dist/create/server-source/crates/feltdb-server/src/delegation_token.rs +5 -15
  29. package/dist/create/server-source/crates/feltdb-server/src/durable_operations.rs +13 -28
  30. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +79 -14
  31. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +28 -7
  32. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +5 -5
  33. package/dist/create/server-source/crates/feltdb-server/src/main.rs +1777 -259
  34. package/dist/create/server-source/crates/feltdb-server/src/managed_diagnostics.rs +10 -30
  35. package/dist/create/server-source/crates/feltdb-server/src/membership_policy.rs +12 -4
  36. package/dist/create/server-source/crates/feltdb-server/src/request_telemetry.rs +8 -6
  37. package/dist/create/server-source/crates/feltdb-server/src/snapshot_cursor.rs +10 -14
  38. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +183 -21
  39. package/dist/create/server-source/crates/feltdb-server/src/tenant_policies.rs +9 -21
  40. package/dist/create/server-source/crates/feltdb-server/src/transaction_idempotency.rs +5 -3
  41. package/dist/create/server-source/crates/feltdb-server/src/transaction_recovery.rs +9 -8
  42. package/dist/create/server-source/crates/feltdb-server/tests/revision_recovery_integration_test.rs +1 -4
  43. package/dist/create/template/default-project/feltdb.flow +5 -0
  44. package/dist/flowspec.d.ts +1 -0
  45. package/dist/flowspec.d.ts.map +1 -1
  46. package/dist/flowspec.js +46 -12
  47. package/dist/http-client.d.ts +135 -14
  48. package/dist/http-client.d.ts.map +1 -1
  49. package/dist/http-client.js +79 -31
  50. package/dist/index-core.d.ts +2 -0
  51. package/dist/index-core.d.ts.map +1 -1
  52. package/dist/index-core.js +2 -0
  53. package/dist/module-intent.d.ts +37 -0
  54. package/dist/module-intent.d.ts.map +1 -0
  55. package/dist/module-intent.js +81 -0
  56. package/dist/module.d.ts +76 -0
  57. package/dist/module.d.ts.map +1 -0
  58. package/dist/module.js +61 -0
  59. package/dist/proposal.d.ts +80 -4
  60. package/dist/proposal.d.ts.map +1 -1
  61. package/dist/proposal.js +132 -5
  62. package/dist/studio/app.d.ts +3 -1
  63. package/dist/studio/app.d.ts.map +1 -1
  64. package/dist/studio/components/AskFeltDB.d.ts +2 -1
  65. package/dist/studio/components/AskFeltDB.d.ts.map +1 -1
  66. package/dist/studio/components/ContractInspector.d.ts +7 -0
  67. package/dist/studio/components/ContractInspector.d.ts.map +1 -0
  68. package/dist/studio/components/ManagedInstancePanel.d.ts.map +1 -1
  69. package/dist/studio/components/ManagedInstancePanel.js +1 -5
  70. package/dist/studio/components/ModuleExplorer.d.ts +6 -0
  71. package/dist/studio/components/ModuleExplorer.d.ts.map +1 -0
  72. package/dist/studio/components/ProposalBrowser.d.ts +10 -1
  73. package/dist/studio/components/ProposalBrowser.d.ts.map +1 -1
  74. package/dist/studio/components/StateExplorer.d.ts +6 -7
  75. package/dist/studio/components/StateExplorer.d.ts.map +1 -1
  76. package/dist/studio/components/index.d.ts +2 -0
  77. package/dist/studio/components/index.d.ts.map +1 -1
  78. package/dist/studio/components/index.js +4 -4
  79. package/dist/studio/components-CNSNrmA9.js +2787 -0
  80. package/dist/studio/index.js +193 -147
  81. package/dist/studio/studio.css +1 -1
  82. package/dist/studio/utils/index.d.ts +2 -0
  83. package/dist/studio/utils/index.d.ts.map +1 -1
  84. package/dist/studio/utils/index.js +14 -12
  85. package/dist/studio/utils/managed-instance.d.ts +0 -4
  86. package/dist/studio/utils/managed-instance.d.ts.map +1 -1
  87. package/dist/studio/utils/managed-instance.js +0 -4
  88. package/dist/studio/utils/proposal-api.d.ts +53 -0
  89. package/dist/studio/utils/proposal-api.d.ts.map +1 -0
  90. package/dist/studio/utils/proposal-api.js +91 -0
  91. package/dist/studio/utils/service-api.d.ts +81 -0
  92. package/dist/studio/utils/service-api.d.ts.map +1 -0
  93. package/dist/studio/utils/service-api.js +51 -0
  94. package/dist/studio-app/assets/{dist-DA1MYSzp.js → dist-CCOk39Uc.js} +1 -1
  95. package/dist/studio-app/assets/{feltdb_wasm-B-Ccgccb.js → feltdb_wasm-CSB6KVgw.js} +1 -1
  96. package/dist/studio-app/assets/feltdb_wasm_bg-Dd1fnO9U.wasm +0 -0
  97. package/dist/studio-app/assets/index-Bncji3aN.js +43 -0
  98. package/dist/studio-app/assets/{index-BRrmvaKw.css → index-yazaWMUN.css} +1 -1
  99. package/dist/studio-app/index.html +2 -2
  100. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  101. package/dist/workspace/local-development-authority.d.ts.map +1 -1
  102. package/dist/workspace/local-development-authority.js +64 -3
  103. package/package.json +1 -1
  104. package/dist/studio/components-Bm4sjhcX.js +0 -2162
  105. package/dist/studio-app/assets/feltdb_wasm_bg-wH3enJIA.wasm +0 -0
  106. package/dist/studio-app/assets/index-CEIqowCh.js +0 -42
@@ -114,10 +114,7 @@ impl DurableOperationStore {
114
114
  .map_err(|_| "store lock poisoned".to_string())?;
115
115
 
116
116
  if ops.contains_key(&operation_id) {
117
- return Err(format!(
118
- "operation already exists: {}",
119
- operation_id
120
- ));
117
+ return Err(format!("operation already exists: {}", operation_id));
121
118
  }
122
119
 
123
120
  let now = now();
@@ -158,7 +155,8 @@ impl DurableOperationStore {
158
155
  .ok_or(format!("operation not found: {}", operation_id))?;
159
156
 
160
157
  // Can only claim Pending or Unknown operations
161
- if operation.state != OperationState::Pending && operation.state != OperationState::Unknown {
158
+ if operation.state != OperationState::Pending && operation.state != OperationState::Unknown
159
+ {
162
160
  return Err(format!(
163
161
  "cannot claim operation in state {:?}",
164
162
  operation.state
@@ -339,7 +337,8 @@ impl DurableOperationStore {
339
337
  }
340
338
 
341
339
  // Must be in Claimed or Running state
342
- if operation.state != OperationState::Claimed && operation.state != OperationState::Running {
340
+ if operation.state != OperationState::Claimed && operation.state != OperationState::Running
341
+ {
343
342
  return Err(format!(
344
343
  "cannot fail operation in state {:?}",
345
344
  operation.state
@@ -356,10 +355,7 @@ impl DurableOperationStore {
356
355
  }
357
356
 
358
357
  /// Marks an operation as Unknown (when lease expires).
359
- pub fn mark_unknown(
360
- &self,
361
- operation_id: &str,
362
- ) -> Result<DurableOperation, String> {
358
+ pub fn mark_unknown(&self, operation_id: &str) -> Result<DurableOperation, String> {
363
359
  let mut ops = self
364
360
  .operations
365
361
  .write()
@@ -372,7 +368,8 @@ impl DurableOperationStore {
372
368
  let now = now();
373
369
 
374
370
  // Can only mark Unknown if in Claimed or Running state
375
- if operation.state != OperationState::Claimed && operation.state != OperationState::Running {
371
+ if operation.state != OperationState::Claimed && operation.state != OperationState::Running
372
+ {
376
373
  return Err(format!(
377
374
  "cannot mark Unknown operation in state {:?}",
378
375
  operation.state
@@ -484,9 +481,7 @@ impl DurableOperationStore {
484
481
  .map_err(|_| "store lock poisoned".to_string())?;
485
482
 
486
483
  let before_len = ops.len();
487
- ops.retain(|_, op| {
488
- !(op.state == OperationState::Completed && op.updated_at < cutoff_time)
489
- });
484
+ ops.retain(|_, op| !(op.state == OperationState::Completed && op.updated_at < cutoff_time));
490
485
  let after_len = ops.len();
491
486
 
492
487
  Ok(before_len - after_len)
@@ -755,9 +750,7 @@ mod tests {
755
750
  .claim_operation("op_123", "worker_1".into(), 60)
756
751
  .expect("claim failed");
757
752
 
758
- let unknown = store
759
- .mark_unknown("op_123")
760
- .expect("mark unknown failed");
753
+ let unknown = store.mark_unknown("op_123").expect("mark unknown failed");
761
754
  assert_eq!(unknown.state, OperationState::Unknown);
762
755
 
763
756
  let reconciled = store
@@ -826,9 +819,7 @@ mod tests {
826
819
  )
827
820
  .expect("create failed");
828
821
 
829
- let ops = store
830
- .list_operations("tenant_prod")
831
- .expect("list failed");
822
+ let ops = store.list_operations("tenant_prod").expect("list failed");
832
823
  assert_eq!(ops.len(), 2);
833
824
  }
834
825
 
@@ -915,9 +906,7 @@ mod tests {
915
906
  .expect("cleanup failed");
916
907
  assert_eq!(deleted, 1);
917
908
 
918
- let ops = store
919
- .list_operations("tenant_prod")
920
- .expect("list failed");
909
+ let ops = store.list_operations("tenant_prod").expect("list failed");
921
910
  assert_eq!(ops.len(), 1);
922
911
  assert_eq!(ops[0].operation_id, "op_pending");
923
912
  }
@@ -978,11 +967,7 @@ mod tests {
978
967
  .expect("start failed");
979
968
 
980
969
  store
981
- .complete_operation(
982
- "op_123",
983
- "worker_1",
984
- serde_json::json!({"result": "done"}),
985
- )
970
+ .complete_operation("op_123", "worker_1", serde_json::json!({"result": "done"}))
986
971
  .expect("complete failed");
987
972
 
988
973
  let final_op = store.get_operation("op_123").expect("get failed");
@@ -137,35 +137,93 @@ impl IdentityStore {
137
137
  .identities
138
138
  .clone())
139
139
  }
140
- pub fn sign_up(&self, email: &str, password: &str, display_name: Option<String>) -> Result<(Identity, IdentitySession, String), String> {
140
+ pub fn sign_up(
141
+ &self,
142
+ email: &str,
143
+ password: &str,
144
+ display_name: Option<String>,
145
+ ) -> Result<(Identity, IdentitySession, String), String> {
141
146
  let normalized = email.trim().to_lowercase();
142
- if normalized.is_empty() || password.len() < 8 { return Err("email and a password of at least 8 characters are required".into()); }
143
- if self.state.read().map_err(|_| "identity lock poisoned")?.identities.iter().any(|value| value.email.as_deref() == Some(&normalized)) {
147
+ if normalized.is_empty() || password.len() < 8 {
148
+ return Err("email and a password of at least 8 characters are required".into());
149
+ }
150
+ if self
151
+ .state
152
+ .read()
153
+ .map_err(|_| "identity lock poisoned")?
154
+ .identities
155
+ .iter()
156
+ .any(|value| value.email.as_deref() == Some(&normalized))
157
+ {
144
158
  return Err("identity already exists".into());
145
159
  }
146
- let mut identity = self.normalize("password", &normalized, Some(normalized.clone()), display_name, serde_json::json!({}))?;
160
+ let mut identity = self.normalize(
161
+ "password",
162
+ &normalized,
163
+ Some(normalized.clone()),
164
+ display_name,
165
+ serde_json::json!({}),
166
+ )?;
147
167
  identity.status = IdentityStatus::Active;
148
168
  let mut state = self.state.write().map_err(|_| "identity lock poisoned")?;
149
- if let Some(stored) = state.identities.iter_mut().find(|value| value.identity_id == identity.identity_id) { stored.status = IdentityStatus::Active; }
150
- state.password_hashes.insert(identity.subject_id.clone(), Self::hash_password(password)?);
169
+ if let Some(stored) = state
170
+ .identities
171
+ .iter_mut()
172
+ .find(|value| value.identity_id == identity.identity_id)
173
+ {
174
+ stored.status = IdentityStatus::Active;
175
+ }
176
+ state
177
+ .password_hashes
178
+ .insert(identity.subject_id.clone(), Self::hash_password(password)?);
151
179
  self.persist(&state)?;
152
180
  drop(state);
153
181
  let (session, token) = self.issue_session(&identity, "password", None, 15 * 60)?;
154
182
  Ok((identity, session, token))
155
183
  }
156
- pub fn sign_in(&self, email: &str, password: &str) -> Result<(Identity, IdentitySession, String), String> {
184
+ pub fn sign_in(
185
+ &self,
186
+ email: &str,
187
+ password: &str,
188
+ ) -> Result<(Identity, IdentitySession, String), String> {
157
189
  let normalized = email.trim().to_lowercase();
158
190
  let state = self.state.read().map_err(|_| "identity lock poisoned")?;
159
- let identity = state.identities.iter().find(|value| value.email.as_deref() == Some(&normalized) && value.status == IdentityStatus::Active).cloned().ok_or("invalid credentials")?;
160
- let valid = state.password_hashes.get(&identity.subject_id).is_some_and(|hash| Self::verify_password(hash, password));
191
+ let identity = state
192
+ .identities
193
+ .iter()
194
+ .find(|value| {
195
+ value.email.as_deref() == Some(&normalized)
196
+ && value.status == IdentityStatus::Active
197
+ })
198
+ .cloned()
199
+ .ok_or("invalid credentials")?;
200
+ let valid = state
201
+ .password_hashes
202
+ .get(&identity.subject_id)
203
+ .is_some_and(|hash| Self::verify_password(hash, password));
161
204
  drop(state);
162
- if !valid { return Err("invalid credentials".into()); }
205
+ if !valid {
206
+ return Err("invalid credentials".into());
207
+ }
163
208
  let (session, token) = self.issue_session(&identity, "password", None, 15 * 60)?;
164
209
  Ok((identity, session, token))
165
210
  }
166
211
  pub fn authenticate_session(&self, token: &str) -> Option<IdentitySession> {
167
212
  let current = now();
168
- self.state.read().ok()?.sessions.iter().find(|value| value.refresh_hash.as_deref().is_some_and(|hash| Self::verify_password(hash, token)) && value.status == "active" && value.expires_at > current).cloned()
213
+ self.state
214
+ .read()
215
+ .ok()?
216
+ .sessions
217
+ .iter()
218
+ .find(|value| {
219
+ value
220
+ .refresh_hash
221
+ .as_deref()
222
+ .is_some_and(|hash| Self::verify_password(hash, token))
223
+ && value.status == "active"
224
+ && value.expires_at > current
225
+ })
226
+ .cloned()
169
227
  }
170
228
  pub fn sessions(&self, subject: &str) -> Result<Vec<IdentitySession>, String> {
171
229
  Ok(self
@@ -282,11 +340,18 @@ mod tests {
282
340
  fn actor_tokens_are_short_lived_hashed_and_revocable() {
283
341
  let p = std::env::temp_dir().join(format!("identity-session-{}.json", id("test")));
284
342
  let store = IdentityStore::load(&p).unwrap();
285
- let (identity, session, token) = store.sign_up("actor@example.com", "correct horse", Some("Actor".into())).unwrap();
343
+ let (identity, session, token) = store
344
+ .sign_up("actor@example.com", "correct horse", Some("Actor".into()))
345
+ .unwrap();
286
346
  assert!(session.expires_at - session.issued_at <= 15 * 60);
287
347
  assert_ne!(session.refresh_hash.as_deref(), Some(token.as_str()));
288
- assert_eq!(store.authenticate_session(&token).unwrap().subject_id, identity.subject_id);
289
- store.revoke_session(&identity.subject_id, &session.session_id, "test").unwrap();
348
+ assert_eq!(
349
+ store.authenticate_session(&token).unwrap().subject_id,
350
+ identity.subject_id
351
+ );
352
+ store
353
+ .revoke_session(&identity.subject_id, &session.session_id, "test")
354
+ .unwrap();
290
355
  assert!(store.authenticate_session(&token).is_none());
291
356
  let _ = std::fs::remove_file(p);
292
357
  }
@@ -1,12 +1,15 @@
1
1
  use axum::{
2
- extract::{Path, State},
2
+ extract::{Extension, Path, State},
3
3
  http::StatusCode,
4
4
  Json,
5
5
  };
6
6
  use serde::{Deserialize, Serialize};
7
7
  use std::time::{SystemTime, UNIX_EPOCH};
8
8
 
9
- use crate::{auth::ApiKeySummary, app_state::AppState};
9
+ use crate::{
10
+ app_state::AppState,
11
+ auth::{ApiKeySummary, Principal},
12
+ };
10
13
 
11
14
  #[derive(Debug, Serialize)]
12
15
  pub struct KeyListResponse {
@@ -18,6 +21,7 @@ pub struct CreateKeyRequest {
18
21
  pub id: Option<String>,
19
22
  pub namespace: String,
20
23
  pub scopes: Vec<String>,
24
+ pub application_id: Option<String>,
21
25
  }
22
26
 
23
27
  #[derive(Debug, Serialize)]
@@ -38,6 +42,7 @@ pub async fn list_keys(
38
42
 
39
43
  pub async fn create_key(
40
44
  State(state): State<AppState>,
45
+ Extension(principal): Extension<Principal>,
41
46
  Json(req): Json<CreateKeyRequest>,
42
47
  ) -> Result<(StatusCode, Json<CreateKeyResponse>), (StatusCode, String)> {
43
48
  let timestamp = SystemTime::now()
@@ -46,15 +51,31 @@ pub async fn create_key(
46
51
  .as_secs();
47
52
 
48
53
  let name = req.id.unwrap_or_else(|| format!("key-{}", timestamp));
54
+ let scopes = req.scopes;
55
+ let mut authentication_namespaces = vec![req.namespace];
56
+ if !authentication_namespaces
57
+ .iter()
58
+ .any(|value| value == state.namespace.as_ref())
59
+ {
60
+ authentication_namespaces.push(state.namespace.to_string());
61
+ }
49
62
  let key = state
50
63
  .keys
51
- .create(
52
- name,
53
- req.scopes,
54
- vec![req.namespace],
55
- )
64
+ .create(name, scopes.clone(), authentication_namespaces)
56
65
  .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e))?;
57
66
 
67
+ if let Some(application_id) = req.application_id {
68
+ if let Err(error) = state.tenancy.register_application_service(
69
+ &principal.key_id,
70
+ &key.id,
71
+ &application_id,
72
+ scopes,
73
+ ) {
74
+ let _ = state.keys.revoke(&key.id);
75
+ return Err((StatusCode::FORBIDDEN, error));
76
+ }
77
+ }
78
+
58
79
  Ok((
59
80
  StatusCode::CREATED,
60
81
  Json(CreateKeyResponse {
@@ -6,17 +6,17 @@ pub mod auth;
6
6
  pub mod authenticated_principal;
7
7
  pub mod authorization;
8
8
  pub mod backup;
9
- pub mod certification_harness;
10
- pub mod delegation_token;
11
- pub mod durable_operations;
12
- pub mod key_management;
13
9
  pub mod causal;
14
10
  pub mod certification;
11
+ pub mod certification_harness;
15
12
  pub mod clock;
16
13
  pub mod cluster;
17
14
  pub mod connections;
18
15
  pub mod content;
16
+ pub mod delegation_token;
17
+ pub mod durable_operations;
19
18
  pub mod identity;
19
+ pub mod key_management;
20
20
  pub mod key_provider;
21
21
  pub mod leases;
22
22
  pub mod managed_diagnostics;
@@ -29,8 +29,8 @@ pub mod releases;
29
29
  pub mod request_telemetry;
30
30
  pub mod sessions;
31
31
  pub mod snapshot_cursor;
32
- pub mod tenant_policies;
33
32
  pub mod tenancy;
33
+ pub mod tenant_policies;
34
34
  pub mod transaction_idempotency;
35
35
  pub mod transaction_recovery;
36
36
  pub mod versions;