@palbase/backend 24.0.2 → 24.1.1

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 (48) hide show
  1. package/dist/bin/palbase-backend.cjs +86 -9
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +3 -2
  4. package/dist/bin/palbase-backend.js.map +1 -1
  5. package/dist/{chunk-RPZNHY7J.js → chunk-2A62DDVO.js} +36 -11
  6. package/dist/chunk-2A62DDVO.js.map +1 -0
  7. package/dist/{chunk-T5IOSOE5.js → chunk-ERDL5VAE.js} +2 -2
  8. package/dist/{chunk-H3JAISUY.js → chunk-UWSYTUGM.js} +11 -1
  9. package/dist/{chunk-H3JAISUY.js.map → chunk-UWSYTUGM.js.map} +1 -1
  10. package/dist/chunk-XABBC7JP.js +55 -0
  11. package/dist/chunk-XABBC7JP.js.map +1 -0
  12. package/dist/db/index.d.cts +2 -2
  13. package/dist/db/index.d.ts +2 -2
  14. package/dist/engine/index.cjs +86 -9
  15. package/dist/engine/index.cjs.map +1 -1
  16. package/dist/engine/index.d.cts +4 -4
  17. package/dist/engine/index.d.ts +4 -4
  18. package/dist/engine/index.js +3 -2
  19. package/dist/{index-BOS_rFBO.d.ts → index-ByBMibIJ.d.ts} +1 -1
  20. package/dist/{index-dJNhDZ7j.d.ts → index-C0PMn5jl.d.ts} +3 -3
  21. package/dist/{index-MoQ31B6M.d.cts → index-D4rts8T7.d.cts} +1 -1
  22. package/dist/{index-C84bLgeO.d.cts → index-DAwHMppB.d.cts} +3 -3
  23. package/dist/index.cjs +8 -0
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +23 -9
  26. package/dist/index.d.ts +23 -9
  27. package/dist/index.js +5 -3
  28. package/dist/index.js.map +1 -1
  29. package/dist/openapi/index.cjs.map +1 -1
  30. package/dist/openapi/index.d.cts +2 -2
  31. package/dist/openapi/index.d.ts +2 -2
  32. package/dist/openapi/index.js +2 -2
  33. package/dist/{registry-CEod_5sz.d.ts → registry-B8pld3fj.d.ts} +1 -1
  34. package/dist/{registry-1X-skBNu.d.cts → registry-CgJvDYUW.d.cts} +1 -1
  35. package/dist/test/index.cjs +58 -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 +12 -0
  40. package/dist/test/index.js.map +1 -1
  41. package/docs/README.md +5 -1
  42. package/docs/llms-full.txt +5 -1
  43. package/package.json +1 -1
  44. package/template/AGENTS.md +34 -22
  45. package/dist/chunk-RPZNHY7J.js.map +0 -1
  46. package/dist/{chunk-T5IOSOE5.js.map → chunk-ERDL5VAE.js.map} +0 -0
  47. package/dist/{endpoint-CcQ1a36a.d.cts → endpoint-BSGw1pTu.d.cts} +16 -16
  48. package/dist/{endpoint-0_DGBajf.d.ts → endpoint-DMwJjEHQ.d.ts} +16 -16
@@ -635,6 +635,14 @@ var Realtime = makeServiceProxy("Realtime");
635
635
 
636
636
  // src/errors.ts
637
637
  var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
638
+ var ENGINE_RAISED = /* @__PURE__ */ Symbol.for("palbase.backend.engineRaised");
639
+ function markEngineRaised(e) {
640
+ e[ENGINE_RAISED] = true;
641
+ return e;
642
+ }
643
+ function isEngineRaised(e) {
644
+ return typeof e === "object" && e !== null && e[ENGINE_RAISED] === true;
645
+ }
638
646
  function isHttpError(err) {
639
647
  if (typeof err !== "object" || err === null) return false;
640
648
  const e = err;
@@ -1029,6 +1037,56 @@ function makeMemoryCache(opts = {}) {
1029
1037
  };
1030
1038
  }
