@palbase/backend 28.0.0 → 30.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.
Files changed (49) hide show
  1. package/dist/bin/palbase-backend.cjs +166 -12
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +3 -3
  4. package/dist/{chunk-RVP6BTEZ.js → chunk-5J5W75A6.js} +15 -4
  5. package/dist/chunk-5J5W75A6.js.map +1 -0
  6. package/dist/{chunk-IVZERLTM.js → chunk-ENZ2RFFJ.js} +2 -2
  7. package/dist/{chunk-SNDXY565.js → chunk-SG4UTNOP.js} +5 -2
  8. package/dist/chunk-SG4UTNOP.js.map +1 -0
  9. package/dist/{chunk-75YROPRZ.js → chunk-VN7NMDUH.js} +166 -15
  10. package/dist/chunk-VN7NMDUH.js.map +1 -0
  11. package/dist/db/index.cjs +4 -1
  12. package/dist/db/index.cjs.map +1 -1
  13. package/dist/db/index.d.cts +1 -1
  14. package/dist/db/index.d.ts +1 -1
  15. package/dist/db/index.js +2 -2
  16. package/dist/engine/index.cjs +166 -12
  17. package/dist/engine/index.cjs.map +1 -1
  18. package/dist/engine/index.d.cts +3 -3
  19. package/dist/engine/index.d.ts +3 -3
  20. package/dist/engine/index.js +3 -3
  21. package/dist/{index-dTTLlHIn.d.ts → index-8qy3kIuA.d.ts} +228 -12
  22. package/dist/{index-DtISj9QX.d.cts → index-DtCgaZAg.d.cts} +228 -12
  23. package/dist/{index-9C3JHxg-.d.cts → index-Zi7MptvP.d.cts} +30 -8
  24. package/dist/{index-BbvOoZFr.d.ts → index-b-Q3l7W5.d.ts} +30 -8
  25. package/dist/index.cjs +66 -3
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +51 -11
  28. package/dist/index.d.ts +51 -11
  29. package/dist/index.js +52 -3
  30. package/dist/index.js.map +1 -1
  31. package/dist/openapi/index.d.cts +2 -2
  32. package/dist/openapi/index.d.ts +2 -2
  33. package/dist/{registry-JQNIX-eA.d.ts → registry-BGJ-Al6F.d.ts} +1 -1
  34. package/dist/{registry-qIPM5BQe.d.cts → registry-CEYLH-Iz.d.cts} +1 -1
  35. package/dist/test/index.cjs +56 -0
  36. package/dist/test/index.cjs.map +1 -1
  37. package/dist/test/index.d.cts +1 -1
  38. package/dist/test/index.d.ts +1 -1
  39. package/dist/test/index.js +56 -0
  40. package/dist/test/index.js.map +1 -1
  41. package/docs/README.md +1 -1
  42. package/docs/database.md +49 -0
  43. package/docs/llms-full.txt +50 -1
  44. package/package.json +1 -1
  45. package/template/package.json +1 -1
  46. package/dist/chunk-75YROPRZ.js.map +0 -1
  47. package/dist/chunk-RVP6BTEZ.js.map +0 -1
  48. package/dist/chunk-SNDXY565.js.map +0 -1
  49. /package/dist/{chunk-IVZERLTM.js.map → chunk-ENZ2RFFJ.js.map} +0 -0
package/docs/README.md CHANGED
@@ -73,7 +73,7 @@ service the controllers call.
73
73
 
74
74
  > **Never** emit `defineController`, `defineHandler`, `defineEndpoint`, `route.get(...)`,
75
75
  > `req.input`, `req.params`, or `req.errors` — those are the removed legacy model
76
- > and will not compile against `@palbase/backend` 28.
76
+ > and will not compile against `@palbase/backend` 30.
77
77
 
78
78
  ### Complete CRUD example (copy-pasteable, compiles)
79
79
 
package/docs/database.md CHANGED
@@ -261,8 +261,57 @@ shift into the wrong column — both silently. A differing row is refused with
261
261
  its index and the columns that differ. Write `null` for a column you do not
262
262
  have, or insert differently-shaped rows in separate calls.
263
263
 
264
+ ### Totals — `aggregate`
265
+
266
+ `count` was the only aggregate, so "the total on this account" meant raw SQL or
267
+ pulling every row and adding it up in JavaScript. The second is wrong twice:
268
+ it is an N-row read, and money loses precision the moment it becomes a JS
269
+ number.
270
+
271
+ ```ts
272
+ const r = await Database.public.entries.aggregate({
273
+ where: { account_id },
274
+ sum: ["amount_kurus"],
275
+ count: true,
276
+ });
277
+ r.sum.amount_kurus; // string | null — exact
278
+ r.count; // number
279
+ ```
280
+
281
+ `sum` and `avg` return **strings**, always. Measured on live pg16:
282
+ `sum(numeric)` = `1000041.50821234567890124`, which as a JS number becomes
283
+ `1000041.5082123457` — seven digits gone; `sum(bigint)` = `18014398509481987`
284
+ becomes `18014398509481988`, a different number. The engine casts both to text
285
+ so the driver never gets the chance.
286
+
287
+ `| null` is not decoration: Postgres answers an empty set with NULL, not 0
288
+ (measured: `sum(...) IS NULL` while `count(*)` = 0). "No rows" and "the total
289
+ was zero" are different answers, and the type makes you say which one you mean.
290
+
291
+ `sum` and `avg` are offered on numeric columns only — the column's Postgres type
292
+ is in the type, so `sum(["note"])` on a text column is a compile error rather
293
+ than `function sum(text) does not exist`.
294
+
295
+ Add `groupBy` and the result becomes an array, each row carrying its group:
296
+
297
+ ```ts
298
+ const rows = await Database.public.entries.aggregate({
299
+ where: { household_id },
300
+ groupBy: ["category"],
301
+ sum: ["amount_kurus"],
302
+ });
303
+ // rows[0].category, rows[0].sum.amount_kurus
304
+ ```
305
+
264
306
  ### Bulk writes refuse an empty filter
