foldlitedb 0.1.0__tar.gz

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.
Files changed (88) hide show
  1. foldlitedb-0.1.0/DESIGN.md +190 -0
  2. foldlitedb-0.1.0/LICENSE +21 -0
  3. foldlitedb-0.1.0/PKG-INFO +239 -0
  4. foldlitedb-0.1.0/README.md +218 -0
  5. foldlitedb-0.1.0/docs/api.html +197 -0
  6. foldlitedb-0.1.0/docs/concepts.html +79 -0
  7. foldlitedb-0.1.0/docs/index.html +67 -0
  8. foldlitedb-0.1.0/docs/layout.html +40 -0
  9. foldlitedb-0.1.0/docs/pages.json +8 -0
  10. foldlitedb-0.1.0/docs/playground.html +67 -0
  11. foldlitedb-0.1.0/docs/portable.html +225 -0
  12. foldlitedb-0.1.0/docs/releasing.html +35 -0
  13. foldlitedb-0.1.0/docs/static/favicon.svg +1 -0
  14. foldlitedb-0.1.0/docs/static/playground.css +2 -0
  15. foldlitedb-0.1.0/docs/static/playground.js +132 -0
  16. foldlitedb-0.1.0/docs/static/site.js +83 -0
  17. foldlitedb-0.1.0/docs/static/styles.css +6 -0
  18. foldlitedb-0.1.0/examples/catalogue.py +76 -0
  19. foldlitedb-0.1.0/examples/portable_metrics.py +37 -0
  20. foldlitedb-0.1.0/examples/telemetry.py +119 -0
  21. foldlitedb-0.1.0/native/Cargo.lock +752 -0
  22. foldlitedb-0.1.0/native/Cargo.toml +7 -0
  23. foldlitedb-0.1.0/native/engine/Cargo.toml +21 -0
  24. foldlitedb-0.1.0/native/engine/src/lib.rs +940 -0
  25. foldlitedb-0.1.0/native/engine/src/runtime.rs +201 -0
  26. foldlitedb-0.1.0/native/engine/src/storage.rs +440 -0
  27. foldlitedb-0.1.0/native/engine/tests/native_storage.rs +405 -0
  28. foldlitedb-0.1.0/native/engine/tests/portable.rs +527 -0
  29. foldlitedb-0.1.0/native/engine/tests/ux.rs +339 -0
  30. foldlitedb-0.1.0/native/licenses/wasmi/LICENSE-APACHE +201 -0
  31. foldlitedb-0.1.0/native/licenses/wasmi/LICENSE-MIT +25 -0
  32. foldlitedb-0.1.0/native/licenses/wasmi/SOURCES.txt +4 -0
  33. foldlitedb-0.1.0/native/licenses/wasmparser/LICENSE-APACHE +201 -0
  34. foldlitedb-0.1.0/native/licenses/wasmparser/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
  35. foldlitedb-0.1.0/native/licenses/wasmparser/LICENSE-MIT +23 -0
  36. foldlitedb-0.1.0/native/licenses/wasmparser/SOURCES.txt +5 -0
  37. foldlitedb-0.1.0/native/python/Cargo.toml +18 -0
  38. foldlitedb-0.1.0/native/python/src/lib.rs +44 -0
  39. foldlitedb-0.1.0/pyproject.toml +47 -0
  40. foldlitedb-0.1.0/scripts/benchmark_database.py +153 -0
  41. foldlitedb-0.1.0/scripts/build_docs.py +105 -0
  42. foldlitedb-0.1.0/scripts/build_native_notices.py +78 -0
  43. foldlitedb-0.1.0/scripts/check_docs.py +154 -0
  44. foldlitedb-0.1.0/scripts/publish_docs.py +148 -0
  45. foldlitedb-0.1.0/src/foldlitedb/NATIVE_THIRD_PARTY_LICENSES.txt +11822 -0
  46. foldlitedb-0.1.0/src/foldlitedb/__init__.py +25 -0
  47. foldlitedb-0.1.0/src/foldlitedb/database.py +305 -0
  48. foldlitedb-0.1.0/src/foldlitedb/fileio.py +162 -0
  49. foldlitedb-0.1.0/src/foldlitedb/portable.py +7 -0
  50. foldlitedb-0.1.0/src/foldlitedb/portable_assets/THIRD_PARTY_LICENSES.txt +407 -0
  51. foldlitedb-0.1.0/src/foldlitedb/portable_assets/catalogue.json +12 -0
  52. foldlitedb-0.1.0/src/foldlitedb/portable_assets/catalogue.wasm +0 -0
  53. foldlitedb-0.1.0/src/foldlitedb/portable_assets/client.js +97 -0
  54. foldlitedb-0.1.0/src/foldlitedb/portable_assets/errors.js +40 -0
  55. foldlitedb-0.1.0/src/foldlitedb/portable_assets/host.js +201 -0
  56. foldlitedb-0.1.0/src/foldlitedb/portable_assets/metrics.json +12 -0
  57. foldlitedb-0.1.0/src/foldlitedb/portable_assets/metrics.wasm +0 -0
  58. foldlitedb-0.1.0/src/foldlitedb/portable_assets/worker.js +206 -0
  59. foldlitedb-0.1.0/src/foldlitedb/projection.py +70 -0
  60. foldlitedb-0.1.0/src/foldlitedb/streaming.py +135 -0
  61. foldlitedb-0.1.0/src/foldlitedb/streams.py +139 -0
  62. foldlitedb-0.1.0/src/foldlitedb/types.py +85 -0
  63. foldlitedb-0.1.0/tests/browser/test_portable_browser.py +350 -0
  64. foldlitedb-0.1.0/tests/test_add.py +117 -0
  65. foldlitedb-0.1.0/tests/test_convenience_api.py +103 -0
  66. foldlitedb-0.1.0/tests/test_database.py +206 -0
  67. foldlitedb-0.1.0/tests/test_docs_publishing.py +54 -0
  68. foldlitedb-0.1.0/tests/test_fileio.py +41 -0
  69. foldlitedb-0.1.0/tests/test_native_cache.py +156 -0
  70. foldlitedb-0.1.0/tests/test_portable.py +14 -0
  71. foldlitedb-0.1.0/tests/test_streaming.py +40 -0
  72. foldlitedb-0.1.0/tests/test_streams.py +103 -0
  73. foldlitedb-0.1.0/tests/test_ux.py +182 -0
  74. foldlitedb-0.1.0/views/.gitignore +2 -0
  75. foldlitedb-0.1.0/views/README.md +191 -0
  76. foldlitedb-0.1.0/views/assembly/examples/catalogue.ts +69 -0
  77. foldlitedb-0.1.0/views/assembly/examples/metrics.ts +83 -0
  78. foldlitedb-0.1.0/views/assembly/sdk/index.ts +195 -0
  79. foldlitedb-0.1.0/views/assembly/sdk/number.ts +168 -0
  80. foldlitedb-0.1.0/views/assembly/sdk/parse.ts +88 -0
  81. foldlitedb-0.1.0/views/package-lock.json +87 -0
  82. foldlitedb-0.1.0/views/package.json +20 -0
  83. foldlitedb-0.1.0/views/scripts/build-view.mjs +77 -0
  84. foldlitedb-0.1.0/views/scripts/build.mjs +53 -0
  85. foldlitedb-0.1.0/views/scripts/inspect-view.mjs +30 -0
  86. foldlitedb-0.1.0/views/scripts/view-build.mjs +60 -0
  87. foldlitedb-0.1.0/views/test/custom-build.test.mjs +136 -0
  88. foldlitedb-0.1.0/views/test/modules.test.mjs +224 -0
