@palbase/backend 22.0.0 → 22.1.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 (43) hide show
  1. package/dist/bin/palbase-backend.cjs +340 -22
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +2 -2
  4. package/dist/{chunk-QYOHMVUW.js → chunk-74XDEF5J.js} +338 -22
  5. package/dist/chunk-74XDEF5J.js.map +1 -0
  6. package/dist/{chunk-SSGAMC26.js → chunk-I3ON7MYF.js} +57 -5
  7. package/dist/chunk-I3ON7MYF.js.map +1 -0
  8. package/dist/{chunk-POYAFBLF.js → chunk-SQC5EIWY.js} +5 -3
  9. package/dist/chunk-SQC5EIWY.js.map +1 -0
  10. package/dist/db/index.cjs +60 -6
  11. package/dist/db/index.cjs.map +1 -1
  12. package/dist/db/index.d.cts +2 -2
  13. package/dist/db/index.d.ts +2 -2
  14. package/dist/db/index.js +7 -3
  15. package/dist/{endpoint-B0LpZixz.d.cts → endpoint-BVT6jcVW.d.cts} +39 -7
  16. package/dist/{endpoint-B0LpZixz.d.ts → endpoint-BVT6jcVW.d.ts} +39 -7
  17. package/dist/engine/index.cjs +340 -22
  18. package/dist/engine/index.cjs.map +1 -1
  19. package/dist/engine/index.d.cts +4 -4
  20. package/dist/engine/index.d.ts +4 -4
  21. package/dist/engine/index.js +2 -2
  22. package/dist/{index-B4W6d2VJ.d.cts → index-BS1gW4nV.d.cts} +24 -25
  23. package/dist/{index-BGSCWlUa.d.cts → index-BqCiHao8.d.cts} +97 -7
  24. package/dist/{index-BCNtlG1w.d.ts → index-CCZqzych.d.ts} +24 -25
  25. package/dist/{index-g-EzitI-.d.ts → index-vwHoS0l2.d.ts} +97 -7
  26. package/dist/index.cjs +244 -6
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +124 -9
  29. package/dist/index.d.ts +124 -9
  30. package/dist/index.js +186 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/openapi/index.d.cts +2 -2
  33. package/dist/openapi/index.d.ts +2 -2
  34. package/dist/{registry-Cw0YEYCg.d.cts → registry-BWttGlaT.d.cts} +1 -1
  35. package/dist/{registry-3BLYv4si.d.ts → registry-Bsuf-orT.d.ts} +1 -1
  36. package/docs/README.md +12 -7
  37. package/docs/endpoints.md +1 -1
  38. package/docs/errors.md +9 -0
  39. package/docs/llms-full.txt +22 -8
  40. package/package.json +1 -1
  41. package/dist/chunk-POYAFBLF.js.map +0 -1
  42. package/dist/chunk-QYOHMVUW.js.map +0 -1
  43. package/dist/chunk-SSGAMC26.js.map +0 -1
@@ -425,7 +425,8 @@ function makeTablesAccessor(ops) {
425
425
  update: (id, data) => ops().update(name, id, data),
426
426
  delete: (id) => ops().delete(name, id),
427
427
  findById: (id) => ops().findById(name, id),
428
- findMany: (query) => ops().findMany(name, query)
428
+ findMany: (query) => ops().findMany(name, query),
429
+ search: (params) => ops().search(name, params)
429
430
  };
430
431
  }
431
432
  }
@@ -440,7 +441,8 @@ function makeTypedSurface(raw) {
440
441
  update: (table, id, data) => raw.update(table, id, data),
441
442
  delete: (table, id) => raw.delete(table, id),
442
443
  findById: (table, id) => raw.findById(table, id),
443
- findMany: (table, query) => raw.findMany(table, query)
444
+ findMany: (table, query) => raw.findMany(table, query),
445
+ search: (table, params) => raw.search(table, params)
444
446
  };
