@monlite/core 1.2.0 → 1.4.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 +28 -4
- package/dist/index.cjs +54 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -30
- package/dist/index.d.ts +68 -30
- package/dist/index.js +54 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -317,6 +317,28 @@ await users.distinct("age", { role: "admin" }); // [28, 31]
|
|
|
317
317
|
await users.distinct("tags"); // ["a", "b", "c"]
|
|
318
318
|
```
|
|
319
319
|
|
|
320
|
+
### Joins (`$lookup` / `$unwind`)
|
|
321
|
+
|
|
322
|
+
Pull in related documents from another collection with a `lookup` on `findMany`
|
|
323
|
+
— a left join, run as **two queries (no N+1)**, in either storage mode:
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
// Attach each user's orders as an array ($lookup):
|
|
327
|
+
await db.collection("users").findMany({
|
|
328
|
+
lookup: { from: "orders", localField: "_id", foreignField: "user_id", as: "orders" },
|
|
329
|
+
});
|
|
330
|
+
// → [{ _id: "u1", name: "Ali", orders: [ {…}, {…} ] }, …]
|
|
331
|
+
|
|
332
|
+
// Flatten to one row per match with `unwind` ($unwind); use "preserve" to keep
|
|
333
|
+
// rows that have no match (left-outer):
|
|
334
|
+
await db.collection("orders").findMany({
|
|
335
|
+
lookup: { from: "users", localField: "user_id", foreignField: "_id", as: "user", unwind: true },
|
|
336
|
+
});
|
|
337
|
+
// → [{ _id: "o1", user_id: "u1", user: { _id: "u1", name: "Ali" } }, …]
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Pass an array of specs to join several collections at once.
|
|
341
|
+
|
|
320
342
|
---
|
|
321
343
|
|
|
322
344
|
## Live queries (reactivity)
|
|
@@ -459,9 +481,10 @@ await engine.start();
|
|
|
459
481
|
```
|
|
460
482
|
|
|
461
483
|
Pull / push / two-way replication, last-write-wins (or custom) conflict
|
|
462
|
-
resolution, and pluggable adapters (`MongoAdapter`, `
|
|
463
|
-
monlite-to-monlite, `MemoryAdapter` for
|
|
464
|
-
|
|
484
|
+
resolution, and pluggable adapters (`MongoAdapter`, `PostgresAdapter`,
|
|
485
|
+
`MySqlAdapter`, `MonliteAdapter` for monlite-to-monlite, `MemoryAdapter` for
|
|
486
|
+
tests) — keep local monlite as the embedded runtime and a server DB as the cloud
|
|
487
|
+
of record. See the
|
|
465
488
|
[`@monlite/sync` README](https://www.npmjs.com/package/@monlite/sync) for details.
|
|
466
489
|
|
|
467
490
|
---
|
|
@@ -600,13 +623,14 @@ Redis/Mongo/Qdrant replacement. For scale, keep the real services and
|
|
|
600
623
|
|
|
601
624
|
## Drivers & zero dependencies
|
|
602
625
|
|
|
603
|
-
monlite talks to SQLite through a tiny driver adapter, so it runs on
|
|
626
|
+
monlite talks to SQLite through a tiny driver adapter, so it runs on
|
|
604
627
|
interchangeable backends:
|
|
605
628
|
|
|
606
629
|
| Backend | When it's used | Notes |
|
|
607
630
|
|---|---|---|
|
|
608
631
|
| **`node:sqlite`** | Built into Node **22.5+** | **Zero dependencies.** Still flagged experimental by Node, so it prints a one-time `ExperimentalWarning`. |
|
|
609
632
|
| **`better-sqlite3`** | When the package is installed | Battle-tested native driver. Works on Node 18/20/22, no warning. Install it yourself: `npm i better-sqlite3`. |
|
|
633
|
+
| **WASM (browser)** | Via [`@monlite/wasm`](https://www.npmjs.com/package/@monlite/wasm) | Runs monlite **in the browser** on SQLite-WASM (sql.js); pass `driver: wasmDriver(SQL)`. Snapshot persistence to IndexedDB/OPFS. |
|
|
610
634
|
|
|
611
635
|
By default (`driver: "auto"`) monlite uses `better-sqlite3` if it's installed,
|
|
612
636
|
otherwise falls back to the built-in `node:sqlite`. Force one explicitly:
|
package/dist/index.cjs
CHANGED
|
@@ -1153,7 +1153,56 @@ var Collection = class {
|
|
|
1153
1153
|
return this.db.prepare(`SELECT 1 FROM "${this.name}" WHERE ${clause} LIMIT 1`).get(...params) != null;
|
|
1154
1154
|
}
|
|
1155
1155
|
async findMany(args = {}) {
|
|
1156
|
-
return this.findManyCore(args);
|
|
1156
|
+
if (!args.lookup) return this.findManyCore(args);
|
|
1157
|
+
const specs = Array.isArray(args.lookup) ? args.lookup : [args.lookup];
|
|
1158
|
+
let rows = this.findManyCore({
|
|
1159
|
+
...args,
|
|
1160
|
+
select: void 0,
|
|
1161
|
+
lookup: void 0
|
|
1162
|
+
});
|
|
1163
|
+
for (const spec of specs) rows = await this.applyLookup(rows, spec);
|
|
1164
|
+
if (args.select) {
|
|
1165
|
+
rows = rows.map((r) => {
|
|
1166
|
+
const projected = project(r, args.select);
|
|
1167
|
+
for (const spec of specs) projected[spec.as] = r[spec.as];
|
|
1168
|
+
return projected;
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
return rows;
|
|
1172
|
+
}
|
|
1173
|
+
/** Resolve one `$lookup` spec against already-fetched rows (2 queries, no N+1). */
|
|
1174
|
+
async applyLookup(rows, spec) {
|
|
1175
|
+
const localValues = [
|
|
1176
|
+
...new Set(
|
|
1177
|
+
rows.map((r) => r[spec.localField]).filter((v) => v !== void 0 && v !== null)
|
|
1178
|
+
)
|
|
1179
|
+
];
|
|
1180
|
+
const foreign = localValues.length ? await this.mon.collection(spec.from).findMany({
|
|
1181
|
+
where: { [spec.foreignField]: { in: localValues } }
|
|
1182
|
+
}) : [];
|
|
1183
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1184
|
+
for (const f of foreign) {
|
|
1185
|
+
const key = f[spec.foreignField];
|
|
1186
|
+
const list = byKey.get(key);
|
|
1187
|
+
if (list) list.push(f);
|
|
1188
|
+
else byKey.set(key, [f]);
|
|
1189
|
+
}
|
|
1190
|
+
if (spec.unwind) {
|
|
1191
|
+
const out = [];
|
|
1192
|
+
for (const r of rows) {
|
|
1193
|
+
const matches = byKey.get(r[spec.localField]) ?? [];
|
|
1194
|
+
if (matches.length === 0) {
|
|
1195
|
+
if (spec.unwind === "preserve") out.push({ ...r, [spec.as]: null });
|
|
1196
|
+
} else {
|
|
1197
|
+
for (const m of matches) out.push({ ...r, [spec.as]: m });
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return out;
|
|
1201
|
+
}
|
|
1202
|
+
return rows.map((r) => ({
|
|
1203
|
+
...r,
|
|
1204
|
+
[spec.as]: byKey.get(r[spec.localField]) ?? []
|
|
1205
|
+
}));
|
|
1157
1206
|
}
|
|
1158
1207
|
async findFirst(args = {}) {
|
|
1159
1208
|
const rows = await this.findMany({ ...args, take: 1 });
|
|
@@ -1615,7 +1664,11 @@ function loadNodeSqlite() {
|
|
|
1615
1664
|
return null;
|
|
1616
1665
|
}
|
|
1617
1666
|
}
|
|
1667
|
+
function isDriverInstance(d) {
|
|
1668
|
+
return typeof d === "object" && d !== null && typeof d.prepare === "function" && typeof d.exec === "function";
|
|
1669
|
+
}
|
|
1618
1670
|
function createDriver(filename, options = {}) {
|
|
1671
|
+
if (isDriverInstance(options.driver)) return options.driver;
|
|
1619
1672
|
const choice = options.driver ?? "auto";
|
|
1620
1673
|
if (options.encryption) {
|
|
1621
1674
|
if (choice === "node:sqlite") {
|