audit fixes, repo restructure, and documentation

Soundness and performance audit (17 fixes):
- See AUDIT.md for full details and @claude comments in code

Repo restructure:
- Inline json-ptr and json-patch submodules as regular directories
- Remove cbor submodule, replace serde_cbor with ciborium
- Rename patch-db/ -> core/, patch-db-macro/ -> macro/,
  patch-db-macro-internals/ -> macro-internals/, patch-db-util/ -> util/
- Purge upstream CI/CD, bench, and release cruft from json-patch
- Remove .gitmodules

Test fixes:
- Fix proptest doesnt_crash (unique file paths, proper close/cleanup)
- Add PatchDb::close() for clean teardown

Documentation:
- Add README.md, ARCHITECTURE.md, CONTRIBUTING.md, CLAUDE.md, AUDIT.md
- Add TSDocs to TypeScript client exports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt Hill
2026-02-23 19:06:42 -07:00
parent 05c93290c7
commit 86b0768bbb
46 changed files with 5744 additions and 95 deletions

105
core/src/test.rs Normal file
View File

@@ -0,0 +1,105 @@
use std::future::Future;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use imbl_value::{json, Value};
use json_ptr::JsonPointer;
use patch_db::{HasModel, PatchDb, Revision};
use proptest::prelude::*;
use tokio::fs;
use tokio::runtime::Builder;
use crate::{self as patch_db};
/// Atomic counter to generate unique file paths across concurrent tests.
static TEST_COUNTER: AtomicUsize = AtomicUsize::new(0);
fn unique_db_path(prefix: &str) -> String {
let id = TEST_COUNTER.fetch_add(1, Ordering::Relaxed);
format!("test-{}-{}.db", prefix, id)
}
async fn init_db(db_name: String) -> PatchDb {
cleanup_db(&db_name).await;
let db = PatchDb::open(db_name).await.unwrap();
db.put(
&JsonPointer::<&'static str>::default(),
&json!({
"a": "test1",
"b": {
"a": "test2",
"b": 1,
"c": null,
},
}),
)
.await
.unwrap();
db
}
async fn cleanup_db(db_name: &str) {
fs::remove_file(db_name).await.ok();
fs::remove_file(format!("{}.bak", db_name)).await.ok();
fs::remove_file(format!("{}.bak.tmp", db_name)).await.ok();
fs::remove_file(format!("{}.failed", db_name)).await.ok();
}
async fn put_string_into_root(db: &PatchDb, s: String) -> Arc<Revision> {
db.put(&JsonPointer::<&'static str>::default(), &s)
.await
.unwrap()
.unwrap()
}
#[tokio::test]
async fn basic() {
let path = unique_db_path("basic");
let db = init_db(path.clone()).await;
let ptr: JsonPointer = "/b/b".parse().unwrap();
let mut get_res: Value = db.get(&ptr).await.unwrap();
assert_eq!(get_res.as_u64(), Some(1));
db.put(&ptr, "hello").await.unwrap();
get_res = db.get(&ptr).await.unwrap();
assert_eq!(get_res.as_str(), Some("hello"));
db.close().await.unwrap();
cleanup_db(&path).await;
}
fn run_future<S: Into<String>, Fut: Future<Output = ()>>(name: S, fut: Fut) {
Builder::new_multi_thread()
.thread_name(name)
.build()
.unwrap()
.block_on(fut)
}
proptest! {
#[test]
fn doesnt_crash(s in "\\PC*") {
run_future("test-doesnt-crash", async {
let path = unique_db_path("proptest");
let db = init_db(path.clone()).await;
put_string_into_root(&db, s).await;
db.close().await.unwrap();
cleanup_db(&path).await;
});
}
}
// #[derive(Debug, serde::Deserialize, serde::Serialize, HasModel)]
// pub struct Sample {
// a: String,
// #[model]
// b: Child,
// }
// #[derive(Debug, serde::Deserialize, serde::Serialize, HasModel)]
// pub struct Child {
// a: String,
// b: usize,
// c: NewType,
// }
// #[derive(Debug, serde::Deserialize, serde::Serialize, HasModel)]
// pub struct NewType(Option<Box<Sample>>);