@asaidimu/utils-database 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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Saidimu
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.
package/README.md ADDED
@@ -0,0 +1,362 @@
1
+ # @asaidimu/utils-database
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@asaidimu/utils-database.svg)](https://www.npmjs.com/package/@asaidimu/utils-database)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/asaidimu/erp-utils/test.yml?branch=main)](https://github.com/asaidimu/erp-utils/actions)
6
+
7
+ A flexible, schema-driven document database layer for browser and Node.js with built‑in validation, migrations, transactions, and telemetry.
8
+
9
+ ---
10
+
11
+ ## Table of Contents
12
+
13
+ - [Overview & Features](#overview--features)
14
+ - [Installation & Setup](#installation--setup)
15
+ - [Usage Documentation](#usage-documentation)
16
+ - [Basic CRUD](#basic-crud)
17
+ - [Transactions](#transactions)
18
+ - [Migrations](#migrations)
19
+ - [Events & Telemetry](#events--telemetry)
20
+ - [Pagination & Filtering](#pagination--filtering)
21
+ - [Project Architecture](#project-architecture)
22
+ - [Development & Contributing](#development--contributing)
23
+ - [Additional Information](#additional-information)
24
+
25
+ ---
26
+
27
+ ## Overview & Features
28
+
29
+ `@asaidimu/utils-database` provides a high‑level, storage‑agnostic document database layer that works seamlessly in both browser (IndexedDB) and Node.js (memory) environments. It combines schema validation, optimistic concurrency control, and a flexible migration system to help you manage evolving data models without losing your mind.
30
+
31
+ The library was built to solve common pain points when working with client‑side storage: schema drift, concurrent edit conflicts, lack of transactions, and poor observability. By wrapping low‑level stores with a unified `Collection` / `Document` API, you get the same developer experience whether you are persisting data locally or just mocking storage for tests.
32
+
33
+ ### Key Features
34
+
35
+ - **Schema‑driven validation** – Define collections using `SchemaDefinition` (compatible with Standard Schema) and automatically validate every write.
36
+ - **Multi‑backend support** – Use IndexedDB for persistent browser storage or an ephemeral in‑memory store for testing.
37
+ - **Optimistic Concurrency Control (OCC)** – Every document has a `$version` field; updates fail with `CONFLICT` if the version has changed.
38
+ - **Atomic transactions** – Group multiple operations across collections and commit them together.
39
+ - **Streaming migrations** – Transform data in a memory‑efficient way using `ReadableStream` and batched writes.
40
+ - **Event system** – Subscribe to database, collection, or document level events (create, update, delete, read).
41
+ - **Built‑in telemetry** – Track operation duration, errors, and metadata (optional).
42
+ - **Middleware pipeline** – Easily add retry logic, logging, or custom behaviour around any operation.
43
+ - **Pagination helpers** – Offset and cursor‑based pagination for lists.
44
+
45
+ ---
46
+
47
+ ## Installation & Setup
48
+
49
+ ### Prerequisites
50
+
51
+ - Node.js 18+ or a modern browser (for IndexedDB)
52
+ - TypeScript 4.5+ (optional, but typings are included)
53
+
54
+ ### Install the package
55
+
56
+ ```bash
57
+ npm install @asaidimu/utils-database
58
+ ```
59
+
60
+ This package has the following peer dependencies – you need to install them manually:
61
+
62
+ ```bash
63
+ npm install @asaidimu/anansi @asaidimu/events
64
+ ```
65
+
66
+ ### Basic Configuration
67
+
68
+ The library exports two factory functions for creating a database:
69
+
70
+ - `createIndexedDbStore` – persistent storage using the browser’s IndexedDB.
71
+ - `createEphemeralStore` – in‑memory storage (useful for tests).
72
+
73
+ Example setup with IndexedDB:
74
+
75
+ ```typescript
76
+ import { DatabaseConnection } from "@asaidimu/utils-database";
77
+ import { createIndexedDbStore } from "@asaidimu/utils-database";
78
+
79
+ const db = await DatabaseConnection(
80
+ {
81
+ name: "my-app-db",
82
+ validate: true,
83
+ enableTelemetry: true,
84
+ predicates: {}, // custom validation predicates (optional)
85
+ },
86
+ createIndexedDbStore
87
+ );
88
+ ```
89
+
90
+ To verify that everything works, try creating a simple collection:
91
+
92
+ ```typescript
93
+ const userSchema = {
94
+ name: "users",
95
+ version: "1.0.0",
96
+ fields: {
97
+ id: { name: "id", type: "string", required: true, unique: true },
98
+ email: { name: "email", type: "string", required: true },
99
+ },
100
+ nestedSchemas: {},
101
+ };
102
+
103
+ const users = await db.createCollection(userSchema);
104
+ console.log("Collection ready");
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Usage Documentation
110
+
111
+ ### Basic CRUD
112
+
113
+ Create, read, update, and delete documents with an easy‑to‑use proxy API.
114
+
115
+ ```typescript
116
+ const users = await db.collection<{ id: string; email: string }>("users");
117
+
118
+ // Create
119
+ const doc = await users.create({ id: "user-1", email: "alice@example.com" });
120
+
121
+ // Read a field directly
122
+ console.log(doc.email); // "alice@example.com"
123
+
124
+ // Update
125
+ await doc.update({ email: "alice.new@example.com" });
126
+
127
+ // Read latest data from store
128
+ await doc.read();
129
+
130
+ // Delete
131
+ await doc.delete();
132
+ ```
133
+
134
+ Documents automatically receive system properties: `$id` (UUID v7), `$created`, `$updated`, and `$version`.
135
+
136
+ ### Transactions
137
+
138
+ Group multiple operations across different collections into an atomic unit.
139
+
140
+ ```typescript
141
+ await db.transaction(async (tx) => {
142
+ const user = await users.create({ id: "u2", email: "bob@example.com" }, tx);
143
+ const profile = await profiles.create({ userId: "u2", bio: "..." }, tx);
144
+ // both saves happen together
145
+ });
146
+ ```
147
+
148
+ If any operation throws, the entire transaction is rolled back.
149
+
150
+ ### Migrations
151
+
152
+ When you change a collection’s schema, you can migrate existing documents using a streaming, backpressure‑aware process.
153
+
154
+ ```typescript
155
+ await db.migrateCollection(
156
+ "users",
157
+ {
158
+ changes: [
159
+ {
160
+ type: "addField",
161
+ id: "isActive",
162
+ definition: { name: "isActive", type: "boolean" },
163
+ },
164
+ ],
165
+ transform: {
166
+ forward: (doc) => ({ ...doc, isActive: true }),
167
+ backward: (doc) => {
168
+ const { isActive, ...rest } = doc;
169
+ return rest;
170
+ },
171
+ },
172
+ description: "Add active flag",
173
+ },
174
+ 100 // batch size (optional)
175
+ );
176
+ ```
177
+
178
+ The migration runs inside a single transaction, updating both the documents and the stored schema definition.
179
+
180
+ ### Events & Telemetry
181
+
182
+ Subscribe to database, collection, or document events.
183
+
184
+ ```typescript
185
+ // Database‑level
186
+ const unsubDb = await db.subscribe("collection:create", (event) => {
187
+ console.log("New collection", event.schema.name);
188
+ });
189
+
190
+ // Collection‑level
191
+ const unsubCol = await users.subscribe("document:create", (event) => {
192
+ console.log("Document created", event.data);
193
+ });
194
+
195
+ // Document‑level
196
+ const unsubDoc = doc.subscribe("document:update", (event) => {
197
+ console.log("Updated at", event.timestamp);
198
+ });
199
+
200
+ // Telemetry (when enableTelemetry: true)
201
+ db.subscribe("telemetry", (event) => {
202
+ console.log(`${event.method} took ${event.metadata.performance.durationMs}ms`);
203
+ });
204
+ ```
205
+
206
+ ### Pagination & Filtering
207
+
208
+ Query documents using the `@asaidimu/query` filter syntax.
209
+
210
+ ```typescript
211
+ // Filter by field
212
+ const activeUsers = await users.filter({
213
+ field: "isActive",
214
+ operator: "eq",
215
+ value: true,
216
+ });
217
+
218
+ // Find a single document
219
+ const bob = await users.find({
220
+ field: "email",
221
+ operator: "eq",
222
+ value: "bob@example.com",
223
+ });
224
+
225
+ // Offset pagination
226
+ const iterator = await users.list({
227
+ type: "offset",
228
+ offset: 0,
229
+ limit: 10,
230
+ });
231
+ const firstPage = await iterator.next();
232
+
233
+ // Cursor pagination (more efficient for large collections)
234
+ const cursorIter = await users.list({
235
+ type: "cursor",
236
+ limit: 10,
237
+ direction: "forward",
238
+ });
239
+ ```
240
+
241
+ ---
242
+
243
+ ## Project Architecture
244
+
245
+ ### Core Components
246
+
247
+ - **`DatabaseConnection`** – The main entry point. It creates a metadata store for schemas, sets up the event bus, and returns a `Database` object.
248
+ - **`Store` interface** – Low‑level storage abstraction. Implementations exist for IndexedDB and memory. All store operations work on clones to avoid mutation.
249
+ - **`Collection`** – A logical grouping of documents. It handles validation, indexing, and querying, and wraps each document in a `Document` proxy.
250
+ - **`Document`** – Proxy that exposes both data fields and methods (`update`, `delete`, `read`, `subscribe`). Implements OCC via version checks.
251
+ - **`Pipeline` & Middleware** – Intercepts every operation (collection or document) to add retries, telemetry, or custom logic.
252
+ - **`MigrationEngine`** – Computes schema transformations and produces a stream of migrated documents.
253
+ - **`TransactionContext`** – Buffers operations and commits them atomically using store‑level batching.
254
+
255
+ ### Data Flow
256
+
257
+ 1. A user calls `collection.create()` → the request goes through the middleware pipeline.
258
+ 2. The `createDocument` factory validates the input against the schema.
259
+ 3. A new document is added to the store and indexed by `IndexManager`.
260
+ 4. Events are emitted (`document:create`, `collection:read`).
261
+ 5. All write operations perform OCC: they fetch the latest version, compare, and increment.
262
+
263
+ For migrations: `migrateCollection` streams documents from the store, passes them through the transformation engine, and writes them back in batches – all without loading the whole collection into memory.
264
+
265
+ ### Extension Points
266
+
267
+ - **Custom stores** – Implement the `Store<T>` interface and pass your own factory to `DatabaseConnection`.
268
+ - **Custom predicates** – Provide extra validation rules via the `predicates` config.
269
+ - **Middleware** – Add global or per‑collection middleware to the pipeline (e.g., logging, metrics, custom error handling).
270
+ - **Event listeners** – React to any internal event to build audit logs, synchronisation, or analytics.
271
+
272
+ ---
273
+
274
+ ## Development & Contributing
275
+
276
+ ### Development Setup
277
+
278
+ 1. Clone the repository:
279
+ ```bash
280
+ git clone https://github.com/asaidimu/erp-utils.git
281
+ cd erp-utils/src/database
282
+ ```
283
+ 2. Install dependencies:
284
+ ```bash
285
+ npm install
286
+ ```
287
+ 3. Build the package:
288
+ ```bash
289
+ npm run build # if defined, otherwise use tsc
290
+ ```
291
+
292
+ ### Available Scripts
293
+
294
+ | Command | Description |
295
+ | ----------------- | -------------------------------------------------- |
296
+ | `npm test` | Run tests once (Vitest) |
297
+ | `npm run test:watch` | Run tests in watch mode |
298
+ | `npm run test:browser` | Run tests in a real browser (Vitest browser mode) |
299
+
300
+ ### Testing
301
+
302
+ The library uses **Vitest** with `fake-indexeddb` to simulate IndexedDB in Node. All store implementations must pass the same conformance test suite (`testStoreImplementation`). When adding a new feature, please include unit tests and ensure the existing tests pass.
303
+
304
+ ### Contributing Guidelines
305
+
306
+ - Fork the repository and create a feature branch.
307
+ - Follow the existing code style (Prettier, ESLint).
308
+ - Write clear commit messages following Conventional Commits.
309
+ - Open a Pull Request with a description of the changes and why they are needed.
310
+ - Ensure all tests pass and coverage does not decrease.
311
+
312
+ ### Issue Reporting
313
+
314
+ Use the [GitHub issue tracker](https://github.com/asaidimu/erp-utils/issues) to report bugs or request features. Please include:
315
+
316
+ - A minimal code reproduction (if possible).
317
+ - Expected vs. actual behaviour.
318
+ - Environment (Node version, browser, package versions).
319
+
320
+ ---
321
+
322
+ ## Additional Information
323
+
324
+ ### Troubleshooting
325
+
326
+ | Error | Likely cause & solution |
327
+ | --------------------------------- | --------------------------------------------------------- |
328
+ | `SCHEMA_NOT_FOUND` | The collection was not created. Call `createCollection` first. |
329
+ | `CONFLICT` | Another operation updated the document. Re‑fetch and retry. |
330
+ | `TRANSACTION_FAILED` | One of the batched writes failed. Check individual operations. |
331
+ | `INVALID_DATA` | The document does not match the schema. Review validation errors. |
332
+
333
+ ### FAQ
334
+
335
+ **Q: Can I use this in a Node.js backend?**
336
+ A: Yes, with the `createEphemeralStore` (in‑memory) or a future persistent backend (e.g., SQLite). The current IndexedDB store only works in browsers.
337
+
338
+ **Q: How do migrations work with IndexedDB?**
339
+ A: Migrations only transform data and update the schema metadata. Structural changes (e.g., creating new indexes) still require a database version upgrade (`onupgradeneeded`). The library does not automate that part yet.
340
+
341
+ **Q: Does telemetry impact performance?**
342
+ A: Telemetry adds minimal overhead – a few timestamp reads and an event emission. You can disable it with `enableTelemetry: false`.
343
+
344
+ **Q: Can I use my own ID generator instead of UUID v7?**
345
+ A: Yes – provide a document with your own `$id` field. The library will not overwrite it.
346
+
347
+ ### Changelog
348
+
349
+ See the [CHANGELOG.md](https://github.com/asaidimu/erp-utils/blob/main/src/database/CHANGELOG.md) for version history.
350
+
351
+ ### License
352
+
353
+ This project is licensed under the **MIT License**. See the [LICENSE](https://github.com/asaidimu/erp-utils/blob/main/LICENSE) file for details.
354
+
355
+ ### Acknowledgments
356
+
357
+ Built with ❤️ using:
358
+ - [`@asaidimu/anansi`](https://github.com/asaidimu/anansi) – schema validation and migrations.
359
+ - [`@asaidimu/events`](https://github.com/asaidimu/events) – type‑safe event bus.
360
+ - [`@standard-schema/spec`](https://github.com/standard-schema/standard-schema) – Standard Schema interoperability.
361
+ - [`uuid`](https://github.com/uuidjs/uuid) – for v7 UUIDs.
362
+ - [`fake-indexeddb`](https://github.com/dumbmatter/fakeIndexedDB) – testing support.