1031
1039
 
1040
+ // src/db/input-guards.ts
1041
+ var KNOWN_OPS = /* @__PURE__ */ new Set(["gt", "gte", "lt", "lte", "neq", "eq", "in"]);
1042
+ function assertUsableFilter(caller, table, where) {
1043
+ if (!where) return;
1044
+ for (const [col, cond] of Object.entries(where)) {
1045
+ if (cond === void 0) {
1046
+ throw new Error(
1047
+ `${caller}(${table}): where.${col} de\u011Feri undefined \u2014 bu bir filtre de\u011Feri de\u011Fil. Ba\u011Flan\u0131nca NULL olur ve '= NULL' hi\xE7bir sat\u0131ra uymaz, yani sorgu sessizce bo\u015F sonu\xE7 d\xF6nerdi. De\u011Fer yoksa anahtar\u0131 filtreye hi\xE7 koymay\u0131n.`
1048
+ );
1049
+ }
1050
+ if (cond === null || typeof cond !== "object" || Array.isArray(cond)) continue;
1051
+ const entries = Object.entries(cond);
1052
+ if (entries.length === 0) {
1053
+ throw new Error(
1054
+ `${caller}(${table}): where.${col} bo\u015F bir operat\xF6r nesnesi ({}) \u2014 hi\xE7bir ko\u015Ful \xFCretmez, yani bu alan filtreden sessizce D\xDC\u015EERD\u0130. Ko\u015Ful kurulmayacaksa anahtar\u0131 filtreye hi\xE7 koymay\u0131n (D-21).`
1055
+ );
1056
+ }
1057
+ for (const [op, v] of entries) {
1058
+ if (op === "in") {
1059
+ if (!Array.isArray(v)) throw new Error(`${caller}(${table}): where.${col}.in bir dizi olmal\u0131`);
1060
+ if (v.some((x) => x === void 0)) {
1061
+ throw new Error(
1062
+ `${caller}(${table}): where.${col}.in listesinde undefined var \u2014 sessizce NULL'a ba\u011Flan\u0131r ve o eleman hi\xE7bir sat\u0131rla e\u015Fle\u015Fmez. Listeyi kurarken eleyin.`
1063
+ );
1064
+ }
1065
+ continue;
1066
+ }
1067
+ if (!KNOWN_OPS.has(op)) {
1068
+ throw new Error(
1069
+ `${caller}(${table}): where.${col} bilinmeyen operat\xF6r "${op}" (gt/gte/lt/lte/neq/in)`
1070
+ );
1071
+ }
1072
+ if (v === void 0) {
1073
+ throw new Error(
1074
+ `${caller}(${table}): where.${col}.${op} de\u011Feri undefined \u2014 kar\u015F\u0131la\u015Ft\u0131rman\u0131n sa\u011F taraf\u0131 NULL olur ve sonu\xE7 hi\xE7bir sat\u0131ra uymaz. Ko\u015Fulu kurmay\u0131n.`
1075
+ );
1076
+ }
1077
+ }
1078
+ }
1079
+ }
1080
+ function assertUsableWriteValues(caller, table, cols, data) {
1081
+ for (const c of cols) {
1082
+ if (data[c] === void 0) {
1083
+ throw new Error(
1084
+ `${caller}(${table}): "${c}" de\u011Feri undefined \u2014 bu bir yazma de\u011Feri de\u011Fil. Kolonu bo\u015Faltmak istiyorsan null yaz; kolonu de\u011Fi\u015Ftirmek istemiyorsan nesneye hi\xE7 koyma (bir eksik istek alan\u0131 sessizce NULL yaz\u0131yordu \u2014 FR-016).`
1085
+ );
1086
+ }
1087
+ }
1088
+ }
1089
+
1032
1090
  // src/engine/db.ts
