@rlabs-inc/fsdb 1.0.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 ADDED
@@ -0,0 +1,358 @@
1
+ # fsDB - Fractal State Database
2
+
3
+ A reactive database with parallel arrays and fine-grained reactivity. Built on the **Father State Pattern** at every level.
4
+
5
+ **F**ather **S**tate **DB** = **F**ractal **S**tate **DB**
6
+
7
+ ## Features
8
+
9
+ - **Parallel Arrays** - One reactive array per field, not array of objects
10
+ - **Fine-Grained Reactivity** - Update one field, only that field triggers
11
+ - **Reactive Queries** - Queries auto-update when data changes
12
+ - **Vector Search** - Cosine similarity with stale detection
13
+ - **Markdown Persistence** - YAML frontmatter + body, human-readable
14
+ - **File Watching** - External changes sync automatically
15
+ - **Zero Global State** - Each database instance is isolated
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ bun add @rlabs-inc/fsdb
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { createDatabase, eq, gt, and, query, effect } from '@rlabs-inc/fsdb'
27
+
28
+ // Create a database
29
+ const db = createDatabase({ name: 'myapp' })
30
+
31
+ // Create a collection with schema
32
+ const users = db.collection('users', {
33
+ schema: {
34
+ name: 'string',
35
+ age: 'number',
36
+ email: 'string',
37
+ active: 'boolean',
38
+ }
39
+ })
40
+
41
+ // Insert records
42
+ const id = users.insert({
43
+ name: 'Alice',
44
+ age: 30,
45
+ email: 'alice@example.com',
46
+ active: true,
47
+ })
48
+
49
+ // Query with filters
50
+ const activeUsers = users.find(eq('active', true))
51
+ const seniors = users.find(and(gt('age', 50), eq('active', true)))
52
+
53
+ // Reactive queries - auto-update on changes
54
+ const activeQuery = query(users, r => r.active)
55
+
56
+ effect(() => {
57
+ console.log('Active count:', activeQuery.value.count)
58
+ })
59
+
60
+ // Updates trigger the effect above
61
+ users.update(id, { active: false })
62
+ ```
63
+
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
+ ## API Reference
92
+
93
+ ### Database
94
+
95
+ ```typescript
96
+ import { createDatabase } from '@rlabs-inc/fsdb'
97
+
98
+ // Global storage (default): ~/.fsdb/myapp/
99
+ const db = createDatabase({ name: 'myapp' })
100
+
101
+ // Project-local storage: ./.fsdb/myapp/
102
+ const localDb = createDatabase({ name: 'myapp', local: true })
103
+
104
+ // Custom path: /custom/path/
105
+ const customDb = createDatabase({ basePath: '/custom/path' })
106
+
107
+ // Create/get collections
108
+ const users = db.collection('users', { schema: {...} })
109
+
110
+ // List collections
111
+ db.listCollections()
112
+
113
+ // Cleanup
114
+ db.close()
115
+ ```
116
+
117
+ ### Collections
118
+
119
+ ```typescript
120
+ const users = db.collection('users', {
121
+ schema: {
122
+ name: 'string',
123
+ age: 'number',
124
+ score: 'number',
125
+ tags: 'string[]',
126
+ embedding: 'vector:384', // 384-dimensional vector
127
+ },
128
+ contentColumn: 'bio', // Stored as markdown body
129
+ autoSave: true, // Auto-persist changes
130
+ watchFiles: true, // Watch for external changes
131
+ })
132
+
133
+ // CRUD
134
+ const id = users.insert({ name: 'Alice', age: 30 })
135
+ const user = users.get(id)
136
+ users.update(id, { age: 31 })
137
+ users.updateField(id, 'score', 100)
138
+ users.delete(id)
139
+
140
+ // Querying
141
+ const all = users.all()
142
+ const found = users.find(filter)
143
+ const first = users.findOne(filter)
144
+ const count = users.count(filter)
145
+
146
+ // Persistence (if path configured)
147
+ await users.load()
148
+ await users.save()
149
+
150
+ // File watching
151
+ users.startWatching()
152
+ users.stopWatching()
153
+ users.onFileChange((event) => {
154
+ console.log(event.type, event.id, event.stale)
155
+ })
156
+ ```
157
+
158
+ ### Schema Types
159
+
160
+ | Type | TypeScript | Example |
161
+ |------|------------|---------|
162
+ | `'string'` | `string` | `'hello'` |
163
+ | `'number'` | `number` | `42` |
164
+ | `'boolean'` | `boolean` | `true` |
165
+ | `'timestamp'` | `number` | `Date.now()` |
166
+ | `'string[]'` | `string[]` | `['a', 'b']` |
167
+ | `'number[]'` | `number[]` | `[1, 2, 3]` |
168
+ | `'vector:N'` | `Float32Array` | 384-dim embedding |
169
+
170
+ ### Filter Helpers
171
+
172
+ ```typescript
173
+ import {
174
+ // Comparison
175
+ eq, neq, gt, gte, lt, lte, between, oneOf,
176
+
177
+ // Text
178
+ fullText, matches, startsWith, endsWith,
179
+
180
+ // Arrays
181
+ contains, containsAny, containsAll, isEmpty, isNotEmpty,
182
+
183
+ // Logic
184
+ and, or, not,
185
+
186
+ // Existence
187
+ exists, isNull,
188
+
189
+ // Time
190
+ after, before, withinLast,
191
+ } from '@rlabs-inc/fsdb'
192
+
193
+ // Examples
194
+ users.find(eq('name', 'Alice'))
195
+ users.find(gt('age', 30))
196
+ users.find(contains('tags', 'admin'))
197
+ users.find(and(gt('age', 18), eq('active', true)))
198
+ users.find(or(eq('role', 'admin'), eq('role', 'moderator')))
199
+ users.find(fullText('search term'))
200
+ users.find(withinLast('created', 24 * 60 * 60 * 1000)) // Last 24h
201
+ ```
202
+
203
+ ### Reactive Queries
204
+
205
+ ```typescript
206
+ import { query, querySorted, queryCount, queryFirst, effect } from '@rlabs-inc/fsdb'
207
+
208
+ // Basic query - returns { indices, records, count }
209
+ const activeUsers = query(users, r => r.active === true)
210
+
211
+ // Sorted query
212
+ const topScorers = querySorted(users, r => r.score > 0, 'score', true)
213
+
214
+ // Count query
215
+ const activeCount = queryCount(users, r => r.active)
216
+
217
+ // First match
218
+ const admin = queryFirst(users, r => r.role === 'admin')
219
+
220
+ // Use in effects - auto-updates!
221
+ effect(() => {
222
+ console.log('Active users:', activeUsers.value.count)
223
+ })
224
+ ```
225
+
226
+ ### Vector Search
227
+
228
+ ```typescript
229
+ import { vectorSearch, cosineSimilarity } from '@rlabs-inc/fsdb'
230
+
231
+ // Schema with vector column
232
+ const docs = db.collection('docs', {
233
+ schema: {
234
+ content: 'string',
235
+ embedding: 'vector:384',
236
+ }
237
+ })
238
+
239
+ // Store embeddings with stale detection
240
+ docs.setEmbedding(id, 'embedding', vector, sourceContent)
241
+
242
+ // Search
243
+ const results = docs.search('embedding', queryVector, {
244
+ topK: 10,
245
+ minSimilarity: 0.5,
246
+ filter: r => r.category === 'tech',
247
+ })
248
+
249
+ // Results include stale flag
250
+ results.forEach(({ record, similarity, stale }) => {
251
+ console.log(record.content, similarity, stale ? '(stale)' : '')
252
+ })
253
+
254
+ // Manual similarity calculation
255
+ const sim = cosineSimilarity(vecA, vecB)
256
+ ```
257
+
258
+ ### Sorting & Pagination
259
+
260
+ ```typescript
261
+ import { sortBy, paginate } from '@rlabs-inc/fsdb'
262
+
263
+ const records = users.all()
264
+
265
+ // Sort
266
+ const sorted = sortBy(records, { column: 'score', direction: 'desc' })
267
+
268
+ // Multi-column sort
269
+ const sorted2 = sortBy(records,
270
+ { column: 'age', direction: 'asc' },
271
+ { column: 'name', direction: 'asc' }
272
+ )
273
+
274
+ // Paginate
275
+ const page = paginate(records, 1, 20)
276
+ // { records, total, page, pageSize, totalPages, hasNext, hasPrev }
277
+ ```
278
+
279
+ ### File Persistence
280
+
281
+ Records are stored as markdown files with YAML frontmatter:
282
+
283
+ ```markdown
284
+ ---
285
+ id: user-1703289600000-abc123
286
+ created: 1703289600000
287
+ updated: 1703289600000
288
+ name: Alice
289
+ age: 30
290
+ embedding: [0.1, 0.2, 0.3, ...]
291
+ ---
292
+
293
+ This is the bio content (contentColumn)
294
+ ```
295
+
296
+ ### Stale Detection
297
+
298
+ When content changes but embedding isn't regenerated:
299
+
300
+ ```typescript
301
+ // Store embedding with content hash
302
+ users.setEmbedding(id, 'embedding', vector, originalContent)
303
+
304
+ // Later, if content changes externally...
305
+ users.isStale(id) // true
306
+ users.getStaleIds() // ['id1', 'id2', ...]
307
+
308
+ // After regenerating embedding
309
+ users.setEmbedding(id, 'embedding', newVector, newContent)
310
+ users.isStale(id) // false
311
+ ```
312
+
313
+ ## Benchmarks
314
+
315
+ On Apple Silicon (M1/M2/M3):
316
+
317
+ | Operation | Time |
318
+ |-----------|------|
319
+ | Insert 1000 records | 2.28ms |
320
+ | Get by ID | 0.13μs |
321
+ | Update field | 0.11μs |
322
+ | Filter 1000 records | 107μs |
323
+ | fullText search | 169μs |
324
+ | Sort 1000 records | 110μs |
325
+ | Cosine similarity (384d) | 0.17μs |
326
+ | Vector search top-10 | 534μs |
327
+ | Vector search with filter | 194μs |
328
+ | Bun.hash (fingerprint) | 0.28μs |
329
+
330
+ **Test Coverage:** 77 tests passing across all features.
331
+
332
+ ## Architecture
333
+
334
+ ```
335
+ Database (FatherState)
336
+ └── Collections (ReactiveMap)
337
+ └── Collection (FatherState)
338
+ ├── Registry (index management)
339
+ └── Columns (parallel arrays)
340
+ ├── names: ['Alice', 'Bob', ...]
341
+ ├── ages: [30, 25, ...]
342
+ └── scores: [100, 85, ...]
343
+ ```
344
+
345
+ The same pattern repeats at every level - **fractal architecture**.
346
+
347
+ ## Requirements
348
+
349
+ - Bun 1.0+
350
+ - @rlabs-inc/signals
351
+
352
+ ## License
353
+
354
+ MIT
355
+
356
+ ---
357
+
358
+ Built with ❤️ by RLabs Inc.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Collection
3
+ *
4
+ * A reactive collection using the Father State Pattern.
5
+ * Combines the registry (index management) with columns (parallel arrays).
6
+ *
7
+ * This is where the fractal pattern lives - each collection is a FatherState.
8
+ */
9
+ import { type DerivedSignal } from '@rlabs-inc/signals';
10
+ import { type Registry } from './registry';
11
+ import { type ColumnsManager } from './columns';
12
+ import type { SchemaDefinition, SchemaToRecord, RecordWithMeta, PartialRecord, CollectionOptions, FilterFn } from './types';
13
+ export interface Collection<S extends SchemaDefinition> {
14
+ /** Collection name */
15
+ readonly name: string;
16
+ /** The schema definition */
17
+ readonly schema: S;
18
+ /** Content column (stored as markdown body) */
19
+ readonly contentColumn: keyof S | undefined;
20
+ /** Access to the registry */
21
+ readonly registry: Registry;
22
+ /** Access to the columns manager */
23
+ readonly columns: ColumnsManager<S>;
24
+ /** Insert a record, returns the ID */
25
+ insert(data: PartialRecord<S>): string;
26
+ /** Insert multiple records, returns array of IDs */
27
+ insertMany(records: PartialRecord<S>[]): string[];
28
+ /** Get a record by ID */
29
+ get(id: string): RecordWithMeta<S> | undefined;
30
+ /** Get all records */
31
+ all(): RecordWithMeta<S>[];
32
+ /** Find records matching a filter */
33
+ find(filter: FilterFn<S>): RecordWithMeta<S>[];
34
+ /** Find first record matching a filter */
35
+ findOne(filter: FilterFn<S>): RecordWithMeta<S> | undefined;
36
+ /** Update a record by ID */
37
+ update(id: string, data: Partial<SchemaToRecord<S>>): boolean;
38
+ /** Update a specific field of a record */
39
+ updateField<K extends keyof S>(id: string, field: K, value: SchemaToRecord<S>[K]): boolean;
40
+ /** Update multiple records matching a filter */
41
+ updateMany(filter: FilterFn<S>, data: Partial<SchemaToRecord<S>>): number;
42
+ /** Delete a record by ID */
43
+ delete(id: string): boolean;
44
+ /** Delete multiple records matching a filter */
45
+ deleteMany(filter: FilterFn<S>): number;
46
+ /** Count records (optionally filtered) */
47
+ count(filter?: FilterFn<S>): number;
48
+ /** Get a reactive count */
49
+ readonly reactiveCount: DerivedSignal<number>;
50
+ /** Get record by array index (not ID) */
51
+ getByIndex(index: number): RecordWithMeta<S> | undefined;
52
+ /** Get all allocated indices */
53
+ getIndices(): number[];
54
+ /** Check if record exists */
55
+ has(id: string): boolean;
56
+ /** Check if a record's embedding is stale */
57
+ isStale(id: string): boolean;
58
+ /** Get all IDs with stale embeddings */
59
+ getStaleIds(): string[];
60
+ /** Manually set stale flag */
61
+ setStale(id: string, stale: boolean): void;
62
+ /** Clear all data */
63
+ clear(): void;
64
+ }
65
+ export declare function createCollection<S extends SchemaDefinition>(name: string, options: CollectionOptions<S>): Collection<S>;
66
+ //# sourceMappingURL=collection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../../src/core/collection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Columns Manager
3
+ *
4
+ * Manages parallel reactive arrays for each schema column.
5
+ * This is the heart of the Father State Pattern - each field
6
+ * gets its own array, indexed by the registry.
7
+ */
8
+ import { type WritableSignal } from '@rlabs-inc/signals';
9
+ import type { SchemaDefinition, SchemaToRecord, ParsedColumnType, ParsedSchema } from './types';
10
+ /**
11
+ * Parse a column type string into structured info
12
+ */
13
+ export declare function parseColumnType(type: string): ParsedColumnType;
14
+ /**
15
+ * Get default value for a column type
16
+ */
17
+ export declare function getDefaultValue(type: string): unknown;
18
+ /**
19
+ * Parse a schema definition into structured info
20
+ */
21
+ export declare function parseSchema<S extends SchemaDefinition>(definition: S): ParsedSchema<S>;
22
+ export interface ColumnsManager<S extends SchemaDefinition> {
23
+ /** Get the parsed schema */
24
+ readonly schema: ParsedSchema<S>;
25
+ /** Get a raw column array (reactive signal) */
26
+ getColumn<K extends keyof S>(name: K): WritableSignal<unknown[]>;
27
+ /** Get a value from a column at an index */
28
+ get<K extends keyof S>(column: K, index: number): SchemaToRecord<S>[K];
29
+ /** Set a value in a column at an index */
30
+ set<K extends keyof S>(column: K, index: number, value: SchemaToRecord<S>[K]): void;
31
+ /** Get all column values for an index as a record */
32
+ getRecord(index: number): SchemaToRecord<S>;
33
+ /** Set all column values for an index from a record */
34
+ setRecord(index: number, record: Partial<SchemaToRecord<S>>): void;
35
+ /** Clear all values at an index (reset to defaults) */
36
+ clearAt(index: number): void;
37
+ /** Reset all columns (for testing/cleanup) */
38
+ reset(): void;
39
+ }
40
+ /**
41
+ * Create a columns manager for a schema
42
+ *
43
+ * Each column is a reactive signal containing an array.
44
+ * Fine-grained updates: modifying arr[i] triggers only effects reading that index.
45
+ */
46
+ export declare function createColumns<S extends SchemaDefinition>(definition: S): ColumnsManager<S>;
47
+ //# sourceMappingURL=columns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"columns.d.ts","sourceRoot":"","sources":["../../src/core/columns.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAU,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAO/F;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAkB9D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAoBrD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,gBAAgB,EAAE,UAAU,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAoBtF;AAMD,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,gBAAgB;IACxD,4BAA4B;IAC5B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,CAAA;IAEhC,+CAA+C;IAC/C,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,cAAc,CAAC,OAAO,EAAE,CAAC,CAAA;IAEhE,4CAA4C;IAC5C,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAEtE,0CAA0C;IAC1C,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;IAEnF,qDAAqD;IACrD,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAA;IAE3C,uDAAuD;IACvD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;IAElE,uDAAuD;IACvD,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAE5B,8CAA8C;IAC9C,KAAK,IAAI,IAAI,CAAA;CACd;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,gBAAgB,EACtD,UAAU,EAAE,CAAC,GACZ,cAAc,CAAC,CAAC,CAAC,CA6FnB"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * fsDB Constants
3
+ */
4
+ /** Default values for column types */
5
+ export declare const DEFAULT_VALUES: {
6
+ readonly string: "";
7
+ readonly number: 0;
8
+ readonly boolean: false;
9
+ readonly timestamp: 0;
10
+ readonly 'string[]': string[];
11
+ readonly 'number[]': number[];
12
+ readonly vector: Float32Array | null;
13
+ };
14
+ /** File watcher debounce time in ms */
15
+ export declare const WATCHER_DEBOUNCE_MS = 100;
16
+ /** Time to wait after save before allowing file watcher to process (prevents loops) */
17
+ export declare const SAVE_GRACE_PERIOD_MS = 200;
18
+ /** Default page size for pagination */
19
+ export declare const DEFAULT_PAGE_SIZE = 20;
20
+ /** Maximum iterations for stale detection loop */
21
+ export declare const MAX_STALE_CHECK_ITERATIONS = 10000;
22
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/core/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,sCAAsC;AACtC,eAAO,MAAM,cAAc;;;;;yBAKP,MAAM,EAAE;yBACR,MAAM,EAAE;qBACV,YAAY,GAAG,IAAI;CAC3B,CAAA;AAEV,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,MAAM,CAAA;AAEtC,uFAAuF;AACvF,eAAO,MAAM,oBAAoB,MAAM,CAAA;AAEvC,uCAAuC;AACvC,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAEnC,kDAAkD;AAClD,eAAO,MAAM,0BAA0B,QAAQ,CAAA"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Database
3
+ *
4
+ * The top level of the fractal - manages collections.
5
+ * Each database is a FatherState at the macro level.
6
+ */
7
+ import { type Collection } from './collection';
8
+ import type { SchemaDefinition, CollectionOptions, DatabaseOptions, FileChangeEvent } from './types';
9
+ import type { VectorSearchOptions, VectorSearchResult } from './types';
10
+ export interface PersistentCollection<S extends SchemaDefinition> extends Collection<S> {
11
+ /** Load all records from disk */
12
+ load(): Promise<number>;
13
+ /** Save all records to disk */
14
+ save(): Promise<number>;
15
+ /** Start file watching */
16
+ startWatching(): void;
17
+ /** Stop file watching */
18
+ stopWatching(): void;
19
+ /** Is currently watching */
20
+ readonly isWatching: boolean;
21
+ /** Register callback for file changes */
22
+ onFileChange(callback: (event: FileChangeEvent<S>) => void | Promise<void>): () => void;
23
+ /** Set embedding with content hash for stale detection */
24
+ setEmbedding(id: string, column: keyof S, embedding: Float32Array | number[], sourceContent: string): void;
25
+ /** Vector similarity search */
26
+ search(column: keyof S, queryVector: Float32Array | number[], options?: VectorSearchOptions<S>): VectorSearchResult<S>[];
27
+ /** Close the collection (stop watching, cleanup) */
28
+ close(): void;
29
+ }
30
+ /**
31
+ * Create a persistent collection with file I/O and watching
32
+ */
33
+ export declare function createPersistentCollection<S extends SchemaDefinition>(name: string, options: CollectionOptions<S> & {
34
+ path: string;
35
+ }): PersistentCollection<S>;
36
+ export interface Database {
37
+ /** Database name */
38
+ readonly name: string;
39
+ /** Base path for collections */
40
+ readonly basePath: string;
41
+ /** Get or create a collection */
42
+ collection<S extends SchemaDefinition>(name: string, options: Omit<CollectionOptions<S>, 'path'>): PersistentCollection<S>;
43
+ /** Get an existing collection */
44
+ getCollection<S extends SchemaDefinition>(name: string): PersistentCollection<S> | undefined;
45
+ /** List all collection names */
46
+ listCollections(): string[];
47
+ /** Close all collections */
48
+ close(): void;
49
+ }
50
+ /**
51
+ * Create a database instance
52
+ */
53
+ export declare function createDatabase(options?: DatabaseOptions): Database;
54
+ export { createDatabase as fsDB };
55
+ //# sourceMappingURL=database.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/core/database.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,eAAe,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAA;AAUpH,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAMtE,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,gBAAgB,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAC;IACrF,iCAAiC;IACjC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IAEvB,+BAA+B;IAC/B,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IAEvB,0BAA0B;IAC1B,aAAa,IAAI,IAAI,CAAA;IAErB,yBAAyB;IACzB,YAAY,IAAI,IAAI,CAAA;IAEpB,4BAA4B;IAC5B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAE5B,yCAAyC;IACzC,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,CAAA;IAEvF,0DAA0D;IAC1D,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,EAAE,YAAY,GAAG,MAAM,EAAE,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAE1G,+BAA+B;IAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,YAAY,GAAG,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAA;IAExH,oDAAoD;IACpD,KAAK,IAAI,IAAI,CAAA;CACd;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,SAAS,gBAAgB,EACnE,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/C,oBAAoB,CAAC,CAAC,CAAC,CA8QzB;AAMD,MAAM,WAAW,QAAQ;IACvB,oBAAoB;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IAEzB,iCAAiC;IACjC,UAAU,CAAC,CAAC,SAAS,gBAAgB,EACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,GAC1C,oBAAoB,CAAC,CAAC,CAAC,CAAA;IAE1B,iCAAiC;IACjC,aAAa,CAAC,CAAC,SAAS,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,oBAAoB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAA;IAE5F,gCAAgC;IAChC,eAAe,IAAI,MAAM,EAAE,CAAA;IAE3B,4BAA4B;IAC5B,KAAK,IAAI,IAAI,CAAA;CACd;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,eAAoB,GAAG,QAAQ,CA0DtE;AAGD,OAAO,EAAE,cAAc,IAAI,IAAI,EAAE,CAAA"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * FatherState Registry
3
+ *
4
+ * The central index manager for the Father State Pattern.
5
+ * Manages ID-to-index mappings with O(1) lookups and index reuse.
6
+ *
7
+ * Uses reactive primitives from @rlabs-inc/signals for fine-grained reactivity.
8
+ */
9
+ import { ReactiveMap, ReactiveSet } from '@rlabs-inc/signals';
10
+ export interface Registry {
11
+ /** Map from record ID to array index */
12
+ readonly idToIndex: ReactiveMap<string, number>;
13
+ /** Map from array index to record ID */
14
+ readonly indexToId: ReactiveMap<number, string>;
15
+ /** Set of currently allocated indices */
16
+ readonly allocatedIndices: ReactiveSet<number>;
17
+ /** Array of free indices available for reuse */
18
+ readonly freeIndices: number[];
19
+ /** Next index to allocate if no free indices */
20
+ nextIndex: number;
21
+ /** Allocate an index for an ID (reuses free indices) */
22
+ allocate(id: string): number;
23
+ /** Release an index, returning it to the free pool */
24
+ release(id: string): boolean;
25
+ /** Get index for an ID (returns -1 if not found) */
26
+ getIndex(id: string): number;
27
+ /** Get ID for an index (returns undefined if not found) */
28
+ getId(index: number): string | undefined;
29
+ /** Check if a record exists */
30
+ has(id: string): boolean;
31
+ /** Get all record IDs */
32
+ getAllIds(): string[];
33
+ /** Get all allocated indices */
34
+ getAllIndices(): number[];
35
+ /** Get count of records */
36
+ readonly count: number;
37
+ /** Reset the registry (for testing/cleanup) */
38
+ reset(): void;
39
+ }
40
+ /**
41
+ * Create a new FatherState registry instance
42
+ *
43
+ * Each registry is independent - no global state.
44
+ */
45
+ export declare function createRegistry(): Registry;
46
+ /**
47
+ * Generate a unique ID
48
+ *
49
+ * Format: timestamp-random (e.g., "1703289600000-a1b2c3")
50
+ */
51
+ export declare function generateId(): string;
52
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/core/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,WAAW,EAAU,MAAM,oBAAoB,CAAA;AAErE,MAAM,WAAW,QAAQ;IACvB,wCAAwC;IACxC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAE/C,wCAAwC;IACxC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAE/C,yCAAyC;IACzC,QAAQ,CAAC,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAE9C,gDAAgD;IAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,CAAA;IAE9B,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAA;IAEjB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IAE5B,sDAAsD;IACtD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAA;IAE5B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IAE5B,2DAA2D;IAC3D,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IAExC,+BAA+B;IAC/B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAA;IAExB,yBAAyB;IACzB,SAAS,IAAI,MAAM,EAAE,CAAA;IAErB,gCAAgC;IAChC,aAAa,IAAI,MAAM,EAAE,CAAA;IAEzB,2BAA2B;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IAEtB,+CAA+C;IAC/C,KAAK,IAAI,IAAI,CAAA;CACd;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,QAAQ,CAkGzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAInC"}