265
307
 
308
+ For a bulk job, pass `returning: false` and `updateMany` resolves to a COUNT
309
+ instead of the rows — the same shape `deleteMany` already has. Measured at
310
+ 200k rows on live pg16: `RETURNING *` costs 55% more server time (7.2s → 11.1s)
311
+ and 45 MB of JS heap, 236 bytes per row, so a million-row update carries 225 MB
312
+ for rows nobody reads. Reading the returned rows is right for most calls and
313
+ stays the default.
314
+
266
315
  `updateMany({}, …)` and `deleteMany({})` throw with the table named. `UPDATE …
267
316
  WHERE true` is a whole-table write, and the way to reach it by accident — a
268
317
  filter object built from request input that happened to come back empty — is
@@ -81,7 +81,7 @@ service the controllers call.
81
81
 
82
82
  > **Never** emit `defineController`, `defineHandler`, `defineEndpoint`, `route.get(...)`,
83
83
  > `req.input`, `req.params`, or `req.errors` — those are the removed legacy model
84
- > and will not compile against `@palbase/backend` 28.
84
+ > and will not compile against `@palbase/backend` 30.
85
85
 
86
86
  ### Complete CRUD example (copy-pasteable, compiles)
87
87
 
@@ -1147,8 +1147,57 @@ shift into the wrong column — both silently. A differing row is refused with
1147
1147
  its index and the columns that differ. Write `null` for a column you do not
1148
1148
  have, or insert differently-shaped rows in separate calls.
1149
1149
 
1150
+ ### Totals — `aggregate`
1151
+
1152
+ `count` was the only aggregate, so "the total on this account" meant raw SQL or
1153
+ pulling every row and adding it up in JavaScript. The second is wrong twice:
1154
+ it is an N-row read, and money loses precision the moment it becomes a JS
1155
+ number.
1156
+
1157
+ ```ts
1158
+ const r = await Database.public.entries.aggregate({
1159
+ where: { account_id },
1160
+ sum: ["amount_kurus"],
1161
+ count: true,
1162
+ });
1163
+ r.sum.amount_kurus; // string | null — exact
1164
+ r.count; // number
1165
+ ```
1166
+
1167
+ `sum` and `avg` return **strings**, always. Measured on live pg16:
1168
+ `sum(numeric)` = `1000041.50821234567890124`, which as a JS number becomes
1169
+ `1000041.5082123457` — seven digits gone; `sum(bigint)` = `18014398509481987`
1170
+ becomes `18014398509481988`, a different number. The engine casts both to text
1171
+ so the driver never gets the chance.
1172
+
1173
+ `| null` is not decoration: Postgres answers an empty set with NULL, not 0
1174
+ (measured: `sum(...) IS NULL` while `count(*)` = 0). "No rows" and "the total
1175
+ was zero" are different answers, and the type makes you say which one you mean.
1176
+
1177
+ `sum` and `avg` are offered on numeric columns only — the column's Postgres type
1178
+ is in the type, so `sum(["note"])` on a text column is a compile error rather
1179
+ than `function sum(text) does not exist`.
1180
+
1181
+ Add `groupBy` and the result becomes an array, each row carrying its group:
1182
+
1183
+ ```ts
1184
+ const rows = await Database.public.entries.aggregate({
1185
+ where: { household_id },
1186
+ groupBy: ["category"],
1187
+ sum: ["amount_kurus"],
1188
+ });
1189
+ // rows[0].category, rows[0].sum.amount_kurus
1190
+ ```
1191
+
1150
1192
  ### Bulk writes refuse an empty filter
1151
1193
 
1194
+ For a bulk job, pass `returning: false` and `updateMany` resolves to a COUNT
1195
+ instead of the rows — the same shape `deleteMany` already has. Measured at
1196
+ 200k rows on live pg16: `RETURNING *` costs 55% more server time (7.2s → 11.1s)
1197
+ and 45 MB of JS heap, 236 bytes per row, so a million-row update carries 225 MB
1198
+ for rows nobody reads. Reading the returned rows is right for most calls and
1199
+ stays the default.
1200
+
1152
1201
  `updateMany({}, …)` and `deleteMany({})` throw with the table named. `UPDATE …
1153
1202
  WHERE true` is a whole-table write, and the way to reach it by accident — a
1154
1203
  filter object built from request input that happened to come back empty — is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@palbase/backend",
3
- "version": "28.0.0",
3
+ "version": "30.0.0",
4
4
  "description": "Palbase Backend SDK — class controllers (@Controller/@Get/@Post + @Body/@QueryParams/@Param), error classes, schema DSL",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -9,7 +9,7 @@
9
9
  "typecheck": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@palbase/backend": "^28.0.0",
12
+ "@palbase/backend": "^30.0.0",
13
13
  "reflect-metadata": "^0.2.2"
14
14
  },
15
15
  "engines": {