@rlabs-inc/fsdb 1.1.0 → 1.2.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 CHANGED
@@ -1,18 +1,21 @@
1
+
1
2
  # fsDB - Fractal State Database
2
3
 
3
- A reactive database with parallel arrays and fine-grained reactivity. Built on the **Father State Pattern** at every level.
4
+ A reactive database built with parallel arrays and fine-grained reactivity.
4
5
 
5
- **F**ather **S**tate **DB** = **F**ractal **S**tate **DB**
6
+ **F**ractal **S**tate **DB**
6
7
 
7
8
  ## Features
8
9
 
9
- - **Parallel Arrays** - One reactive array per field, not array of objects
10
- - **Fine-Grained Reactivity** - Update one field, only that field triggers
10
+ - **Fine-grained reactivity** at field level
11
+ - **Markdown Persistence** - YAML frontmatter + body, human-readable
12
+ - **O(1) array access** vs object property lookup
13
+ - **Zero Global State** - Each database instance is isolated
11
14
  - **Reactive Queries** - Queries auto-update when data changes
12
15
  - **Vector Search** - Cosine similarity with stale detection
13
- - **Markdown Persistence** - YAML frontmatter + body, human-readable
14
16
  - **File Watching** - External changes sync automatically
15
- - **Zero Global State** - Each database instance is isolated
17
+ - **CPU cache locality**
18
+ - **Minimal garbage collection**
16
19
 
17
20
  ## Installation
18
21
 
