@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 +13 -37
- package/dist/index.js +44 -8
- package/dist/index.mjs +44 -8
- package/dist/persistence/markdown.d.ts +2 -2
- package/dist/persistence/markdown.d.ts.map +1 -1
- package/package.json +1 -1
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.
|
|
4
|
+
A reactive database built with parallel arrays and fine-grained reactivity.
|
|
4
5
|
|
|
5
|
-
**F**
|
|
6
|
+
**F**ractal **S**tate **DB**
|
|
6
7
|
|
|
7
8
|
## Features
|
|
8
9
|
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
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
|
-
- **
|
|
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
|
|
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 (
|
|
311
|
+
Database (ECS Arrays)
|
|
336
312
|
└── Collections (ReactiveMap)
|
|
337
|
-
└── Collection (
|
|
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
|
-
|
|
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
|
-
|
|
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('"')
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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('"')
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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",
|