@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
@@ -459,7 +459,8 @@ function makeTablesAccessor(ops) {
459
459
  update: (id, data) => ops().update(name, id, data),
460
460
  delete: (id) => ops().delete(name, id),
461
461
  findById: (id) => ops().findById(name, id),
462
- findMany: (query) => ops().findMany(name, query)
462
+ findMany: (query) => ops().findMany(name, query),
463
+ search: (params) => ops().search(name, params)
463
464
  };
464
465
  }
465
466
  }
@@ -474,7 +475,8 @@ function makeTypedSurface(raw) {
474
475
  update: (table, id, data) => raw.update(table, id, data),
475
476
  delete: (table, id) => raw.delete(table, id),
476
477
  findById: (table, id) => raw.findById(table, id),
477
- findMany: (table, query) => raw.findMany(table, query)
478
+ findMany: (table, query) => raw.findMany(table, query),
479
+ search: (table, params) => raw.search(table, params)
478
480
  };
479
481
  return Object.assign(ops, {
480
482
  tables: makeTablesAccessor(() => raw),
@@ -1005,6 +1007,194 @@ function asWireRow(row) {
1005
1007
  function asWireRows(rows) {
1006
1008
  return rows.map((row) => asWireRow(row));
1007
1009
  }
1010
+ function toVectorLiteral(v) {
1011
+ return `[${v.join(",")}]`;
1012
+ }
1013
+ function vectorColumnsOf(schema, table) {
1014
+ const out = /* @__PURE__ */ new Set();
1015
+ for (const [key, def] of Object.entries(schema.tables ?? {})) {
1016
+ if ((def.name ?? key) !== table) continue;
1017
+ for (const [col, c] of Object.entries(def.columns ?? {})) {
1018
+ const d = c !== null && typeof c === "object" && "_def" in c ? c._def : c;
1019
+ if (d !== null && typeof d === "object" && d.type === "vector") out.add(col);
1020
+ }
1021
+ }
1022
+ return out;
1023
+ }
1024
+ function reviveVectors(row, vectorCols) {
1025
+ if (row === null || typeof row !== "object" || vectorCols.size === 0) return row;
1026
+ const out = row;
1027
+ for (const col of vectorCols) {
1028
+ const v = out[col];
1029
+ if (typeof v === "string") out[col] = JSON.parse(v);
1030
+ }
1031
+ return row;
1032
+ }
1033
+ function asTableRow(table, row) {
1034
+ return reviveVectors(asWireRow(row), vectorColumnsOf(currentSchema, table));
1035
+ }
1036
+ function asTableRows(table, rows) {
1037
+ const vectorCols = vectorColumnsOf(currentSchema, table);
1038
+ return rows.map((row) => reviveVectors(asWireRow(row), vectorCols));
1039
+ }
1040
+ function asBindParams(table, cols, data) {
1041
+ const vectorCols = vectorColumnsOf(currentSchema, table);
1042
+ return cols.map((c) => {
1043
+ const v = data[c];
1044
+ return Array.isArray(v) && vectorCols.has(c) ? toVectorLiteral(v) : v;
1045
+ });
1046
+ }
1047
+ var SELECTIVITY_EXACT_THRESHOLD = 1e4;
1048
+ var METRIC_OPERATOR = {
1049
+ cosine: "<=>",
1050
+ euclidean: "<->",
1051
+ inner_product: "<#>"
1052
+ };
1053
+ function searchConfigFor(table) {
1054
+ const t = currentSchema.tables?.[table];
1055
+ if (!t) return null;
1056
+ const columns = t.columns ?? {};
1057
+ const defOf = (c) => c !== null && typeof c === "object" && "_def" in c ? c._def : c ?? {};
1058
+ const cols = Object.keys(columns);
1059
+ const vectorCols = cols.filter((c) => defOf(columns[c]).type === "vector");
1060
+ const defOfFull = (c) => c !== null && typeof c === "object" && "_def" in c ? c._def : c ?? {};
1061
+ const pkCols = cols.filter((c) => defOfFull(columns[c]).primaryKey === true);
1062
+ const pk = pkCols.length === 1 ? pkCols[0] : cols.includes("id") ? "id" : null;
1063
+ if (pk === null) {
1064
+ throw new Error(
1065
+ `search(${table}): tek-kolon primary key bulunamad\u0131 \u2014 arama s\u0131ralamas\u0131 ve sat\u0131r birle\u015Fimi PK ister (FR-020)`
1066
+ );
1067
+ }
1068
+ const search = t.search;
1069
+ const ftsCols = search?.text ?? [];
1070
+ const rawLegs = search?.vector === void 0 ? [] : Array.isArray(search.vector) ? search.vector : [search.vector];
1071
+ let legs;
1072
+ if (rawLegs.length > 0) {
1073
+ legs = rawLegs.map((leg) => {
1074
+ const l = leg;
1075
+ const column = l.column ?? (vectorCols.length === 1 ? vectorCols[0] : void 0);
1076
+ if (column === void 0) {
1077
+ throw new Error(`search(${table}): birden \xE7ok vector kolonu var \u2014 beyanda 'column' zorunlu (FR-010)`);
1078
+ }
1079
+ const model = l.model;
1080
+ return {
1081
+ column,
1082
+ metric: l.metric ?? "cosine",
1083
+ ...model !== void 0 ? { embed: {
1084
+ model: model.model,
1085
+ apiKeyName: model.apiKeyName ?? "OPENAI_API_KEY",
1086
+ ...model.baseURL !== void 0 ? { baseURL: model.baseURL } : {},
1087
+ ...model.dimensions !== void 0 ? { dimensions: model.dimensions } : {}
1088
+ } } : {}
1089
+ };
1090
+ });
1091
+ } else {
1092
+ legs = vectorCols.map((column) => ({ column, metric: "cosine" }));
1093
+ }
1094
+ if (ftsCols.length === 0 && legs.length === 0) return null;
1095
+ return { pk, cols, colSet: new Set(cols), ftsCols, legs };
1096
+ }
1097
+ function pickLeg(table, legs, using) {
1098
+ if (legs.length === 0) return null;
1099
+ if (using !== void 0) {
1100
+ const hit = legs.find((l) => l.column === using);
1101
+ if (!hit) {
1102
+ throw new Error(
1103
+ `search(${table}): using "${using}" bir vekt\xF6r kolunu adlam\u0131yor \u2014 mevcut: ${legs.map((l) => l.column).join(", ")}`
1104
+ );
1105
+ }
1106
+ return hit;
1107
+ }
1108
+ if (legs.length === 1) return legs[0];
1109
+ 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`);
1110
+ }
1111
+ var WHERE_OPS = { gt: ">", gte: ">=", lt: "<", lte: "<=", neq: "<>" };
1112
+ function compileWhere(table, colSet, where, add) {
1113
+ const parts = [];
1114
+ for (const [col, cond] of Object.entries(where)) {
1115
+ if (!colSet.has(col)) {
1116
+ throw new Error(`search(${table}): where kolonu "${col}" tabloda yok (FR-016)`);
1117
+ }
1118
+ const q = `t.${quoteIdent(col)}`;
1119
+ if (cond !== null && typeof cond === "object" && !Array.isArray(cond)) {
1120
+ for (const [op, v] of Object.entries(cond)) {
1121
+ if (op === "in") {
1122
+ if (!Array.isArray(v)) throw new Error(`search(${table}): where.${col}.in bir dizi olmal\u0131`);
1123
+ if (v.length === 0) {
1124
+ parts.push("false");
1125
+ continue;
1126
+ }
1127
+ parts.push(`${q} IN (${v.map((x) => add(x)).join(", ")})`);
1128
+ } else if (op in WHERE_OPS) {
1129
+ parts.push(`${q} ${WHERE_OPS[op]} ${add(v)}`);
1130
+ } else {
1131
+ throw new Error(`search(${table}): where.${col} bilinmeyen operat\xF6r "${op}" (gt/gte/lt/lte/neq/in)`);
1132
+ }
1133
+ }
1134
+ } else {
1135
+ parts.push(`${q} = ${add(cond)}`);
1136
+ }
1137
+ }
1138
+ return parts.length === 0 ? "" : ` AND ${parts.join(" AND ")}`;
1139
+ }
1140
+ var cachedVectorSchema = null;
1141
+ async function vectorSchemaWithGuc(runner) {
1142
+ if (cachedVectorSchema !== null) {
1143
+ await runner.unsafe(
1144
+ "select set_config('hnsw.iterative_scan','relaxed_order',true), set_config('hnsw.max_scan_tuples','200000',true), set_config('hnsw.ef_search','200',true)"
1145
+ );
1146
+ return cachedVectorSchema;
1147
+ }
1148
+ const rows = await runner.unsafe(
1149
+ "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"
1150
+ );
1151
+ const name = rows?.[0]?.nspname;
1152
+ if (typeof name !== "string" || name === "") {
1153
+ throw new Error("pgvector extension kurulu de\u011Fil \u2014 vector aramas\u0131 \xE7al\u0131\u015Famaz (extensions beyan\u0131 deploy'dan ge\xE7ti mi?)");
1154
+ }
1155
+ cachedVectorSchema = name;
1156
+ return name;
1157
+ }
1158
+ var secretReader = null;
1159
+ function setSecretReader(fn) {
1160
+ secretReader = fn;
1161
+ }
1162
+ var embedFetch = (url, init) => fetch(url, init);
1163
+ async function embedQuery(embed, text) {
1164
+ if (secretReader === null) {
1165
+ throw new Error(`query embed: secret reader ba\u011Flanmam\u0131\u015F \u2014 ${embed.apiKeyName} okunam\u0131yor`);
1166
+ }
1167
+ const key = await secretReader(embed.apiKeyName);
1168
+ if (key === null || key === "") {
1169
+ throw new Error(`query embed: vault'ta ${embed.apiKeyName} yok (FR-021/FR-025)`);
1170
+ }
1171
+ const url = (embed.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "") + "/embeddings";
1172
+ const controller = new AbortController();
1173
+ const timer = setTimeout(() => controller.abort(), 1e4);
1174
+ try {
1175
+ const res = await embedFetch(url, {
1176
+ method: "POST",
1177
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
1178
+ body: JSON.stringify({
1179
+ model: embed.model,
1180
+ input: [text],
1181
+ ...embed.dimensions !== void 0 ? { dimensions: embed.dimensions } : {}
1182
+ }),
1183
+ signal: controller.signal
1184
+ });
1185
+ if (!res.ok) {
1186
+ throw new Error(`query embed: sa\u011Flay\u0131c\u0131 ${res.status} d\xF6nd\xFC (${embed.apiKeyName} ile) \u2014 anahtar/model do\u011Fru mu?`);
1187
+ }
1188
+ const data = await res.json();
1189
+ const vec = data.data?.[0]?.embedding;
1190
+ if (!Array.isArray(vec)) {
1191
+ throw new Error("query embed: sa\u011Flay\u0131c\u0131 yan\u0131t\u0131nda data[0].embedding yok (CLAIM-N1 \u015Fekli)");
1192
+ }
1193
+ return vec;
1194
+ } finally {
1195
+ clearTimeout(timer);
1196
+ }
1197
+ }
1008
1198
  function createOps(tx) {
1009
1199
  const at = () => resolveTx(tx);
1010
1200
  const ops = {
@@ -1016,22 +1206,22 @@ function createOps(tx) {
1016
1206
  if (cols.length === 0) throw new Error(`insert into ${table}: no columns given`);
1017
1207
  const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
1018
1208
  const sql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`;
1019
- const rows = await (await at()).unsafe(sql, cols.map((c) => data[c]));
1209
+ const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data));
1020
1210
  const inserted = rows[0];
1021
1211
  if (!inserted) {
1022
1212
  throw new Error(
1023
1213
  `insert into ${table} returned no row \u2014 the write was rejected (an RLS policy, most likely).`
1024
1214
  );
1025
1215
  }
1026
- return asWireRow(inserted);
1216
+ return asTableRow(table, inserted);
1027
1217
  },
1028
1218
  async update(table, id, data) {
1029
1219
  const cols = Object.keys(data);
1030
1220
  if (cols.length === 0) return ops.findById(table, id);
1031
1221
  const assignments = cols.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(", ");
1032
1222
  const sql = `UPDATE ${quoteIdent(table)} SET ${assignments} WHERE id = $${cols.length + 1} RETURNING *`;
1033
- const rows = await (await at()).unsafe(sql, [...cols.map((c) => data[c]), id]);
1034
- return rows[0] ? asWireRow(rows[0]) : null;
1223
+ const rows = await (await at()).unsafe(sql, [...asBindParams(table, cols, data), id]);
1224
+ return rows[0] ? asTableRow(table, rows[0]) : null;
1035
1225
  },
1036
1226
  async delete(table, id) {
1037
1227
  await (await at()).unsafe(`DELETE FROM ${quoteIdent(table)} WHERE id = $1`, [id]);
@@ -1041,16 +1231,96 @@ function createOps(tx) {
1041
1231
  `SELECT * FROM ${quoteIdent(table)} WHERE id = $1`,
1042
1232
  [id]
1043
1233
  );
1044
- return rows[0] ? asWireRow(rows[0]) : null;
1234
+ return rows[0] ? asTableRow(table, rows[0]) : null;
1045
1235
  },
1046
1236
  async findMany(table, query = {}) {
1047
1237
  const cols = Object.keys(query);
1048
1238
  const where = cols.length ? ` WHERE ${cols.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ")}` : "";
1049
- return asWireRows(await (await at()).unsafe(
1239
+ return asTableRows(table, await (await at()).unsafe(
1050
1240
  `SELECT * FROM ${quoteIdent(table)}${where}`,
1051
1241
  cols.map((c) => query[c])
1052
1242
  ));
1053
1243
  },
1244
+ /**
1245
+ * Tek-SQL hibrit arama (FR-014): iki kol CTE + FULL OUTER JOIN + RRF
1246
+ * (1/(50+rank), CLAIM-N4). Operatör şema-nitelikli (C-10, M-1); GUC
1247
+ * hnsw.iterative_scan=relaxed_order aynı tx'te set_config ile (CLAIM-N3 —
1248
+ * RLS/filtre altında LIMIT-altı dönüş açığını kapatır). Sorgu-anı embed
1249
+ * T024'te gelir; o zamana dek vector kolu yalnız params.vector ile koşar.
1250
+ */
1251
+ async search(table, params = {}) {
1252
+ const cfg = searchConfigFor(table);
1253
+ if (!cfg) {
1254
+ throw new Error(`search(${table}): tablo aranabilir de\u011Fil \u2014 ne vector kolonu ne search beyan\u0131 var (FR-013)`);
1255
+ }
1256
+ const rawLimit = params.limit ?? 20;
1257
+ if (typeof rawLimit !== "number" || !Number.isFinite(rawLimit)) {
1258
+ throw new Error(`search(${table}): limit sonlu bir say\u0131 olmal\u0131, ${String(rawLimit)} verildi (FR-013)`);
1259
+ }
1260
+ const limit = Math.min(Math.max(1, Math.trunc(rawLimit)), 100);
1261
+ const pool = Math.max(limit * 3, 30);
1262
+ const wantText = params.mode !== "vector" && cfg.ftsCols.length > 0 && typeof params.query === "string" && params.query !== "";
1263
+ const anyEmbed = cfg.legs.some((l) => l.embed !== void 0);
1264
+ const vectorAsked = params.mode !== "text" && (Array.isArray(params.vector) || params.using !== void 0 || params.mode === "vector" || anyEmbed && typeof params.query === "string" && params.query !== "");
1265
+ const leg = vectorAsked ? pickLeg(table, cfg.legs, params.using) : null;
1266
+ let qv = Array.isArray(params.vector) ? params.vector : null;
1267
+ if (qv === null && leg?.embed !== void 0 && typeof params.query === "string" && params.query !== "") {
1268
+ try {
1269
+ qv = await embedQuery(leg.embed, params.query);
1270
+ } catch (e) {
1271
+ if (!wantText) throw e;
1272
+ qv = null;
1273
+ }
1274
+ }
1275
+ const wantVector = leg !== null && qv !== null;
1276
+ if (!wantText && !wantVector) {
1277
+ throw new Error(
1278
+ `search(${table}): ko\u015Fulabilir kol yok \u2014 metin i\xE7in 'query' (FTS beyan\u0131 gerekir), semantik i\xE7in 'vector' verin (FR-015)`
1279
+ );
1280
+ }
1281
+ const bind = [];
1282
+ const add = (v) => {
1283
+ bind.push(v);
1284
+ return `$${bind.length}`;
1285
+ };
1286
+ const whereSql = compileWhere(table, cfg.colSet, params.where ?? {}, add);
1287
+ const live = await at();
1288
+ const K = 50;
1289
+ let semSql = "";
1290
+ let kwSql = "";
1291
+ if (wantVector && leg) {
1292
+ const sch = await vectorSchemaWithGuc(live);
1293
+ const op = METRIC_OPERATOR[leg.metric] ?? METRIC_OPERATOR.cosine;
1294
+ let exactOrder = "";
1295
+ if (whereSql !== "") {
1296
+ const probeRows = await live.unsafe(
1297
+ `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`,
1298
+ bind.slice()
1299
+ );
1300
+ const n = probeRows?.[0]?.n;
1301
+ if (typeof n === "number" && n <= SELECTIVITY_EXACT_THRESHOLD) {
1302
+ exactOrder = " + 0.0";
1303
+ }
1304
+ }
1305
+ const vp = add(toVectorLiteral(qv));
1306
+ 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}`;
1307
+ }
1308
+ if (wantText) {
1309
+ const qp = add(params.query);
1310
+ 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}`;
1311
+ }
1312
+ const colList = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
1313
+ let sql;
1314
+ if (semSql !== "" && kwSql !== "") {
1315
+ 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}`;
1316
+ } else {
1317
+ const single = semSql !== "" ? `sem AS (${semSql})` : `kw AS (${kwSql})`;
1318
+ const alias = semSql !== "" ? "sem" : "kw";
1319
+ 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}`;
1320
+ }
1321
+ const rows = await live.unsafe(sql, bind);
1322
+ return asTableRows(table, rows);
1323
+ },
1054
1324
  /** A real SAVEPOINT inside the request's transaction. */
1055
1325
  async transaction(cb) {
1056
1326
  const live = await at();
@@ -1079,7 +1349,7 @@ function createOps(tx) {
1079
1349
  return live.savepoint(async (sp) => {
1080
1350
  const results = [];
1081
1351
  for (const op of plan.ops) {
1082
- const rows = await runPlanOp(sp, op, results);
1352
+ const rows = asTableRows(op.table, await runPlanOp(sp, op, results));
1083
1353
  const result = { rows, rows_affected: rows.length };
1084
1354
  results.push(result);
1085
1355
  assertGuard(op, result);
@@ -1092,6 +1362,7 @@ function createOps(tx) {
1092
1362
  }
1093
1363
  var currentSchema = {};
1094
1364
  function setSchema(schema) {
1365
+ cachedVectorSchema = null;
1095
1366
  const s = schema;
1096
1367
  currentSchema = (s && "default" in s ? s.default : s) ?? {};
1097
1368
  }
@@ -1178,7 +1449,7 @@ function isRef(v) {
1178
1449
  function isExpr(v) {
1179
1450
  return typeof v === "object" && v !== null && "$expr" in v;
1180
1451
  }
1181
- function renderValue(value, column, args, results) {
1452
+ function renderValue(value, column, args, results, vectorCols) {
1182
1453
  if (isRef(value)) {
1183
1454
  const source = results[value.$ref.op];
1184
1455
  const row = source?.rows[0];
@@ -1187,15 +1458,15 @@ function renderValue(value, column, args, results) {
1187
1458
  error_code: "tx_ref_unresolved"
1188
1459
  });
1189
1460
  }
1190
- return args.bind(row[value.$ref.field]);
1461
+ return bindMaybeVector(args, column, vectorCols, row[value.$ref.field]);
1191
1462
  }
1192
1463
  if (isExpr(value)) {
1193
1464
  const fn = value.$expr;
1194
1465
  if (fn.fn === "now") return "now()";
1195
1466
  const operator = fn.fn === "inc" ? "+" : "-";
1196
- return `${quoteIdent(column)} ${operator} ${args.bind(fn.by)}`;
1467
+ return `${quoteIdent(column)} ${operator} ${bindMaybeVector(args, column, vectorCols, fn.by)}`;
1197
1468
  }
1198
- return args.bind(value);
1469
+ return bindMaybeVector(args, column, vectorCols, value);
1199
1470
  }
1200
1471
  function renderWhere(where, args, results) {
1201
1472
  const cols = Object.keys(where ?? {});
@@ -1207,14 +1478,21 @@ function renderWhere(where, args, results) {
1207
1478
  });
1208
1479
  return ` WHERE ${terms.join(" AND ")}`;
1209
1480
  }
1481
+ function bindMaybeVector(args, column, vectorCols, value) {
1482
+ if (column !== void 0 && vectorCols?.has(column) && Array.isArray(value)) {
1483
+ return args.bind(toVectorLiteral(value));
1484
+ }
1485
+ return args.bind(value);
1486
+ }
1210
1487
  async function runPlanOp(sp, op, results) {
1488
+ const opVectorCols = vectorColumnsOf(currentSchema, op.table);
1211
1489
  const args = new Args();
1212
1490
  const table = quoteIdent(op.table);
1213
1491
  let sql;
1214
1492
  switch (op.op) {
1215
1493
  case "insert": {
1216
1494
  const cols = Object.keys(op.values ?? {});
1217
- const rendered = cols.map((c) => renderValue(op.values[c], c, args, results));
1495
+ const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols));
1218
1496
  sql = cols.length ? `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) RETURNING *` : `INSERT INTO ${table} DEFAULT VALUES RETURNING *`;
1219
1497
  break;
1220
1498
  }
@@ -1223,7 +1501,7 @@ async function runPlanOp(sp, op, results) {
1223
1501
  if (rows.length === 0 || !rows[0]) return [];
1224
1502
  const cols = Object.keys(rows[0]);
1225
1503
  const tuples = rows.map(
1226
- (r) => `(${cols.map((c) => renderValue(r[c], c, args, results)).join(", ")})`
1504
+ (r) => `(${cols.map((c) => renderValue(r[c], c, args, results, opVectorCols)).join(", ")})`
1227
1505
  );
1228
1506
  sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")} RETURNING *`;
1229
1507
  break;
@@ -1232,7 +1510,7 @@ async function runPlanOp(sp, op, results) {
1232
1510
  const cols = Object.keys(op.set ?? {});
1233
1511
  if (cols.length === 0) throw new Error(`update ${op.table}: nothing to set`);
1234
1512
  const assignments = cols.map(
1235
- (c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results)}`
1513
+ (c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results, opVectorCols)}`
1236
1514
  );
1237
1515
  sql = `UPDATE ${table} SET ${assignments.join(", ")}${renderWhere(op.where, args, results)} RETURNING *`;
1238
1516
  break;
@@ -1379,7 +1657,8 @@ function grantFor(entry, ctx, bucketLimits) {
1379
1657
  filename: ctx.filename
1380
1658
  }),
1381
1659
  maxBytes: bucketLimits?.maxBytes ?? null,
1382
- mimeTypes: bucketLimits?.mimeTypes ?? null
1660
+ mimeTypes: bucketLimits?.mimeTypes ?? null,
1661
+ ownerUid: ctx.userId ?? null
1383
1662
  };
1384
1663
  }
1385
1664
  var CompletionLedger = class {
@@ -1476,6 +1755,23 @@ function installEgressFence(policy) {
1476
1755
 
1477
1756
  // src/engine/index.ts
1478
1757
  var JSON_HEADERS = { "content-type": "application/json" };
1758
+ function fieldErrors(err) {
1759
+ return err.issues.map((i) => ({ field: i.path.join("."), message: i.message }));
1760
+ }
1761
+ function headersFor(raw, schema) {
1762
+ const shape = schema.shape;
1763
+ if (typeof shape !== "object" || shape === null) return raw;
1764
+ let aliased = null;
1765
+ for (const declared of Object.keys(shape)) {
1766
+ const lower = declared.toLowerCase();
1767
+ if (lower === declared) continue;
1768
+ const value = raw[lower];
1769
+ if (value === void 0) continue;
1770
+ aliased ??= { ...raw };
1771
+ aliased[declared] = value;
1772
+ }
1773
+ return aliased ?? raw;
1774
+ }
1479
1775
  function envelope(error, description, status, requestId, extra) {
1480
1776
  return new Response(
1481
1777
  JSON.stringify({ error, error_description: description, status, request_id: requestId, ...extra }),
@@ -1509,6 +1805,7 @@ async function defaultSqlDriver(config) {
1509
1805
  async function createApp(opts) {
1510
1806
  const { config, controllers } = opts;
1511
1807
  setSchema(opts.schema ?? {});
1808
+ setSecretReader(opts.secretReader ?? null);
1512
1809
  const routes = buildRouteTable(controllers);
1513
1810
  if (routes.length === 0) {
1514
1811
  throw new BootRefused([], "boot refused: zero endpoints collected \u2014 nothing would answer.");
@@ -1621,7 +1918,8 @@ async function createApp(opts) {
1621
1918
  error: "too_many_requests",
1622
1919
  error_description: "Rate limit exceeded for this endpoint",
1623
1920
  status: 429,
1624
- request_id: requestId
1921
+ request_id: requestId,
1922
+ data: { retryAfter }
1625
1923
  }),
1626
1924
  { status: 429, headers: { ...JSON_HEADERS, "retry-after": String(retryAfter) } }
1627
1925
  );
@@ -1640,7 +1938,7 @@ async function createApp(opts) {
1640
1938
  const r = p.schema.safeParse(parsedBody);
1641
1939
  if (!r.success) {
1642
1940
  return envelope("bad_request", "Request body failed validation", 400, requestId, {
1643
- fields: r.error.issues.map((i) => ({ field: i.path.join("."), message: i.message }))
1941
+ data: { fields: fieldErrors(r.error) }
1644
1942
  });
1645
1943
  }
1646
1944
  args[p.index] = r.data;
@@ -1650,7 +1948,7 @@ async function createApp(opts) {
1650
1948
  const r = p.schema.safeParse(Object.fromEntries(url.searchParams));
1651
1949
  if (!r.success) {
1652
1950
  return envelope("bad_request", "Query parameters failed validation", 400, requestId, {
1653
- fields: r.error.issues.map((i) => ({ field: i.path.join("."), message: i.message }))
1951
+ data: { fields: fieldErrors(r.error) }
1654
1952
  });
1655
1953
  }
1656
1954
  args[p.index] = r.data;
@@ -1659,9 +1957,21 @@ async function createApp(opts) {
1659
1957
  case "param":
1660
1958
  args[p.index] = hit.params[p.name];
1661
1959
  break;
1662
- case "headers":
1663
- args[p.index] = Object.fromEntries(req.headers);
1960
+ case "headers": {
1961
+ const raw = Object.fromEntries(req.headers);
1962
+ if (!p.schema) {
1963
+ args[p.index] = raw;
1964
+ break;
1965
+ }
1966
+ const r = p.schema.safeParse(headersFor(raw, p.schema));
1967
+ if (!r.success) {
1968
+ return envelope("bad_request", "Request headers failed validation", 400, requestId, {
1969
+ data: { fields: fieldErrors(r.error) }
1970
+ });
1971
+ }
1972
+ args[p.index] = r.data;
1664
1973
  break;
1974
+ }
1665
1975
  case "user":
1666
1976
  case "optionalUser":
1667
1977
  args[p.index] = claims ? {
@@ -1710,6 +2020,14 @@ async function createApp(opts) {
1710
2020
  case "traceId":
1711
2021
  args[p.index] = requestId;
1712
2022
  break;
2023
+ case "client":
2024
+ args[p.index] = {
2025
+ sdkVersion: req.headers.get("x-palbase-sdk-version"),
2026
+ appVersion: req.headers.get("x-palbase-client-version"),
2027
+ platform: req.headers.get("x-platform"),
2028
+ osVersion: req.headers.get("x-os-version")
2029
+ };
2030
+ break;
1713
2031
  case "req":
1714
2032
  args[p.index] = req;
1715
2033
  break;