@@ -0,0 +1,190 @@
1
+ # Foldlite DB design
2
+
3
+ Foldlite DB derives live views from immutable documents. Its computation model is
4
+ inspired by CouchDB: map each document into keyed contributions, then reduce each
5
+ key. Its persistence model is a Rust append-only document log with a disposable
6
+ cache. Python exposes that engine directly. One compiled WebAssembly module
7
+ contains the full map and reducer, and runs unchanged in the native host and a
8
+ browser worker.
9
+
10
+ ## Separate facts from derived state
11
+
12
+ The document log is the persistent source for this engine. Accepted documents are
13
+ immutable. An identical replay is a no-op; conflicting reuse of an identity or
14
+ source position is an error. Changes in a domain are new documents, rather than
15
+ mutations of old observations.
16
+
17
+ Document indexes, source checkpoints, group states, finalized results, and change
18
+ history are derived from those facts and the exact view module. They can be
19
+ reconstructed. A cache is useful only while its dependencies can be verified:
20
+ format version, document-log identity, committed prefix and digest, and compiled
21
+ module digest. Missing, corrupt, or incompatible cache data cannot silently
22
+ become authoritative.
23
+
24
+ Applications must back up the document log and computation artifacts, or retain
25
+ an external authoritative document stream with stable identities and order.
26
+ Deleting a cache is safe; deleting the only copy of the log is data loss. A
27
+ reference version is insufficient to reproduce a lost reference snapshot; any
28
+ reference data needed by a deterministic view must remain reproducible.
29
+
30
+ ## Native durability
31
+
32
+ `native/engine` owns storage and Wasm execution. The path passed to `Database`
33
+ is a log file; `.cache` and `.lock` siblings hold an optional cache checkpoint and
34
+ an operating-system lock. One active owner may open a persistent log. `:memory:`
35
+ uses the same computation without persistent files or durable acknowledgements.
36
+
37
+ The log has an identity and checksummed, hash-linked frames containing accepted
38
+ document batches. The source format does not contain a view-module dependency.
39
+ An ingestion operation validates identities, source order, limits, mapping, and
40
+ reduction before writing. The engine appends the complete frame and syncs it
41
+ before acknowledging the batch, then exposes its already-computed derived state.
42
+ A failed validation or view operation cannot commit a batch prefix. An I/O error
43
+ can leave commit outcome uncertain, as with any durable append; reopen and retry
44
+ using explicit document identities rather than assuming a failed acknowledgement
45
+ means absence.
46
+
47
+ Opening verifies the complete log. An incomplete final frame can be discarded
48
+ as an interrupted append. A complete frame with corrupt checksums or invalid
49
+ content is an error; the engine does not skip committed source data. Frame size
50
+ is bounded to 8 MiB. The ordinary map-request limit is smaller and often limits a
51
+ batch first.
52
+
53
+ `checkpoint()` writes an atomic derived snapshot, and graceful `close()` also
54
+ checkpoints. Checkpoints are not written after every ingestion. Encoded cache
55
+ snapshots are limited to 1 GiB. Checkpoint errors, including on close, are reported;
56
+ they do not undo already-synced document batches. A failed close still releases
57
+ the native handle. A valid snapshot
58
+ must match the log identity, exact module digest, and verified prefix. The host
59
+ loads it and replays only the original suffix batches through Wasm. A missing,
60
+ invalid, or different-module cache triggers a full rebuild from the log.
61
+
62
+ A checkpoint is a performance hint: failure or absence must not change the
63
+ meaning of accepted documents. Its checksum and verified log prefix detect
64
+ accidental corruption and stale files; they are not an authenticity guarantee
65
+ against an adversary who can rewrite local files and checksums.
66
+
67
+ ## Rebuild as the ordinary computation path
68
+
69
+ Live ingestion and replay use the same map, grouping, ordered fold, and finalizer.
70
+ Rebuild preserves accepted document order and original batch boundaries. That
71
+ also preserves batch-dependent engine sequence allocation; a separate export and
72
+ re-import with different boundaries need only preserve view state and results.
73
+
74
+ `rebuild()` discards derived state, replays the log, creates a new cursor
75
+ generation, and checkpoints it. Opening the same log with a changed module does
76
+ the same reconstruction automatically. Neither operation rewrites documents.
77
+ An unchanged valid cache preserves its generation across reopen; cursor consumers
78
+ must obtain a new snapshot after reconstruction.
79
+
80
+ Rebuild cost is a linear read of the log plus Wasm work for uncached documents
81
+ and their emissions. A valid cache avoids Wasm execution for its verified prefix,
82
+ but opening still reads that prefix to validate it. There is no unmeasured
83
+ throughput promise. Current documents, indexes, grouped states, and change history
84
+ grow in host memory; bounded per-call Wasm memory is not a total-memory bound.
85
+ Compaction, incremental checkpoint formats, and multiple native views remain
86
+ future work rather than implicit capabilities.
87
+
88
+ ## Identity and order
89
+
90
+ The public input is a `Document(id, payload, position, source)`. Payloads are
91
+ finite JSON. `add` can generate IDs and source positions; `add_many` resolves one
92
+ atomic batch. Generated IDs are stable only after resolution, so an unnamed
93
+ retry creates another observation. External sources can provide their own IDs
94
+ and monotonically increasing positions with gaps. Positions start at zero when
95
+ assigned automatically and are scoped to a source.
96
+
97
+ An identical named append reuses its original position even after later records
98
+ arrive. A supplied source position can also identify a replay without a supplied
99
+ ID. Reusing an ID or position with different content fails. Previously unseen
100
+ positions behind the source checkpoint fail. Source progress and document
101
+ acceptance are one logical commit.
102
+
103
+ Per-key reduction follows ingestion order, including within a batch. Source
104
+ positions do not sort documents across sources, and timestamps do not reorder a
105
+ fold. Applications choose domain order before ingestion. Maps receive payloads;
106
+ identity, deduplication, positions, and transaction boundaries belong to the host.
107
+ Any domain metadata a view needs belongs in its explicit payload or declared
108
+ reproducible module data.
109
+
110
+ ## Portable computation
111
+
112
+ A view exports `descriptor`, `map`, `identity`, `add`, `finalize`, and optionally
113
+ `merge` through ABI v1. AssemblyScript is the first SDK; the ABI does not require
114
+ that source language. A map returns zero or more JSON key/value contributions.
115
+ The host partitions by canonical keys and supplies ordered contributions to the
116
+ module's reducer.
117
+
118
+ State is explicit. Fresh Wasm instances prevent module globals from carrying
119
+ state between requests. Views must not depend on clocks, randomness, mutable
120
+ external data, or request boundaries. Splitting one ordered contribution sequence
121
+ across successive `add` calls must produce equivalent state. Implement `merge`
122
+ only when independently covered compatible states can be combined correctly;
123
+ an ordered lifecycle fold need not be mergeable.
124
+
125
+ The exact module SHA-256 binds cached state to computation, state schema, compiler,
126
+ and SDK runtime. Human-readable view and state versions explain changes but do
127
+ not override a changed digest. Preserve the previous module to reproduce its
128
+ previous result. Opening the same documents with another module is a new derived
129
+ view and invalidates old cursors.
130
+
131
+ Modules have no host imports. ABI requests and responses are limited to 256 KiB,
132
+ linear memory to 16 MiB, and native execution to a fuel budget. Ingestion is
133
+ bounded to 1024 documents and 4096 emissions per batch. JSON nesting is bounded
134
+ to 64 levels. Python releases the GIL while the native engine runs. These limits
135
+ isolate individual operations; they do not bound accumulated host storage.
136
+
137
+ JSON numbers follow finite IEEE-754 double semantics in both hosts. Python
138
+ rejects integers that cannot be represented exactly as doubles. Source positions
139
+ and engine sequences stay within JavaScript's nonnegative safe integer range.
140
+ Canonical object keys and numeric formatting agree across hosts. Floating-point
141
+ addition is not associative, so arbitrary merge trees can change rounding.
142
+
143
+ ## Reads and cursor generations
144
+
145
+ A snapshot identifies a database generation, module digest, and committed
146
+ sequence. Bounded pages retain a stable prefix while ingestion continues. Change
147
+ pages return complete resulting key states after a cursor, coalesced per key
148
+ within each batch. Zero-emission documents still advance progress. Sequence is
149
+ not a document count: inserted documents and changed group states allocate it.
150
+
151
+ `rows()` and `documents()` pin their prefix on first iteration and release the
152
+ engine between pages. No lock spans a yield into application code. The persistent
153
+ file's ownership lock is retained for the database lifetime. Cursors are local
154
+ continuation markers, not access credentials. Rebuild or module change starts a
155
+ new generation and explicitly expires old cursors. Current history is retained;
156
+ there is no compaction or reader-retention policy yet.
157
+
158
+ ## Python packaging and the browser
159
+
160
+ `Database` and `View` are the primary Python types; `PortableDatabase` and
161
+ `PortableView` are exact aliases. The package has one native storage and
162
+ computation path. `native/python` provides PyO3 bindings, and maturin builds the
163
+ extension into wheels. Source builds need Python 3.11+, Rust/Cargo 1.90+, and a C
164
+ toolchain. Bundled modules need no Node; authoring uses Node 20+.
165
+
166
+ The browser worker runs the same module bytes, with JavaScript owning grouping
167
+ and in-memory storage. It does not run the Rust log engine and has no persistent
168
+ browser storage yet. Export and replay resolved documents to preserve work.
169
+ Worker deadlines, retained-byte limits, and document/group/history caps bound
170
+ this preview's requests and retained data. A timeout discards its memory.
171
+
172
+ ## Acceptance and evidence
173
+
174
+ Correctness requires immutable-identity conflicts, source-order enforcement,
175
+ atomic batches, interrupted-tail recovery, complete-corruption rejection,
176
+ single-owner locking, checkpoint validation, suffix replay, module replacement,
177
+ and explicit rebuild. Rebuild and live execution must agree on structured keys,
178
+ ordered state, and results. Snapshot/change continuity survives a valid cache
179
+ reopen and fails explicitly after a new generation. Native and actual browser
180
+ execution must agree for the same module and supported numerical inputs.
181
+
182
+ Installation checks exercise built wheels without local compilers, and source
183
+ distributions with build prerequisites. PyPI publication remains pending. Future
184
+ performance evidence must record workload, fixture, batch size, documents,
185
+ emissions, key cardinality, host/runtime, durability, log/cache bytes, peak memory,
186
+ rebuild time, reopen validation time, and document-to-query latency. Run `python scripts/benchmark_database.py --documents 10000 --keys 16
187
+ --batch-size 100 --live-documents 20 --output native-benchmark.json` to produce a
188
+ reproducible local report; `--storage-root` selects the measured filesystem. The
189
+ design promises reconstructibility and a clear cost model, rather than an
190
+ unmeasured speedup.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 255BITS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,239 @@
1
+ Metadata-Version: 2.4
2
+ Name: foldlitedb
3
+ Version: 0.1.0
4
+ Requires-Dist: playwright>=1.58,<2 ; extra == 'browser'
5
+ Requires-Dist: build>=1,<2 ; extra == 'dev'
6
+ Requires-Dist: maturin>=1.9,<2 ; extra == 'dev'
7
+ Requires-Dist: pytest>=8,<10 ; extra == 'dev'
8
+ Requires-Dist: pytest>=8,<10 ; extra == 'test'
9
+ Provides-Extra: browser
10
+ Provides-Extra: dev
11
+ Provides-Extra: test
12
+ License-File: LICENSE
13
+ Summary: Immutable document logs and rebuildable views with Rust and portable WebAssembly.
14
+ License-Expression: MIT
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
17
+ Project-URL: Documentation, https://255bits.github.io/foldlitedb/
18
+ Project-URL: Issues, https://github.com/255BITS/foldlitedb/issues
19
+ Project-URL: Repository, https://github.com/255BITS/foldlitedb
20
+
21
+ # Foldlite DB
22
+
23
+ **Immutable documents. Rebuildable live views. Rust and WebAssembly.**
24
+
25
+ Foldlite DB is a CouchDB-inspired map/reduce engine built around a simple rule:
26
+ keep immutable documents, derive everything else. The Rust engine appends accepted
27
+ document batches to a durable log. Indexes, source checkpoints, group states, and
28
+ change history form a disposable cache that can be reconstructed from that log.
29
+ There is one native implementation, exposed to Python; storage needs no database
30
+ server or external database library.
31
+
32
+ Write a view in AssemblyScript and compile it to WebAssembly. The same module
33
+ maps and reduces documents in Rust or entirely in a browser worker. Try the
34
+ [browser playground](https://255bits.github.io/foldlitedb/playground.html) or read
35
+ the [view authoring guide](docs/portable.html).
36
+
37
+ ## Install
38
+
39
+ Use Python 3.11+ to install the 0.1.0 release:
40
+
41
+ ```sh
42
+ python -m pip install foldlitedb==0.1.0
43
+ ```
44
+
45
+ Native wheels are provided for Linux x86-64 and ARM64, macOS Intel and Apple
46
+ Silicon, and Windows x86-64. Installing a matching wheel needs neither Rust nor
47
+ Node. The [hosted documentation](https://255bits.github.io/foldlitedb/) describes
48
+ the API and bundled portable views.
49
+
50
+ ### Build from this checkout
51
+
52
+ Building from source requires Python 3.11+, Rust/Cargo 1.90+, and a platform C
53
+ compiler and linker. From the repository root:
54
+
55
+ ```sh
56
+ python -m venv .venv
57
+ . .venv/bin/activate
58
+ python -m pip install --upgrade pip
59
+ python -m pip install .
60
+ python examples/portable_metrics.py
61
+ ```
62
+
63
+ On Windows, activate with `.venv\Scripts\activate` and use an MSVC build
64
+ environment. Node.js is unnecessary for installation and bundled views. To make
65
+ a wheel, run `python -m pip wheel . --no-deps --wheel-dir dist`. Install that
66
+ wheel on a matching platform with `python -m pip install /path/to/the-built.whl`;
67
+ installing a built wheel needs neither Rust nor Node. See the
68
+ [release guide](docs/releasing.html) for PyPI Trusted Publisher setup and the
69
+ 0.1.0 publication workflow.
70
+
71
+ ## Append documents, read a live view
72
+
73
+ Start with a fresh `metrics.fold` file:
74
+
75
+ ```python
76
+ from foldlitedb import Database, View
77
+
78
+ view = View.bundled("metrics")
79
+ with Database("metrics.fold", view) as db:
80
+ documents = db.add_many([
81
+ {"metric": "loss", "value": 4},
82
+ {"metric": "loss", "value": 8},
83
+ ])
84
+ assert list(db.rows())[0].result == {
85
+ "count": 2, "sum": 12, "min": 4, "max": 8, "mean": 6,
86
+ }
87
+ snapshot = db.query()
88
+ documents += (db.add({"metric": "loss", "value": 3}),)
89
+ assert db.changes(since=snapshot.cursor).rows[0].result["mean"] == 5
90
+ retained = tuple(db.documents())
91
+ db.checkpoint()
92
+ ```
93
+
94
+ Rust executes both map and reduce through a bounded Wasm interpreter. `metrics`
95
+ groups finite numbers by a string `metric`; `catalogue` demonstrates an ordered
96
+ fold over structured events. Each database holds one view module. `Database` and
97
+ `View` are the primary Python names; `PortableDatabase` and `PortableView` are
98
+ aliases for the same types.
99
+
100
+ Try the [telemetry collector](examples/telemetry.py) for bulk import, retries, and
101
+ a persistent changes consumer, or the [catalogue example](examples/catalogue.py)
102
+ for an ordered item lifecycle. If a payload produces no rows, inspect its map
103
+ output with [`db.preview(payload)`](docs/api.html#preview).
104
+
105
+ Every unnamed append creates a new observation. Give an observation an ID when
106
+ identical retries should count once. IDs and positions are scoped to a source;
107
+ conflicting reuse raises an error. Documents cannot be updated or deleted. Append
108
+ new observations to represent changes, then let the view fold their history.
109
+
110
+ `add_many` commits one atomic batch of at most 1024 documents. Its complete Wasm
111
+ map request must fit 256 KiB, so larger payloads need smaller batches.
112
+ `rows()` follows a stable snapshot in bounded pages; `query()` and
113
+ `changes(since=...)` expose continuation cursors. See the
114
+ [Python API](docs/api.html#database) for signatures and limits.
115
+
116
+ ## A log you keep; a cache you can discard
117
+
118
+ For a persistent database, `metrics.fold` contains the immutable document log.
119
+ `metrics.fold.cache` is an optional checkpoint; `metrics.fold.lock` coordinates
120
+ one active owner. A batch is acknowledged after its log frame is synced. The
121
+ engine validates and computes a batch before appending it, so a map or reducer
122
+ failure does not commit a prefix.
123
+
124
+ `checkpoint()` writes an atomic cache snapshot; closing cleanly does the same.
125
+ On reopen, the engine verifies the log and matching cache prefix, then runs Wasm
126
+ only for batches after the checkpoint. Missing, corrupt, or incompatible caches
127
+ are rebuilt from the documents. `rebuild()` explicitly recomputes the view and
128
+ starts a new cursor generation. Neither operation changes the source documents.
129
+
130
+ The document log is independent of the view module. Opening it with different
131
+ module bytes rebuilds the cache in place; retain the exact module if you need to
132
+ reproduce its previous results. Close the database before backing up the log and
133
+ view artifacts; Windows locks prevent other handles from reading an active log.
134
+ The cache is replaceable; the only copy of a document log is not.
135
+
136
+ Rebuild uses the original batch order. It scans the log linearly and runs each
137
+ uncached batch through the same map/reduce path as live ingestion. A valid cache
138
+ avoids repeated Wasm work, but reopening still verifies the complete log. Current
139
+ indexes and history grow in memory with the data. Cache snapshots are limited to
140
+ 1 GiB of encoded checkpoint data; a checkpoint or close error reports a failure
141
+ to save that accelerator, while committed log data remains intact. There is no
142
+ claim of constant memory or measured rebuild throughput.
143
+
144
+ To copy retained records into a fresh database:
145
+
146
+ ```python
147
+ with Database("copy.fold", view) as copied:
148
+ copied.ingest(retained)
149
+ assert list(copied.rows())[0].result["mean"] == 5
150
+ before = [(r.key, r.state, r.result) for r in copied.rows()]
151
+ copied.rebuild()
152
+ assert [(r.key, r.state, r.result) for r in copied.rows()] == before
153
+ ```
154
+
155
+ Use `ingest_many(records, batch_size=100)` for larger archives. **Iterate its
156
+ results to perform the import**: each yielded result commits one separate bounded
157
+ batch, and earlier batches remain committed if a later batch fails. See the
158
+ [retry-safe source import](docs/api.html#bulk-import) for constructing records with
159
+ upstream positions or stable IDs alone. Preserve record order; preserve original
160
+ batch boundaries too if you need identical engine sequence values.
161
+
162
+ ## Author a view, run it on a website
163
+
164
+ The [AssemblyScript SDK](views/README.md) provides JSON helpers and the Wasm ABI.
165
+ Implement `map`, `identity`, `add`, and `finalize`; implement `merge` only for
166
+ states that can be combined correctly. Inputs and state are explicit, and views
167
+ must be deterministic. An ordered fold must give equivalent state when the same
168
+ contributions are split across successive batches.
169
+
170
+ Rebuild the two bundled examples with Node.js 20+:
171
+
172
+ ```sh
173
+ npm ci --prefix views
174
+ npm run build --prefix views
175
+ npm test --prefix views
176
+ npm run check --prefix views
177
+ ```
178
+
179
+ The build writes modules and SHA-256 manifests into
180
+ `src/foldlitedb/portable_assets/`. It builds the named `metrics` and `catalogue`
181
+ examples. Compile a custom view and generate its manifest with:
182
+
183
+ ```sh
184
+ npm run build:view --prefix views -- assembly/examples/my-view.ts --out-dir .build
185
+ ```
186
+
187
+ Both paths are relative to `views/`. The
188
+ [authoring guide](docs/portable.html#authoring) includes a complete custom view
189
+ and the build command to generate its Wasm and digest manifest.
190
+
191
+ `foldlitedb.portable_assets()` exposes compiled modules plus `client.js`,
192
+ `worker.js`, and `host.js`. Copy them to a static site and import
193
+ `BrowserDatabase` from `client.js`. The [browser example](docs/portable.html#browser)
194
+ covers appends, snapshots, changes, export, and replay verification. JavaScript
195
+ owns in-memory grouping and storage in the browser worker; the Rust log engine
196
+ is not compiled into this browser build.
197
+
198
+ Browser state is lost on reload or close. Export resolved documents first. The
199
+ preview bounds documents, groups, history, and request sizes. Persistent browser
200
+ storage, log/history compaction, and multiple views per native database remain
201
+ future work. JSON numbers use IEEE-754 double precision; use strings for exact
202
+ arbitrary-precision identifiers.
203
+
204
+ ## Development and documentation
205
+
206
+ ```sh
207
+ python -m pip install '.[dev,browser]'
208
+ python -m playwright install chromium
209
+ python -m pytest
210
+ python -m pytest tests/browser
211
+ cargo test --locked --manifest-path native/Cargo.toml -p foldlitedb-portable-engine
212
+ npm ci --prefix views
213
+ npm test --prefix views
214
+ npm run check --prefix views
215
+ python scripts/build_docs.py
216
+ python scripts/check_docs.py --execute --browser
217
+ python -m http.server 8000 --directory _site
218
+ ```
219
+
220
+ The docs checker executes annotated Python examples in fresh temporary
221
+ directories and checks links, search, and desktop/mobile rendering in Chromium.
222
+ Open `http://localhost:8000` for the built docs and playground. An authorized
223
+ maintainer can publish checked generated docs with
224
+ `python scripts/publish_docs.py`; building alone does not publish them.
225
+
226
+ Measure the native path on your target filesystem with:
227
+
228
+ ```sh
229
+ python scripts/benchmark_database.py --documents 10000 --keys 16 --batch-size 100 --live-documents 20 --output native-benchmark.json
230
+ ```
231
+
232
+ Use `--storage-root /path/to/filesystem` to select the filesystem being measured.
233
+ The report compares durable ingestion, checkpointing, cached reopen, suffix
234
+ replay, and full reconstruction, while checking unchanged log bytes and matching
235
+ results. Local measurements describe that fixture and host.
236
+
237
+ See [DESIGN.md](DESIGN.md) for durability, replay, cache validation, and execution
238
+ contracts.
239
+
@@ -0,0 +1,218 @@
1
+ # Foldlite DB
2
+
3
+ **Immutable documents. Rebuildable live views. Rust and WebAssembly.**
4
+
5
+ Foldlite DB is a CouchDB-inspired map/reduce engine built around a simple rule:
6
+ keep immutable documents, derive everything else. The Rust engine appends accepted
7
+ document batches to a durable log. Indexes, source checkpoints, group states, and
8
+ change history form a disposable cache that can be reconstructed from that log.
9
+ There is one native implementation, exposed to Python; storage needs no database
10
+ server or external database library.
11
+
12
+ Write a view in AssemblyScript and compile it to WebAssembly. The same module
13
+ maps and reduces documents in Rust or entirely in a browser worker. Try the
14
+ [browser playground](https://255bits.github.io/foldlitedb/playground.html) or read
15
+ the [view authoring guide](docs/portable.html).
16
+
17
+ ## Install
18
+
19
+ Use Python 3.11+ to install the 0.1.0 release:
20
+
21
+ ```sh
22
+ python -m pip install foldlitedb==0.1.0
23
+ ```
24
+
25
+ Native wheels are provided for Linux x86-64 and ARM64, macOS Intel and Apple
26
+ Silicon, and Windows x86-64. Installing a matching wheel needs neither Rust nor
27
+ Node. The [hosted documentation](https://255bits.github.io/foldlitedb/) describes
28
+ the API and bundled portable views.
29
+
30
+ ### Build from this checkout
31
+
32
+ Building from source requires Python 3.11+, Rust/Cargo 1.90+, and a platform C
33
+ compiler and linker. From the repository root:
34
+
35
+ ```sh
36
+ python -m venv .venv
37
+ . .venv/bin/activate
38
+ python -m pip install --upgrade pip
39
+ python -m pip install .
40
+ python examples/portable_metrics.py
41
+ ```
42
+
43
+ On Windows, activate with `.venv\Scripts\activate` and use an MSVC build
44
+ environment. Node.js is unnecessary for installation and bundled views. To make
45
+ a wheel, run `python -m pip wheel . --no-deps --wheel-dir dist`. Install that
46
+ wheel on a matching platform with `python -m pip install /path/to/the-built.whl`;
47
+ installing a built wheel needs neither Rust nor Node. See the
48
+ [release guide](docs/releasing.html) for PyPI Trusted Publisher setup and the
49
+ 0.1.0 publication workflow.
50
+
51
+ ## Append documents, read a live view
52
+
53
+ Start with a fresh `metrics.fold` file:
54
+
55
+ ```python
56
+ from foldlitedb import Database, View
57
+
58
+ view = View.bundled("metrics")
59
+ with Database("metrics.fold", view) as db:
60
+ documents = db.add_many([
61
+ {"metric": "loss", "value": 4},
62
+ {"metric": "loss", "value": 8},
63
+ ])
64
+ assert list(db.rows())[0].result == {
65
+ "count": 2, "sum": 12, "min": 4, "max": 8, "mean": 6,
66
+ }
67
+ snapshot = db.query()
68
+ documents += (db.add({"metric": "loss", "value": 3}),)
69
+ assert db.changes(since=snapshot.cursor).rows[0].result["mean"] == 5
70
+ retained = tuple(db.documents())
71
+ db.checkpoint()
72
+ ```
73
+
74
+ Rust executes both map and reduce through a bounded Wasm interpreter. `metrics`
75
+ groups finite numbers by a string `metric`; `catalogue` demonstrates an ordered
76
+ fold over structured events. Each database holds one view module. `Database` and
77
+ `View` are the primary Python names; `PortableDatabase` and `PortableView` are
78
+ aliases for the same types.
79
+
80
+ Try the [telemetry collector](examples/telemetry.py) for bulk import, retries, and
81
+ a persistent changes consumer, or the [catalogue example](examples/catalogue.py)
82
+ for an ordered item lifecycle. If a payload produces no rows, inspect its map
83
+ output with [`db.preview(payload)`](docs/api.html#preview).
84
+
85
+ Every unnamed append creates a new observation. Give an observation an ID when
86
+ identical retries should count once. IDs and positions are scoped to a source;
87
+ conflicting reuse raises an error. Documents cannot be updated or deleted. Append
88
+ new observations to represent changes, then let the view fold their history.
89
+
90
+ `add_many` commits one atomic batch of at most 1024 documents. Its complete Wasm
91
+ map request must fit 256 KiB, so larger payloads need smaller batches.
92
+ `rows()` follows a stable snapshot in bounded pages; `query()` and
93
+ `changes(since=...)` expose continuation cursors. See the
94
+ [Python API](docs/api.html#database) for signatures and limits.
95
+
96
+ ## A log you keep; a cache you can discard
97
+
98
+ For a persistent database, `metrics.fold` contains the immutable document log.
99
+ `metrics.fold.cache` is an optional checkpoint; `metrics.fold.lock` coordinates
100
+ one active owner. A batch is acknowledged after its log frame is synced. The
101
+ engine validates and computes a batch before appending it, so a map or reducer
102
+ failure does not commit a prefix.
103
+
104
+ `checkpoint()` writes an atomic cache snapshot; closing cleanly does the same.
105
+ On reopen, the engine verifies the log and matching cache prefix, then runs Wasm
106
+ only for batches after the checkpoint. Missing, corrupt, or incompatible caches
107
+ are rebuilt from the documents. `rebuild()` explicitly recomputes the view and
108
+ starts a new cursor generation. Neither operation changes the source documents.
109
+
110
+ The document log is independent of the view module. Opening it with different
111
+ module bytes rebuilds the cache in place; retain the exact module if you need to
112
+ reproduce its previous results. Close the database before backing up the log and
113
+ view artifacts; Windows locks prevent other handles from reading an active log.
114
+ The cache is replaceable; the only copy of a document log is not.
115
+
116
+ Rebuild uses the original batch order. It scans the log linearly and runs each
117
+ uncached batch through the same map/reduce path as live ingestion. A valid cache
118
+ avoids repeated Wasm work, but reopening still verifies the complete log. Current
119
+ indexes and history grow in memory with the data. Cache snapshots are limited to
120
+ 1 GiB of encoded checkpoint data; a checkpoint or close error reports a failure
121
+ to save that accelerator, while committed log data remains intact. There is no
122
+ claim of constant memory or measured rebuild throughput.
123
+
124
+ To copy retained records into a fresh database:
125
+
126
+ ```python
127
+ with Database("copy.fold", view) as copied:
128
+ copied.ingest(retained)
129
+ assert list(copied.rows())[0].result["mean"] == 5
130
+ before = [(r.key, r.state, r.result) for r in copied.rows()]
131
+ copied.rebuild()
132
+ assert [(r.key, r.state, r.result) for r in copied.rows()] == before
133
+ ```
134
+
135
+ Use `ingest_many(records, batch_size=100)` for larger archives. **Iterate its
136
+ results to perform the import**: each yielded result commits one separate bounded
137
+ batch, and earlier batches remain committed if a later batch fails. See the
138
+ [retry-safe source import](docs/api.html#bulk-import) for constructing records with
139
+ upstream positions or stable IDs alone. Preserve record order; preserve original
140
+ batch boundaries too if you need identical engine sequence values.
141
+
142
+ ## Author a view, run it on a website
143
+
144
+ The [AssemblyScript SDK](views/README.md) provides JSON helpers and the Wasm ABI.
145
+ Implement `map`, `identity`, `add`, and `finalize`; implement `merge` only for
146
+ states that can be combined correctly. Inputs and state are explicit, and views
147
+ must be deterministic. An ordered fold must give equivalent state when the same
148
+ contributions are split across successive batches.
149
+
150
+ Rebuild the two bundled examples with Node.js 20+:
151
+
152
+ ```sh
153
+ npm ci --prefix views
154
+ npm run build --prefix views
155
+ npm test --prefix views
156
+ npm run check --prefix views
157
+ ```
158
+
159
+ The build writes modules and SHA-256 manifests into
160
+ `src/foldlitedb/portable_assets/`. It builds the named `metrics` and `catalogue`
161
+ examples. Compile a custom view and generate its manifest with:
162
+
163
+ ```sh
164
+ npm run build:view --prefix views -- assembly/examples/my-view.ts --out-dir .build
165
+ ```
166
+
167
+ Both paths are relative to `views/`. The
168
+ [authoring guide](docs/portable.html#authoring) includes a complete custom view
169
+ and the build command to generate its Wasm and digest manifest.
170
+
171
+ `foldlitedb.portable_assets()` exposes compiled modules plus `client.js`,
172
+ `worker.js`, and `host.js`. Copy them to a static site and import
173
+ `BrowserDatabase` from `client.js`. The [browser example](docs/portable.html#browser)
174
+ covers appends, snapshots, changes, export, and replay verification. JavaScript
175
+ owns in-memory grouping and storage in the browser worker; the Rust log engine
176
+ is not compiled into this browser build.
177
+
178
+ Browser state is lost on reload or close. Export resolved documents first. The
179
+ preview bounds documents, groups, history, and request sizes. Persistent browser
180
+ storage, log/history compaction, and multiple views per native database remain
181
+ future work. JSON numbers use IEEE-754 double precision; use strings for exact
182
+ arbitrary-precision identifiers.
183
+
184
+ ## Development and documentation
185
+
186
+ ```sh
187
+ python -m pip install '.[dev,browser]'
188
+ python -m playwright install chromium
189
+ python -m pytest
190
+ python -m pytest tests/browser
191
+ cargo test --locked --manifest-path native/Cargo.toml -p foldlitedb-portable-engine
192
+ npm ci --prefix views
193
+ npm test --prefix views
194
+ npm run check --prefix views
195
+ python scripts/build_docs.py
196
+ python scripts/check_docs.py --execute --browser
197
+ python -m http.server 8000 --directory _site
198
+ ```
199
+
200
+ The docs checker executes annotated Python examples in fresh temporary
201
+ directories and checks links, search, and desktop/mobile rendering in Chromium.
202
+ Open `http://localhost:8000` for the built docs and playground. An authorized
203
+ maintainer can publish checked generated docs with
204
+ `python scripts/publish_docs.py`; building alone does not publish them.
205
+
206
+ Measure the native path on your target filesystem with:
207
+
208
+ ```sh
209
+ python scripts/benchmark_database.py --documents 10000 --keys 16 --batch-size 100 --live-documents 20 --output native-benchmark.json
210
+ ```
211
+
212
+ Use `--storage-root /path/to/filesystem` to select the filesystem being measured.
213
+ The report compares durable ingestion, checkpointing, cached reopen, suffix
214
+ replay, and full reconstruction, while checking unchanged log bytes and matching
215
+ results. Local measurements describe that fixture and host.
216
+
217
+ See [DESIGN.md](DESIGN.md) for durability, replay, cache validation, and execution
218
+ contracts.