@@ -61,33 +64,6 @@ effect(() => {
61
64
  users.update(id, { active: false })
62
65
  ```
63
66
 
64
- ## The Father State Pattern
65
-
66
- Instead of storing records as objects (slow):
67
-
68
- ```typescript
69
- // Traditional - array of objects
70
- const records = [
71
- { id: '1', name: 'Alice', age: 30 },
72
- { id: '2', name: 'Bob', age: 25 },
73
- ]
74
- ```
75
-
76
- We use parallel arrays indexed by a central registry (fast):
77
-
78
- ```typescript
79
- // Father State Pattern - parallel arrays
80
- registry = { idToIndex: { '1': 0, '2': 1 } }
81
- names = ['Alice', 'Bob']
82
- ages = [30, 25]
83
- ```
84
-
85
- **Benefits:**
86
- - O(1) array access vs object property lookup
87
- - Better CPU cache locality
88
- - Fine-grained reactivity at field level
89
- - Minimal garbage collection
90
-
91
67
  ## API Reference
92
68
 
93
69
  ### Database
@@ -217,7 +193,7 @@ const activeCount = queryCount(users, r => r.active)
217
193
  // First match
218
194
  const admin = queryFirst(users, r => r.role === 'admin')
219
195
 
220
- // Use in effects - auto-updates!
196
+ // Use in effects
221
197
  effect(() => {
222
198
  console.log('Active users:', activeUsers.value.count)
223
199
  })
@@ -290,7 +266,7 @@ age: 30
290
266
  embedding: [0.1, 0.2, 0.3, ...]
291
267
  ---
292
268
 
293
- This is the bio content (contentColumn)
269
+ This is the bio content in markdown format (contentColumn)
294
270
  ```
295
271
 
296
272
  ### Stale Detection
@@ -332,9 +308,9 @@ On Apple Silicon (M1/M2/M3):
332
308
  ## Architecture
333
309
 
334
310
  ```
335
- Database (FatherState)
311
+ Database (ECS Arrays)
336
312
  └── Collections (ReactiveMap)
337
- └── Collection (FatherState)
313
+ └── Collection (ECS Arrays)
338
314
  ├── Registry (index management)
339
315
  └── Columns (parallel arrays)
340
316
  ├── names: ['Alice', 'Bob', ...]
package/dist/index.js CHANGED
@@ -1761,10 +1761,21 @@ function listMarkdownFiles(dirpath) {
1761
1761
  }
1762
1762
  }
1763
1763
  function idToFilename(id) {
1764
- return id.replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") + ".md";
1764
+ const encoded = id.replace(/[^a-zA-Z0-9\-_.]/g, (char) => {
1765
+ const code = char.charCodeAt(0);
1766
+ if (code > 255) {
1767
+ return `%u${code.toString(16).padStart(4, "0")}`;
1768
+ }
1769
+ return `%${code.toString(16).padStart(2, "0")}`;
1770
+ });
1771
+ const safe = encoded.startsWith(".") ? `%2e${encoded.slice(1)}` : encoded;
1772
+ return safe + ".md";
1765
1773
  }
1766
1774
  function filenameToId(filename) {
1767
- return filename.replace(/\.md$/, "");
1775
+ const withoutExt = filename.replace(/\.md$/, "");
1776
+ return withoutExt.replace(/%u([0-9a-fA-F]{4})|%([0-9a-fA-F]{2})/g, (_, quad, pair) => {
1777
+ return String.fromCharCode(parseInt(quad || pair, 16));
1778
+ });
1768
1779
  }
1769
1780
  function parseMarkdown(text) {
1770
1781
  const frontmatter = {};
@@ -1802,6 +1813,14 @@ function parseYamlValue(value) {
1802
1813
  return true;
1803
1814
  if (value === "false")
1804
1815
  return false;
1816
+ if (value === ".nan" || value === "NaN")
1817
+ return NaN;
1818
+ if (value === ".inf" || value === "Infinity")
1819
+ return Infinity;
1820
+ if (value === "-.inf" || value === "-Infinity")
1821
+ return -Infinity;
1822
+ if (value === "-0.0" || value === "-0")
1823
+ return -0;
1805
1824
  if (/^-?\d+$/.test(value)) {
1806
1825
  return parseInt(value, 10);
1807
1826
  }
@@ -1815,7 +1834,14 @@ function parseYamlValue(value) {
1815
1834
  return value;
1816
1835
  }
1817
1836
  }
1818
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1837
+ if (value.startsWith('"') && value.endsWith('"')) {
1838
+ try {
1839
+ return JSON.parse(value);
1840
+ } catch {
1841
+ return value.slice(1, -1);
1842
+ }
1843
+ }
1844
+ if (value.startsWith("'") && value.endsWith("'")) {
1819
1845
  return value.slice(1, -1);
1820
1846
  }
1821
1847
  return value;
@@ -1828,6 +1854,14 @@ function toYamlValue(value) {
1828
1854
  return value ? "true" : "false";
1829
1855
  }
1830
1856
  if (typeof value === "number") {
1857
+ if (Number.isNaN(value))
1858
+ return ".nan";
1859
+ if (value === Infinity)
1860
+ return ".inf";
1861
+ if (value === -Infinity)
1862
+ return "-.inf";
1863
+ if (Object.is(value, -0))
1864
+ return "-0.0";
1831
1865
  return String(value);
1832
1866
  }
1833
1867
  if (Array.isArray(value)) {
@@ -1837,11 +1871,7 @@ function toYamlValue(value) {
1837
1871
  return JSON.stringify(Array.from(value));
1838
1872
  }
1839
1873
  if (typeof value === "string") {
1840
- if (value.includes(":") || value.includes("#") || value.includes(`
1841
- `) || value.startsWith('"') || value.startsWith("'") || value === "true" || value === "false" || value === "null") {
1842
- return JSON.stringify(value);
1843
- }
1844
- return value;
1874
+ return JSON.stringify(value);
1845
1875
  }
1846
1876
  return JSON.stringify(value);
1847
1877
  }
@@ -1893,6 +1923,12 @@ async function loadFromMarkdown(filepath, schema, contentColumn) {
1893
1923
  const parsed = parseColumnType(schema[key]);
1894
1924
  if (parsed.baseType === "vector" && Array.isArray(value)) {
1895
1925
  value = new Float32Array(value);
1926
+ } else if (parsed.baseType === "string" && typeof value !== "string") {
1927
+ value = value === null ? "" : String(value);
1928
+ } else if (parsed.baseType === "number" && typeof value !== "number") {
1929
+ value = Number(value) || 0;
1930
+ } else if (parsed.baseType === "boolean" && typeof value !== "boolean") {
1931
+ value = value === "true" || value === true;
1896
1932
  }
1897
1933
  record[key] = value;
1898
1934
  }
package/dist/index.mjs CHANGED
@@ -1670,10 +1670,21 @@ function listMarkdownFiles(dirpath) {
1670
1670
  }
1671
1671
  }
1672
1672
  function idToFilename(id) {
1673
- return id.replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") + ".md";
1673
+ const encoded = id.replace(/[^a-zA-Z0-9\-_.]/g, (char) => {
1674
+ const code = char.charCodeAt(0);
1675
+ if (code > 255) {
1676
+ return `%u${code.toString(16).padStart(4, "0")}`;
1677
+ }
1678
+ return `%${code.toString(16).padStart(2, "0")}`;
1679
+ });
1680
+ const safe = encoded.startsWith(".") ? `%2e${encoded.slice(1)}` : encoded;
1681
+ return safe + ".md";
1674
1682
  }
1675
1683
  function filenameToId(filename) {
1676
- return filename.replace(/\.md$/, "");
1684
+ const withoutExt = filename.replace(/\.md$/, "");
1685
+ return withoutExt.replace(/%u([0-9a-fA-F]{4})|%([0-9a-fA-F]{2})/g, (_, quad, pair) => {
1686
+ return String.fromCharCode(parseInt(quad || pair, 16));
1687
+ });
1677
1688
  }
1678
1689
  function parseMarkdown(text) {
1679
1690
  const frontmatter = {};
@@ -1711,6 +1722,14 @@ function parseYamlValue(value) {
1711
1722
  return true;
1712
1723
  if (value === "false")
1713
1724
  return false;
1725
+ if (value === ".nan" || value === "NaN")
1726
+ return NaN;
1727
+ if (value === ".inf" || value === "Infinity")
1728
+ return Infinity;
1729
+ if (value === "-.inf" || value === "-Infinity")
1730
+ return -Infinity;
1731
+ if (value === "-0.0" || value === "-0")
1732
+ return -0;
1714
1733
  if (/^-?\d+$/.test(value)) {
1715
1734
  return parseInt(value, 10);
1716
1735
  }
@@ -1724,7 +1743,14 @@ function parseYamlValue(value) {
1724
1743
  return value;
1725
1744
  }
1726
1745
  }
1727
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1746
+ if (value.startsWith('"') && value.endsWith('"')) {
1747
+ try {
1748
+ return JSON.parse(value);
1749
+ } catch {
1750
+ return value.slice(1, -1);
1751
+ }
1752
+ }
1753
+ if (value.startsWith("'") && value.endsWith("'")) {
1728
1754
  return value.slice(1, -1);
1729
1755
  }
1730
1756
  return value;
@@ -1737,6 +1763,14 @@ function toYamlValue(value) {
1737
1763
  return value ? "true" : "false";
1738
1764
  }
1739
1765
  if (typeof value === "number") {
1766
+ if (Number.isNaN(value))
1767
+ return ".nan";
1768
+ if (value === Infinity)
1769
+ return ".inf";
1770
+ if (value === -Infinity)
1771
+ return "-.inf";
1772
+ if (Object.is(value, -0))
1773
+ return "-0.0";
1740
1774
  return String(value);
1741
1775
  }
1742
1776
  if (Array.isArray(value)) {
@@ -1746,11 +1780,7 @@ function toYamlValue(value) {
1746
1780
  return JSON.stringify(Array.from(value));
1747
1781
  }
1748
1782
  if (typeof value === "string") {
1749
- if (value.includes(":") || value.includes("#") || value.includes(`
1750
- `) || value.startsWith('"') || value.startsWith("'") || value === "true" || value === "false" || value === "null") {
1751
- return JSON.stringify(value);
1752
- }
1753
- return value;
1783
+ return JSON.stringify(value);
1754
1784
  }
1755
1785
  return JSON.stringify(value);
1756
1786
  }
@@ -1802,6 +1832,12 @@ async function loadFromMarkdown(filepath, schema, contentColumn) {
1802
1832
  const parsed = parseColumnType(schema[key]);
1803
1833
  if (parsed.baseType === "vector" && Array.isArray(value)) {
1804
1834
  value = new Float32Array(value);
1835
+ } else if (parsed.baseType === "string" && typeof value !== "string") {
1836
+ value = value === null ? "" : String(value);
1837
+ } else if (parsed.baseType === "number" && typeof value !== "number") {
1838
+ value = Number(value) || 0;
1839
+ } else if (parsed.baseType === "boolean" && typeof value !== "boolean") {
1840
+ value = value === "true" || value === true;
1805
1841
  }
1806
1842
  record[key] = value;
1807
1843
  }
@@ -5,9 +5,9 @@
5
5
  * Uses Bun file APIs when available, falls back to Node.js fs/promises.
6
6
  */
7
7
  import type { SchemaDefinition, RecordWithMeta } from '../core/types';
8
- /** Convert an ID to a safe filename */
8
+ /** Convert an ID to a safe filename (reversible via percent-encoding) */
9
9
  export declare function idToFilename(id: string): string;
10
- /** Extract ID from a filename */
10
+ /** Extract ID from a filename (decodes percent-encoding) */
11
11
  export declare function filenameToId(filename: string): string;
12
12
  interface ParsedMarkdown {
13
13
  frontmatter: Record<string, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/persistence/markdown.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAkB,cAAc,EAAE,MAAM,eAAe,CAAA;AA2DrF,uCAAuC;AACvC,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,iCAAiC;AACjC,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAErD;AAMD,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAqC1D;AAkFD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EACzD,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,CAAC,EACT,aAAa,CAAC,EAAE,MAAM,CAAC,GACtB,MAAM,CA4BR;AAMD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EAC/D,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,CAAC,EACT,aAAa,CAAC,EAAE,MAAM,CAAC,GACtB,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,GAAG,IAAI,CAAC,CA6CpE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,CAAC,SAAS,gBAAgB,EAC7D,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,CAAC,EACT,aAAa,CAAC,EAAE,MAAM,CAAC,GACtB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,SAAS,gBAAgB,EAChE,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,EACT,aAAa,CAAC,EAAE,MAAM,CAAC,GACtB,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,EAAE,CAAC,CAkB/D;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ3E;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpE"}
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/persistence/markdown.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAkB,cAAc,EAAE,MAAM,eAAe,CAAA;AA2DrF,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAe/C;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMrD;AAMD,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAqC1D;AAqGD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EACzD,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,CAAC,EACT,aAAa,CAAC,EAAE,MAAM,CAAC,GACtB,MAAM,CA4BR;AAMD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EAC/D,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,CAAC,EACT,aAAa,CAAC,EAAE,MAAM,CAAC,GACtB,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,GAAG,IAAI,CAAC,CAmDpE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,CAAC,SAAS,gBAAgB,EAC7D,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,CAAC,EACT,aAAa,CAAC,EAAE,MAAM,CAAC,GACtB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,SAAS,gBAAgB,EAChE,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,EACT,aAAa,CAAC,EAAE,MAAM,CAAC,GACtB,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,EAAE,CAAC,CAkB/D;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ3E;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rlabs-inc/fsdb",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Fractal State Database - A reactive in memory database with markdown files persistence made with parallel arrays and fine-grained reactivity.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",