nx_browser_profile_store.nx source
↩ module page · 72 lines · 3404 B
1// nx_browser_profile_store.nx -- the Nishi browser's PERSISTENT PROFILE STORE (lib, no main).
2// Operator 2026-07-02: contacts/chat/settings "stored locally in nishi browser in a DIFFERENT cache
3// than the history-clearing cache so that this more permanent info" survives; backup = nx_profile_backup.
4//
5// THE SPLIT (the whole point): TWO seg_store families under knowledge/store/ --
6// PROFILE prefix "knowledge/store/browser_profile-" = permanent (contacts/chat/settings).
7// CACHE prefix "knowledge/store/browser_cache-" = clearable (http cache/history when they land).
8// A history-clear deletes ONLY browser_cache-* files; it never touches browser_profile-*. The gate
9// (nx_browser_profile_gate) proves this split mechanically: clear the cache family -> profile intact.
10//
11// Storage = nx_seg_store (append-only, content-addressed, crash-safe temp->rename, additive law: deletes
12// are TOMBSTONES so history is never destroyed). Key scheme: "contact:<id>", "chat:<room>:<ts_us>",
13// "setting:<name>". ⚠ each bp_put = one ss_commit = one new segment (manifest cap 256) -- BATCH bursty
14// writers (chat) with bp_put2 pairs per commit or run ss_compact periodically. license_tier: ORIGINAL
15import "nx_syscalls.nx"
16import "nx_seg_store.nx"
17
18const BP_PROFILE_PREFIX: *u8 = "knowledge/store/browser_profile-"
19const BP_CACHE_PREFIX: *u8 = "knowledge/store/browser_cache-"
20
21func bp_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
22
23// put one record (last-wins for the same key; history stays readable in older segments).
24func bp_put(prefix: *u8, key: *u8, val: *u8, vlen: i64) -> i64 {
25 let w: *i64 = ss_begin()
26 if ss_add(w, 1, key, val, vlen) != 0 { return 0 - 1 }
27 if ss_commit(prefix, w, sys_now_us()) != 0 { return 0 - 2 }
28 return 0
29}
30
31// put TWO records in ONE commit (one segment) -- the batching seam for bursty writers.
32func bp_put2(prefix: *u8, key1: *u8, val1: *u8, vlen1: i64, key2: *u8, val2: *u8, vlen2: i64) -> i64 {
33 let w: *i64 = ss_begin()
34 if ss_add(w, 1, key1, val1, vlen1) != 0 { return 0 - 1 }
35 if ss_add(w, 1, key2, val2, vlen2) != 0 { return 0 - 1 }
36 if ss_commit(prefix, w, sys_now_us()) != 0 { return 0 - 2 }
37 return 0
38}
39
40// tombstone-delete (additive law: the record's history stays; the CURRENT view misses).
41func bp_del(prefix: *u8, key: *u8) -> i64 {
42 let w: *i64 = ss_begin()
43 let z: *u8 = sys_mmap(1)
44 if ss_add(w, 2, key, z, 0) != 0 { return 0 - 1 }
45 if ss_commit(prefix, w, sys_now_us()) != 0 { return 0 - 2 }
46 return 0
47}
48
49// current value of key -> copied into out (cap bytes); returns length, or -1 on miss/tombstone.
50func bp_get(prefix: *u8, key: *u8, out: *u8, cap: i64) -> i64 {
51 let h: *i64 = ss_open(prefix)
52 if (h as i64) == 0 { return 0 - 1 }
53 let pp: *i64 = sys_mmap(16) as *i64
54 let ll: *i64 = sys_mmap(16) as *i64
55 if ss_hget(h, key, pp, ll) != 1 { return 0 - 1 }
56 let src: *u8 = pp[0] as *u8
57 var n: i64 = ll[0]
58 if n > cap { n = cap }
59 var i: i64 = 0
60 while i < n { out[i] = src[i]; i = i + 1 }
61 return n
62}
63
64// 1 if the key currently exists (not tombstoned), else 0.
65func bp_exists(prefix: *u8, key: *u8) -> i64 {
66 let h: *i64 = ss_open(prefix)
67 if (h as i64) == 0 { return 0 }
68 let pp: *i64 = sys_mmap(16) as *i64
69 let ll: *i64 = sys_mmap(16) as *i64
70 if ss_hget(h, key, pp, ll) != 1 { return 0 }
71 return 1
72}