1033
1091
  function quoteIdent(name) {
1034
1092
  return `"${name.replace(/"/g, '""')}"`;
@@ -1156,7 +1214,8 @@ function asTableRows(table, rows) {
1156
1214
  const transforms = transformsOf(currentSchema, table);
1157
1215
  return rows.map((row) => applyFromDb(reviveVectors(asWireRow(row), vectorCols), transforms));
1158
1216
  }
1159
- function asBindParams(table, cols, data) {
1217
+ function asBindParams(table, cols, data, caller) {
1218
+ assertUsableWriteValues(caller, table, cols, data);
1160
1219
  const vectorCols = vectorColumnsOf(currentSchema, table);
1161
1220
  const transforms = transformsOf(currentSchema, table);
1162
1221
  return cols.map((c) => {
@@ -1379,6 +1438,7 @@ function compileWhere(table, colSet, where, add, caller = "search") {
1379
1438
  const t = transforms.get(col);
1380
1439
  return t?.toDb === void 0 ? add : (v) => add(v === null || v === void 0 ? v : t.toDb(v));
1381
1440
  };
1441
+ assertUsableFilter(caller, table, where);
1382
1442
  for (const [col, cond] of Object.entries(where)) {
1383
1443
  if (colSet !== null && !colSet.has(col)) {
1384
1444
  throw new Error(`${caller}(${table}): where kolonu "${col}" tabloda yok (FR-016)`);
@@ -1395,11 +1455,17 @@ function compileWhere(table, colSet, where, add, caller = "search") {
1395
1455
  }
1396
1456
  parts.push(`${q} IN (${v.map((x) => bind(x)).join(", ")})`);
1397
1457
  } else if (op in WHERE_OPS) {
1458
+ if (v === null && (op === "neq" || op === "eq")) {
1459
+ parts.push(`${q} IS ${op === "neq" ? "NOT " : ""}NULL`);
1460
+ continue;
1461
+ }
1398
1462
  parts.push(`${q} ${WHERE_OPS[op]} ${bind(v)}`);
1399
1463
  } else {
1400
1464
  throw new Error(`${caller}(${table}): where.${col} bilinmeyen operat\xF6r "${op}" (gt/gte/lt/lte/neq/in)`);
1401
1465
  }
1402
1466
  }
1467
+ } else if (cond === null) {
1468
+ parts.push(`${q} IS NULL`);
1403
1469
  } else {
1404
1470
  parts.push(`${q} = ${bind(cond)}`);
1405
1471
  }
@@ -1492,7 +1558,7 @@ function createOps(tx) {
1492
1558
  if (cols.length === 0) throw new Error(`insert into ${table}: no columns given`);
1493
1559
  const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
1494
1560
  const sql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`;
1495
- const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data));
1561
+ const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data, "insert"));
1496
1562
  const inserted = rows[0];
1497
1563
  if (!inserted) {
1498
1564
  throw new Error(
@@ -1526,7 +1592,7 @@ function createOps(tx) {
1526
1592
  const ph = cols.map((_, i) => `$${i + 1}`).join(", ");
1527
1593
  const insertSql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${ph}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) WHERE "valid_to" IS NULL DO NOTHING RETURNING *`;
1528
1594
  const live = await at();
1529
- const bindData = asBindParams(table, cols, data);
1595
+ const bindData = asBindParams(table, cols, data, "upsert");
1530
1596
  const first = await live.unsafe(insertSql, bindData);
1531
1597
  if (first[0]) return asTableRow(table, first[0]);
1532
1598
  const condSql = conflict.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ");
@@ -1560,7 +1626,7 @@ function createOps(tx) {
1560
1626
  const assignments = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
1561
1627
  const action = assignments.length ? `DO UPDATE SET ${assignments.join(", ")}` : `DO UPDATE SET ${quoteIdent(conflict[0])} = EXCLUDED.${quoteIdent(conflict[0])}`;
1562
1628
  const sql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING *`;
1563
- const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data));
1629
+ const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data, "upsert"));
1564
1630
  const row = rows[0];
1565
1631
  if (!row) {
1566
1632
  throw new Error(
@@ -1574,7 +1640,7 @@ function createOps(tx) {
1574
1640
  if (cols.length === 0) return ops.findById(table, id);
1575
1641
  const assignments = cols.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(", ");
1576
1642
  const sql = `UPDATE ${quoteIdent(table)} SET ${assignments} WHERE id = $${cols.length + 1} RETURNING *`;
1577
- const rows = await (await at()).unsafe(sql, [...asBindParams(table, cols, data), id]);
1643
+ const rows = await (await at()).unsafe(sql, [...asBindParams(table, cols, data, "update"), id]);
1578
1644
  return rows[0] ? asTableRow(table, rows[0]) : null;
1579
1645
  },
1580
1646
  async delete(table, id) {
@@ -1605,13 +1671,20 @@ function createOps(tx) {
1605
1671
  `updateMany(${table}): nothing to set. An update with no columns is not a no-op worth pretending happened \u2014 pass the columns to write.`
1606
1672
  );
1607
1673
  }
1674
+ const known = schemaColumns(table);
1675
+ if (known !== null) {
1676
+ for (const c of cols) {
1677
+ if (!known.has(c)) {
1678
+ throw new Error(`updateMany(${table}): set kolonu "${c}" tabloda yok (FR-016)`);
1679
+ }
1680
+ }
1681
+ }
1608
1682
  const params = [];
1609
1683
  const add = (v) => {
1610
1684
  params.push(v);
1611
1685
  return `$${params.length}`;
1612
1686
  };
1613
- const assignments = cols.map((c) => `${quoteIdent(c)} = ${add(asBindParams(table, [c], set)[0])}`).join(", ");
1614
- const known = schemaColumns(table);
1687
+ const assignments = cols.map((c) => `${quoteIdent(c)} = ${add(asBindParams(table, [c], set, "updateMany")[0])}`).join(", ");
1615
1688
  const whereSql = compileWhere(table, known, where, add, "updateMany");
1616
1689
  assertHasPredicate(whereSql, "updateMany", table);
1617
1690
  const sql = `UPDATE ${quoteIdent(table)} AS t SET ${assignments} WHERE true${whereSql} RETURNING *`;
@@ -2083,7 +2156,7 @@ function createOps(tx) {
2083
2156
  const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
2084
2157
  const rows = await sp.unsafe(
2085
2158
  `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`,
2086
- asBindParams(table, cols, data)
2159
+ asBindParams(table, cols, data, "supersede")
2087
2160
  );
2088
2161
  if (!rows[0]) {
2089
2162
  throw new Error(
@@ -2223,7 +2296,7 @@ function diagnosingDriver(sql, surface = "service") {
2223
2296
  );
2224
2297
  }
2225
2298
  if (isUniqueViolation(e)) {
2226
- return new UniqueViolation(constraintOf(e));
2299
+ return markEngineRaised(new UniqueViolation(constraintOf(e)));
2227
2300
  }
2228
2301
  if (surface === "service" && isForeignKeyViolation(e)) {
2229
2302
  return new Error(
@@ -2679,6 +2752,7 @@ async function defaultSqlDriver(config) {
2679
2752
  }
2680
2753
  return new g.Bun.SQL({ url: config.databaseUrl, max: config.poolMax });
2681
2754
  }
2755
+ var AUTHOR_ANSWERED = /* @__PURE__ */ new Set([400, 401, 403, 404, 409, 429]);
2682
2756
  async function createApp(opts) {
2683
2757
  const { config, controllers } = opts;
2684
2758
  setSchema(opts.schema ?? {});
@@ -3064,6 +3138,9 @@ async function createApp(opts) {
3064
3138
  } catch (err) {
3065
3139
  await db.rollback(err);
3066
3140
  if (isHttpError(err)) {
3141
+ if (isEngineRaised(err) || !AUTHOR_ANSWERED.has(err.status)) {
3142
+ log.error(`[engine] unhandled ${err.error} in ${hit.entry.id}`, err);
3143
+ }
3067
3144
  return envelope(
3068
3145
  err.error,
3069
3146
  err.errorDescription,