@monlite/core 0.5.0 β†’ 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,7 @@
1
1
  # πŸŒ™ monlite
2
2
 
3
- > An embedded document database for TypeScript apps.
4
- > MongoDB-like API. Prisma-like DX. SQLite under the hood. Zero config.
5
-
6
- monlite is a local-first document database that lives inside your app as a
7
- single `.db` file. No server to run, no schema to define, no migrations to
8
- manage. You get the flexibility of MongoDB, the familiarity of a Prisma-style
9
- API, and the reliability of SQLite β€” all in one `npm install`.
3
+ > Your app's local database. A MongoDB-like API and Prisma-style DX in a single
4
+ > file. Zero config, zero migrations, zero server.
10
5
 
11
6
  ```ts
12
7
  import { createDb } from "@monlite/core";
@@ -18,7 +13,36 @@ await users.create({ data: { name: "Ali", age: 28 } });
18
13
  await users.findMany({ where: { age: { gte: 18 } } });
19
14
  ```
20
15
 
21
- That's it. No setup. No config. Your data is in `app.db`.
16
+ That's the whole setup. Your data is in `app.db`.
17
+
18
+ ---
19
+
20
+ ## Mental model (read this first)
21
+
22
+ monlite is **one database with one query API.** You never have to choose "SQL or
23
+ NoSQL." You only choose, per collection, **where each field is stored:**
24
+
25
+ - **Document mode** (default) β€” the whole document is stored as JSON. Flexible
26
+ and schema-free, like MongoDB.
27
+ - **Structured mode** (`db.collection(name, { schema })`) β€” the fields you
28
+ declare become real SQL columns (typed, indexed, joinable). Anything else
29
+ overflows into JSON automatically.
30
+
31
+ > **A schema changes the _storage_, never the _syntax_.**
32
+ > `create`, `find`, `where`, `orderBy`, `groupBy` are identical in both modes and
33
+ > return identical results β€” structured mode is just faster and SQL-native underneath.
34
+
35
+ Raw SQL is the one optional place SQL becomes visible: the `$queryRaw` escape
36
+ hatch, for joins/CTEs/window functions the document API doesn't cover.
37
+
38
+ | You decide… | Document (default) | Structured (`{ schema }`) |
39
+ | --- | --- | --- |
40
+ | **How you query** | `find` / `where` / `orderBy` / `groupBy` | **identical** |
41
+ | **Where a field lives** | JSON `data` blob | a native column (declared) β€” the rest overflow to JSON |
42
+ | **Pick it when** | the shape is unknown or varies per record | the shape is stable and you want joins, FKs, reporting, or fast native indexes |
43
+
44
+ You can mix both in the same `.db`, and move a collection from document to
45
+ structured later without changing a single query.
22
46
 
23
47
  ---
24
48
 
@@ -57,6 +81,20 @@ If your data is structured and you already know your schema, plain SQLite adds
57
81
  nothing on top of monlite β€” use it directly. monlite earns its keep when your
58
82
  documents are dynamic, schema-free, or mirror a cloud NoSQL store.
59
83
 
84
+ ### How monlite compares
85
+
86
+ | | monlite | MongoDB | better-sqlite3 | Prisma + SQLite |
87
+ |---|---|---|---|---|
88
+ | Schema-free documents | βœ… | βœ… | ⚠️ manual JSON | ❌ |
89
+ | Native typed columns | βœ… (opt-in) | ❌ | βœ… | βœ… |
90
+ | Same API for both | βœ… | β€” | β€” | β€” |
91
+ | Raw SQL escape hatch | βœ… | ❌ | βœ… | βœ… (`$queryRaw`) |
92
+ | No server / single file | βœ… | ❌ | βœ… | βœ… |
93
+ | No migrations / codegen | βœ… | βœ… | βœ… | ❌ |
94
+ | Aggregation API | βœ… | βœ… | ⚠️ manual | ⚠️ limited |
95
+ | Local-first sync | βœ… (`@monlite/sync`) | ⚠️ Atlas/Realm | ❌ | ❌ |
96
+ | Runtime dependencies | **0** (Node 22.5+) | server | 1 (native) | several |
97
+
60
98
  ---
61
99
 
62
100
  ## Setup
@@ -124,6 +162,9 @@ await users.createMany({ data: [{ name: "Sara" }, { name: "Omar" }] });
124
162
  // read
125
163
  await users.findById("…"); // doc | null
126
164
  await users.findFirst({ where: { name: "Ali" } }); // doc | null
