@monlite/core 0.2.0 → 0.5.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
@@ -250,10 +250,97 @@ const grouped = await users.groupBy({
250
250
  orderBy: { _count: "desc" },
251
251
  });
252
252
  // [ { role: "admin", _count: 5, _sum: { age: 140 } }, … ]
253
+
254
+ // groupBy + having (filter groups by an aggregate, like SQL HAVING)
255
+ await users.groupBy({
256
+ by: ["role"],
257
+ _count: true,
258
+ _sum: { age: true },
259
+ having: {
260
+ _count: { gte: 2 }, // keep groups with COUNT(*) >= 2
261
+ _sum: { age: { gt: 50 } }, // and SUM(age) > 50
262
+ },
263
+ });
264
+ // having comparisons: equals, not, gt, gte, lt, lte — on _count and on
265
+ // _sum/_avg/_min/_max of any field.
266
+ ```
267
+
268
+ ### distinct
269
+
270
+ ```ts
271
+ await users.distinct("role"); // ["admin", "editor"]
272
+ await users.distinct("age", { role: "admin" }); // [28, 31]
273
+
274
+ // Array fields are unwound — each element is a value (like MongoDB):
275
+ await users.distinct("tags"); // ["a", "b", "c"]
253
276
  ```
254
277
 
255
278
  ---
256
279
 
280
+ ## Structured collections (the SQL skin)
281
+
282
+ By default a collection is **document mode** — schema-free, every field stored
283
+ as JSON. Pass a `schema` to make it a **structured collection**: the declared
284
+ fields become real, typed SQL columns (fast, indexable, joinable, constrainable)
285
+ 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.
288
+
289
+ ```ts
290
+ const orders = db.collection("orders", {
291
+ schema: {
292
+ user_id: { type: "TEXT", index: true, references: "users(_id)" },
293
+ amount: "REAL",
294
+ status: { type: "TEXT", notNull: true, default: "pending" },
295
+ meta: "JSON", // objects/arrays, transparently (de)serialized
296
+ },
297
+ });
298
+
299
+ // Same API as document collections — but `amount`/`status` are real columns:
300
+ await orders.create({ data: { user_id: "u1", amount: 100, status: "paid", note: "rush" } });
301
+ await orders.findMany({ where: { amount: { gte: 50 }, status: "paid" } });
302
+ await orders.groupBy({ by: ["status"], _sum: { amount: true } });
303
+
304
+ // Undeclared fields (like `note`) still work — they overflow into JSON.
305
+ await orders.findMany({ where: { note: { contains: "rush" } } });
306
+ ```
307
+
308
+ Because the columns are native, they join, constrain, and index like any SQL
309
+ table — including from the raw SQL hatch with no `json_extract`:
310
+
311
+ ```ts
312
+ await db.$queryRaw`
313
+ SELECT u.name, SUM(o.amount) AS revenue
314
+ FROM users u JOIN orders o ON o.user_id = u._id
315
+ GROUP BY u._id
316
+ `;
317
+ ```
318
+
319
+ Column types: `"TEXT" | "INTEGER" | "REAL" | "BLOB" | "JSON"`. A full column
320
+ definition supports `index`, `unique`, `notNull`, `default`, and `references`.
321
+
322
+ ### Do I have to care: JSON vs native columns?
323
+
324
+ - **For correctness — no.** Both modes return identical results through the same API.
325
+ - **For performance & SQL interop — a little.** Native columns + native indexes are
326
+ faster and join/constrain cleanly; JSON is for when the shape is unknown or varies.
327
+
328
+ monlite never hides which is which:
329
+
330
+ ```ts
331
+ orders.mode; // "structured" | "document"
332
+ await db.$schema("orders"); // physical columns: [{ name, type, notNull, primaryKey }, …]
333
+ createDb("./app.db", { verbose: (sql) => console.log(sql) }); // see json_extract vs bare columns
334
+ ```
335
+
336
+ > **Rule of thumb:** unknown/flexible shape → document (JSON); known/stable shape
337
+ > with heavy joins, reporting, or external SQL tooling → structured (native columns).
338
+
339
+ > Note: structured collections are not yet covered by `@monlite/sync` (document
340
+ > collections are) — that's planned follow-up work.
341
+
342
+ ---
343
+
257
344
  ## SQL escape hatch
258
345
 
259
346
  When you need full SQL power — complex joins, analytics, cross-collection