445
447
  return Object.assign(ops, {
446
448
  tables: makeTablesAccessor(() => raw),
@@ -971,6 +973,194 @@ function asWireRow(row) {
971
973
  function asWireRows(rows) {
972
974
  return rows.map((row) => asWireRow(row));
973
975
  }
976
+ function toVectorLiteral(v) {
977
+ return `[${v.join(",")}]`;
978
+ }
979
+ function vectorColumnsOf(schema, table) {
980
+ const out = /* @__PURE__ */ new Set();
981
+ for (const [key, def] of Object.entries(schema.tables ?? {})) {
982
+ if ((def.name ?? key) !== table) continue;
983
+ for (const [col, c] of Object.entries(def.columns ?? {})) {
984
+ const d = c !== null && typeof c === "object" && "_def" in c ? c._def : c;
985
+ if (d !== null && typeof d === "object" && d.type === "vector") out.add(col);
986
+ }
987
+ }
988
+ return out;
989
+ }
990
+ function reviveVectors(row, vectorCols) {
991
+ if (row === null || typeof row !== "object" || vectorCols.size === 0) return row;
992
+ const out = row;
993
+ for (const col of vectorCols) {
994
+ const v = out[col];
995
+ if (typeof v === "string") out[col] = JSON.parse(v);
996
+ }
997
+ return row;
998
+ }
999
+ function asTableRow(table, row) {
1000
+ return reviveVectors(asWireRow(row), vectorColumnsOf(currentSchema, table));
1001
+ }
1002
+ function asTableRows(table, rows) {
1003
+ const vectorCols = vectorColumnsOf(currentSchema, table);
1004
+ return rows.map((row) => reviveVectors(asWireRow(row), vectorCols));
1005
+ }
1006
+ function asBindParams(table, cols, data) {
1007
+ const vectorCols = vectorColumnsOf(currentSchema, table);
1008
+ return cols.map((c) => {
1009
+ const v = data[c];
1010
+ return Array.isArray(v) && vectorCols.has(c) ? toVectorLiteral(v) : v;
1011
+ });
1012
+ }
1013
+ var SELECTIVITY_EXACT_THRESHOLD = 1e4;
1014
+ var METRIC_OPERATOR = {
1015
+ cosine: "<=>",
1016
+ euclidean: "<->",
1017
+ inner_product: "<#>"
1018
+ };
1019
+ function searchConfigFor(table) {
1020
+ const t = currentSchema.tables?.[table];
1021
+ if (!t) return null;
1022
+ const columns = t.columns ?? {};
1023
+ const defOf = (c) => c !== null && typeof c === "object" && "_def" in c ? c._def : c ?? {};
1024
+ const cols = Object.keys(columns);
1025
+ const vectorCols = cols.filter((c) => defOf(columns[c]).type === "vector");
1026
+ const defOfFull = (c) => c !== null && typeof c === "object" && "_def" in c ? c._def : c ?? {};
1027
+ const pkCols = cols.filter((c) => defOfFull(columns[c]).primaryKey === true);
1028
+ const pk = pkCols.length === 1 ? pkCols[0] : cols.includes("id") ? "id" : null;
1029
+ if (pk === null) {
1030
+ throw new Error(
1031
+ `search(${table}): tek-kolon primary key bulunamad\u0131 \u2014 arama s\u0131ralamas\u0131 ve sat\u0131r birle\u015Fimi PK ister (FR-020)`
1032
+ );
1033
+ }
1034
+ const search = t.search;
1035
+ const ftsCols = search?.text ?? [];
1036
+ const rawLegs = search?.vector === void 0 ? [] : Array.isArray(search.vector) ? search.vector : [search.vector];
1037
+ let legs;
1038
+ if (rawLegs.length > 0) {
1039
+ legs = rawLegs.map((leg) => {
1040
+ const l = leg;
1041
+ const column = l.column ?? (vectorCols.length === 1 ? vectorCols[0] : void 0);
1042
+ if (column === void 0) {
1043
+ throw new Error(`search(${table}): birden \xE7ok vector kolonu var \u2014 beyanda 'column' zorunlu (FR-010)`);
1044
+ }
1045
+ const model = l.model;
1046
+ return {
1047
+ column,
1048
+ metric: l.metric ?? "cosine",
1049
+ ...model !== void 0 ? { embed: {
1050
+ model: model.model,
1051
+ apiKeyName: model.apiKeyName ?? "OPENAI_API_KEY",
1052
+ ...model.baseURL !== void 0 ? { baseURL: model.baseURL } : {},
1053
+ ...model.dimensions !== void 0 ? { dimensions: model.dimensions } : {}
1054
+ } } : {}
1055
+ };
1056
+ });
1057
+ } else {
1058
+ legs = vectorCols.map((column) => ({ column, metric: "cosine" }));
1059
+ }
1060
+ if (ftsCols.length === 0 && legs.length === 0) return null;
1061
+ return { pk, cols, colSet: new Set(cols), ftsCols, legs };
1062
+ }
1063
+ function pickLeg(table, legs, using) {
1064
+ if (legs.length === 0) return null;
1065
+ if (using !== void 0) {
1066
+ const hit = legs.find((l) => l.column === using);
1067
+ if (!hit) {
1068
+ throw new Error(
1069
+ `search(${table}): using "${using}" bir vekt\xF6r kolunu adlam\u0131yor \u2014 mevcut: ${legs.map((l) => l.column).join(", ")}`
1070
+ );
1071
+ }
1072
+ return hit;
1073
+ }
1074
+ if (legs.length === 1) return legs[0];
1075
+ throw new Error(`search(${table}): birden \xE7ok vekt\xF6r kolu var \u2014 'using' ile se\xE7in (FR-013) \u2014 salt metin ar\u0131yorsan mode:"text" kullan`);
1076
+ }
1077
+ var WHERE_OPS = { gt: ">", gte: ">=", lt: "<", lte: "<=", neq: "<>" };
1078
+ function compileWhere(table, colSet, where, add) {
1079
+ const parts = [];
1080
+ for (const [col, cond] of Object.entries(where)) {
1081
+ if (!colSet.has(col)) {
1082
+ throw new Error(`search(${table}): where kolonu "${col}" tabloda yok (FR-016)`);
1083
+ }
1084
+ const q = `t.${quoteIdent(col)}`;
1085
+ if (cond !== null && typeof cond === "object" && !Array.isArray(cond)) {
1086
+ for (const [op, v] of Object.entries(cond)) {
1087
+ if (op === "in") {
1088
+ if (!Array.isArray(v)) throw new Error(`search(${table}): where.${col}.in bir dizi olmal\u0131`);
1089
+ if (v.length === 0) {
1090
+ parts.push("false");
1091
+ continue;
1092
+ }
1093
+ parts.push(`${q} IN (${v.map((x) => add(x)).join(", ")})`);
1094
+ } else if (op in WHERE_OPS) {
1095
+ parts.push(`${q} ${WHERE_OPS[op]} ${add(v)}`);
1096
+ } else {
1097
+ throw new Error(`search(${table}): where.${col} bilinmeyen operat\xF6r "${op}" (gt/gte/lt/lte/neq/in)`);
1098
+ }
1099
+ }
1100
+ } else {
1101
+ parts.push(`${q} = ${add(cond)}`);
1102
+ }
1103
+ }
1104
+ return parts.length === 0 ? "" : ` AND ${parts.join(" AND ")}`;
1105
+ }
1106
+ var cachedVectorSchema = null;
1107
+ async function vectorSchemaWithGuc(runner) {
1108
+ if (cachedVectorSchema !== null) {
1109
+ await runner.unsafe(
1110
+ "select set_config('hnsw.iterative_scan','relaxed_order',true), set_config('hnsw.max_scan_tuples','200000',true), set_config('hnsw.ef_search','200',true)"
1111
+ );
1112
+ return cachedVectorSchema;
1113
+ }
1114
+ const rows = await runner.unsafe(
1115
+ "select set_config('hnsw.iterative_scan','relaxed_order',true), set_config('hnsw.max_scan_tuples','200000',true), set_config('hnsw.ef_search','200',true), (select n.nspname from pg_extension e join pg_namespace n on n.oid = e.extnamespace where e.extname = 'vector') as nspname"
1116
+ );
1117
+ const name = rows?.[0]?.nspname;
1118
+ if (typeof name !== "string" || name === "") {
1119
+ throw new Error("pgvector extension kurulu de\u011Fil \u2014 vector aramas\u0131 \xE7al\u0131\u015Famaz (extensions beyan\u0131 deploy'dan ge\xE7ti mi?)");
1120
+ }
1121
+ cachedVectorSchema = name;
1122
+ return name;
1123
+ }
1124
+ var secretReader = null;
1125
+ function setSecretReader(fn) {
1126
+ secretReader = fn;
1127
+ }
1128
+ var embedFetch = (url, init) => fetch(url, init);
1129
+ async function embedQuery(embed, text) {
1130
+ if (secretReader === null) {
1131
+ throw new Error(`query embed: secret reader ba\u011Flanmam\u0131\u015F \u2014 ${embed.apiKeyName} okunam\u0131yor`);
1132
+ }
1133
+ const key = await secretReader(embed.apiKeyName);
1134
+ if (key === null || key === "") {
1135
+ throw new Error(`query embed: vault'ta ${embed.apiKeyName} yok (FR-021/FR-025)`);
1136
+ }
1137
+ const url = (embed.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "") + "/embeddings";
1138
+ const controller = new AbortController();
1139
+ const timer = setTimeout(() => controller.abort(), 1e4);
1140
+ try {
1141
+ const res = await embedFetch(url, {
1142
+ method: "POST",
1143
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
1144
+ body: JSON.stringify({
1145
+ model: embed.model,
1146
+ input: [text],
1147
+ ...embed.dimensions !== void 0 ? { dimensions: embed.dimensions } : {}
1148
+ }),
1149
+ signal: controller.signal
1150
+ });
1151
+ if (!res.ok) {
1152
+ throw new Error(`query embed: sa\u011Flay\u0131c\u0131 ${res.status} d\xF6nd\xFC (${embed.apiKeyName} ile) \u2014 anahtar/model do\u011Fru mu?`);
1153
+ }
1154
+ const data = await res.json();
1155
+ const vec = data.data?.[0]?.embedding;
1156
+ if (!Array.isArray(vec)) {
1157
+ throw new Error("query embed: sa\u011Flay\u0131c\u0131 yan\u0131t\u0131nda data[0].embedding yok (CLAIM-N1 \u015Fekli)");
1158
+ }
1159
+ return vec;
1160
+ } finally {
1161
+ clearTimeout(timer);
1162
+ }
1163
+ }
974
1164
  function createOps(tx) {
975
1165
  const at = () => resolveTx(tx);
976
1166
  const ops = {
@@ -982,22 +1172,22 @@ function createOps(tx) {
982
1172
  if (cols.length === 0) throw new Error(`insert into ${table}: no columns given`);
983
1173
  const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
984
1174
  const sql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`;
985
- const rows = await (await at()).unsafe(sql, cols.map((c) => data[c]));
1175
+ const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data));
986
1176
  const inserted = rows[0];
987
1177
  if (!inserted) {
988
1178
  throw new Error(
989
1179
  `insert into ${table} returned no row \u2014 the write was rejected (an RLS policy, most likely).`
990
1180
  );
991
1181
  }
992
- return asWireRow(inserted);
1182
+ return asTableRow(table, inserted);
993
1183
  },
994
1184
  async update(table, id, data) {
995
1185
  const cols = Object.keys(data);
996
1186
  if (cols.length === 0) return ops.findById(table, id);
997
1187
  const assignments = cols.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(", ");
998
1188
  const sql = `UPDATE ${quoteIdent(table)} SET ${assignments} WHERE id = $${cols.length + 1} RETURNING *`;
999
- const rows = await (await at()).unsafe(sql, [...cols.map((c) => data[c]), id]);
1000
- return rows[0] ? asWireRow(rows[0]) : null;
1189
+ const rows = await (await at()).unsafe(sql, [...asBindParams(table, cols, data), id]);
1190
+ return rows[0] ? asTableRow(table, rows[0]) : null;
1001
1191
  },
1002
1192
  async delete(table, id) {
1003
1193
  await (await at()).unsafe(`DELETE FROM ${quoteIdent(table)} WHERE id = $1`, [id]);
@@ -1007,16 +1197,96 @@ function createOps(tx) {
1007
1197
  `SELECT * FROM ${quoteIdent(table)} WHERE id = $1`,
1008
1198
  [id]
1009
1199
  );
1010
- return rows[0] ? asWireRow(rows[0]) : null;
1200
+ return rows[0] ? asTableRow(table, rows[0]) : null;
1011
1201
  },
1012
1202
  async findMany(table, query = {}) {
1013
1203
  const cols = Object.keys(query);
1014
1204
  const where = cols.length ? ` WHERE ${cols.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ")}` : "";
1015
- return asWireRows(await (await at()).unsafe(
1205
+ return asTableRows(table, await (await at()).unsafe(
1016
1206
  `SELECT * FROM ${quoteIdent(table)}${where}`,
1017
1207
  cols.map((c) => query[c])
1018
1208
  ));
1019
1209
  },
1210
+ /**
1211
+ * Tek-SQL hibrit arama (FR-014): iki kol CTE + FULL OUTER JOIN + RRF
1212
+ * (1/(50+rank), CLAIM-N4). Operatör şema-nitelikli (C-10, M-1); GUC
1213
+ * hnsw.iterative_scan=relaxed_order aynı tx'te set_config ile (CLAIM-N3 —
1214
+ * RLS/filtre altında LIMIT-altı dönüş açığını kapatır). Sorgu-anı embed
1215
+ * T024'te gelir; o zamana dek vector kolu yalnız params.vector ile koşar.
1216
+ */
1217
+ async search(table, params = {}) {
1218
+ const cfg = searchConfigFor(table);
1219
+ if (!cfg) {
1220
+ throw new Error(`search(${table}): tablo aranabilir de\u011Fil \u2014 ne vector kolonu ne search beyan\u0131 var (FR-013)`);
1221
+ }
1222
+ const rawLimit = params.limit ?? 20;
1223
+ if (typeof rawLimit !== "number" || !Number.isFinite(rawLimit)) {
1224
+ throw new Error(`search(${table}): limit sonlu bir say\u0131 olmal\u0131, ${String(rawLimit)} verildi (FR-013)`);
1225
+ }
1226
+ const limit = Math.min(Math.max(1, Math.trunc(rawLimit)), 100);
1227
+ const pool = Math.max(limit * 3, 30);
1228
+ const wantText = params.mode !== "vector" && cfg.ftsCols.length > 0 && typeof params.query === "string" && params.query !== "";
1229
+ const anyEmbed = cfg.legs.some((l) => l.embed !== void 0);
1230
+ const vectorAsked = params.mode !== "text" && (Array.isArray(params.vector) || params.using !== void 0 || params.mode === "vector" || anyEmbed && typeof params.query === "string" && params.query !== "");
1231
+ const leg = vectorAsked ? pickLeg(table, cfg.legs, params.using) : null;
1232
+ let qv = Array.isArray(params.vector) ? params.vector : null;
1233
+ if (qv === null && leg?.embed !== void 0 && typeof params.query === "string" && params.query !== "") {
1234
+ try {
1235
+ qv = await embedQuery(leg.embed, params.query);
1236
+ } catch (e) {
1237
+ if (!wantText) throw e;
1238
+ qv = null;
1239
+ }
1240
+ }
1241
+ const wantVector = leg !== null && qv !== null;
1242
+ if (!wantText && !wantVector) {
1243
+ throw new Error(
1244
+ `search(${table}): ko\u015Fulabilir kol yok \u2014 metin i\xE7in 'query' (FTS beyan\u0131 gerekir), semantik i\xE7in 'vector' verin (FR-015)`
1245
+ );
1246
+ }
1247
+ const bind = [];
1248
+ const add = (v) => {
1249
+ bind.push(v);
1250
+ return `$${bind.length}`;
1251
+ };
1252
+ const whereSql = compileWhere(table, cfg.colSet, params.where ?? {}, add);
1253
+ const live = await at();
1254
+ const K = 50;
1255
+ let semSql = "";
1256
+ let kwSql = "";
1257
+ if (wantVector && leg) {
1258
+ const sch = await vectorSchemaWithGuc(live);
1259
+ const op = METRIC_OPERATOR[leg.metric] ?? METRIC_OPERATOR.cosine;
1260
+ let exactOrder = "";
1261
+ if (whereSql !== "") {
1262
+ const probeRows = await live.unsafe(
1263
+ `SELECT count(*)::int AS n FROM (SELECT 1 FROM ${quoteIdent(table)} t WHERE t.${quoteIdent(leg.column)} IS NOT NULL${whereSql} LIMIT ${SELECTIVITY_EXACT_THRESHOLD + 1}) s`,
1264
+ bind.slice()
1265
+ );
1266
+ const n = probeRows?.[0]?.n;
1267
+ if (typeof n === "number" && n <= SELECTIVITY_EXACT_THRESHOLD) {
1268
+ exactOrder = " + 0.0";
1269
+ }
1270
+ }
1271
+ const vp = add(toVectorLiteral(qv));
1272
+ semSql = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY (t.${quoteIdent(leg.column)} OPERATOR(${quoteIdent(sch)}.${op}) ${vp}::${quoteIdent(sch)}.vector)${exactOrder}) AS r FROM ${quoteIdent(table)} t WHERE t.${quoteIdent(leg.column)} IS NOT NULL${whereSql} ORDER BY r LIMIT ${pool}`;
1273
+ }
1274
+ if (wantText) {
1275
+ const qp = add(params.query);
1276
+ kwSql = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(t.palbase_fts, websearch_to_tsquery('simple', ${qp})) DESC) AS r FROM ${quoteIdent(table)} t WHERE t.palbase_fts @@ websearch_to_tsquery('simple', ${qp})${whereSql} ORDER BY r LIMIT ${pool}`;
1277
+ }
1278
+ const colList = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
1279
+ let sql;
1280
+ if (semSql !== "" && kwSql !== "") {
1281
+ sql = `WITH sem AS (${semSql}), kw AS (${kwSql}), fused AS (SELECT COALESCE(sem.id, kw.id) AS id, (COALESCE(1.0/(${K} + sem.r), 0) + COALESCE(1.0/(${K} + kw.r), 0))::float8 AS _score FROM sem FULL OUTER JOIN kw ON sem.id = kw.id) SELECT ${colList}, fused._score AS _score FROM fused JOIN ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = fused.id ORDER BY fused._score DESC, t.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
1282
+ } else {
1283
+ const single = semSql !== "" ? `sem AS (${semSql})` : `kw AS (${kwSql})`;
1284
+ const alias = semSql !== "" ? "sem" : "kw";
1285
+ sql = `WITH ${single} SELECT ${colList}, (1.0/(${K} + ${alias}.r))::float8 AS _score FROM ${alias} JOIN ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = ${alias}.id ORDER BY _score DESC, t.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
1286
+ }
1287
+ const rows = await live.unsafe(sql, bind);
1288
+ return asTableRows(table, rows);
1289
+ },
1020
1290
  /** A real SAVEPOINT inside the request's transaction. */
1021
1291
  async transaction(cb) {
1022
1292
  const live = await at();
@@ -1045,7 +1315,7 @@ function createOps(tx) {
1045
1315
  return live.savepoint(async (sp) => {
1046
1316
  const results = [];
1047
1317
  for (const op of plan.ops) {
1048
- const rows = await runPlanOp(sp, op, results);
1318
+ const rows = asTableRows(op.table, await runPlanOp(sp, op, results));
1049
1319
  const result = { rows, rows_affected: rows.length };
1050
1320
  results.push(result);
1051
1321
  assertGuard(op, result);
@@ -1058,6 +1328,7 @@ function createOps(tx) {
1058
1328
  }
1059
1329
  var currentSchema = {};
1060
1330
  function setSchema(schema) {
1331
+ cachedVectorSchema = null;
1061
1332
  const s = schema;
1062
1333
  currentSchema = (s && "default" in s ? s.default : s) ?? {};
1063
1334
  }
@@ -1144,7 +1415,7 @@ function isRef(v) {
1144
1415
  function isExpr(v) {
1145
1416
  return typeof v === "object" && v !== null && "$expr" in v;
1146
1417
  }
1147
- function renderValue(value, column, args, results) {
1418
+ function renderValue(value, column, args, results, vectorCols) {
1148
1419
  if (isRef(value)) {
1149
1420
  const source = results[value.$ref.op];
1150
1421
  const row = source?.rows[0];
@@ -1153,15 +1424,15 @@ function renderValue(value, column, args, results) {
1153
1424
  error_code: "tx_ref_unresolved"
1154
1425
  });
1155
1426
  }
1156
- return args.bind(row[value.$ref.field]);
1427
+ return bindMaybeVector(args, column, vectorCols, row[value.$ref.field]);
1157
1428
  }
1158
1429
  if (isExpr(value)) {
1159
1430
  const fn = value.$expr;
1160
1431
  if (fn.fn === "now") return "now()";
1161
1432
  const operator = fn.fn === "inc" ? "+" : "-";
1162
- return `${quoteIdent(column)} ${operator} ${args.bind(fn.by)}`;
1433
+ return `${quoteIdent(column)} ${operator} ${bindMaybeVector(args, column, vectorCols, fn.by)}`;
1163
1434
  }
1164
- return args.bind(value);
1435
+ return bindMaybeVector(args, column, vectorCols, value);
1165
1436
  }
1166
1437
  function renderWhere(where, args, results) {
1167
1438
  const cols = Object.keys(where ?? {});
@@ -1173,14 +1444,21 @@ function renderWhere(where, args, results) {
1173
1444
  });
1174
1445
  return ` WHERE ${terms.join(" AND ")}`;
1175
1446
  }
1447
+ function bindMaybeVector(args, column, vectorCols, value) {
1448
+ if (column !== void 0 && vectorCols?.has(column) && Array.isArray(value)) {
1449
+ return args.bind(toVectorLiteral(value));
1450
+ }
1451
+ return args.bind(value);
1452
+ }
1176
1453
  async function runPlanOp(sp, op, results) {
1454
+ const opVectorCols = vectorColumnsOf(currentSchema, op.table);
1177
1455
  const args = new Args();
1178
1456
  const table = quoteIdent(op.table);
1179
1457
  let sql;
1180
1458
  switch (op.op) {
1181
1459
  case "insert": {
1182
1460
  const cols = Object.keys(op.values ?? {});
1183
- const rendered = cols.map((c) => renderValue(op.values[c], c, args, results));
1461
+ const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols));
1184
1462
  sql = cols.length ? `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) RETURNING *` : `INSERT INTO ${table} DEFAULT VALUES RETURNING *`;
1185
1463
  break;
1186
1464
  }
@@ -1189,7 +1467,7 @@ async function runPlanOp(sp, op, results) {
1189
1467
  if (rows.length === 0 || !rows[0]) return [];
1190
1468
  const cols = Object.keys(rows[0]);
1191
1469
  const tuples = rows.map(
1192
- (r) => `(${cols.map((c) => renderValue(r[c], c, args, results)).join(", ")})`
1470
+ (r) => `(${cols.map((c) => renderValue(r[c], c, args, results, opVectorCols)).join(", ")})`
1193
1471
  );
1194
1472
  sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")} RETURNING *`;
1195
1473
  break;
@@ -1198,7 +1476,7 @@ async function runPlanOp(sp, op, results) {
1198
1476
  const cols = Object.keys(op.set ?? {});
1199
1477
  if (cols.length === 0) throw new Error(`update ${op.table}: nothing to set`);
1200
1478
  const assignments = cols.map(
1201
- (c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results)}`
1479
+ (c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results, opVectorCols)}`
1202
1480
  );
1203
1481
  sql = `UPDATE ${table} SET ${assignments.join(", ")}${renderWhere(op.where, args, results)} RETURNING *`;
1204
1482
  break;
@@ -1345,7 +1623,8 @@ function grantFor(entry, ctx, bucketLimits) {
1345
1623
  filename: ctx.filename
1346
1624
  }),
1347
1625
  maxBytes: bucketLimits?.maxBytes ?? null,
1348
- mimeTypes: bucketLimits?.mimeTypes ?? null
1626
+ mimeTypes: bucketLimits?.mimeTypes ?? null,
1627
+ ownerUid: ctx.userId ?? null
1349
1628
  };
1350
1629
  }
1351
1630
  var CompletionLedger = class {
@@ -1381,6 +1660,23 @@ function verifySignature(presented, expected) {
1381
1660
 
1382
1661
  // src/engine/index.ts
1383
1662
  var JSON_HEADERS = { "content-type": "application/json" };
1663
+ function fieldErrors(err) {
1664
+ return err.issues.map((i) => ({ field: i.path.join("."), message: i.message }));
1665
+ }
1666
+ function headersFor(raw, schema) {
1667
+ const shape = schema.shape;
1668
+ if (typeof shape !== "object" || shape === null) return raw;
1669
+ let aliased = null;
1670
+ for (const declared of Object.keys(shape)) {
1671
+ const lower = declared.toLowerCase();
1672
+ if (lower === declared) continue;
1673
+ const value = raw[lower];
1674
+ if (value === void 0) continue;
1675
+ aliased ??= { ...raw };
1676
+ aliased[declared] = value;
1677
+ }
1678
+ return aliased ?? raw;
1679
+ }
1384
1680
  function envelope(error, description, status, requestId, extra) {
1385
1681
  return new Response(
1386
1682
  JSON.stringify({ error, error_description: description, status, request_id: requestId, ...extra }),
@@ -1414,6 +1710,7 @@ async function defaultSqlDriver(config) {
1414
1710
  async function createApp(opts) {
1415
1711
  const { config, controllers } = opts;
1416
1712
  setSchema(opts.schema ?? {});
1713
+ setSecretReader(opts.secretReader ?? null);
1417
1714
  const routes = buildRouteTable(controllers);
1418
1715
  if (routes.length === 0) {
1419
1716
  throw new BootRefused([], "boot refused: zero endpoints collected \u2014 nothing would answer.");
@@ -1526,7 +1823,8 @@ async function createApp(opts) {
1526
1823
  error: "too_many_requests",
1527
1824
  error_description: "Rate limit exceeded for this endpoint",
1528
1825
  status: 429,
1529
- request_id: requestId
1826
+ request_id: requestId,
1827
+ data: { retryAfter }
1530
1828
  }),
1531
1829
  { status: 429, headers: { ...JSON_HEADERS, "retry-after": String(retryAfter) } }
1532
1830
  );
@@ -1545,7 +1843,7 @@ async function createApp(opts) {
1545
1843
  const r = p.schema.safeParse(parsedBody);
1546
1844
  if (!r.success) {
1547
1845
  return envelope("bad_request", "Request body failed validation", 400, requestId, {
1548
- fields: r.error.issues.map((i) => ({ field: i.path.join("."), message: i.message }))
1846
+ data: { fields: fieldErrors(r.error) }
1549
1847
  });
1550
1848
  }
1551
1849
  args[p.index] = r.data;
@@ -1555,7 +1853,7 @@ async function createApp(opts) {
1555
1853
  const r = p.schema.safeParse(Object.fromEntries(url.searchParams));
1556
1854
  if (!r.success) {
1557
1855
  return envelope("bad_request", "Query parameters failed validation", 400, requestId, {
1558
- fields: r.error.issues.map((i) => ({ field: i.path.join("."), message: i.message }))
1856
+ data: { fields: fieldErrors(r.error) }
1559
1857
  });
1560
1858
  }
1561
1859
  args[p.index] = r.data;
@@ -1564,9 +1862,21 @@ async function createApp(opts) {
1564
1862
  case "param":
1565
1863
  args[p.index] = hit.params[p.name];
1566
1864
  break;
1567
- case "headers":
1568
- args[p.index] = Object.fromEntries(req.headers);
1865
+ case "headers": {
1866
+ const raw = Object.fromEntries(req.headers);
1867
+ if (!p.schema) {
1868
+ args[p.index] = raw;
1869
+ break;
1870
+ }
1871
+ const r = p.schema.safeParse(headersFor(raw, p.schema));
1872
+ if (!r.success) {
1873
+ return envelope("bad_request", "Request headers failed validation", 400, requestId, {
1874
+ data: { fields: fieldErrors(r.error) }
1875
+ });
1876
+ }
1877
+ args[p.index] = r.data;
1569
1878
  break;
1879
+ }
1570
1880
  case "user":
1571
1881
  case "optionalUser":
1572
1882
  args[p.index] = claims ? {
@@ -1615,6 +1925,14 @@ async function createApp(opts) {
1615
1925
  case "traceId":
1616
1926
  args[p.index] = requestId;
1617
1927
  break;
1928
+ case "client":
1929
+ args[p.index] = {
1930
+ sdkVersion: req.headers.get("x-palbase-sdk-version"),
1931
+ appVersion: req.headers.get("x-palbase-client-version"),
1932
+ platform: req.headers.get("x-platform"),
1933
+ osVersion: req.headers.get("x-os-version")
1934
+ };
1935
+ break;
1618
1936
  case "req":
1619
1937
  args[p.index] = req;
1620
1938
  break;