165
+ await users.findUnique({ where: { email: "a@x.com" } }); // alias of findFirst
166
+ await users.findFirstOrThrow({ where: { name: "Ali" } }); // throws if missing
167
+ await users.exists({ role: "admin" }); // boolean
127
168
  await users.findMany({
128
169
  where: { age: { gte: 18 } },
129
170
  orderBy: { age: "desc" },
@@ -167,10 +208,11 @@ where: { age: { gt: 18 } } // gt, gte, lt, lte
167
208
  where: { role: { in: ["admin", "editor"] } }
168
209
  where: { role: { notIn: ["guest"] } }
169
210
 
170
- // String (case-sensitive; wildcards are matched literally)
211
+ // String (case-sensitive by default; wildcards are matched literally)
171
212
  where: { name: { contains: "li" } }
172
213
  where: { name: { startsWith: "A" } }
173
214
  where: { name: { endsWith: "i" } }
215
+ where: { name: { contains: "ALI", mode: "insensitive" } } // case-insensitive (ASCII)
174
216
 
175
217
  // Arrays
176
218
  where: { tags: { contains: "admin" } } // element membership
@@ -277,14 +319,15 @@ await users.distinct("tags"); // ["a", "b", "c"]
277
319
 
278
320
  ---
279
321
 
280
- ## Structured collections (the SQL skin)
322
+ ## Structured collections (native SQL columns)
281
323
 
282
324
  By default a collection is **document mode** β€” schema-free, every field stored
283
325
  as JSON. Pass a `schema` to make it a **structured collection**: the declared
284
326
  fields become real, typed SQL columns (fast, indexable, joinable, constrainable)
285
327
  and any *other* fields overflow into a JSON column. **The CRUD/query API is
286
- identical** β€” `find`, `where`, `orderBy`, `groupBy`, `distinct`, updates β€” only
287
- the storage underneath changes.
328
+ identical** β€” `find`, `where`, `orderBy`, `groupBy`, `distinct`, updates. As the
329
+ [mental model](#mental-model-read-this-first) says: a schema changes the storage,
330
+ not the syntax.
288
331
 
289
332
  ```ts
290
333
  const orders = db.collection("orders", {
@@ -336,8 +379,47 @@ createDb("./app.db", { verbose: (sql) => console.log(sql) }); // see json_extrac
336
379
  > **Rule of thumb:** unknown/flexible shape β†’ document (JSON); known/stable shape
337
380
  > with heavy joins, reporting, or external SQL tooling β†’ structured (native columns).
338
381
 
339
- > Note: structured collections are not yet covered by `@monlite/sync` (document
340
- > collections are) β€” that's planned follow-up work.
382
+ > Both document and structured collections are syncable via
383
+ > [`@monlite/sync`](#sync--local-first). To sync a structured collection, open it
384
+ > with its `schema` on every node before syncing (so each side knows the native
385
+ > columns).
386
+
387
+ ---
388
+
389
+ ## Sync & local-first
390
+
391
+ The companion package [`@monlite/sync`](https://www.npmjs.com/package/@monlite/sync)
392
+ replicates a local monlite database with a remote source of truth β€” MongoDB
393
+ first β€” so apps can work offline and converge when reconnected.
394
+
395
+ Opt in with `{ sync: true }` (adds a change feed + tombstones + versioning; zero
396
+ overhead when off), then drive an engine:
397
+
398
+ ```ts
399
+ import { createDb } from "@monlite/core";
400
+ import { sync, MongoAdapter } from "@monlite/sync";
401
+ import { MongoClient } from "mongodb";
402
+
403
+ const db = createDb("./app.db", { sync: true });
404
+ const mongo = new MongoClient(uri);
405
+ await mongo.connect();
406
+
407
+ const engine = sync(db, {
408
+ adapter: new MongoAdapter({ client: mongo, db: "app" }),
409
+ collections: "*",
410
+ mode: "two-way", // "pull" | "push" | "two-way"
411
+ conflict: "lww", // or a custom resolver
412
+ interval: 5000,
413
+ });
414
+
415
+ await engine.start();
416
+ ```
417
+
418
+ Pull / push / two-way replication, last-write-wins (or custom) conflict
419
+ resolution, and pluggable adapters (`MongoAdapter`, `MonliteAdapter` for
420
+ monlite-to-monlite, `MemoryAdapter` for tests). monlite's ObjectId-compatible
421
+ `_id`s map 1:1 to Mongo `_id`s. See the
422
+ [`@monlite/sync` README](https://www.npmjs.com/package/@monlite/sync) for details.
341
423
 
342
424
  ---
343
425