@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.
@@ -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
@@ -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;CAC9H;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB"}
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;
@@ -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;AAmDxC,UAAU,QAAQ;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,aAAa;IAAG,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE;AAyBrK,qBAAa,QAAS,YAAW,IAAI;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,MAAM,CAAuB;gBAEzB,IAAI,EAAE,MAAM;IAgCxB,OAAO,CAAC,SAAS;IAiCjB,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IAuBnB,OAAO,CAAC,gBAAgB;YAWV,gBAAgB;IA6B9B,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,SAAS;IAkCjB,OAAO,CAAC,KAAK;IAeb,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ;IAa5C,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ;IAO5C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ;IAO1B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ;IAW7B,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ;IAQnC,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ;IAIpD,UAAU,IAAI,QAAQ;IAItB,SAAS,IAAI,QAAQ;IAiBrB,QAAQ,IAAI,QAAQ;IAIpB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IAKvC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IAK1C,2BAA2B,IAAI,QAAQ;IAIvC,oBAAoB,IAAI,QAAQ;IAIhC,WAAW,IAAI,MAAM;IAIrB,YAAY,IAAI,MAAM;IAItB,YAAY;IAIZ,iBAAiB,CAAC,aAAa,EAAE,MAAM;IAIvC,uBAAuB,CAAC,UAAU,EAAE,aAAa,EAAE;;;;IA0C7C,gBAAgB,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAS7E,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAwCtF,GAAG,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;IAoCjI,KAAK,IAAI,IAAI;CAId"}
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';
@@ -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';