@feltdb/core 0.4.17 → 0.5.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/README.md +169 -0
- package/dist/cli/index.js +1 -1
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +12 -0
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -0
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +2 -0
- package/dist/create/server-source/crates/feltdb-server/src/request_telemetry.rs +381 -0
- package/dist/create/server-source/crates/feltdb-server/src/transaction_idempotency.rs +280 -0
- package/dist/error-codes.d.ts +53 -0
- package/dist/error-codes.d.ts.map +1 -0
- package/dist/error-codes.js +46 -0
- package/dist/feltdb.d.ts +4 -0
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.d.ts +3 -0
- package/dist/file-db.d.ts.map +1 -1
- package/dist/file-db.js +130 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/operation-admission.d.ts +141 -0
- package/dist/operation-admission.d.ts.map +1 -0
- package/dist/operation-admission.js +135 -0
- package/dist/revision-recovery.d.ts +162 -0
- package/dist/revision-recovery.d.ts.map +1 -0
- package/dist/revision-recovery.js +69 -0
- package/dist/state-contract.d.ts +2 -0
- package/dist/state-contract.d.ts.map +1 -1
- package/dist/state-contract.js +17 -7
- package/dist/studio-app/assets/{index-5siPkRSN.js → index-3cvTQ0Mv.js} +10 -10
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
use std::sync::{Arc, Mutex};
|
|
3
|
+
use serde::{Deserialize, Serialize};
|
|
4
|
+
use sha2::{Digest, Sha256};
|
|
5
|
+
|
|
6
|
+
/// Durable transaction idempotency: prevents re-execution of already-committed transactions.
|
|
7
|
+
/// Every committed transaction is stored with its result. If the client retries with the
|
|
8
|
+
/// same transaction_id + payload hash, the server returns the original result without re-executing.
|
|
9
|
+
|
|
10
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
11
|
+
pub struct CommittedTransaction {
|
|
12
|
+
pub transaction_id: String,
|
|
13
|
+
pub transaction_hash: String,
|
|
14
|
+
pub status: TransactionStatus,
|
|
15
|
+
pub committed_result: serde_json::Value,
|
|
16
|
+
pub commit_metadata: CommitMetadata,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
20
|
+
pub enum TransactionStatus {
|
|
21
|
+
#[serde(rename = "COMMITTED")]
|
|
22
|
+
Committed,
|
|
23
|
+
#[serde(rename = "CONFLICT")]
|
|
24
|
+
Conflict,
|
|
25
|
+
#[serde(rename = "VALIDATION_FAILED")]
|
|
26
|
+
ValidationFailed,
|
|
27
|
+
#[serde(rename = "INTERNAL_ERROR")]
|
|
28
|
+
InternalError,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
32
|
+
pub struct CommitMetadata {
|
|
33
|
+
pub timestamp_ms: u64,
|
|
34
|
+
pub revision_id: String,
|
|
35
|
+
pub application_id: String,
|
|
36
|
+
pub state_before: u64,
|
|
37
|
+
pub state_after: u64,
|
|
38
|
+
pub retry_count: usize,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub struct TransactionIdempotencyStore {
|
|
42
|
+
// Map: transaction_id -> list of committed transactions (to detect hash mismatches)
|
|
43
|
+
store: Arc<Mutex<HashMap<String, Vec<CommittedTransaction>>>>,
|
|
44
|
+
max_per_transaction: usize,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
impl TransactionIdempotencyStore {
|
|
48
|
+
pub fn new(max_per_transaction: usize) -> Self {
|
|
49
|
+
Self {
|
|
50
|
+
store: Arc::new(Mutex::new(HashMap::new())),
|
|
51
|
+
max_per_transaction,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Record a committed transaction for idempotency.
|
|
56
|
+
/// If the same transaction_id is seen again with the same hash, return the original result.
|
|
57
|
+
/// If the same transaction_id is seen with a different hash, return CONFLICT.
|
|
58
|
+
pub fn record_committed(
|
|
59
|
+
&self,
|
|
60
|
+
transaction_id: String,
|
|
61
|
+
transaction_hash: String,
|
|
62
|
+
status: TransactionStatus,
|
|
63
|
+
result: serde_json::Value,
|
|
64
|
+
metadata: CommitMetadata,
|
|
65
|
+
) {
|
|
66
|
+
if let Ok(mut store) = self.store.lock() {
|
|
67
|
+
let tx_id_clone = transaction_id.clone();
|
|
68
|
+
let committed = CommittedTransaction {
|
|
69
|
+
transaction_id,
|
|
70
|
+
transaction_hash,
|
|
71
|
+
status,
|
|
72
|
+
committed_result: result,
|
|
73
|
+
commit_metadata: metadata,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
store
|
|
77
|
+
.entry(tx_id_clone.clone())
|
|
78
|
+
.or_insert_with(Vec::new)
|
|
79
|
+
.push(committed);
|
|
80
|
+
|
|
81
|
+
// Keep only the most recent attempts
|
|
82
|
+
if let Some(entries) = store.get_mut(&tx_id_clone) {
|
|
83
|
+
if entries.len() > self.max_per_transaction {
|
|
84
|
+
let to_remove = entries.len() - self.max_per_transaction;
|
|
85
|
+
entries.drain(0..to_remove);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Check if a transaction has been seen before.
|
|
92
|
+
/// Returns:
|
|
93
|
+
/// - Some(Ok(result)) if same transaction_id + same hash (idempotent replay)
|
|
94
|
+
/// - Some(Err("CONFLICT")) if same transaction_id + different hash (reuse with different payload)
|
|
95
|
+
/// - None if this is a new transaction_id
|
|
96
|
+
pub fn check_idempotent(
|
|
97
|
+
&self,
|
|
98
|
+
transaction_id: &str,
|
|
99
|
+
transaction_hash: &str,
|
|
100
|
+
) -> Option<Result<serde_json::Value, String>> {
|
|
101
|
+
self.store
|
|
102
|
+
.lock()
|
|
103
|
+
.ok()
|
|
104
|
+
.and_then(|store| store.get(transaction_id).map(|entries| entries.clone()))
|
|
105
|
+
.and_then(|entries| {
|
|
106
|
+
if entries.is_empty() {
|
|
107
|
+
return None;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let first_entry = &entries[0];
|
|
111
|
+
|
|
112
|
+
// If hashes match, return the original result
|
|
113
|
+
if first_entry.transaction_hash == transaction_hash {
|
|
114
|
+
return Some(Ok(first_entry.committed_result.clone()));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// If hashes differ, it's a conflict (transaction ID reused with different payload)
|
|
118
|
+
Some(Err(format!(
|
|
119
|
+
"Transaction ID {} reused with different payload",
|
|
120
|
+
transaction_id
|
|
121
|
+
)))
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Get all committed transactions for a transaction_id
|
|
126
|
+
pub fn get_history(&self, transaction_id: &str) -> Vec<CommittedTransaction> {
|
|
127
|
+
self.store
|
|
128
|
+
.lock()
|
|
129
|
+
.ok()
|
|
130
|
+
.and_then(|store| store.get(transaction_id).cloned())
|
|
131
|
+
.unwrap_or_default()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Clear all idempotency records (for testing)
|
|
135
|
+
pub fn clear(&self) {
|
|
136
|
+
if let Ok(mut store) = self.store.lock() {
|
|
137
|
+
store.clear();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Get statistics about idempotency store
|
|
142
|
+
pub fn statistics(&self) -> IdempotencyStatistics {
|
|
143
|
+
self.store
|
|
144
|
+
.lock()
|
|
145
|
+
.ok()
|
|
146
|
+
.map(|store| {
|
|
147
|
+
let total_transactions = store.len();
|
|
148
|
+
let total_entries = store.values().map(|v| v.len()).sum();
|
|
149
|
+
let replay_protected = store
|
|
150
|
+
.values()
|
|
151
|
+
.filter(|entries| !entries.is_empty() && entries[0].status == TransactionStatus::Committed)
|
|
152
|
+
.count();
|
|
153
|
+
|
|
154
|
+
IdempotencyStatistics {
|
|
155
|
+
total_transaction_ids: total_transactions,
|
|
156
|
+
total_committed_entries: total_entries,
|
|
157
|
+
replay_protected_count: replay_protected,
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
.unwrap_or_default()
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#[derive(Debug, Clone, Serialize, Default)]
|
|
165
|
+
pub struct IdempotencyStatistics {
|
|
166
|
+
pub total_transaction_ids: usize,
|
|
167
|
+
pub total_committed_entries: usize,
|
|
168
|
+
pub replay_protected_count: usize,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// Compute a hash of a transaction request for idempotency detection.
|
|
172
|
+
pub fn hash_transaction_request(request: &serde_json::Value) -> String {
|
|
173
|
+
let mut hasher = Sha256::new();
|
|
174
|
+
let json_str = serde_json::to_string(request).unwrap_or_default();
|
|
175
|
+
hasher.update(json_str.as_bytes());
|
|
176
|
+
format!("{:x}", hasher.finalize())
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#[cfg(test)]
|
|
180
|
+
mod tests {
|
|
181
|
+
use super::*;
|
|
182
|
+
|
|
183
|
+
#[test]
|
|
184
|
+
fn test_idempotency_store_same_hash() {
|
|
185
|
+
let store = TransactionIdempotencyStore::new(10);
|
|
186
|
+
let tx_id = "tx-123".to_string();
|
|
187
|
+
let tx_hash = "hash-abc".to_string();
|
|
188
|
+
let result = serde_json::json!({ "status": "committed" });
|
|
189
|
+
|
|
190
|
+
store.record_committed(
|
|
191
|
+
tx_id.clone(),
|
|
192
|
+
tx_hash.clone(),
|
|
193
|
+
TransactionStatus::Committed,
|
|
194
|
+
result.clone(),
|
|
195
|
+
CommitMetadata {
|
|
196
|
+
timestamp_ms: 1000,
|
|
197
|
+
revision_id: "rev-1".to_string(),
|
|
198
|
+
application_id: "app-1".to_string(),
|
|
199
|
+
state_before: 1,
|
|
200
|
+
state_after: 2,
|
|
201
|
+
retry_count: 0,
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// First replay should return the same result
|
|
206
|
+
let replay = store.check_idempotent(&tx_id, &tx_hash);
|
|
207
|
+
assert!(replay.is_some());
|
|
208
|
+
assert!(replay.unwrap().is_ok());
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
#[test]
|
|
212
|
+
fn test_idempotency_store_different_hash() {
|
|
213
|
+
let store = TransactionIdempotencyStore::new(10);
|
|
214
|
+
let tx_id = "tx-456".to_string();
|
|
215
|
+
let tx_hash_1 = "hash-1".to_string();
|
|
216
|
+
let tx_hash_2 = "hash-2".to_string();
|
|
217
|
+
let result = serde_json::json!({ "status": "committed" });
|
|
218
|
+
|
|
219
|
+
store.record_committed(
|
|
220
|
+
tx_id.clone(),
|
|
221
|
+
tx_hash_1.clone(),
|
|
222
|
+
TransactionStatus::Committed,
|
|
223
|
+
result,
|
|
224
|
+
CommitMetadata {
|
|
225
|
+
timestamp_ms: 1000,
|
|
226
|
+
revision_id: "rev-1".to_string(),
|
|
227
|
+
application_id: "app-1".to_string(),
|
|
228
|
+
state_before: 1,
|
|
229
|
+
state_after: 2,
|
|
230
|
+
retry_count: 0,
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// Replay with different hash should fail
|
|
235
|
+
let replay = store.check_idempotent(&tx_id, &tx_hash_2);
|
|
236
|
+
assert!(replay.is_some());
|
|
237
|
+
assert!(replay.unwrap().is_err());
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#[test]
|
|
241
|
+
fn test_hash_transaction_request() {
|
|
242
|
+
let request1 = serde_json::json!({ "id": "test", "value": 123 });
|
|
243
|
+
let request2 = serde_json::json!({ "id": "test", "value": 123 });
|
|
244
|
+
let request3 = serde_json::json!({ "id": "test", "value": 456 });
|
|
245
|
+
|
|
246
|
+
let hash1 = hash_transaction_request(&request1);
|
|
247
|
+
let hash2 = hash_transaction_request(&request2);
|
|
248
|
+
let hash3 = hash_transaction_request(&request3);
|
|
249
|
+
|
|
250
|
+
assert_eq!(hash1, hash2); // Same content = same hash
|
|
251
|
+
assert_ne!(hash1, hash3); // Different content = different hash
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#[test]
|
|
255
|
+
fn test_idempotency_statistics() {
|
|
256
|
+
let store = TransactionIdempotencyStore::new(10);
|
|
257
|
+
|
|
258
|
+
for i in 0..5 {
|
|
259
|
+
store.record_committed(
|
|
260
|
+
format!("tx-{}", i),
|
|
261
|
+
"hash-abc".to_string(),
|
|
262
|
+
TransactionStatus::Committed,
|
|
263
|
+
serde_json::json!({}),
|
|
264
|
+
CommitMetadata {
|
|
265
|
+
timestamp_ms: 1000,
|
|
266
|
+
revision_id: "rev-1".to_string(),
|
|
267
|
+
application_id: "app-1".to_string(),
|
|
268
|
+
state_before: 1,
|
|
269
|
+
state_after: 2,
|
|
270
|
+
retry_count: 0,
|
|
271
|
+
},
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let stats = store.statistics();
|
|
276
|
+
assert_eq!(stats.total_transaction_ids, 5);
|
|
277
|
+
assert_eq!(stats.total_committed_entries, 5);
|
|
278
|
+
assert_eq!(stats.replay_protected_count, 5);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FeltDB Deterministic Error Codes
|
|
3
|
+
*
|
|
4
|
+
* All FeltDB API responses use these semantic error codes.
|
|
5
|
+
* Never returns empty {} or untyped errors.
|
|
6
|
+
*
|
|
7
|
+
* Clients must handle each code appropriately:
|
|
8
|
+
* - CONFLICT: Retry with backoff (CAS mismatch)
|
|
9
|
+
* - PRECONDITION_FAILED: Do not retry (validation error)
|
|
10
|
+
* - TOO_BUSY: Retry with exponential backoff (queue full)
|
|
11
|
+
* - INTERNAL_ERROR: Log and escalate (server error)
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Semantic error codes matching HTTP status codes
|
|
15
|
+
*/
|
|
16
|
+
export declare enum FeltDBErrorCode {
|
|
17
|
+
/** 200: Request succeeded */
|
|
18
|
+
OK = "OK",
|
|
19
|
+
/** 409: Version conflict; concurrent writer won; retry intelligently */
|
|
20
|
+
CONFLICT = "CONFLICT",
|
|
21
|
+
/** 422: Validation or precondition failed; do not retry */
|
|
22
|
+
PRECONDITION_FAILED = "PRECONDITION_FAILED",
|
|
23
|
+
/** 429: Queue depth exceeded; retry with exponential backoff */
|
|
24
|
+
TOO_BUSY = "TOO_BUSY",
|
|
25
|
+
/** 500: Unrecoverable server error; audit trail available */
|
|
26
|
+
INTERNAL_ERROR = "INTERNAL_ERROR"
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Error response structure that all FeltDB APIs must return
|
|
30
|
+
*/
|
|
31
|
+
export interface FeltDBErrorResponse {
|
|
32
|
+
/** Semantic error code (never empty) */
|
|
33
|
+
code: FeltDBErrorCode | string;
|
|
34
|
+
/** Human-readable message explaining what happened */
|
|
35
|
+
message: string;
|
|
36
|
+
/** Unique request ID for debugging/auditing */
|
|
37
|
+
request_id: string;
|
|
38
|
+
/** Transaction ID if applicable */
|
|
39
|
+
transaction_id?: string;
|
|
40
|
+
/** HTTP status code for routing */
|
|
41
|
+
http_status: number;
|
|
42
|
+
/** Recovery hint for client ("retry", "fail", "queue", etc.) */
|
|
43
|
+
recovery_hint?: 'retry_backoff' | 'dont_retry' | 'check_queue_depth' | 'contact_support';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Determines if a FeltDB error is retryable
|
|
47
|
+
*/
|
|
48
|
+
export declare function isRetryableError(code: FeltDBErrorCode | string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Determines retry strategy based on error code
|
|
51
|
+
*/
|
|
52
|
+
export declare function getRetryStrategy(code: FeltDBErrorCode | string): 'exponential_backoff' | 'linear_backoff' | 'dont_retry';
|
|
53
|
+
//# sourceMappingURL=error-codes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;GAEG;AACH,oBAAY,eAAe;IACzB,6BAA6B;IAC7B,EAAE,OAAO;IAET,wEAAwE;IACxE,QAAQ,aAAa;IAErB,2DAA2D;IAC3D,mBAAmB,wBAAwB;IAE3C,gEAAgE;IAChE,QAAQ,aAAa;IAErB,6DAA6D;IAC7D,cAAc,mBAAmB;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,wCAAwC;IACxC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC;IAE/B,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAEhB,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IAEnB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,mCAAmC;IACnC,WAAW,EAAE,MAAM,CAAC;IAEpB,gEAAgE;IAChE,aAAa,CAAC,EAAE,eAAe,GAAG,YAAY,GAAG,mBAAmB,GAAG,iBAAiB,CAAC;CAC1F;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,GAAG,OAAO,CAExE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,GAAG,qBAAqB,GAAG,gBAAgB,GAAG,YAAY,CAQxH"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FeltDB Deterministic Error Codes
|
|
3
|
+
*
|
|
4
|
+
* All FeltDB API responses use these semantic error codes.
|
|
5
|
+
* Never returns empty {} or untyped errors.
|
|
6
|
+
*
|
|
7
|
+
* Clients must handle each code appropriately:
|
|
8
|
+
* - CONFLICT: Retry with backoff (CAS mismatch)
|
|
9
|
+
* - PRECONDITION_FAILED: Do not retry (validation error)
|
|
10
|
+
* - TOO_BUSY: Retry with exponential backoff (queue full)
|
|
11
|
+
* - INTERNAL_ERROR: Log and escalate (server error)
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Semantic error codes matching HTTP status codes
|
|
15
|
+
*/
|
|
16
|
+
export var FeltDBErrorCode;
|
|
17
|
+
(function (FeltDBErrorCode) {
|
|
18
|
+
/** 200: Request succeeded */
|
|
19
|
+
FeltDBErrorCode["OK"] = "OK";
|
|
20
|
+
/** 409: Version conflict; concurrent writer won; retry intelligently */
|
|
21
|
+
FeltDBErrorCode["CONFLICT"] = "CONFLICT";
|
|
22
|
+
/** 422: Validation or precondition failed; do not retry */
|
|
23
|
+
FeltDBErrorCode["PRECONDITION_FAILED"] = "PRECONDITION_FAILED";
|
|
24
|
+
/** 429: Queue depth exceeded; retry with exponential backoff */
|
|
25
|
+
FeltDBErrorCode["TOO_BUSY"] = "TOO_BUSY";
|
|
26
|
+
/** 500: Unrecoverable server error; audit trail available */
|
|
27
|
+
FeltDBErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
28
|
+
})(FeltDBErrorCode || (FeltDBErrorCode = {}));
|
|
29
|
+
/**
|
|
30
|
+
* Determines if a FeltDB error is retryable
|
|
31
|
+
*/
|
|
32
|
+
export function isRetryableError(code) {
|
|
33
|
+
return code === FeltDBErrorCode.CONFLICT || code === FeltDBErrorCode.TOO_BUSY;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Determines retry strategy based on error code
|
|
37
|
+
*/
|
|
38
|
+
export function getRetryStrategy(code) {
|
|
39
|
+
if (code === FeltDBErrorCode.CONFLICT) {
|
|
40
|
+
return 'exponential_backoff';
|
|
41
|
+
}
|
|
42
|
+
if (code === FeltDBErrorCode.TOO_BUSY) {
|
|
43
|
+
return 'exponential_backoff';
|
|
44
|
+
}
|
|
45
|
+
return 'dont_retry';
|
|
46
|
+
}
|
package/dist/feltdb.d.ts
CHANGED
|
@@ -85,6 +85,10 @@ export interface JsDb {
|
|
|
85
85
|
updated: boolean;
|
|
86
86
|
currentVersion: number;
|
|
87
87
|
}>;
|
|
88
|
+
/** Atomic idempotent operation admission: durably admit unique operations. */
|
|
89
|
+
admitOperation?(input: any): Promise<any>;
|
|
90
|
+
/** Atomic operation lifecycle transition: versioned conditional state changes. */
|
|
91
|
+
transitionOperation?(input: any): Promise<any>;
|
|
88
92
|
}
|
|
89
93
|
/**
|
|
90
94
|
* A row stored in the database
|
package/dist/feltdb.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feltdb.d.ts","sourceRoot":"","sources":["../src/feltdb.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,GAAG,GAAG,GAAG,CAAC;IAEhC;;OAEG;IACH,SAAS,IAAI,GAAG,CAAC;IAEjB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC;IAE/B;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC;IAEpC;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC;IAEvC;;OAEG;IACH,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,GAAG,CAAC;IAEnE;;OAEG;IACH,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC;IAEpE;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAEzC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAErC;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAEvD,6DAA6D;IAC7D,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAExD,qDAAqD;IACrD,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAEzC,2EAA2E;IAC3E,iBAAiB,CAAC,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,IAAI,CAAC;IAC5F,KAAK,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/B;;OAEG;IACH,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC;IAEhD;;OAEG;IACH,WAAW,IAAI,MAAM,CAAC;IAEtB;;OAEG;IACH,YAAY,IAAI,MAAM,CAAC;IAEvB,0EAA0E;IAC1E,YAAY,CAAC,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAEpC,6DAA6D;IAC7D,iBAAiB,CAAC,CAAC,cAAc,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/D,uBAAuB,CAAC,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAEhE,oEAAoE;IACpE,WAAW,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAExF,kEAAkE;IAClE,GAAG,CAAC,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"feltdb.d.ts","sourceRoot":"","sources":["../src/feltdb.ts"],"names":[],"mappings":"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"}
|
package/dist/file-db.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JsDb } from './feltdb.js';
|
|
2
|
+
import type { OperationAdmissionInput, OperationAdmissionResult, OperationTransitionInput, OperationTransitionResult } from './operation-admission.js';
|
|
2
3
|
interface JsResult {
|
|
3
4
|
success: boolean;
|
|
4
5
|
data?: string;
|
|
@@ -61,6 +62,8 @@ export declare class FileJsDb implements JsDb {
|
|
|
61
62
|
inserted: boolean;
|
|
62
63
|
value: string;
|
|
63
64
|
}>;
|
|
65
|
+
admitOperation(input: OperationAdmissionInput): Promise<OperationAdmissionResult>;
|
|
66
|
+
transitionOperation(input: OperationTransitionInput): Promise<OperationTransitionResult>;
|
|
64
67
|
cas(params: {
|
|
65
68
|
key: string;
|
|
66
69
|
expectedVersion: number;
|
package/dist/file-db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-db.d.ts","sourceRoot":"","sources":["../src/file-db.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"file-db.d.ts","sourceRoot":"","sources":["../src/file-db.ts"],"names":[],"mappings":"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"}
|
package/dist/file-db.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateOperationAdmissionInput, operationsConflict, generateOperationId, validateTransitionInput, isValidTransition, isTerminalStatus, isIdempotentTerminalTransition, } from './operation-admission.js';
|
|
1
2
|
import { createRequire } from 'module';
|
|
2
3
|
// Create a require function for use in ESM context
|
|
3
4
|
let requireFunc = null;
|
|
@@ -399,6 +400,135 @@ export class FileJsDb {
|
|
|
399
400
|
throw new Error(`putIfAbsent failed: ${error}`);
|
|
400
401
|
}
|
|
401
402
|
}
|
|
403
|
+
async admitOperation(input) {
|
|
404
|
+
// Validate input
|
|
405
|
+
const validation = validateOperationAdmissionInput(input);
|
|
406
|
+
if (!validation.valid) {
|
|
407
|
+
throw new Error(`Operation admission validation failed: ${validation.error}`);
|
|
408
|
+
}
|
|
409
|
+
// Generate operation ID for this admission attempt
|
|
410
|
+
const operationId = generateOperationId();
|
|
411
|
+
// Create durable operation record
|
|
412
|
+
const durableOperation = {
|
|
413
|
+
operationId,
|
|
414
|
+
idempotencyKey: input.idempotencyKey,
|
|
415
|
+
kind: input.kind,
|
|
416
|
+
status: 'accepted',
|
|
417
|
+
version: 0,
|
|
418
|
+
createdAt: Date.now(),
|
|
419
|
+
operationFingerprint: input.operationFingerprint,
|
|
420
|
+
metadata: input.metadata,
|
|
421
|
+
};
|
|
422
|
+
// Use putIfAbsent to atomically store the operation
|
|
423
|
+
// Key format: "operation:{idempotencyKey}" for idempotency lookup
|
|
424
|
+
// Key format: "op-id:{operationId}" for direct operation lookup
|
|
425
|
+
const idempotencyKey = `operation:${input.idempotencyKey}`;
|
|
426
|
+
const operationIdKey = `op-id:${operationId}`;
|
|
427
|
+
const operationValue = JSON.stringify(durableOperation);
|
|
428
|
+
try {
|
|
429
|
+
const result = await this.putIfAbsent(idempotencyKey, operationValue);
|
|
430
|
+
// Also store by operationId for direct lookups
|
|
431
|
+
if (result.inserted) {
|
|
432
|
+
this.state.rows[operationIdKey] = durableOperation;
|
|
433
|
+
this.saveState();
|
|
434
|
+
}
|
|
435
|
+
// Parse the existing or newly inserted operation
|
|
436
|
+
const existingOperation = JSON.parse(result.value);
|
|
437
|
+
// Check for conflicts if operation already existed
|
|
438
|
+
if (!result.inserted) {
|
|
439
|
+
const conflict = operationsConflict(existingOperation, input);
|
|
440
|
+
if (conflict) {
|
|
441
|
+
throw new Error(conflict.message);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
admitted: result.inserted,
|
|
446
|
+
operationId: existingOperation.operationId,
|
|
447
|
+
operation: existingOperation,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
throw new Error(`Operation admission failed: ${error}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
async transitionOperation(input) {
|
|
455
|
+
// Validate input
|
|
456
|
+
const validation = validateTransitionInput(input);
|
|
457
|
+
if (!validation.valid) {
|
|
458
|
+
throw new Error(`Transition validation failed: ${validation.error}`);
|
|
459
|
+
}
|
|
460
|
+
try {
|
|
461
|
+
await this.acquireLockAsync();
|
|
462
|
+
try {
|
|
463
|
+
// Reload state from disk to ensure we have latest
|
|
464
|
+
this.state = this.loadState();
|
|
465
|
+
// Look up operation by its operationId
|
|
466
|
+
const operationIdKey = `op-id:${input.operationId}`;
|
|
467
|
+
// Get current operation state
|
|
468
|
+
if (!(operationIdKey in this.state.rows)) {
|
|
469
|
+
throw new Error(`Operation ${input.operationId} not found`);
|
|
470
|
+
}
|
|
471
|
+
const currentOperation = this.state.rows[operationIdKey];
|
|
472
|
+
// Check if version matches (CAS)
|
|
473
|
+
if (currentOperation.version !== input.expectedVersion) {
|
|
474
|
+
return {
|
|
475
|
+
transitioned: false,
|
|
476
|
+
reason: 'VERSION_CONFLICT',
|
|
477
|
+
operation: currentOperation,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
// Check if transition is legal
|
|
481
|
+
if (!isValidTransition(currentOperation.status, input.to)) {
|
|
482
|
+
return {
|
|
483
|
+
transitioned: false,
|
|
484
|
+
reason: 'INVALID_TRANSITION',
|
|
485
|
+
operation: currentOperation,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
// If transitioning to terminal state, check for idempotent re-entry
|
|
489
|
+
if (isTerminalStatus(input.to) && isTerminalStatus(currentOperation.status)) {
|
|
490
|
+
if (isIdempotentTerminalTransition(currentOperation, input.resultSnapshot, input.error)) {
|
|
491
|
+
return {
|
|
492
|
+
transitioned: false,
|
|
493
|
+
reason: 'VERSION_CONFLICT', // Treat as version conflict (already transitioned)
|
|
494
|
+
operation: currentOperation,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
// Different result data = conflict
|
|
498
|
+
throw new Error(`Terminal state conflict: operation already in ${currentOperation.status} with different result`);
|
|
499
|
+
}
|
|
500
|
+
// Perform the transition
|
|
501
|
+
const transitionedOperation = {
|
|
502
|
+
...currentOperation,
|
|
503
|
+
status: input.to,
|
|
504
|
+
version: currentOperation.version + 1,
|
|
505
|
+
completedAt: isTerminalStatus(input.to) ? Date.now() : currentOperation.completedAt,
|
|
506
|
+
startedAt: input.to === 'executing' ? Date.now() : currentOperation.startedAt,
|
|
507
|
+
resultSnapshot: input.resultSnapshot ?? currentOperation.resultSnapshot,
|
|
508
|
+
error: input.error ?? currentOperation.error,
|
|
509
|
+
};
|
|
510
|
+
// Persist the transition atomically within lock (update both indices)
|
|
511
|
+
this.state.rows[operationIdKey] = transitionedOperation;
|
|
512
|
+
const idempotencyIndexKey = `operation:${currentOperation.idempotencyKey}`;
|
|
513
|
+
if (idempotencyIndexKey in this.state.rows) {
|
|
514
|
+
this.state.rows[idempotencyIndexKey] = transitionedOperation;
|
|
515
|
+
}
|
|
516
|
+
const collection = operationIdKey.split(':')[0];
|
|
517
|
+
this.event(collection, operationIdKey, 'put', transitionedOperation);
|
|
518
|
+
this.saveState();
|
|
519
|
+
return {
|
|
520
|
+
transitioned: true,
|
|
521
|
+
operation: transitionedOperation,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
finally {
|
|
525
|
+
this.releaseLockAsync();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
throw new Error(`Transition failed: ${error}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
402
532
|
async cas(params) {
|
|
403
533
|
try {
|
|
404
534
|
const parsed = JSON.parse(params.value);
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,9 @@ export * from './file-db.js';
|
|
|
31
31
|
export * from './flowspec.js';
|
|
32
32
|
export * from './application-manifest.js';
|
|
33
33
|
export * from './state-contract.js';
|
|
34
|
+
export * from './revision-recovery.js';
|
|
35
|
+
export * from './operation-admission.js';
|
|
36
|
+
export * from './error-codes.js';
|
|
34
37
|
export * from './authorization.js';
|
|
35
38
|
export * from './sync-contract.js';
|
|
36
39
|
export * from './workload.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC;AAC1C,OAAO,EACL,eAAe,EACf,KAAK,OAAO,EACZ,KAAK,SAAS,IAAI,iBAAiB,EACnC,KAAK,QAAQ,IAAI,gBAAgB,GAClC,MAAM,eAAe,CAAC;AAEvB;;GAEG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AAEnC;;GAEG;AACH,YAAY,EACV,eAAe,EACf,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC;AAC1C,OAAO,EACL,eAAe,EACf,KAAK,OAAO,EACZ,KAAK,SAAS,IAAI,iBAAiB,EACnC,KAAK,QAAQ,IAAI,gBAAgB,GAClC,MAAM,eAAe,CAAC;AAEvB;;GAEG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AAEnC;;GAEG;AACH,YAAY,EACV,eAAe,EACf,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,9 @@ export * from './file-db.js';
|
|
|
31
31
|
export * from './flowspec.js';
|
|
32
32
|
export * from './application-manifest.js';
|
|
33
33
|
export * from './state-contract.js';
|
|
34
|
+
export * from './revision-recovery.js';
|
|
35
|
+
export * from './operation-admission.js';
|
|
36
|
+
export * from './error-codes.js';
|
|
34
37
|
export * from './authorization.js';
|
|
35
38
|
export * from './sync-contract.js';
|
|
36
39
|
export * from './workload.js';
|