naro-cli 0.11.0 → 0.12.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.
@@ -7,7 +7,7 @@ import {
7
7
  import { existsSync as existsSync7 } from "fs";
8
8
  import { cp as cp2, mkdtemp as mkdtemp2, readFile as readFile13, rm as rm3 } from "fs/promises";
9
9
  import { tmpdir as tmpdir2 } from "os";
10
- import { basename as basename3, dirname as dirname4, join as join24, resolve as resolve9 } from "path";
10
+ import { basename as basename3, dirname as dirname4, join as join24, resolve as resolve10 } from "path";
11
11
  import { fileURLToPath } from "url";
12
12
 
13
13
  // ../../packages/build/src/artifact-upload.ts
@@ -6730,13 +6730,13 @@ var error17 = () => {
6730
6730
  return `${sizing?.longLabel ?? "\u05D0\u05E8\u05D5\u05DA"} \u05DE\u05D3\u05D9: ${subject} \u05E6\u05E8\u05D9\u05DB\u05D4 \u05DC\u05D4\u05DB\u05D9\u05DC ${issue2.maximum.toString()} ${sizing?.unit ?? ""} ${issue2.inclusive ? "\u05D0\u05D5 \u05E4\u05D7\u05D5\u05EA" : "\u05DC\u05DB\u05DC \u05D4\u05D9\u05D5\u05EA\u05E8"}`.trim();
6731
6731
  }
6732
6732
  if (issue2.origin === "number") {
6733
- const comparison = issue2.inclusive ? `\u05E7\u05D8\u05DF \u05D0\u05D5 \u05E9\u05D5\u05D5\u05D4 \u05DC-${issue2.maximum}` : `\u05E7\u05D8\u05DF \u05DE-${issue2.maximum}`;
6734
- return `\u05D2\u05D3\u05D5\u05DC \u05DE\u05D3\u05D9: ${subject} \u05E6\u05E8\u05D9\u05DA \u05DC\u05D4\u05D9\u05D5\u05EA ${comparison}`;
6733
+ const comparison2 = issue2.inclusive ? `\u05E7\u05D8\u05DF \u05D0\u05D5 \u05E9\u05D5\u05D5\u05D4 \u05DC-${issue2.maximum}` : `\u05E7\u05D8\u05DF \u05DE-${issue2.maximum}`;
6734
+ return `\u05D2\u05D3\u05D5\u05DC \u05DE\u05D3\u05D9: ${subject} \u05E6\u05E8\u05D9\u05DA \u05DC\u05D4\u05D9\u05D5\u05EA ${comparison2}`;
6735
6735
  }
6736
6736
  if (issue2.origin === "array" || issue2.origin === "set") {
6737
6737
  const verb = issue2.origin === "set" ? "\u05E6\u05E8\u05D9\u05DB\u05D4" : "\u05E6\u05E8\u05D9\u05DA";
6738
- const comparison = issue2.inclusive ? `${issue2.maximum} ${sizing?.unit ?? ""} \u05D0\u05D5 \u05E4\u05D7\u05D5\u05EA` : `\u05E4\u05D7\u05D5\u05EA \u05DE-${issue2.maximum} ${sizing?.unit ?? ""}`;
6739
- return `\u05D2\u05D3\u05D5\u05DC \u05DE\u05D3\u05D9: ${subject} ${verb} \u05DC\u05D4\u05DB\u05D9\u05DC ${comparison}`.trim();
6738
+ const comparison2 = issue2.inclusive ? `${issue2.maximum} ${sizing?.unit ?? ""} \u05D0\u05D5 \u05E4\u05D7\u05D5\u05EA` : `\u05E4\u05D7\u05D5\u05EA \u05DE-${issue2.maximum} ${sizing?.unit ?? ""}`;
6739
+ return `\u05D2\u05D3\u05D5\u05DC \u05DE\u05D3\u05D9: ${subject} ${verb} \u05DC\u05D4\u05DB\u05D9\u05DC ${comparison2}`.trim();
6740
6740
  }
6741
6741
  const adj = issue2.inclusive ? "<=" : "<";
6742
6742
  const be = verbFor(issue2.origin ?? "value");
@@ -6752,8 +6752,8 @@ var error17 = () => {
6752
6752
  return `${sizing?.shortLabel ?? "\u05E7\u05E6\u05E8"} \u05DE\u05D3\u05D9: ${subject} \u05E6\u05E8\u05D9\u05DB\u05D4 \u05DC\u05D4\u05DB\u05D9\u05DC ${issue2.minimum.toString()} ${sizing?.unit ?? ""} ${issue2.inclusive ? "\u05D0\u05D5 \u05D9\u05D5\u05EA\u05E8" : "\u05DC\u05E4\u05D7\u05D5\u05EA"}`.trim();
6753
6753
  }
6754
6754
  if (issue2.origin === "number") {
6755
- const comparison = issue2.inclusive ? `\u05D2\u05D3\u05D5\u05DC \u05D0\u05D5 \u05E9\u05D5\u05D5\u05D4 \u05DC-${issue2.minimum}` : `\u05D2\u05D3\u05D5\u05DC \u05DE-${issue2.minimum}`;
6756
- return `\u05E7\u05D8\u05DF \u05DE\u05D3\u05D9: ${subject} \u05E6\u05E8\u05D9\u05DA \u05DC\u05D4\u05D9\u05D5\u05EA ${comparison}`;
6755
+ const comparison2 = issue2.inclusive ? `\u05D2\u05D3\u05D5\u05DC \u05D0\u05D5 \u05E9\u05D5\u05D5\u05D4 \u05DC-${issue2.minimum}` : `\u05D2\u05D3\u05D5\u05DC \u05DE-${issue2.minimum}`;
6756
+ return `\u05E7\u05D8\u05DF \u05DE\u05D3\u05D9: ${subject} \u05E6\u05E8\u05D9\u05DA \u05DC\u05D4\u05D9\u05D5\u05EA ${comparison2}`;
6757
6757
  }
6758
6758
  if (issue2.origin === "array" || issue2.origin === "set") {
6759
6759
  const verb = issue2.origin === "set" ? "\u05E6\u05E8\u05D9\u05DB\u05D4" : "\u05E6\u05E8\u05D9\u05DA";
@@ -6761,8 +6761,8 @@ var error17 = () => {
6761
6761
  const singularPhrase = issue2.origin === "set" ? "\u05DC\u05E4\u05D7\u05D5\u05EA \u05E4\u05E8\u05D9\u05D8 \u05D0\u05D7\u05D3" : "\u05DC\u05E4\u05D7\u05D5\u05EA \u05E4\u05E8\u05D9\u05D8 \u05D0\u05D7\u05D3";
6762
6762
  return `\u05E7\u05D8\u05DF \u05DE\u05D3\u05D9: ${subject} ${verb} \u05DC\u05D4\u05DB\u05D9\u05DC ${singularPhrase}`;
6763
6763
  }
6764
- const comparison = issue2.inclusive ? `${issue2.minimum} ${sizing?.unit ?? ""} \u05D0\u05D5 \u05D9\u05D5\u05EA\u05E8` : `\u05D9\u05D5\u05EA\u05E8 \u05DE-${issue2.minimum} ${sizing?.unit ?? ""}`;
6765
- return `\u05E7\u05D8\u05DF \u05DE\u05D3\u05D9: ${subject} ${verb} \u05DC\u05D4\u05DB\u05D9\u05DC ${comparison}`.trim();
6764
+ const comparison2 = issue2.inclusive ? `${issue2.minimum} ${sizing?.unit ?? ""} \u05D0\u05D5 \u05D9\u05D5\u05EA\u05E8` : `\u05D9\u05D5\u05EA\u05E8 \u05DE-${issue2.minimum} ${sizing?.unit ?? ""}`;
6765
+ return `\u05E7\u05D8\u05DF \u05DE\u05D3\u05D9: ${subject} ${verb} \u05DC\u05D4\u05DB\u05D9\u05DC ${comparison2}`.trim();
6766
6766
  }
6767
6767
  const adj = issue2.inclusive ? ">=" : ">";
6768
6768
  const be = verbFor(issue2.origin ?? "value");
@@ -7967,8 +7967,8 @@ function ko_default() {
7967
7967
  }
7968
7968
 
7969
7969
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/lt.js
7970
- var capitalizeFirstCharacter = (text) => {
7971
- return text.charAt(0).toUpperCase() + text.slice(1);
7970
+ var capitalizeFirstCharacter = (text2) => {
7971
+ return text2.charAt(0).toUpperCase() + text2.slice(1);
7972
7972
  };
7973
7973
  function getUnitTypeFromNumber(number4) {
7974
7974
  const abs = Math.abs(number4);
@@ -14939,7 +14939,238 @@ var connectRepositorySchema = external_exports.object({
14939
14939
  });
14940
14940
 
14941
14941
  // ../../packages/core/src/sql-classifier.ts
14942
+ var WORD_START = /[A-Za-z_\u0080-\uffff]/;
14943
+ var WORD_PART = /[A-Za-z0-9_$\u0080-\uffff]/;
14944
+ var DIGIT = /[0-9]/;
14945
+ var HEX = /[0-9A-Fa-f]/;
14946
+ var NUMBER_PART = /[0-9A-Za-z_.]/;
14947
+ var SPACE = /[ \t\n\r\f]/;
14948
+ var ISSPACE = /[ \t\n\r\f\v]/;
14942
14949
  var NUL = String.fromCharCode(0);
14950
+ var BOM = 65279;
14951
+ var PRAGMA_PREFIX = "PRAGMA_";
14952
+ function asciiUpper(text2) {
14953
+ return text2.replace(/[a-z]+/g, (run) => run.toUpperCase());
14954
+ }
14955
+ function pragmaCandidate(sql, start, end, doubled) {
14956
+ const prefix = sql.slice(start + 1, start + 1 + PRAGMA_PREFIX.length);
14957
+ if (asciiUpper(prefix) !== PRAGMA_PREFIX) return "";
14958
+ const inner = sql.slice(start + 1, end - 1);
14959
+ return asciiUpper(
14960
+ doubled === null ? inner : inner.split(doubled + doubled).join(doubled)
14961
+ );
14962
+ }
14963
+ function readDoubled(sql, start, delimiter) {
14964
+ let i = start;
14965
+ for (; ; ) {
14966
+ const close = sql.indexOf(delimiter, i);
14967
+ if (close < 0) return -1;
14968
+ if (sql.charAt(close + 1) === delimiter) {
14969
+ i = close + 2;
14970
+ continue;
14971
+ }
14972
+ return close + 1;
14973
+ }
14974
+ }
14975
+ function withoutExplain(tokens) {
14976
+ if (!isWord(tokens[0], "EXPLAIN")) return tokens;
14977
+ return tokens.slice(
14978
+ isWord(tokens[1], "QUERY") && isWord(tokens[2], "PLAN") ? 3 : 1
14979
+ );
14980
+ }
14981
+ function isCreateTrigger(tokens) {
14982
+ const rest = withoutExplain(tokens);
14983
+ if (!isWord(rest[0], "CREATE")) return false;
14984
+ const second = rest[1];
14985
+ if (isWord(second, "TRIGGER")) return true;
14986
+ return (isWord(second, "TEMP") || isWord(second, "TEMPORARY")) && isWord(rest[2], "TRIGGER");
14987
+ }
14988
+ function lex(sql, names = false) {
14989
+ const statements = [];
14990
+ const spans = [];
14991
+ let current = [];
14992
+ let tokenStart = 0;
14993
+ let currentStart = 0;
14994
+ const push = (token) => {
14995
+ if (current.length === 0) currentStart = tokenStart;
14996
+ current.push(token);
14997
+ };
14998
+ let triggerBody = false;
14999
+ let afterBodySemicolon = -1;
15000
+ const fail2 = (reason) => ({
15001
+ ok: false,
15002
+ reason,
15003
+ statements: statements.length + 1
15004
+ });
15005
+ if (sql.includes(NUL)) return fail2("NUL byte in SQL");
15006
+ let i = 0;
15007
+ while (i < sql.length) {
15008
+ tokenStart = i;
15009
+ const c = sql.charAt(i);
15010
+ const next = sql.charAt(i + 1);
15011
+ if (c === "\v") return fail2("illegal control character");
15012
+ if (SPACE.test(c)) {
15013
+ i += 1;
15014
+ while (i < sql.length && ISSPACE.test(sql.charAt(i))) i += 1;
15015
+ continue;
15016
+ }
15017
+ if (sql.charCodeAt(i) === BOM) {
15018
+ i += 1;
15019
+ continue;
15020
+ }
15021
+ if (c === "-" && next === "-") {
15022
+ const end = sql.indexOf("\n", i + 2);
15023
+ i = end < 0 ? sql.length : end;
15024
+ continue;
15025
+ }
15026
+ if (c === "/" && next === "*") {
15027
+ const end = sql.indexOf("*/", i + 2);
15028
+ if (end < 0) return fail2("unterminated block comment");
15029
+ i = end + 2;
15030
+ continue;
15031
+ }
15032
+ if ((c === "x" || c === "X") && next === "'") {
15033
+ let end = i + 2;
15034
+ while (end < sql.length && HEX.test(sql.charAt(end))) end += 1;
15035
+ if (sql.charAt(end) !== "'") return fail2("malformed blob literal");
15036
+ push({ type: "blob" });
15037
+ i = end + 1;
15038
+ continue;
15039
+ }
15040
+ if (c === "'") {
15041
+ const end = readDoubled(sql, i + 1, "'");
15042
+ if (end < 0) return fail2("unterminated string literal");
15043
+ push({
15044
+ type: "string",
15045
+ value: pragmaCandidate(sql, i, end, "'")
15046
+ });
15047
+ i = end;
15048
+ continue;
15049
+ }
15050
+ if (c === '"' || c === "`") {
15051
+ const end = readDoubled(sql, i + 1, c);
15052
+ if (end < 0) return fail2("unterminated quoted identifier");
15053
+ push({
15054
+ type: "quoted",
15055
+ value: pragmaCandidate(sql, i, end, c),
15056
+ ...names ? {
15057
+ name: sql.slice(i + 1, end - 1).split(c + c).join(c)
15058
+ } : {}
15059
+ });
15060
+ i = end;
15061
+ continue;
15062
+ }
15063
+ if (c === "[") {
15064
+ const end = sql.indexOf("]", i + 1);
15065
+ if (end < 0) return fail2("unterminated bracketed identifier");
15066
+ push({
15067
+ type: "quoted",
15068
+ // No doubling inside brackets: the first `]` closes.
15069
+ value: pragmaCandidate(sql, i, end + 1, null),
15070
+ ...names ? { name: sql.slice(i + 1, end) } : {}
15071
+ });
15072
+ i = end + 1;
15073
+ continue;
15074
+ }
15075
+ if (c === ";") {
15076
+ if (triggerBody) {
15077
+ afterBodySemicolon = current.length;
15078
+ } else {
15079
+ if (current.length > 0) {
15080
+ statements.push(current);
15081
+ spans.push({ start: currentStart, end: i });
15082
+ }
15083
+ current = [];
15084
+ }
15085
+ i += 1;
15086
+ continue;
15087
+ }
15088
+ if (c === "(" || c === ")" || c === ",") {
15089
+ push({ type: "punct", value: c });
15090
+ i += 1;
15091
+ continue;
15092
+ }
15093
+ if (WORD_START.test(c)) {
15094
+ let end = i + 1;
15095
+ while (end < sql.length && WORD_PART.test(sql.charAt(end))) end += 1;
15096
+ const value = asciiUpper(sql.slice(i, end));
15097
+ push({ type: "word", value });
15098
+ if (triggerBody) {
15099
+ if (value === "END" && current.length - 1 === afterBodySemicolon)
15100
+ triggerBody = false;
15101
+ } else if (value === "BEGIN" && isCreateTrigger(current)) {
15102
+ triggerBody = true;
15103
+ afterBodySemicolon = -1;
15104
+ }
15105
+ i = end;
15106
+ continue;
15107
+ }
15108
+ if (DIGIT.test(c) || c === "." && DIGIT.test(next)) {
15109
+ let end = i + 1;
15110
+ while (end < sql.length && NUMBER_PART.test(sql.charAt(end))) end += 1;
15111
+ push({ type: "number" });
15112
+ i = end;
15113
+ continue;
15114
+ }
15115
+ if (c === "?") {
15116
+ let end = i + 1;
15117
+ while (end < sql.length && DIGIT.test(sql.charAt(end))) end += 1;
15118
+ push({ type: "other" });
15119
+ i = end;
15120
+ continue;
15121
+ }
15122
+ if (c === ":" || c === "@" || c === "$" || c === "#") {
15123
+ let end = i + 1;
15124
+ let nameChars = 0;
15125
+ for (; ; ) {
15126
+ const ch = sql.charAt(end);
15127
+ if (ch !== "" && WORD_PART.test(ch)) {
15128
+ nameChars += 1;
15129
+ end += 1;
15130
+ continue;
15131
+ }
15132
+ if (ch === ":" && sql.charAt(end + 1) === ":") {
15133
+ end += 2;
15134
+ continue;
15135
+ }
15136
+ if (ch === "(" && nameChars > 0) {
15137
+ let close = end + 1;
15138
+ while (close < sql.length && sql.charAt(close) !== ")" && !ISSPACE.test(sql.charAt(close)))
15139
+ close += 1;
15140
+ if (sql.charAt(close) !== ")")
15141
+ return fail2("malformed bound-parameter suffix");
15142
+ end = close + 1;
15143
+ }
15144
+ break;
15145
+ }
15146
+ push({ type: "other" });
15147
+ i = end;
15148
+ continue;
15149
+ }
15150
+ push(names ? { type: "other", char: c } : { type: "other" });
15151
+ i += 1;
15152
+ }
15153
+ if (current.length > 0) {
15154
+ statements.push(current);
15155
+ spans.push({ start: currentStart, end: sql.length });
15156
+ }
15157
+ return { ok: true, statements, spans };
15158
+ }
15159
+ function isWord(token, value) {
15160
+ return token?.type === "word" && (value === void 0 || token.value === value);
15161
+ }
15162
+ function splitStatements(sql) {
15163
+ const lexed = lex(sql);
15164
+ if (!lexed.ok) return { ok: false, reason: lexed.reason };
15165
+ return {
15166
+ ok: true,
15167
+ statements: lexed.spans.map(({ start, end }) => ({
15168
+ text: sql.slice(start, end),
15169
+ start,
15170
+ end
15171
+ }))
15172
+ };
15173
+ }
14943
15174
 
14944
15175
  // ../../packages/build/src/next-version.ts
14945
15176
  var NEXT_RELEASE_DATES = {
@@ -16853,7 +17084,7 @@ function createSmokeTester(options = {}) {
16853
17084
  }
16854
17085
  }
16855
17086
  );
16856
- const probes = [];
17087
+ const probes2 = [];
16857
17088
  const withOutput = (reason) => recent.length > 0 ? `${reason} Last output: ${recent.join(" | ")}` : reason;
16858
17089
  try {
16859
17090
  const deadline = Date.now() + bootTimeoutMs;
@@ -16870,7 +17101,7 @@ function createSmokeTester(options = {}) {
16870
17101
  reason: withOutput(
16871
17102
  `The server exited with code ${String(exit.exited)} before answering a request.`
16872
17103
  ),
16873
- probes,
17104
+ probes: probes2,
16874
17105
  // The port was free when allocated and taken by the time the
16875
17106
  // server bound it — a race, not a broken build.
16876
17107
  portTaken: recent.some(
@@ -16896,7 +17127,7 @@ function createSmokeTester(options = {}) {
16896
17127
  reason: withOutput(
16897
17128
  `The server did not answer within ${String(bootTimeoutMs)}ms (${lastError}).`
16898
17129
  ),
16899
- probes
17130
+ probes: probes2
16900
17131
  };
16901
17132
  }
16902
17133
  for (const probe of request.probes) {
@@ -16906,24 +17137,24 @@ function createSmokeTester(options = {}) {
16906
17137
  probeTimeoutMs
16907
17138
  );
16908
17139
  const result = "status" in answer ? { ...probe, status: answer.status, ok: probeOk(answer.status) } : { ...probe, status: null, ok: false, detail: answer.error };
16909
- probes.push(result);
17140
+ probes2.push(result);
16910
17141
  await observer.onLog({
16911
17142
  level: result.ok ? "INFO" : "ERROR",
16912
17143
  stage,
16913
17144
  message: `Smoke ${probe.label}: GET ${probe.path} -> ${result.status === null ? result.detail ?? "no response" : String(result.status)}`
16914
17145
  });
16915
17146
  }
16916
- const failed2 = probes.filter((probe) => !probe.ok);
17147
+ const failed2 = probes2.filter((probe) => !probe.ok);
16917
17148
  if (failed2.length > 0) {
16918
17149
  return {
16919
17150
  ok: false,
16920
- reason: `${String(failed2.length)} of ${String(probes.length)} smoke probes failed: ${failed2.map(
17151
+ reason: `${String(failed2.length)} of ${String(probes2.length)} smoke probes failed: ${failed2.map(
16921
17152
  (probe) => `${probe.path} (${probe.status === null ? probe.detail ?? "no response" : String(probe.status)})`
16922
17153
  ).join(", ")}.`,
16923
- probes
17154
+ probes: probes2
16924
17155
  };
16925
17156
  }
16926
- return { ok: true, probes };
17157
+ return { ok: true, probes: probes2 };
16927
17158
  } finally {
16928
17159
  await server.stop();
16929
17160
  }
@@ -16942,12 +17173,12 @@ function createSmokeTester(options = {}) {
16942
17173
  };
16943
17174
  }
16944
17175
  function defaultSmokeProbes(matcherRoutes) {
16945
- const probes = [{ path: "/", label: "root" }];
17176
+ const probes2 = [{ path: "/", label: "root" }];
16946
17177
  for (const route of matcherRoutes) {
16947
17178
  if (route === "/") continue;
16948
- probes.push({ path: route, label: "middleware matcher" });
17179
+ probes2.push({ path: route, label: "middleware matcher" });
16949
17180
  }
16950
- return probes;
17181
+ return probes2;
16951
17182
  }
16952
17183
 
16953
17184
  // ../../packages/build/src/strategy/strategies/opennext-aws.ts
@@ -18514,8 +18745,8 @@ function sendPage(res, status, html) {
18514
18745
  async function startCallbackServer() {
18515
18746
  let resolveCode;
18516
18747
  let rejectCode;
18517
- const codePromise = new Promise((resolve10, reject) => {
18518
- resolveCode = resolve10;
18748
+ const codePromise = new Promise((resolve11, reject) => {
18749
+ resolveCode = resolve11;
18519
18750
  rejectCode = reject;
18520
18751
  });
18521
18752
  const server = createServer2((req, res) => {
@@ -18544,7 +18775,7 @@ async function startCallbackServer() {
18544
18775
  sendPage(res, 200, successPage());
18545
18776
  resolveCode(code);
18546
18777
  });
18547
- await new Promise((resolve10) => server.listen(0, "127.0.0.1", resolve10));
18778
+ await new Promise((resolve11) => server.listen(0, "127.0.0.1", resolve11));
18548
18779
  const port = server.address().port;
18549
18780
  return {
18550
18781
  port,
@@ -19028,7 +19259,7 @@ var DeploymentsApi = class {
19028
19259
  async wait(deploymentId, options = {}) {
19029
19260
  const intervalMs = options.intervalMs ?? 2500;
19030
19261
  const timeoutMs = options.timeoutMs ?? 15 * 6e4;
19031
- const sleep2 = options.sleep ?? ((ms) => new Promise((resolve10) => setTimeout(resolve10, ms)));
19262
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve11) => setTimeout(resolve11, ms)));
19032
19263
  const startedAt = Date.now();
19033
19264
  let lastStatus = null;
19034
19265
  let lastSequence = -1;
@@ -19293,14 +19524,12 @@ var DbApi = class {
19293
19524
  * retrying rather than re-running the file blind.
19294
19525
  */
19295
19526
  async applyMigration(projectId, input) {
19296
- return this.client.request(
19297
- this.path(projectId, "/migrations"),
19298
- {
19299
- method: "POST",
19300
- body: { name: input.name, sql: input.sql },
19301
- context: "Apply migration"
19302
- }
19303
- );
19527
+ const body = await this.client.request(this.path(projectId, "/migrations"), {
19528
+ method: "POST",
19529
+ body: { name: input.name, sql: input.sql },
19530
+ context: "Apply migration"
19531
+ });
19532
+ return { ...body, warnings: body.warnings ?? [] };
19304
19533
  }
19305
19534
  /**
19306
19535
  * The current Time Travel bookmark, or the one `timestamp` (RFC 3339 with a
@@ -19368,7 +19597,7 @@ var DbApi = class {
19368
19597
  */
19369
19598
  async export(projectId, options = {}, wait = {}) {
19370
19599
  const timeoutMs = wait.timeoutMs ?? EXPORT_TIMEOUT_MS;
19371
- const sleep2 = wait.sleep ?? ((ms) => new Promise((resolve10) => setTimeout(resolve10, ms)));
19600
+ const sleep2 = wait.sleep ?? ((ms) => new Promise((resolve11) => setTimeout(resolve11, ms)));
19372
19601
  const given = wait.pollIntervalMs;
19373
19602
  const firstPollMs = given !== void 0 && Number.isFinite(given) && given > 0 ? given : EXPORT_FIRST_POLL_MS;
19374
19603
  const maxPollMs = Math.max(EXPORT_MAX_POLL_MS, firstPollMs);
@@ -19838,29 +20067,29 @@ var RESTORE_OUTCOME_UNKNOWN_CODES = /* @__PURE__ */ new Set([
19838
20067
  "TIMEOUT",
19839
20068
  "NETWORK_ERROR"
19840
20069
  ]);
19841
- function cellText(statement, row, column) {
19842
- const index = statement.columns.indexOf(column);
20070
+ function cellText(statement2, row, column2) {
20071
+ const index = statement2.columns.indexOf(column2);
19843
20072
  const value = index < 0 ? void 0 : row[index];
19844
20073
  return value === null || value === void 0 ? "" : String(value);
19845
20074
  }
19846
20075
  var MAX_CELL_CHARS = 60;
19847
20076
  function formatCell(value) {
19848
20077
  if (value === null || value === void 0) return "NULL";
19849
- const text = typeof value === "string" ? value : typeof value === "object" ? JSON.stringify(value) : String(value);
19850
- const oneLine = text.replace(/\s+/g, " ");
20078
+ const text2 = typeof value === "string" ? value : typeof value === "object" ? JSON.stringify(value) : String(value);
20079
+ const oneLine = text2.replace(/\s+/g, " ");
19851
20080
  return oneLine.length > MAX_CELL_CHARS ? `${oneLine.slice(0, MAX_CELL_CHARS - 1)}\u2026` : oneLine;
19852
20081
  }
19853
- function renderRows(statement) {
19854
- if (statement.columns.length === 0) return [];
19855
- const cells = statement.rows.map(
19856
- (row) => statement.columns.map((_, index) => formatCell(row[index]))
20082
+ function renderRows(statement2) {
20083
+ if (statement2.columns.length === 0) return [];
20084
+ const cells = statement2.rows.map(
20085
+ (row) => statement2.columns.map((_, index) => formatCell(row[index]))
19857
20086
  );
19858
- const widths = statement.columns.map(
19859
- (column, index) => Math.max(column.length, ...cells.map((row) => row[index]?.length ?? 0))
20087
+ const widths = statement2.columns.map(
20088
+ (column2, index) => Math.max(column2.length, ...cells.map((row) => row[index]?.length ?? 0))
19860
20089
  );
19861
20090
  const line = (values) => values.map((value, index) => value.padEnd(widths[index] ?? 0)).join(" ").trimEnd();
19862
20091
  return [
19863
- line(statement.columns),
20092
+ line(statement2.columns),
19864
20093
  widths.map((width) => "-".repeat(width)).join(" "),
19865
20094
  ...cells.map(line)
19866
20095
  ];
@@ -19868,18 +20097,18 @@ function renderRows(statement) {
19868
20097
  function formatMs(ms) {
19869
20098
  return ms >= 100 ? String(Math.round(ms)) : ms.toFixed(1);
19870
20099
  }
19871
- function summarizeStatement(statement) {
19872
- const rows = statement.rows.length;
19873
- const { meta: meta3 } = statement;
20100
+ function summarizeStatement(statement2) {
20101
+ const rows = statement2.rows.length;
20102
+ const { meta: meta3 } = statement2;
19874
20103
  return `\u21B3 ${String(rows)} row${rows === 1 ? "" : "s"} \xB7 ${String(meta3.rowsRead)} read \xB7 ${String(meta3.rowsWritten)} written \xB7 ${formatMs(meta3.durationMs)} ms`;
19875
20104
  }
19876
20105
 
19877
20106
  // ../../packages/sdk/src/dotenv.ts
19878
20107
  import { existsSync, readFileSync as readFileSync2 } from "fs";
19879
20108
  import { join as join19 } from "path";
19880
- function parseDotenv(text) {
20109
+ function parseDotenv(text2) {
19881
20110
  const out = {};
19882
- for (const line of text.split(/\r?\n/)) {
20111
+ for (const line of text2.split(/\r?\n/)) {
19883
20112
  const trimmed = line.trim();
19884
20113
  if (!trimmed || trimmed.startsWith("#")) continue;
19885
20114
  const eq = trimmed.indexOf("=");
@@ -19982,14 +20211,14 @@ function scanEnvReferences(dir) {
19982
20211
  const files = [];
19983
20212
  collect(dir, files);
19984
20213
  for (const file2 of files) {
19985
- let text;
20214
+ let text2;
19986
20215
  try {
19987
20216
  if (statSync(file2).size > MAX_FILE_BYTES2) continue;
19988
- text = readFileSync3(file2, "utf8");
20217
+ text2 = readFileSync3(file2, "utf8");
19989
20218
  } catch {
19990
20219
  continue;
19991
20220
  }
19992
- for (const match of text.matchAll(REFERENCE)) {
20221
+ for (const match of text2.matchAll(REFERENCE)) {
19993
20222
  const key = match[1] ?? match[2];
19994
20223
  if (!key || ignored(key)) continue;
19995
20224
  found.set(key, "code");
@@ -20177,32 +20406,156 @@ var SYSTEM_TABLE_PREFIXES = [
20177
20406
  "sqlite_"
20178
20407
  ];
20179
20408
  var SYSTEM_TABLE_NAMES = ["d1_migrations"];
20180
- function asciiLower(text) {
20181
- return text.replace(/[A-Z]+/g, (run) => run.toLowerCase());
20409
+ function asciiLower(text2) {
20410
+ return text2.replace(/[A-Z]+/g, (run) => run.toLowerCase());
20182
20411
  }
20183
20412
  function isSystemTable(name) {
20184
20413
  const lower = asciiLower(name);
20185
20414
  return SYSTEM_TABLE_PREFIXES.some((prefix) => lower.startsWith(prefix)) || SYSTEM_TABLE_NAMES.includes(lower);
20186
20415
  }
20187
- function likeLiteral(text) {
20188
- return text.replace(/[\\%_]/g, (char) => `\\${char}`).replace(/'/g, "''");
20416
+ function likeLiteral(text2) {
20417
+ return text2.replace(/[\\%_]/g, (char) => `\\${char}`).replace(/'/g, "''");
20189
20418
  }
20190
20419
  var COLUMN_REFERENCE = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?$/;
20191
- function systemTablePredicate(column = "name") {
20192
- if (!COLUMN_REFERENCE.test(column))
20193
- throw new Error(`Not a column reference: ${column}`);
20420
+ function systemTablePredicate(column2 = "name") {
20421
+ if (!COLUMN_REFERENCE.test(column2))
20422
+ throw new Error(`Not a column reference: ${column2}`);
20194
20423
  return [
20195
20424
  ...SYSTEM_TABLE_PREFIXES.map(
20196
- (prefix) => `${column} NOT LIKE '${likeLiteral(prefix)}%' ESCAPE '\\'`
20425
+ (prefix) => `${column2} NOT LIKE '${likeLiteral(prefix)}%' ESCAPE '\\'`
20197
20426
  ),
20198
20427
  ...SYSTEM_TABLE_NAMES.map(
20199
- (name) => `${column} NOT LIKE '${likeLiteral(name)}' ESCAPE '\\'`
20428
+ (name) => `${column2} NOT LIKE '${likeLiteral(name)}' ESCAPE '\\'`
20200
20429
  ),
20201
- `${column} NOT IN (SELECT name FROM pragma_table_list WHERE schema = 'main' AND type = 'shadow')`
20430
+ `${column2} NOT IN (SELECT name FROM pragma_table_list WHERE schema = 'main' AND type = 'shadow')`
20202
20431
  ].join(" AND ");
20203
20432
  }
20204
20433
  var SYSTEM_TABLE_SQL_PREDICATE = systemTablePredicate("name");
20205
20434
 
20435
+ // ../../packages/data-core/src/errors.ts
20436
+ var DATABASE_FULL_MESSAGE = "The database has reached D1's 10 GB limit, which cannot be raised. Delete data to free space; writes fail until it is under the limit.";
20437
+ var CATALOG = {
20438
+ INVALID_API_KEY: {
20439
+ status: 401,
20440
+ message: () => "The API key is missing or not valid for this project."
20441
+ },
20442
+ INVALID_SESSION: {
20443
+ status: 401,
20444
+ message: () => "The session is expired or not valid. Sign in again."
20445
+ },
20446
+ INVALID_QUERY: {
20447
+ status: 400,
20448
+ message: () => "The query is not a valid Naro query."
20449
+ },
20450
+ TABLE_NOT_FOUND: {
20451
+ status: 404,
20452
+ message: ({ table }) => `Table ${table} was not found.`
20453
+ },
20454
+ COLUMN_NOT_FOUND: {
20455
+ status: 400,
20456
+ message: ({ column: column2, table }) => `Column ${column2} was not found on ${table}.`
20457
+ },
20458
+ PERMISSION_DENIED: {
20459
+ status: 403,
20460
+ // P2-7 §6: Postgres's words for a WITH CHECK that fails —
20461
+ // `new row violates row-level security policy ["p"] for table t`, the
20462
+ // restrictive policy's name in double quotes as Postgres writes it.
20463
+ message: ({ action, table, check: check2, policy }) => check2 ? `New row violates row-level security policy ${policy === void 0 ? "" : `"${policy}" `}for table ${table}.` : `This request is not allowed by the ${action} policy of ${table}.`
20464
+ },
20465
+ POLICY_VIOLATION: {
20466
+ status: 403,
20467
+ message: ({ column: column2 }) => `${column2} must be the signed-in user's id.`
20468
+ },
20469
+ FILTER_REQUIRED: {
20470
+ status: 400,
20471
+ message: () => "update and delete need at least one filter."
20472
+ },
20473
+ TOO_MANY_PARAMETERS: {
20474
+ status: 400,
20475
+ message: () => "The query needs more than 100 values in one statement."
20476
+ },
20477
+ NOT_SINGLE_ROW: {
20478
+ status: 406,
20479
+ message: ({ count: count2 }) => `Expected exactly one row, got ${String(count2)}.`
20480
+ },
20481
+ CONFLICT: {
20482
+ status: 409,
20483
+ message: ({ column: column2 }) => `A row with the same ${column2} already exists.`
20484
+ },
20485
+ CONSTRAINT_VIOLATION: {
20486
+ status: 400,
20487
+ message: ({ kind, column: column2 }) => `The row breaks a ${kind} constraint on ${column2}.`
20488
+ },
20489
+ PAYLOAD_TOO_LARGE: {
20490
+ status: 413,
20491
+ message: () => "The request body is larger than 1 MiB."
20492
+ },
20493
+ INTERNAL_ERROR: {
20494
+ status: 500,
20495
+ message: () => "Something went wrong. Try again."
20496
+ },
20497
+ // Not in spec §14: an `owner` rule whose owner column is unset or no longer
20498
+ // on the table. The control plane refuses to write one, but `_naro_policies`
20499
+ // is an ordinary table an operator can edit by hand, and a column can be
20500
+ // dropped after the policy was set. It is the project's configuration that
20501
+ // is broken, not the request, hence 500 — and never an allow.
20502
+ //
20503
+ // P2-7 §6: a named policy that cannot be applied (it cannot be read, or
20504
+ // names a column, table or problem the schema does not allow), policies
20505
+ // that read each other (Postgres 42P17's words), or too many values.
20506
+ POLICY_INVALID: {
20507
+ status: 500,
20508
+ message: ({
20509
+ action,
20510
+ table,
20511
+ condition,
20512
+ policy,
20513
+ recursion,
20514
+ values,
20515
+ bytes
20516
+ }) => {
20517
+ if (recursion)
20518
+ return `Infinite recursion detected in policy for relation ${table}.`;
20519
+ if (values)
20520
+ return `The policies of ${table} bind more than 50 values in one statement.`;
20521
+ if (bytes)
20522
+ return `The policies of ${table} write more than 50000 bytes of SQL into one statement.`;
20523
+ if (policy !== void 0)
20524
+ return `Policy ${policy} on ${table} cannot be applied.`;
20525
+ return condition ? `The ${action} policy of ${table} has a condition that cannot be applied.` : `The ${action} policy of ${table} is owner but has no valid owner column.`;
20526
+ }
20527
+ },
20528
+ // PR C (2026-09-23-d1-write-quota-design.md §5.2, §8): the project's D1
20529
+ // wrote more rows today (UTC) than its daily limit. Refuses Data API writes
20530
+ // and sign-up; the worker adds `x-retry-after`. No numbers — this answer
20531
+ // goes to anyone on the internet holding the anon key.
20532
+ WRITE_QUOTA_EXCEEDED: {
20533
+ status: 429,
20534
+ message: () => "This project has reached its daily database write limit. Reads still work; writes resume at 00:00 UTC."
20535
+ },
20536
+ // PR D: D1 refused a write because the database is at its size limit,
20537
+ // which Cloudflare does not raise. 507 Insufficient Storage: no retry
20538
+ // helps until rows are deleted. Reads still work.
20539
+ DATABASE_FULL: {
20540
+ status: 507,
20541
+ message: () => DATABASE_FULL_MESSAGE
20542
+ }
20543
+ };
20544
+ function dataError(code, ...args) {
20545
+ const [vars, details] = args;
20546
+ const entry = CATALOG[code];
20547
+ const error51 = {
20548
+ code,
20549
+ message: entry.message(vars),
20550
+ status: entry.status
20551
+ };
20552
+ if (details !== void 0) error51.details = details;
20553
+ return error51;
20554
+ }
20555
+ function invalidQuery(path, reason) {
20556
+ return dataError("INVALID_QUERY", void 0, { path, reason });
20557
+ }
20558
+
20206
20559
  // ../../packages/data-core/src/d1.ts
20207
20560
  function rowsOf(result) {
20208
20561
  const rows = result?.results ?? [];
@@ -20256,11 +20609,11 @@ function isDigit(char) {
20256
20609
  return char >= "0" && char <= "9";
20257
20610
  }
20258
20611
  var NUMBER = /^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/;
20259
- function tokenize(text) {
20612
+ function tokenize(text2) {
20260
20613
  const tokens = [];
20261
20614
  let index = 0;
20262
- const at = (offset = 0) => text.charAt(index + offset);
20263
- while (index < text.length) {
20615
+ const at = (offset = 0) => text2.charAt(index + offset);
20616
+ while (index < text2.length) {
20264
20617
  const char = at();
20265
20618
  const position = index + 1;
20266
20619
  if (char === "\0")
@@ -20270,12 +20623,12 @@ function tokenize(text) {
20270
20623
  continue;
20271
20624
  }
20272
20625
  if (char === "-" && at(1) === "-") {
20273
- while (index < text.length && at() !== "\n" && at() !== "\r") index++;
20626
+ while (index < text2.length && at() !== "\n" && at() !== "\r") index++;
20274
20627
  continue;
20275
20628
  }
20276
20629
  if (char === "/" && at(1) === "*") {
20277
20630
  let depth = 0;
20278
- while (index < text.length) {
20631
+ while (index < text2.length) {
20279
20632
  if (at() === "/" && at(1) === "*") {
20280
20633
  depth++;
20281
20634
  index += 2;
@@ -20293,7 +20646,7 @@ function tokenize(text) {
20293
20646
  let value = "";
20294
20647
  index++;
20295
20648
  for (; ; ) {
20296
- if (index >= text.length)
20649
+ if (index >= text2.length)
20297
20650
  throw new PolicySyntaxError("unterminated quoted string", position);
20298
20651
  if (at() === "'") {
20299
20652
  if (at(1) === "'") {
@@ -20314,7 +20667,7 @@ function tokenize(text) {
20314
20667
  let value = "";
20315
20668
  index++;
20316
20669
  for (; ; ) {
20317
- if (index >= text.length)
20670
+ if (index >= text2.length)
20318
20671
  throw new PolicySyntaxError(
20319
20672
  "unterminated quoted identifier",
20320
20673
  position
@@ -20342,10 +20695,10 @@ function tokenize(text) {
20342
20695
  position
20343
20696
  );
20344
20697
  if (isDigit(char) || char === "." && isDigit(at(1))) {
20345
- const match = NUMBER.exec(text.slice(index));
20698
+ const match = NUMBER.exec(text2.slice(index));
20346
20699
  const value = match?.[0] ?? char;
20347
20700
  index += value.length;
20348
- if (index < text.length && isIdentChar(at()))
20701
+ if (index < text2.length && isIdentChar(at()))
20349
20702
  throw new PolicySyntaxError(
20350
20703
  `trailing junk after numeric literal ${value}`,
20351
20704
  position
@@ -20355,9 +20708,9 @@ function tokenize(text) {
20355
20708
  }
20356
20709
  if (isIdentStart(char)) {
20357
20710
  let end = index + 1;
20358
- while (end < text.length && isIdentChar(text.charAt(end))) end++;
20359
- const value = text.slice(index, end);
20360
- if (text.charAt(end) === "'")
20711
+ while (end < text2.length && isIdentChar(text2.charAt(end))) end++;
20712
+ const value = text2.slice(index, end);
20713
+ if (text2.charAt(end) === "'")
20361
20714
  throw new PolicySyntaxError(
20362
20715
  `${value}'\u2026' strings are not supported: use a plain '\u2026' string`,
20363
20716
  position
@@ -20367,7 +20720,7 @@ function tokenize(text) {
20367
20720
  continue;
20368
20721
  }
20369
20722
  const operator = OPERATORS.find(
20370
- (candidate) => text.startsWith(candidate, index)
20723
+ (candidate) => text2.startsWith(candidate, index)
20371
20724
  );
20372
20725
  if (!operator)
20373
20726
  throw new PolicySyntaxError(
@@ -20381,7 +20734,7 @@ function tokenize(text) {
20381
20734
  kind: "eof",
20382
20735
  value: "",
20383
20736
  quoted: false,
20384
- position: text.length + 1
20737
+ position: text2.length + 1
20385
20738
  });
20386
20739
  return tokens;
20387
20740
  }
@@ -20450,7 +20803,155 @@ var TokenStream = class {
20450
20803
  }
20451
20804
  };
20452
20805
 
20806
+ // ../../packages/data-core/src/collation.ts
20807
+ var COLLATIONS = ["BINARY", "NOCASE", "RTRIM"];
20808
+ function isSpace2(char) {
20809
+ return char === " " || char === " " || char === "\n" || char === "\f" || char === "\r";
20810
+ }
20811
+ function isWordChar(char) {
20812
+ return /[A-Za-z0-9_$]/.test(char) || char.charCodeAt(0) >= 128;
20813
+ }
20814
+ function asciiUpper2(text2) {
20815
+ return text2.replace(/[a-z]/g, (letter) => letter.toUpperCase());
20816
+ }
20817
+ function quoted(sql, start, close, doubles) {
20818
+ let value = "";
20819
+ for (let index = start + 1; index < sql.length; index++) {
20820
+ const char = sql.charAt(index);
20821
+ if (char !== close) {
20822
+ value += char;
20823
+ continue;
20824
+ }
20825
+ if (doubles && sql.charAt(index + 1) === close) {
20826
+ value += close;
20827
+ index++;
20828
+ continue;
20829
+ }
20830
+ return { value, end: index + 1 };
20831
+ }
20832
+ return void 0;
20833
+ }
20834
+ function tokenize2(sql) {
20835
+ const tokens = [];
20836
+ let index = 0;
20837
+ while (index < sql.length) {
20838
+ const char = sql.charAt(index);
20839
+ if (isSpace2(char)) {
20840
+ index++;
20841
+ } else if (char === "-" && sql.charAt(index + 1) === "-") {
20842
+ const end = sql.indexOf("\n", index);
20843
+ index = end < 0 ? sql.length : end + 1;
20844
+ } else if (char === "/" && sql.charAt(index + 1) === "*") {
20845
+ const end = sql.indexOf("*/", index + 2);
20846
+ if (end < 0) return void 0;
20847
+ index = end + 2;
20848
+ } else if (char === '"' || char === "`" || char === "[" || char === "'") {
20849
+ const close = char === "[" ? "]" : char;
20850
+ const read = quoted(sql, index, close, char !== "[");
20851
+ if (!read) return void 0;
20852
+ tokens.push({
20853
+ kind: char === "'" ? "string" : "name",
20854
+ value: read.value
20855
+ });
20856
+ index = read.end;
20857
+ } else if (char === "(" || char === ")" || char === ",") {
20858
+ tokens.push({ kind: "punct", value: char });
20859
+ index++;
20860
+ } else if (isWordChar(char)) {
20861
+ let end = index;
20862
+ while (end < sql.length && isWordChar(sql.charAt(end))) end++;
20863
+ tokens.push({ kind: "word", value: sql.slice(index, end) });
20864
+ index = end;
20865
+ } else {
20866
+ tokens.push({ kind: "other", value: char });
20867
+ index++;
20868
+ }
20869
+ }
20870
+ return tokens;
20871
+ }
20872
+ function isWord2(token, word) {
20873
+ return token?.kind === "word" && asciiLower(token.value) === word;
20874
+ }
20875
+ function definitions(tokens) {
20876
+ const open = tokens.findIndex(
20877
+ (token) => token.kind === "punct" && token.value === "("
20878
+ );
20879
+ if (open < 0) return void 0;
20880
+ const found = [[]];
20881
+ let depth = 0;
20882
+ for (const token of tokens.slice(open + 1)) {
20883
+ if (token.kind === "punct" && token.value === "(") depth++;
20884
+ if (token.kind === "punct" && token.value === ")") {
20885
+ if (depth === 0) return found;
20886
+ depth--;
20887
+ }
20888
+ if (depth === 0 && token.kind === "punct" && token.value === ",") {
20889
+ found.push([]);
20890
+ continue;
20891
+ }
20892
+ found.at(-1)?.push(token);
20893
+ }
20894
+ return void 0;
20895
+ }
20896
+ function collationOf(definition) {
20897
+ const named = [];
20898
+ let depth = 0;
20899
+ for (const [index, token] of definition.entries()) {
20900
+ if (token.kind === "punct" && token.value === "(") depth++;
20901
+ else if (token.kind === "punct" && token.value === ")") depth--;
20902
+ else if (depth === 0 && index > 0 && isWord2(token, "collate")) {
20903
+ const name = definition[index + 1];
20904
+ if (!name || name.kind === "punct" || name.kind === "other") return null;
20905
+ named.push(asciiUpper2(name.value));
20906
+ }
20907
+ }
20908
+ if (named.length === 0) return "BINARY";
20909
+ const [first] = named;
20910
+ if (named.some((name) => name !== first)) return null;
20911
+ return COLLATIONS.includes(first ?? "") ? first : null;
20912
+ }
20913
+ function declaredCollations(sql, columns) {
20914
+ const unknown2 = columns.map(() => null);
20915
+ if (typeof sql !== "string") return unknown2;
20916
+ const tokens = tokenize2(sql);
20917
+ if (!tokens || !isWord2(tokens[0], "create")) return unknown2;
20918
+ const kind = isWord2(tokens[1], "temp") || isWord2(tokens[1], "temporary") ? tokens[2] : tokens[1];
20919
+ if (!isWord2(kind, "table")) return unknown2;
20920
+ const found = definitions(tokens);
20921
+ if (!found || found.length < columns.length) return unknown2;
20922
+ const collations = [];
20923
+ for (const [index, column2] of columns.entries()) {
20924
+ const definition = found[index] ?? [];
20925
+ const name = definition[0];
20926
+ if (!name || name.kind === "punct" || name.kind === "other" || asciiLower(name.value) !== asciiLower(column2))
20927
+ return unknown2;
20928
+ collations.push(collationOf(definition));
20929
+ }
20930
+ return collations;
20931
+ }
20932
+
20453
20933
  // ../../packages/data-core/src/schema.ts
20934
+ function columnAffinity(column2) {
20935
+ const type = asciiLower(column2.type);
20936
+ if (type.includes("int")) return "INTEGER";
20937
+ if (type.includes("char") || type.includes("clob") || type.includes("text"))
20938
+ return "TEXT";
20939
+ if (type.includes("blob") || type.trim() === "") return "BLOB";
20940
+ if (type.includes("real") || type.includes("floa") || type.includes("doub"))
20941
+ return "REAL";
20942
+ return "NUMERIC";
20943
+ }
20944
+ function numberClass(type) {
20945
+ const affinity2 = columnAffinity({ type });
20946
+ if (affinity2 === "TEXT" || affinity2 === "BLOB") return void 0;
20947
+ if (affinity2 !== "NUMERIC") return "number";
20948
+ const lower = asciiLower(type);
20949
+ return ["num", "dec", "bool", "bit"].some((word) => lower.includes(word)) ? "number" : "fallback";
20950
+ }
20951
+ var NUMBER_TEXT = /^[ \t\n\v\f\r]*[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[ \t\n\v\f\r]*$/;
20952
+ function storesAsNumber(value) {
20953
+ return value.includes("\0") || NUMBER_TEXT.test(value);
20954
+ }
20454
20955
  var SCHEMA_SQL = [
20455
20956
  `SELECT t.name AS table_name, t.type AS table_type, c.cid AS cid, c.name AS column_name, c.type AS column_type, c."notnull" AS not_null, c.dflt_value AS default_value, c.pk AS pk, c.hidden AS hidden FROM pragma_table_list AS t JOIN pragma_table_xinfo(t.name, t.schema) AS c WHERE t.schema = 'main' AND t.type IN ('table', 'virtual') AND ${systemTablePredicate("t.name")} ORDER BY t.name, c.cid`,
20456
20957
  // Phase 2-2 (적대적 리뷰 반영): each table's CREATE TABLE text, where
@@ -20469,6 +20970,80 @@ function tableNamesFromRows(rows) {
20469
20970
  }
20470
20971
  return names;
20471
20972
  }
20973
+ function tableNames(snapshot) {
20974
+ return [...snapshot.tables.values()].map((table) => table.name);
20975
+ }
20976
+ function text(value) {
20977
+ return typeof value === "string" ? value : "";
20978
+ }
20979
+ function integer2(value) {
20980
+ const number4 = Number(value);
20981
+ return Number.isFinite(number4) ? number4 : 0;
20982
+ }
20983
+ function snapshotFromRows(rows) {
20984
+ const building = /* @__PURE__ */ new Map();
20985
+ const ddl = /* @__PURE__ */ new Map();
20986
+ for (const row of rows)
20987
+ if (Object.hasOwn(row, "table_sql") && typeof row.table_name === "string")
20988
+ ddl.set(row.table_name, row.table_sql);
20989
+ for (const row of rows) {
20990
+ if (Object.hasOwn(row, "table_sql")) continue;
20991
+ const tableName = text(row.table_name);
20992
+ const kind = row.table_type;
20993
+ if (tableName === "" || kind !== "table" && kind !== "virtual") continue;
20994
+ if (isSystemTable(tableName)) continue;
20995
+ const hidden = integer2(row.hidden);
20996
+ if (hidden === 1) continue;
20997
+ const columnName = text(row.column_name);
20998
+ if (columnName === "") continue;
20999
+ const key = asciiLower(tableName);
21000
+ let table = building.get(key);
21001
+ if (!table) {
21002
+ table = { name: tableName, kind, columns: [] };
21003
+ building.set(key, table);
21004
+ }
21005
+ const defaultValue = row.default_value;
21006
+ table.columns.push({
21007
+ name: columnName,
21008
+ cid: integer2(row.cid),
21009
+ type: text(row.column_type),
21010
+ notNull: integer2(row.not_null) === 1,
21011
+ primaryKey: integer2(row.pk),
21012
+ defaultValue: defaultValue === null || defaultValue === void 0 ? null : String(defaultValue),
21013
+ generated: hidden === 2 || hidden === 3,
21014
+ collation: null
21015
+ });
21016
+ }
21017
+ const tables = /* @__PURE__ */ new Map();
21018
+ for (const [key, table] of building) {
21019
+ const sorted2 = table.columns.toSorted(
21020
+ (left, right) => left.cid - right.cid
21021
+ );
21022
+ const collations = table.kind === "table" ? declaredCollations(
21023
+ ddl.get(table.name),
21024
+ sorted2.map((column2) => column2.name)
21025
+ ) : sorted2.map(() => null);
21026
+ const columns = sorted2.map((column2, index) => ({
21027
+ ...column2,
21028
+ collation: collations[index] ?? null
21029
+ }));
21030
+ const sql = ddl.get(table.name);
21031
+ tables.set(key, {
21032
+ name: table.name,
21033
+ kind: table.kind,
21034
+ columns,
21035
+ byName: new Map(
21036
+ columns.map((column2) => [asciiLower(column2.name), column2])
21037
+ ),
21038
+ sql: table.kind === "table" && typeof sql === "string" ? sql : null
21039
+ });
21040
+ }
21041
+ return { tables };
21042
+ }
21043
+ function resolveTable(snapshot, name) {
21044
+ if (isSystemTable(name)) return void 0;
21045
+ return snapshot.tables.get(asciiLower(name));
21046
+ }
20472
21047
  async function readTableNames(db) {
20473
21048
  return tableNamesFromRows(rowsOf(await db.prepare(TABLE_NAMES_SQL).all()));
20474
21049
  }
@@ -20483,14 +21058,20 @@ function isPlainObject2(value) {
20483
21058
  function isValue(value) {
20484
21059
  return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
20485
21060
  }
20486
- function utf8ByteLength(text) {
21061
+ function toBoundValue(value) {
21062
+ if (value === null) return null;
21063
+ if (typeof value === "boolean") return value ? 1 : 0;
21064
+ if (typeof value === "string" || typeof value === "number") return value;
21065
+ return JSON.stringify(value);
21066
+ }
21067
+ function utf8ByteLength(text2) {
20487
21068
  let bytes = 0;
20488
- for (let index = 0; index < text.length; index++) {
20489
- const unit = text.charCodeAt(index);
21069
+ for (let index = 0; index < text2.length; index++) {
21070
+ const unit = text2.charCodeAt(index);
20490
21071
  if (unit < 128) bytes += 1;
20491
21072
  else if (unit < 2048) bytes += 2;
20492
- else if (unit >= 55296 && unit <= 56319 && index + 1 < text.length) {
20493
- const next = text.charCodeAt(index + 1);
21073
+ else if (unit >= 55296 && unit <= 56319 && index + 1 < text2.length) {
21074
+ const next = text2.charCodeAt(index + 1);
20494
21075
  if (next >= 56320 && next <= 57343) {
20495
21076
  bytes += 4;
20496
21077
  index++;
@@ -20501,6 +21082,19 @@ function utf8ByteLength(text) {
20501
21082
  }
20502
21083
 
20503
21084
  // ../../packages/data-core/src/sql-fragments.ts
21085
+ function quoteIdentifier(name) {
21086
+ return `"${name.replaceAll('"', '""')}"`;
21087
+ }
21088
+ var Failure = class extends Error {
21089
+ constructor(error51, reason) {
21090
+ super(error51.code);
21091
+ this.error = error51;
21092
+ this.reason = reason;
21093
+ }
21094
+ };
21095
+ function fail(error51, reason) {
21096
+ throw new Failure(error51, reason);
21097
+ }
20504
21098
  function globLiteral(char) {
20505
21099
  return char === "*" || char === "?" || char === "[" ? `[${char}]` : char;
20506
21100
  }
@@ -20525,6 +21119,121 @@ function patternTooLong(pattern, caseInsensitive) {
20525
21119
  if (pattern.length > MAX_PATTERN_BYTES * 2) return true;
20526
21120
  return utf8ByteLength(boundPattern(pattern, caseInsensitive)) > MAX_PATTERN_BYTES;
20527
21121
  }
21122
+ function isBoolean(target, value) {
21123
+ return `coalesce(CAST(${target} AS NUMERIC) ${value ? "<>" : "="} 0, 0)`;
21124
+ }
21125
+ var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
21126
+ function distinctArrayValues(values) {
21127
+ const seen = /* @__PURE__ */ new Set();
21128
+ const out = [];
21129
+ for (const item of values) {
21130
+ const bound = typeof item === "string" ? item.replace(LONE_SURROGATE, "\uFFFD") : item;
21131
+ const key = `${typeof bound}:${String(bound)}`;
21132
+ if (seen.has(key)) continue;
21133
+ seen.add(key);
21134
+ out.push(bound);
21135
+ }
21136
+ return out;
21137
+ }
21138
+ var MAX_ARRAY_TEXT_LENGTH = 65536;
21139
+ var ELEMENT = "_naro_j";
21140
+ function arraySql(target, operator, count2, bind2, nullElement = false) {
21141
+ const isNull = target();
21142
+ const length = target();
21143
+ const valid = target();
21144
+ const type = target();
21145
+ const each = () => `json_each(${target()}) AS ${ELEMENT}`;
21146
+ const element = `${ELEMENT}.value`;
21147
+ const list = () => `(${Array.from({ length: count2 }, bind2).join(", ")})`;
21148
+ let test;
21149
+ if (operator === "contains" && nullElement) test = "0";
21150
+ else if (count2 === 0)
21151
+ test = operator === "containedBy" ? `NOT EXISTS (SELECT 1 FROM ${each()})` : "0";
21152
+ else if (operator === "contains")
21153
+ test = `(SELECT COUNT(DISTINCT ${element}) FROM ${each()} WHERE ${element} IN ${list()}) = ${String(count2)}`;
21154
+ else if (operator === "overlaps")
21155
+ test = `EXISTS (SELECT 1 FROM ${each()} WHERE ${element} IN ${list()})`;
21156
+ else
21157
+ test = `NOT EXISTS (SELECT 1 FROM ${each()} WHERE ${element} IS NULL OR ${element} NOT IN ${list()})`;
21158
+ return `(CASE WHEN ${isNull} IS NULL OR length(${length}) > ${String(MAX_ARRAY_TEXT_LENGTH)} THEN NULL WHEN json_valid(${valid}) THEN json_type(${type}) = 'array' AND ${test} ELSE 0 END)`;
21159
+ }
21160
+ function isNumeric(item) {
21161
+ const affinity2 = columnAffinity(item);
21162
+ return affinity2 !== "TEXT" && affinity2 !== "BLOB";
21163
+ }
21164
+ function refusesText(item, text2) {
21165
+ const kind = numberClass(item.type);
21166
+ return kind === "number" || kind === "fallback" && storesAsNumber(text2);
21167
+ }
21168
+ function readsAsIs(item, value) {
21169
+ return typeof value === "string" && numberClass(item.type) === "fallback" && !storesAsNumber(value);
21170
+ }
21171
+ function asStored(item, sql) {
21172
+ switch (columnAffinity(item)) {
21173
+ case "INTEGER":
21174
+ case "NUMERIC":
21175
+ return `CAST(${sql} AS NUMERIC)`;
21176
+ case "REAL":
21177
+ return `CAST(${sql} AS REAL)`;
21178
+ case "TEXT":
21179
+ return `CAST(${sql} AS TEXT)`;
21180
+ case "BLOB":
21181
+ return sql;
21182
+ }
21183
+ }
21184
+ var LITERAL_DEFAULT = /^(?:null|true|false|[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|'(?:[^']|'')*')$/i;
21185
+ function omittedSql(item) {
21186
+ if (item.primaryKey > 0) return void 0;
21187
+ const text2 = item.defaultValue;
21188
+ if (text2 === null) return "NULL";
21189
+ if (!LITERAL_DEFAULT.test(text2)) return void 0;
21190
+ const word = asciiLower(text2);
21191
+ if (word === "null") return "NULL";
21192
+ if (word === "true" || word === "false")
21193
+ return asStored(item, word === "true" ? "1" : "0");
21194
+ if (text2.startsWith("'") && isNumeric(item)) return void 0;
21195
+ return asStored(item, text2);
21196
+ }
21197
+ var DEFAULT_KEYWORDS = /* @__PURE__ */ new Set([
21198
+ "true",
21199
+ "false",
21200
+ "null",
21201
+ "current_date",
21202
+ "current_time",
21203
+ "current_timestamp"
21204
+ ]);
21205
+ var BARE_WORD = /^[A-Za-z_\u0080-￿][A-Za-z0-9_$\u0080-￿]*$/;
21206
+ function quotedName(text2) {
21207
+ const double = /^"((?:[^"]|"")*)"$/.exec(text2);
21208
+ if (double) return (double[1] ?? "").replaceAll('""', '"');
21209
+ const tick = /^`((?:[^`]|``)*)`$/.exec(text2);
21210
+ if (tick) return (tick[1] ?? "").replaceAll("``", "`");
21211
+ const bracket = /^\[([^\]]*)\]$/.exec(text2);
21212
+ return bracket ? bracket[1] ?? "" : void 0;
21213
+ }
21214
+ function stringDefault(item) {
21215
+ let text2 = item.defaultValue?.trim();
21216
+ if (text2 === void 0) return void 0;
21217
+ while (text2.startsWith("(") && text2.endsWith(")"))
21218
+ text2 = text2.slice(1, -1).trim();
21219
+ if (/^\+\s*(?:'|x')/i.test(text2)) text2 = text2.slice(1).trim();
21220
+ if (/^'(?:[^']|'')*'$/.test(text2))
21221
+ return { kind: "text", text: text2.slice(1, -1).replaceAll("''", "'") };
21222
+ if (/^x'[0-9a-f]*'$/i.test(text2)) return { kind: "blob" };
21223
+ const name = quotedName(text2);
21224
+ if (name !== void 0) return { kind: "text", text: name };
21225
+ if (BARE_WORD.test(text2) && !DEFAULT_KEYWORDS.has(asciiLower(text2)))
21226
+ return { kind: "text", text: text2 };
21227
+ return void 0;
21228
+ }
21229
+ var DEFAULT_CHECK_NONCE = "0";
21230
+ function checkMarker(action, nonce, restrictive) {
21231
+ const index = restrictive === void 0 ? "" : `:${String(restrictive)}`;
21232
+ return `json_extract('{}', 'naro:check:${action}:${nonce}${index}')`;
21233
+ }
21234
+ function schemaMarker(nonce) {
21235
+ return `json_extract('{}', 'naro:schema:${nonce}')`;
21236
+ }
20528
21237
 
20529
21238
  // ../../packages/data-core/src/policy-expr.ts
20530
21239
  var MAX_EXPRESSION_BYTES = 3 * 16384;
@@ -20626,25 +21335,25 @@ function nested(state, token, read) {
20626
21335
  }
20627
21336
  }
20628
21337
  function truthProblem(operand) {
20629
- const text = "is text, where 'true' reads as false";
21338
+ const text2 = "is text, where 'true' reads as false";
20630
21339
  switch (operand.kind) {
20631
21340
  case "column":
20632
21341
  return void 0;
20633
21342
  case "literal":
20634
- return typeof operand.value === "string" ? `a '\u2026' string ${text}: write TRUE or FALSE, or compare the string with =` : void 0;
21343
+ return typeof operand.value === "string" ? `a '\u2026' string ${text2}: write TRUE or FALSE, or compare the string with =` : void 0;
20635
21344
  case "jwt": {
20636
21345
  const claim = printValue(operand);
20637
- return `${claim} ${text}: compare it with text, ${claim} = 'true'`;
21346
+ return `${claim} ${text2}: compare it with text, ${claim} = 'true'`;
20638
21347
  }
20639
21348
  case "auth":
20640
21349
  return operand.fn === "uid" ? "auth.uid() is text: compare it, auth.uid() = user_id, or test auth.uid() IS NOT NULL" : "auth.role() is text: compare it, auth.role() = 'authenticated'";
20641
21350
  case "cast":
20642
- return `a ::text cast ${text}: compare it with text, (\u2026)::text = 'true'`;
21351
+ return `a ::text cast ${text2}: compare it with text, (\u2026)::text = 'true'`;
20643
21352
  case "func":
20644
21353
  switch (operand.name) {
20645
21354
  case "lower":
20646
21355
  case "upper":
20647
- return `${operand.name}(\u2026) ${text}: compare it with text, ${operand.name}(\u2026) = 'true'`;
21356
+ return `${operand.name}(\u2026) ${text2}: compare it with text, ${operand.name}(\u2026) = 'true'`;
20648
21357
  case "coalesce":
20649
21358
  return "coalesce(\u2026) is not a boolean: compare it, coalesce(\u2026) = 'true'";
20650
21359
  case "length":
@@ -20703,9 +21412,9 @@ function booleanComparisonProblem(expr) {
20703
21412
  const met = [...operandValues(left), ...operandValues(right)];
20704
21413
  if (!met.some(isBooleanConstant)) continue;
20705
21414
  const texts = met.filter(isTextValue);
20706
- const text = texts.find((value) => value.kind !== "literal") ?? texts[0];
20707
- if (text === void 0) continue;
20708
- const problem = text.kind === "literal" ? "a '\u2026' string is text, where 'true' reads as false: write TRUE or FALSE on both sides, or a '\u2026' string on both" : truthProblem(text);
21415
+ const text2 = texts.find((value) => value.kind !== "literal") ?? texts[0];
21416
+ if (text2 === void 0) continue;
21417
+ const problem = text2.kind === "literal" ? "a '\u2026' string is text, where 'true' reads as false: write TRUE or FALSE on both sides, or a '\u2026' string on both" : truthProblem(text2);
20709
21418
  return `a comparison with TRUE or FALSE compares a boolean, and ${String(problem)}`;
20710
21419
  }
20711
21420
  return void 0;
@@ -21261,17 +21970,17 @@ function readCondition(stream) {
21261
21970
  return requireCondition(expr, token);
21262
21971
  }
21263
21972
  function checkExpressionSize(expr, position) {
21264
- const text = printExpr(expr);
21265
- if (utf8ByteLength(text) > MAX_EXPRESSION_BYTES)
21973
+ const text2 = printExpr(expr);
21974
+ if (utf8ByteLength(text2) > MAX_EXPRESSION_BYTES)
21266
21975
  throw new PolicySyntaxError(
21267
21976
  `the expression is longer than ${String(MAX_EXPRESSION_BYTES)} bytes`,
21268
21977
  position
21269
21978
  );
21270
- return text;
21979
+ return text2;
21271
21980
  }
21272
- function parseExpression(text) {
21981
+ function parseExpression(text2) {
21273
21982
  try {
21274
- const stream = new TokenStream(tokenize(text));
21983
+ const stream = new TokenStream(tokenize(text2));
21275
21984
  if (stream.peek().kind === "eof")
21276
21985
  throw new PolicySyntaxError("the expression is empty", 1);
21277
21986
  const expr = readCondition(stream);
@@ -21286,22 +21995,22 @@ function parseExpression(text) {
21286
21995
  function quoteIdent(name) {
21287
21996
  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !SPECIAL_WORDS.has(asciiLower(name)) ? name : `"${name.replaceAll('"', '""')}"`;
21288
21997
  }
21289
- function commentText(text) {
21998
+ function commentText(text2) {
21290
21999
  let out = "";
21291
- for (const char of text) {
22000
+ for (const char of text2) {
21292
22001
  const code = char.charCodeAt(0);
21293
22002
  const control = code < 32 || code >= 127 && code <= 159 || code === 8232 || code === 8233;
21294
22003
  out += control ? `\\u${code.toString(16).padStart(4, "0")}` : char;
21295
22004
  }
21296
22005
  return out;
21297
22006
  }
21298
- function quoteString(text) {
21299
- return `'${text.replaceAll("'", "''")}'`;
22007
+ function quoteString(text2) {
22008
+ return `'${text2.replaceAll("'", "''")}'`;
21300
22009
  }
21301
22010
  function printNumber(value) {
21302
22011
  if (Object.is(value, -0)) return "-0.0";
21303
- const text = String(value);
21304
- return Number.isSafeInteger(value) || /[.e]/.test(text) ? text : `${text}.0`;
22012
+ const text2 = String(value);
22013
+ return Number.isSafeInteger(value) || /[.e]/.test(text2) ? text2 : `${text2}.0`;
21305
22014
  }
21306
22015
  function printLiteral(value) {
21307
22016
  if (value === null) return "NULL";
@@ -21433,6 +22142,30 @@ function unqualifiedProblem(ref) {
21433
22142
  const name = quoteIdent(ref.name);
21434
22143
  return `column ${name} has no table or alias in front of it inside a subquery: naro policy create writes one on every column there, so that a column added to a table later cannot change which table the policy reads it from \u2014 drop the policy and create it again`;
21435
22144
  }
22145
+ function subqueriesOf(expr) {
22146
+ const found = [];
22147
+ const visit = (node) => {
22148
+ if (isValueExpr(node)) return;
22149
+ switch (node.kind) {
22150
+ case "and":
22151
+ case "or":
22152
+ node.args.forEach(visit);
22153
+ return;
22154
+ case "not":
22155
+ visit(node.arg);
22156
+ return;
22157
+ case "exists":
22158
+ case "inSelect":
22159
+ found.push(node.subquery);
22160
+ if (node.subquery.where) visit(node.subquery.where);
22161
+ return;
22162
+ default:
22163
+ return;
22164
+ }
22165
+ };
22166
+ visit(expr);
22167
+ return found;
22168
+ }
21436
22169
 
21437
22170
  // ../../packages/data-core/src/named-policy.ts
21438
22171
  var POLICY_COMMANDS = [
@@ -21487,17 +22220,33 @@ function policyShapeProblem(policy) {
21487
22220
  return `${command === "all" ? "An ALL" : "An UPDATE"} policy needs USING (\u2026), WITH CHECK (\u2026) or both.`;
21488
22221
  return void 0;
21489
22222
  }
21490
- function ownerExpression(column) {
22223
+ function policySql(policy) {
22224
+ const parts = [
22225
+ `CREATE POLICY ${quotePolicyName(policy.name)} ON ${quoteIdent(policy.table)}`
22226
+ ];
22227
+ if (!policy.permissive) parts.push("AS RESTRICTIVE");
22228
+ if (policy.command !== "all")
22229
+ parts.push(`FOR ${policy.command.toUpperCase()}`);
22230
+ const roles = rolesText(policy.roles);
22231
+ if (roles !== "public") parts.push(`TO ${roles}`);
22232
+ if (policy.using !== null) parts.push(`USING (${policy.using})`);
22233
+ if (policy.check !== null) parts.push(`WITH CHECK (${policy.check})`);
22234
+ const sql = `${parts.join(" ")};`;
22235
+ if (policy.owner === null) return sql;
22236
+ const owner = commentText(quoteIdent(policy.owner));
22237
+ return `${sql} -- naro: owner ${owner}${policy.command === "insert" || policy.command === "all" ? ` (an insert that leaves ${owner} out gets auth.uid())` : ""}`;
22238
+ }
22239
+ function ownerExpression(column2) {
21491
22240
  return printExpr({
21492
22241
  kind: "compare",
21493
22242
  op: "=",
21494
- left: { kind: "column", qualifier: null, name: column },
22243
+ left: { kind: "column", qualifier: null, name: column2 },
21495
22244
  right: { kind: "auth", fn: "uid" }
21496
22245
  });
21497
22246
  }
21498
- function ownerClauses(column, command) {
21499
- const text = ownerExpression(column);
21500
- return command === "insert" ? { using: null, check: text } : { using: text, check: null };
22247
+ function ownerClauses(column2, command) {
22248
+ const text2 = ownerExpression(column2);
22249
+ return command === "insert" ? { using: null, check: text2 } : { using: text2, check: null };
21501
22250
  }
21502
22251
  var NAMED_POLICY_TABLE = "_naro_policies_v2";
21503
22252
  var NAMED_POLICY_TABLE_SQL = `CREATE TABLE IF NOT EXISTS ${NAMED_POLICY_TABLE} (table_name TEXT NOT NULL, name TEXT NOT NULL, kind TEXT NOT NULL DEFAULT 'permissive', command TEXT NOT NULL DEFAULT 'all', roles TEXT NOT NULL DEFAULT 'public', using_expr TEXT, check_expr TEXT, owner_column TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')), PRIMARY KEY (table_name, name))`;
@@ -21543,21 +22292,21 @@ function storedExpression(value, field) {
21543
22292
  function loadPolicy(row, log2) {
21544
22293
  const table = row.table_name;
21545
22294
  if (typeof table !== "string" || table === "") return void 0;
21546
- const problems = [];
22295
+ const problems2 = [];
21547
22296
  const name = typeof row.name === "string" && row.name !== "" ? row.name : "(unnamed)";
21548
- if (name === "(unnamed)") problems.push("the name is missing");
22297
+ if (name === "(unnamed)") problems2.push("the name is missing");
21549
22298
  const kind = row.kind;
21550
22299
  if (kind !== "permissive" && kind !== "restrictive")
21551
- problems.push("kind is not permissive or restrictive");
22300
+ problems2.push("kind is not permissive or restrictive");
21552
22301
  const command = POLICY_COMMANDS.includes(row.command) ? row.command : void 0;
21553
22302
  if (!command)
21554
- problems.push("command is not one of all, select, insert, update, delete");
22303
+ problems2.push("command is not one of all, select, insert, update, delete");
21555
22304
  const roles = typeof row.roles === "string" && ["public", "anon", "authenticated"].includes(row.roles) ? readRoles([row.roles]) : void 0;
21556
- if (!roles?.ok) problems.push("roles is not public, anon or authenticated");
22305
+ if (!roles?.ok) problems2.push("roles is not public, anon or authenticated");
21557
22306
  const using = storedExpression(row.using_expr, "using");
21558
22307
  const check2 = storedExpression(row.check_expr, "check");
21559
- if (using.problem) problems.push(using.problem);
21560
- if (check2.problem) problems.push(check2.problem);
22308
+ if (using.problem) problems2.push(using.problem);
22309
+ if (check2.problem) problems2.push(check2.problem);
21561
22310
  const owner = row.owner_column;
21562
22311
  const policy = {
21563
22312
  table,
@@ -21573,30 +22322,47 @@ function loadPolicy(row, log2) {
21573
22322
  usingExpr: using.expr,
21574
22323
  checkExpr: check2.expr
21575
22324
  };
21576
- if (command && problems.length === 0) {
22325
+ if (command && problems2.length === 0) {
21577
22326
  const shape = policyShapeProblem(policy);
21578
- if (shape) problems.push(shape);
22327
+ if (shape) problems2.push(shape);
21579
22328
  }
21580
- if (policy.owner !== null && policy.owner !== "" && command && problems.length === 0) {
22329
+ if (policy.owner !== null && policy.owner !== "" && command && problems2.length === 0) {
21581
22330
  const expected = ownerClauses(policy.owner, command);
21582
22331
  if (policy.using !== expected.using || policy.check !== expected.check)
21583
- problems.push(
22332
+ problems2.push(
21584
22333
  "an owner policy's expressions are not its owner condition"
21585
22334
  );
21586
22335
  }
21587
- if (problems.length > 0) {
21588
- policy.invalid = problems.join("; ");
22336
+ if (problems2.length > 0) {
22337
+ policy.invalid = problems2.join("; ");
21589
22338
  log2?.(
21590
22339
  `naro data-core: policy ${name} on ${table} cannot be read: ${policy.invalid}`
21591
22340
  );
21592
22341
  }
21593
22342
  return policy;
21594
22343
  }
21595
- function foldPolicyName(name, quoted) {
21596
- return quoted ? name : asciiLower(name);
22344
+ function applicablePolicies(policies, table, command, role) {
22345
+ const found = policiesOn(policies, table).filter(
22346
+ (policy) => (policy.command === "all" || policy.command === command) && policy.roles.includes(role)
22347
+ );
22348
+ return {
22349
+ permissive: found.filter((policy) => policy.permissive),
22350
+ restrictive: found.filter((policy) => !policy.permissive),
22351
+ invalid: found.find((policy) => policy.invalid !== null)
22352
+ };
22353
+ }
22354
+ function policiesOn(policies, table) {
22355
+ const folded = asciiLower(table);
22356
+ return policies.filter((policy) => asciiLower(policy.table) === folded);
22357
+ }
22358
+ function foldPolicyName(name, quoted2) {
22359
+ return quoted2 ? name : asciiLower(name);
22360
+ }
22361
+ function sqlStringLiteral(text2) {
22362
+ return text2 === null ? "NULL" : quoteString(text2);
21597
22363
  }
21598
- function policySqlTooLong(text) {
21599
- return utf8ByteLength(text) > MAX_POLICY_SQL_BYTES;
22364
+ function policySqlTooLong(text2) {
22365
+ return utf8ByteLength(text2) > MAX_POLICY_SQL_BYTES;
21600
22366
  }
21601
22367
 
21602
22368
  // ../../packages/data-core/src/query.ts
@@ -21620,6 +22386,7 @@ function isAuthUid(value) {
21620
22386
  return isPlainObject2(value) && Object.keys(value).length === 1 && Object.hasOwn(value, "auth") && value.auth === "uid";
21621
22387
  }
21622
22388
  var MAX_BODY_BYTES = 1024 * 1024;
22389
+ var MAX_LIMIT = 1e3;
21623
22390
  var MAX_FILTERS = 100;
21624
22391
  var MAX_FILTER_DEPTH = 4;
21625
22392
  var MAX_ARRAY_VALUES = 100;
@@ -21682,11 +22449,11 @@ function readValue2(value, path, auth = false) {
21682
22449
  );
21683
22450
  return value;
21684
22451
  }
21685
- function longerThan(text, max) {
21686
- if (text.length <= max) return false;
21687
- if (text.length > max * 2) return true;
22452
+ function longerThan(text2, max) {
22453
+ if (text2.length <= max) return false;
22454
+ if (text2.length > max * 2) return true;
21688
22455
  let count2 = 0;
21689
- for (const _ of text) if (++count2 > max) return true;
22456
+ for (const _ of text2) if (++count2 > max) return true;
21690
22457
  return false;
21691
22458
  }
21692
22459
  function endsWithLoneEscape(pattern) {
@@ -21706,7 +22473,7 @@ function readFilter(value, path, auth) {
21706
22473
  `${path}.operator`,
21707
22474
  `must be one of ${FILTER_OPERATORS.join(", ")}`
21708
22475
  );
21709
- const column = readName(own(object2, "column"), `${path}.column`);
22476
+ const column2 = readName(own(object2, "column"), `${path}.column`);
21710
22477
  if (!Object.hasOwn(object2, "value")) invalid(`${path}.value`, "is required");
21711
22478
  const raw = own(object2, "value");
21712
22479
  const valuePath = `${path}.value`;
@@ -21716,12 +22483,12 @@ function readFilter(value, path, auth) {
21716
22483
  const values = raw.map(
21717
22484
  (item, index) => readValue2(item, `${valuePath}[${index}]`, auth)
21718
22485
  );
21719
- return { operator, column, value: values };
22486
+ return { operator, column: column2, value: values };
21720
22487
  }
21721
22488
  case "is":
21722
22489
  if (raw !== null && raw !== true && raw !== false)
21723
22490
  invalid(valuePath, "must be null, true or false");
21724
- return { operator, column, value: raw };
22491
+ return { operator, column: column2, value: raw };
21725
22492
  case "like":
21726
22493
  case "ilike": {
21727
22494
  if (typeof raw !== "string") invalid(valuePath, "must be a string");
@@ -21731,7 +22498,7 @@ function readFilter(value, path, auth) {
21731
22498
  invalid(valuePath, "must not contain a NUL character");
21732
22499
  if (endsWithLoneEscape(raw))
21733
22500
  invalid(valuePath, "must not end with an unescaped backslash");
21734
- return { operator, column, value: raw };
22501
+ return { operator, column: column2, value: raw };
21735
22502
  }
21736
22503
  case "contains":
21737
22504
  case "containedBy":
@@ -21745,13 +22512,13 @@ function readFilter(value, path, auth) {
21745
22512
  );
21746
22513
  return {
21747
22514
  operator,
21748
- column,
22515
+ column: column2,
21749
22516
  value: raw.map(
21750
22517
  (item) => isAuthUid(item) ? { auth: "uid" } : item
21751
22518
  )
21752
22519
  };
21753
22520
  default:
21754
- return { operator, column, value: readValue2(raw, valuePath, auth) };
22521
+ return { operator, column: column2, value: readValue2(raw, valuePath, auth) };
21755
22522
  }
21756
22523
  }
21757
22524
  var NODE_KEYS = ["operator", "or", "and", "not"];
@@ -21843,8 +22610,8 @@ function parseCondition(action, value) {
21843
22610
  }
21844
22611
  function conditionsTooLarge(texts) {
21845
22612
  let total = 0;
21846
- for (const text of texts)
21847
- if (typeof text === "string") total += utf8ByteLength(text);
22613
+ for (const text2 of texts)
22614
+ if (typeof text2 === "string") total += utf8ByteLength(text2);
21848
22615
  return total > MAX_CONDITION_BYTES;
21849
22616
  }
21850
22617
  function isInvalidCondition(value) {
@@ -21904,8 +22671,8 @@ var LEGACY_ROW_COLUMNS = [
21904
22671
  ];
21905
22672
  function legacyRowAsRead(row) {
21906
22673
  const values = [];
21907
- for (const column of LEGACY_ROW_COLUMNS) {
21908
- const value = row[column];
22674
+ for (const column2 of LEGACY_ROW_COLUMNS) {
22675
+ const value = row[column2];
21909
22676
  if (value === null || value === void 0) values.push(null);
21910
22677
  else if (typeof value === "string") values.push(value);
21911
22678
  else return void 0;
@@ -21930,14 +22697,14 @@ var POLICY_SQL = {
21930
22697
  */
21931
22698
  insertConverted: `INSERT OR IGNORE INTO _naro_policies_v2 (table_name, name, kind, command, roles, using_expr, check_expr, owner_column) SELECT ?, ?, ?, ?, ?, ?, ?, ? WHERE EXISTS (SELECT 1 FROM _naro_policies WHERE ${ROW_AS_READ})`
21932
22699
  };
21933
- function readStoredCondition(action, text, tooLarge) {
21934
- if (text === void 0 || text === null) return "is missing";
21935
- if (typeof text !== "string") return "is not JSON";
22700
+ function readStoredCondition(action, text2, tooLarge) {
22701
+ if (text2 === void 0 || text2 === null) return "is missing";
22702
+ if (typeof text2 !== "string") return "is not JSON";
21936
22703
  if (tooLarge)
21937
22704
  return `the conditions of the policy are larger than ${String(MAX_CONDITION_BYTES)} bytes`;
21938
22705
  let value;
21939
22706
  try {
21940
- value = JSON.parse(text);
22707
+ value = JSON.parse(text2);
21941
22708
  } catch {
21942
22709
  return "is not JSON";
21943
22710
  }
@@ -21952,7 +22719,7 @@ function policyFromRow(row, log2) {
21952
22719
  (action) => row[`${action}_rule`] === CONDITION_RULE ? row[`${action}_condition`] : void 0
21953
22720
  );
21954
22721
  const tooLarge = conditionsTooLarge(
21955
- texts.filter((text) => typeof text === "string")
22722
+ texts.filter((text2) => typeof text2 === "string")
21956
22723
  );
21957
22724
  const read = POLICY_ACTIONS2.map(
21958
22725
  (action, index) => {
@@ -22042,7 +22809,7 @@ function filterNodeToExpr(node, negated = false) {
22042
22809
  return negated ? { kind: "or", args: [leaf, uidIsNull(false)] } : { kind: "and", args: [leaf, uidIsNull(true)] };
22043
22810
  }
22044
22811
  function leafToExpr(node) {
22045
- const column = {
22812
+ const column2 = {
22046
22813
  kind: "column",
22047
22814
  qualifier: null,
22048
22815
  name: node.column
@@ -22058,7 +22825,7 @@ function leafToExpr(node) {
22058
22825
  return {
22059
22826
  kind: "compare",
22060
22827
  op: COMPARISONS[node.operator],
22061
- left: column,
22828
+ left: column2,
22062
22829
  right: valueExpr(Array.isArray(value) ? null : value)
22063
22830
  };
22064
22831
  case "in": {
@@ -22067,7 +22834,7 @@ function leafToExpr(node) {
22067
22834
  return {
22068
22835
  kind: "in",
22069
22836
  not: false,
22070
- operand: column,
22837
+ operand: column2,
22071
22838
  list: list.map(valueExpr)
22072
22839
  };
22073
22840
  }
@@ -22075,7 +22842,7 @@ function leafToExpr(node) {
22075
22842
  return {
22076
22843
  kind: "is",
22077
22844
  not: false,
22078
- operand: column,
22845
+ operand: column2,
22079
22846
  value: value === true || value === false ? value : null
22080
22847
  };
22081
22848
  case "like":
@@ -22084,7 +22851,7 @@ function leafToExpr(node) {
22084
22851
  kind: "like",
22085
22852
  not: false,
22086
22853
  ci: node.operator === "ilike",
22087
- operand: column,
22854
+ operand: column2,
22088
22855
  pattern: typeof value === "string" ? value : ""
22089
22856
  };
22090
22857
  case "contains":
@@ -22123,12 +22890,12 @@ function namedFromRule(table, action, rule, ownerColumn) {
22123
22890
  check: action === "insert" ? TRUE_TEXT : null
22124
22891
  };
22125
22892
  if (rule === "owner") {
22126
- const column = ownerColumn ?? "";
22893
+ const column2 = ownerColumn ?? "";
22127
22894
  return {
22128
22895
  ...base2,
22129
22896
  roles: ["authenticated"],
22130
- ...column === "" ? { using: null, check: null } : ownerClauses(column, action),
22131
- owner: column
22897
+ ...column2 === "" ? { using: null, check: null } : ownerClauses(column2, action),
22898
+ owner: column2
22132
22899
  };
22133
22900
  }
22134
22901
  if (isInvalidCondition(rule) || (action === "insert" ? rule.check : rule.using) === void 0)
@@ -22166,7 +22933,7 @@ function loadNamed(policies, log2) {
22166
22933
  const values = policyValues(policy);
22167
22934
  const loaded = loadPolicy(
22168
22935
  Object.fromEntries(
22169
- columns.map((column, index) => [column, values[index]])
22936
+ columns.map((column2, index) => [column2, values[index]])
22170
22937
  ),
22171
22938
  log2
22172
22939
  );
@@ -22190,64 +22957,2112 @@ function unreadableConversions(policy) {
22190
22957
  return found;
22191
22958
  }
22192
22959
 
22193
- // ../../packages/data-core/src/policy-index.ts
22194
- var INDEX_SQL = `SELECT t.name AS table_name, il.name AS index_name, il."partial" AS partial, ix.name AS column_name, ix.coll AS collation FROM pragma_table_list AS t JOIN pragma_index_list(t.name, t.schema) AS il JOIN pragma_index_xinfo(il.name, t.schema) AS ix WHERE t.schema = 'main' AND t.type = 'table' AND ${systemTablePredicate("t.name")} AND ix.seqno = 0`;
22195
-
22196
- // ../../packages/data-core/src/policy-statement.ts
22197
- function readPolicyName(stream) {
22198
- const token = stream.peek();
22199
- if (token.kind !== "ident")
22200
- throw new PolicySyntaxError(
22201
- `expected a policy name, found ${describeToken(token)}`,
22202
- token.position
22203
- );
22204
- stream.next();
22205
- return foldPolicyName(token.value, token.quoted);
22960
+ // ../../packages/data-core/src/rls-compile.ts
22961
+ var MAX_INLINE_DEPTH = 4;
22962
+ var MAX_STATEMENT_SUBQUERIES = 16;
22963
+ var MAX_POLICY_VALUES = 50;
22964
+ var MAX_POLICY_STATEMENT_BYTES = 5e4;
22965
+ function policyInvalid(policy, action) {
22966
+ return policy.owner !== null ? dataError("POLICY_INVALID", { action, table: policy.table }) : dataError("POLICY_INVALID", {
22967
+ action,
22968
+ table: policy.table,
22969
+ policy: policy.name
22970
+ });
22206
22971
  }
22207
- function readRoleName(token) {
22208
- if (token.kind !== "ident")
22209
- throw new PolicySyntaxError(
22210
- `expected a role, found ${describeToken(token)}`,
22211
- token.position
22212
- );
22213
- return token.quoted ? token.value : asciiLower(token.value);
22972
+ function newState2() {
22973
+ return { aliases: 0, subqueries: 0, depth: 0, active: [] };
22214
22974
  }
22215
- function readCommand(stream) {
22216
- const token = stream.next();
22217
- for (const command of [
22218
- "all",
22219
- "select",
22220
- "insert",
22221
- "update",
22222
- "delete"
22223
- ])
22224
- if (isKeyword(token, command)) return command;
22225
- throw new PolicySyntaxError(
22226
- `expected ALL, SELECT, INSERT, UPDATE or DELETE, found ${describeToken(token)}`,
22227
- token.position
22228
- );
22975
+ function bind(env, value) {
22976
+ env.params.push(value);
22977
+ return "?";
22229
22978
  }
22230
- function readClause(stream) {
22231
- const open = stream.expectOperator("(");
22232
- const expr = readCondition(stream);
22233
- stream.expectOperator(")");
22234
- checkExpressionSize(expr, open.position);
22235
- return { text: printExpr(expr), position: open.position };
22979
+ function blamed(env, reason) {
22980
+ return fail(policyInvalid(env.blame.policy, env.blame.action), reason);
22236
22981
  }
22237
- function readCreate(stream, start) {
22238
- stream.expectKeyword("policy");
22239
- const name = readPolicyName(stream);
22240
- stream.expectKeyword("on");
22241
- const table = readTableName(stream);
22242
- let permissive = true;
22243
- if (stream.acceptKeyword("as")) {
22244
- const token = stream.next();
22245
- if (isKeyword(token, "restrictive")) permissive = false;
22246
- else if (!isKeyword(token, "permissive"))
22247
- throw new PolicySyntaxError(
22248
- `expected PERMISSIVE or RESTRICTIVE, found ${describeToken(token)}`,
22249
- token.position
22250
- );
22982
+ function storedSource(table, names, alias) {
22983
+ return {
22984
+ table,
22985
+ names,
22986
+ read: (column2, _env, how) => alias !== null ? `${quoteIdentifier(alias)}.${quoteIdentifier(column2.name)}` : how.qualified ? `${quoteIdentifier(table.name)}.${quoteIdentifier(column2.name)}` : quoteIdentifier(column2.name),
22987
+ operand: (column2) => ({
22988
+ stable: true,
22989
+ newColumnish: true,
22990
+ newCollation: alias === null ? column2.collation : null
22991
+ })
22992
+ };
22993
+ }
22994
+ function missingColumnReason(name, tables) {
22995
+ return `column ${name} does not exist on ${tables.join(" or ")}`;
22996
+ }
22997
+ function missingRelationReason(table) {
22998
+ return `relation ${table} does not exist (a system table cannot be read either)`;
22999
+ }
23000
+ function resolveColumn(env, ref) {
23001
+ const name = asciiLower(ref.name);
23002
+ if (ref.qualifier !== null) {
23003
+ const qualifier = asciiLower(ref.qualifier);
23004
+ for (let index = env.scopes.length - 1; index >= 0; index--) {
23005
+ const source = env.scopes[index];
23006
+ if (!source?.names.includes(qualifier)) continue;
23007
+ const column2 = source.table.byName.get(name);
23008
+ if (!column2)
23009
+ return blamed(
23010
+ env,
23011
+ `column ${ref.qualifier}.${ref.name} does not exist: ${source.table.name} has no column ${ref.name}`
23012
+ );
23013
+ return { source, column: column2 };
23014
+ }
23015
+ return blamed(
23016
+ env,
23017
+ `${ref.qualifier}.${ref.name} names no table here: use the policy's table or a subquery's table or alias`
23018
+ );
23019
+ }
23020
+ for (let index = env.scopes.length - 1; index >= 0; index--) {
23021
+ const source = env.scopes[index];
23022
+ const column2 = source?.table.byName.get(name);
23023
+ if (!source || !column2) continue;
23024
+ if (env.scopes.length > 1) return blamed(env, unqualifiedProblem(ref));
23025
+ return { source, column: column2 };
23026
+ }
23027
+ return blamed(
23028
+ env,
23029
+ missingColumnReason(
23030
+ ref.name,
23031
+ env.scopes.map((source) => source.table.name)
23032
+ )
23033
+ );
23034
+ }
23035
+ function jwtValue(env, expr) {
23036
+ const value = env.req.claims[expr.claim];
23037
+ if (value === null) return "NULL";
23038
+ if (expr.op === "->") return bind(env, JSON.stringify(value));
23039
+ return bind(env, typeof value === "boolean" ? String(value) : value);
23040
+ }
23041
+ function valueSql(env, expr, textual = null) {
23042
+ switch (expr.kind) {
23043
+ case "literal":
23044
+ if (expr.value === null) return "NULL";
23045
+ if (typeof expr.value === "boolean") return expr.value ? "1" : "0";
23046
+ return bind(env, expr.value);
23047
+ case "column": {
23048
+ const { source, column: column2 } = resolveColumn(env, expr);
23049
+ return source.read(column2, env, {
23050
+ qualified: env.scopes.length > 1,
23051
+ textual
23052
+ });
23053
+ }
23054
+ case "auth":
23055
+ if (expr.fn === "role") return bind(env, env.req.role);
23056
+ return env.req.uid === null ? "NULL" : bind(env, env.req.uid);
23057
+ case "jwt":
23058
+ return jwtValue(env, expr);
23059
+ case "func": {
23060
+ if (expr.name === "now") return "CURRENT_TIMESTAMP";
23061
+ if (expr.name === "coalesce") {
23062
+ const args = expr.args.map((arg2) => valueSql(env, arg2, textual));
23063
+ return args.length === 1 ? `(${String(args[0])})` : `coalesce(${args.join(", ")})`;
23064
+ }
23065
+ const [arg] = expr.args;
23066
+ if (!arg) return blamed(env, `${expr.name}() needs an argument`);
23067
+ return `${expr.name}(${valueSql(env, arg, "textual")})`;
23068
+ }
23069
+ case "cast":
23070
+ return `CAST(${valueSql(env, expr.operand, "textual")} AS TEXT)`;
23071
+ }
23072
+ }
23073
+ function operandSide(env, expr) {
23074
+ if (expr.kind === "cast") return operandSide(env, expr.operand);
23075
+ if (expr.kind !== "column")
23076
+ return {
23077
+ stored: null,
23078
+ stable: true,
23079
+ newColumnish: false,
23080
+ newCollation: null
23081
+ };
23082
+ const { source, column: column2 } = resolveColumn(env, expr);
23083
+ const operand = source.operand(column2);
23084
+ return {
23085
+ stored: column2,
23086
+ stable: operand.stable,
23087
+ newColumnish: operand.newColumnish,
23088
+ newCollation: operand.newCollation
23089
+ };
23090
+ }
23091
+ function checkCollation(env, left, right) {
23092
+ if (!env.check) return "";
23093
+ const sides = right === null ? [left] : [left, right];
23094
+ const stored = sides.find((side) => side.stored !== null);
23095
+ const fresh = sides.find((side) => side.newColumnish);
23096
+ if (stored && stored === fresh && stored.stable) return "";
23097
+ if (!stored && !fresh) return "";
23098
+ const want = stored?.stored ? stored.stored.collation ?? env.check.problem(stored.stored.name, "collation", env.blame.policy) : "BINARY";
23099
+ const have = fresh ? fresh.newCollation : "BINARY";
23100
+ return have === want ? "" : ` COLLATE ${want}`;
23101
+ }
23102
+ var ARRAY_OPERATORS3 = {
23103
+ "@>": "contains",
23104
+ "<@": "containedBy",
23105
+ "&&": "overlaps"
23106
+ };
23107
+ function comparison(env, left, op, right) {
23108
+ const l = valueSql(env, left);
23109
+ const collate = env.check ? checkCollation(env, operandSide(env, left), operandSide(env, right)) : "";
23110
+ const r = valueSql(env, right);
23111
+ return `(${l}${collate} ${op} ${r})`;
23112
+ }
23113
+ function knownNullTest(env, expr) {
23114
+ if (expr.value !== null) return void 0;
23115
+ const { operand } = expr;
23116
+ let value;
23117
+ if (operand.kind === "auth" && operand.fn === "uid") value = env.req.uid;
23118
+ else if (operand.kind === "jwt") {
23119
+ const claim = env.req.claims[operand.claim];
23120
+ value = claim === null ? null : String(claim);
23121
+ } else return void 0;
23122
+ return value === null !== expr.not ? "1" : "0";
23123
+ }
23124
+ function conditionSql(env, expr) {
23125
+ if (isValueExpr(expr)) {
23126
+ if (expr.kind === "literal")
23127
+ return expr.value === null ? "NULL" : expr.value === true ? "1" : "0";
23128
+ if (expr.kind === "column") return `(${valueSql(env, expr)})`;
23129
+ return blamed(env, "a value alone is not a condition");
23130
+ }
23131
+ switch (expr.kind) {
23132
+ case "and":
23133
+ case "or":
23134
+ return `(${expr.args.map((arg) => conditionSql(env, arg)).join(expr.kind === "and" ? " AND " : " OR ")})`;
23135
+ case "not":
23136
+ return `(NOT ${conditionSql(env, expr.arg)})`;
23137
+ default:
23138
+ break;
23139
+ }
23140
+ const mismatched = booleanComparisonProblem(expr);
23141
+ if (mismatched !== void 0) return blamed(env, mismatched);
23142
+ switch (expr.kind) {
23143
+ case "compare":
23144
+ return comparison(env, expr.left, expr.op, expr.right);
23145
+ case "distinct":
23146
+ return comparison(env, expr.left, expr.not ? "IS" : "IS NOT", expr.right);
23147
+ case "is": {
23148
+ const untestable = expr.value === null ? void 0 : truthTestProblem(expr.operand);
23149
+ if (untestable !== void 0) return blamed(env, untestable);
23150
+ const known = knownNullTest(env, expr);
23151
+ if (known !== void 0) return known;
23152
+ const target = valueSql(env, expr.operand);
23153
+ if (expr.value === null)
23154
+ return `(${target} IS ${expr.not ? "NOT " : ""}NULL)`;
23155
+ const test = isBoolean(target, expr.value);
23156
+ return expr.not ? `(NOT (${test}))` : `(${test})`;
23157
+ }
23158
+ case "in": {
23159
+ const left = valueSql(env, expr.operand);
23160
+ const collate = env.check ? checkCollation(env, operandSide(env, expr.operand), null) : "";
23161
+ const list = expr.list.map((item) => valueSql(env, item));
23162
+ return `(${left}${collate} ${expr.not ? "NOT " : ""}IN (${list.join(", ")}))`;
23163
+ }
23164
+ case "inSelect": {
23165
+ const left = valueSql(env, expr.operand);
23166
+ const leftSide = env.check ? operandSide(env, expr.operand) : null;
23167
+ const sub = subquerySql(env, expr.subquery, "value");
23168
+ const collate = leftSide && sub.item ? checkCollation(env, leftSide, sub.item) : "";
23169
+ return `(${left}${collate} ${expr.not ? "NOT " : ""}IN (${sub.sql}))`;
23170
+ }
23171
+ case "like": {
23172
+ const problem = policyPatternProblem(expr.pattern, expr.ci);
23173
+ if (problem !== void 0) return blamed(env, problem);
23174
+ const target = valueSql(env, expr.operand, "pattern");
23175
+ const not = expr.not ? "NOT " : "";
23176
+ const pattern = bind(env, boundPattern(expr.pattern, expr.ci));
23177
+ return expr.ci ? `(${target} ${not}LIKE ${pattern} ESCAPE '\\')` : `(${target} ${not}GLOB ${pattern})`;
23178
+ }
23179
+ case "between": {
23180
+ if (env.check) {
23181
+ const low2 = comparison(env, expr.operand, ">=", expr.low);
23182
+ const high2 = comparison(env, expr.operand, "<=", expr.high);
23183
+ const both = `(${low2} AND ${high2})`;
23184
+ return expr.not ? `(NOT ${both})` : both;
23185
+ }
23186
+ const target = valueSql(env, expr.operand);
23187
+ const low = valueSql(env, expr.low);
23188
+ const high = valueSql(env, expr.high);
23189
+ return `(${target} ${expr.not ? "NOT " : ""}BETWEEN ${low} AND ${high})`;
23190
+ }
23191
+ case "array": {
23192
+ const values = distinctArrayValues(
23193
+ expr.values.flatMap(
23194
+ (value) => typeof value !== "object" ? [value] : env.req.uid === null ? [] : [env.req.uid]
23195
+ )
23196
+ );
23197
+ const nullElement = expr.values.some(
23198
+ (value) => typeof value === "object" && env.req.uid === null
23199
+ );
23200
+ let next = 0;
23201
+ return arraySql(
23202
+ () => (() => {
23203
+ const { source, column: column2 } = resolveColumn(env, expr.operand);
23204
+ return source.read(column2, env, { qualified: true, textual: null });
23205
+ })(),
23206
+ ARRAY_OPERATORS3[expr.op],
23207
+ values.length,
23208
+ () => bind(env, values[next++] ?? null),
23209
+ nullElement
23210
+ );
23211
+ }
23212
+ case "exists":
23213
+ return `(EXISTS (${subquerySql(env, expr.subquery, "one").sql}))`;
23214
+ }
23215
+ }
23216
+ function subquerySql(env, subquery, select) {
23217
+ const table = resolveTable(env.snapshot, subquery.table);
23218
+ if (!table) return blamed(env, missingRelationReason(subquery.table));
23219
+ const { state } = env;
23220
+ if (++state.subqueries > MAX_STATEMENT_SUBQUERIES)
23221
+ return blamed(
23222
+ env,
23223
+ `the policies read more than ${String(MAX_STATEMENT_SUBQUERIES)} subqueries in one statement, counting the policies of the tables they read`
23224
+ );
23225
+ if (state.depth + 1 > MAX_INLINE_DEPTH)
23226
+ return blamed(
23227
+ env,
23228
+ `subqueries nest more than ${String(MAX_INLINE_DEPTH)} deep, counting the policies of the tables they read`
23229
+ );
23230
+ state.depth++;
23231
+ try {
23232
+ const alias = `_naro_s${String(++state.aliases)}`;
23233
+ const source = storedSource(
23234
+ table,
23235
+ [asciiLower(subquery.alias ?? table.name)],
23236
+ alias
23237
+ );
23238
+ const inner = { ...env, scopes: [...env.scopes, source] };
23239
+ let item = null;
23240
+ let selected = "1";
23241
+ if (select === "value") {
23242
+ if (subquery.select === "*")
23243
+ return blamed(env, "IN (SELECT \u2026) selects one value, not *");
23244
+ selected = valueSql(inner, subquery.select);
23245
+ if (env.check) item = operandSide(inner, subquery.select);
23246
+ }
23247
+ const own2 = storedSource(table, [asciiLower(table.name)], alias);
23248
+ const rls = inlinedPolicies(env, table, own2);
23249
+ const where = subquery.where === null ? "" : ` AND ${conditionSql(inner, subquery.where)}`;
23250
+ return {
23251
+ sql: `SELECT ${selected} FROM ${quoteIdentifier(table.name)} AS ${quoteIdentifier(alias)} WHERE ${rls}${where}`,
23252
+ item
23253
+ };
23254
+ } finally {
23255
+ state.depth--;
23256
+ }
23257
+ }
23258
+ function hasSubqueries(policies) {
23259
+ return policies.some(
23260
+ (policy) => [policy.usingExpr, policy.checkExpr].some(
23261
+ (expr) => expr !== null && subqueriesOf(expr).length > 0
23262
+ )
23263
+ );
23264
+ }
23265
+ function withTableActive(state, table, policies, compile2) {
23266
+ if (!hasSubqueries(policies)) return compile2();
23267
+ const key = asciiLower(table);
23268
+ if (state.active.includes(key))
23269
+ fail(
23270
+ dataError("POLICY_INVALID", { action: "select", table, recursion: true }),
23271
+ `a policy's subquery reads ${table}, whose own policies are still being expanded`
23272
+ );
23273
+ state.active.push(key);
23274
+ try {
23275
+ return compile2();
23276
+ } finally {
23277
+ state.active.pop();
23278
+ }
23279
+ }
23280
+ function groupTerms(env, sources, action, permissive, restrictive, pick2) {
23281
+ const compileOne = (policy, expr) => {
23282
+ const params = [];
23283
+ const sql = conditionSql(
23284
+ { ...env, scopes: sources, params, blame: { policy, action } },
23285
+ expr
23286
+ );
23287
+ const [source] = sources;
23288
+ const problem = source === void 0 ? void 0 : textTruthProblem(env.snapshot, source.table, expr);
23289
+ if (problem !== void 0) fail(policyInvalid(policy, action), problem);
23290
+ return { sql, params };
23291
+ };
23292
+ const terms = [];
23293
+ const seen = /* @__PURE__ */ new Set();
23294
+ for (const policy of permissive) {
23295
+ const expr = pick2(policy);
23296
+ if (expr === null) continue;
23297
+ const term = compileOne(policy, expr);
23298
+ const key = JSON.stringify([term.sql, term.params]);
23299
+ if (seen.has(key)) continue;
23300
+ seen.add(key);
23301
+ terms.push(term);
23302
+ }
23303
+ const group = terms.length === 0 ? { sql: "0", params: [] } : terms.length === 1 ? terms[0] : {
23304
+ sql: `(${terms.map((term) => term.sql).join(" OR ")})`,
23305
+ params: terms.flatMap((term) => term.params)
23306
+ };
23307
+ const restricted = restrictive.flatMap((policy) => {
23308
+ const expr = pick2(policy);
23309
+ return expr === null ? [] : [{ policy, term: compileOne(policy, expr) }];
23310
+ });
23311
+ return { permissive: group, restrictive: restricted };
23312
+ }
23313
+ function inlinedPolicies(env, table, source) {
23314
+ const found = applicablePolicies(
23315
+ env.req.policies,
23316
+ table.name,
23317
+ "select",
23318
+ env.req.role
23319
+ );
23320
+ if (found.invalid)
23321
+ fail(
23322
+ policyInvalid(found.invalid, "select"),
23323
+ found.invalid.invalid ?? void 0
23324
+ );
23325
+ if (found.permissive.length === 0) return "0";
23326
+ return withTableActive(
23327
+ env.state,
23328
+ table.name,
23329
+ [...found.permissive, ...found.restrictive],
23330
+ () => {
23331
+ const inner = { ...env, check: null, params: [] };
23332
+ const terms = groupTerms(
23333
+ inner,
23334
+ [source],
23335
+ "select",
23336
+ found.permissive,
23337
+ found.restrictive,
23338
+ (policy) => policy.usingExpr
23339
+ );
23340
+ const parts = uniqueTerms([
23341
+ terms.permissive,
23342
+ ...terms.restrictive.map((entry) => entry.term)
23343
+ ]);
23344
+ for (const part of parts) env.params.push(...part.params);
23345
+ const [only] = parts;
23346
+ if (parts.length === 1 && only) return only.sql;
23347
+ return `(${parts.map((part) => part.sql).join(" AND ")})`;
23348
+ }
23349
+ );
23350
+ }
23351
+ function uniqueTerms(terms) {
23352
+ const seen = /* @__PURE__ */ new Set();
23353
+ return terms.filter((term) => {
23354
+ const key = JSON.stringify([term.sql, term.params]);
23355
+ if (seen.has(key)) return false;
23356
+ seen.add(key);
23357
+ return true;
23358
+ });
23359
+ }
23360
+ function outerColumns(table, expr) {
23361
+ if (subqueriesOf(expr).length > 0) return void 0;
23362
+ const found = [];
23363
+ let missing = false;
23364
+ const visitValue = (value) => {
23365
+ switch (value.kind) {
23366
+ case "column": {
23367
+ const column2 = table.byName.get(asciiLower(value.name));
23368
+ if (!column2) missing = true;
23369
+ else found.push(column2);
23370
+ return;
23371
+ }
23372
+ case "func":
23373
+ value.args.forEach(visitValue);
23374
+ return;
23375
+ case "cast":
23376
+ visitValue(value.operand);
23377
+ return;
23378
+ default:
23379
+ return;
23380
+ }
23381
+ };
23382
+ const visit = (node) => {
23383
+ if (isValueExpr(node)) {
23384
+ visitValue(node);
23385
+ return;
23386
+ }
23387
+ switch (node.kind) {
23388
+ case "and":
23389
+ case "or":
23390
+ node.args.forEach(visit);
23391
+ return;
23392
+ case "not":
23393
+ visit(node.arg);
23394
+ return;
23395
+ case "compare":
23396
+ case "distinct":
23397
+ visitValue(node.left);
23398
+ visitValue(node.right);
23399
+ return;
23400
+ case "is":
23401
+ case "like":
23402
+ visitValue(node.operand);
23403
+ return;
23404
+ case "in":
23405
+ visitValue(node.operand);
23406
+ node.list.forEach(visitValue);
23407
+ return;
23408
+ case "between":
23409
+ [node.operand, node.low, node.high].forEach(visitValue);
23410
+ return;
23411
+ case "array":
23412
+ visitValue(node.operand);
23413
+ return;
23414
+ default:
23415
+ missing = true;
23416
+ }
23417
+ };
23418
+ visit(expr);
23419
+ return missing ? void 0 : found;
23420
+ }
23421
+ function resolveRef2(ref, scopes) {
23422
+ const name = asciiLower(ref.name);
23423
+ const qualifier = ref.qualifier === null ? null : asciiLower(ref.qualifier);
23424
+ for (const scope of scopes.toReversed()) {
23425
+ if (qualifier !== null) {
23426
+ if (!scope.names.includes(qualifier)) continue;
23427
+ const column3 = scope.table.byName.get(name);
23428
+ return column3 ? { table: scope.table, column: column3 } : void 0;
23429
+ }
23430
+ const column2 = scope.table.byName.get(name);
23431
+ if (column2) return { table: scope.table, column: column2 };
23432
+ }
23433
+ return void 0;
23434
+ }
23435
+ function scopedValues(operand, scopes) {
23436
+ return operandValues(operand).map((value) => ({ value, scopes }));
23437
+ }
23438
+ function subqueryScopes(snapshot, subquery, scopes) {
23439
+ const inner = resolveTable(snapshot, subquery.table);
23440
+ return inner ? [
23441
+ ...scopes,
23442
+ { names: [asciiLower(subquery.alias ?? inner.name)], table: inner }
23443
+ ] : void 0;
23444
+ }
23445
+ function columnsReadOn(snapshot, table, expr, written) {
23446
+ const found = [];
23447
+ const visitValue = (value, scopes) => {
23448
+ switch (value.kind) {
23449
+ case "column": {
23450
+ const resolved = resolveRef2(value, scopes);
23451
+ if (resolved?.table === written) found.push(resolved.column);
23452
+ return;
23453
+ }
23454
+ case "func":
23455
+ for (const arg of value.args) visitValue(arg, scopes);
23456
+ return;
23457
+ case "cast":
23458
+ visitValue(value.operand, scopes);
23459
+ return;
23460
+ default:
23461
+ return;
23462
+ }
23463
+ };
23464
+ const visitSubquery = (subquery, scopes) => {
23465
+ const within = subqueryScopes(snapshot, subquery, scopes);
23466
+ if (!within) return;
23467
+ if (subquery.select !== "*") visitValue(subquery.select, within);
23468
+ if (subquery.where) visit(subquery.where, within);
23469
+ };
23470
+ const visit = (node, scopes) => {
23471
+ if (isValueExpr(node)) {
23472
+ visitValue(node, scopes);
23473
+ return;
23474
+ }
23475
+ switch (node.kind) {
23476
+ case "and":
23477
+ case "or":
23478
+ for (const arg of node.args) visit(arg, scopes);
23479
+ return;
23480
+ case "not":
23481
+ visit(node.arg, scopes);
23482
+ return;
23483
+ case "compare":
23484
+ case "distinct":
23485
+ visitValue(node.left, scopes);
23486
+ visitValue(node.right, scopes);
23487
+ return;
23488
+ case "is":
23489
+ case "like":
23490
+ case "array":
23491
+ visitValue(node.operand, scopes);
23492
+ return;
23493
+ case "in":
23494
+ visitValue(node.operand, scopes);
23495
+ for (const item of node.list) visitValue(item, scopes);
23496
+ return;
23497
+ case "between":
23498
+ for (const item of [node.operand, node.low, node.high])
23499
+ visitValue(item, scopes);
23500
+ return;
23501
+ case "exists":
23502
+ visitSubquery(node.subquery, scopes);
23503
+ return;
23504
+ case "inSelect":
23505
+ visitValue(node.operand, scopes);
23506
+ visitSubquery(node.subquery, scopes);
23507
+ return;
23508
+ }
23509
+ };
23510
+ visit(expr, [{ names: [asciiLower(table.name)], table }]);
23511
+ return found;
23512
+ }
23513
+ function truthColumns(snapshot, table, expr) {
23514
+ return comparedColumns(snapshot, table, expr, isBooleanConstant, true);
23515
+ }
23516
+ function numberComparedColumns(snapshot, table, expr) {
23517
+ return comparedColumns(
23518
+ snapshot,
23519
+ table,
23520
+ expr,
23521
+ (value) => value.kind === "literal" && typeof value.value === "number",
23522
+ false
23523
+ );
23524
+ }
23525
+ function comparedColumns(snapshot, table, expr, constant, truth) {
23526
+ const found = [];
23527
+ const read = (ref, scopes) => {
23528
+ const resolved = resolveRef2(ref, scopes);
23529
+ if (resolved) found.push({ written: printExpr(ref), ...resolved });
23530
+ };
23531
+ const compared = (node, scopes) => {
23532
+ for (const { left, right, inner } of comparedPairs(node)) {
23533
+ const within = inner && node.kind === "inSelect" ? subqueryScopes(snapshot, node.subquery, scopes) : scopes;
23534
+ const met = [
23535
+ ...scopedValues(left, scopes),
23536
+ ...within ? scopedValues(right, within) : []
23537
+ ];
23538
+ if (!met.some(({ value }) => constant(value))) continue;
23539
+ for (const { value, scopes: where } of met)
23540
+ if (value.kind === "column") read(value, where);
23541
+ }
23542
+ };
23543
+ const visit = (node, scopes) => {
23544
+ if (isValueExpr(node)) {
23545
+ if (truth && node.kind === "column") read(node, scopes);
23546
+ return;
23547
+ }
23548
+ compared(node, scopes);
23549
+ switch (node.kind) {
23550
+ case "and":
23551
+ case "or":
23552
+ for (const arg of node.args) visit(arg, scopes);
23553
+ return;
23554
+ case "not":
23555
+ visit(node.arg, scopes);
23556
+ return;
23557
+ case "is":
23558
+ if (truth && node.value !== null && node.operand.kind === "column")
23559
+ read(node.operand, scopes);
23560
+ return;
23561
+ case "exists":
23562
+ case "inSelect": {
23563
+ const within = subqueryScopes(snapshot, node.subquery, scopes);
23564
+ if (within && node.subquery.where) visit(node.subquery.where, within);
23565
+ return;
23566
+ }
23567
+ default:
23568
+ return;
23569
+ }
23570
+ };
23571
+ visit(expr, [{ names: [asciiLower(table.name)], table }]);
23572
+ return found;
23573
+ }
23574
+ function textTruthProblem(snapshot, table, expr) {
23575
+ for (const { written, column: column2 } of truthColumns(snapshot, table, expr))
23576
+ if (!isNumeric(column2))
23577
+ return `${written} is a ${columnAffinity(column2)} column, whose truth value is not what its text says ('true' reads as false): compare it, ${written} = 'true'`;
23578
+ return void 0;
23579
+ }
23580
+ function selectCheckRefusal(path, column2, problem) {
23581
+ const reason = {
23582
+ generated: `is not available: the select policy reads the generated column ${column2}, which a new row cannot be checked against`,
23583
+ collation: `is not available: the select policy reads ${column2}, whose collation naro cannot read, so a new row cannot be checked against it`,
23584
+ pattern: `is not available: the select policy compares the number column ${column2} with like or ilike, which a new row cannot be checked against`,
23585
+ textual: `is not available: the select policy turns the number column ${column2} into text, which a new row cannot be checked against`
23586
+ }[problem];
23587
+ return invalidQuery(path, reason);
23588
+ }
23589
+
23590
+ // ../../packages/data-core/src/policy.ts
23591
+ function commandsOf(query) {
23592
+ switch (query.action) {
23593
+ case "select":
23594
+ return ["select"];
23595
+ case "insert":
23596
+ return query.returning === void 0 ? ["insert"] : ["insert", "select"];
23597
+ default:
23598
+ return [query.action, "select"];
23599
+ }
23600
+ }
23601
+ var ANON_CLAIMS = {
23602
+ sub: null,
23603
+ role: "anon",
23604
+ email: null,
23605
+ email_verified: null
23606
+ };
23607
+ function rlsIdentity(context) {
23608
+ if (context.role !== "authenticated" || context.userId === "")
23609
+ return { role: "anon", uid: null, claims: ANON_CLAIMS };
23610
+ const verified = context.emailVerified === true;
23611
+ return {
23612
+ role: "authenticated",
23613
+ uid: context.userId,
23614
+ claims: {
23615
+ sub: context.userId,
23616
+ role: "authenticated",
23617
+ email: verified ? context.email ?? null : null,
23618
+ email_verified: context.emailVerified ?? null
23619
+ }
23620
+ };
23621
+ }
23622
+ function decideQuery(context, query, policies, table) {
23623
+ if (context.role === "service") return { allow: true };
23624
+ const identity = rlsIdentity(context);
23625
+ const all = policies ?? [];
23626
+ for (const command of commandsOf(query)) {
23627
+ const found = applicablePolicies(all, table, command, identity.role);
23628
+ if (found.invalid)
23629
+ return {
23630
+ allow: false,
23631
+ error: policyInvalid(found.invalid, command),
23632
+ reason: found.invalid.invalid ?? "it cannot be read"
23633
+ };
23634
+ if (found.permissive.length === 0)
23635
+ return {
23636
+ allow: false,
23637
+ error: dataError("PERMISSION_DENIED", { action: command, table })
23638
+ };
23639
+ }
23640
+ return { allow: true, rls: { ...identity, policies: all } };
23641
+ }
23642
+
23643
+ // ../../packages/data-core/src/compiler.ts
23644
+ var MAX_PARAMETERS = 100;
23645
+ var MAX_STATEMENT_BYTES = 1e5;
23646
+ var MAX_INSERT_STATEMENTS = 100;
23647
+ var OR_ABORT = "OR ABORT";
23648
+ function column(table, name) {
23649
+ return table.byName.get(asciiLower(name)) ?? fail(dataError("COLUMN_NOT_FOUND", { column: name, table: table.name }));
23650
+ }
23651
+ function columnList(table, names) {
23652
+ const seen = /* @__PURE__ */ new Set();
23653
+ const columns = [];
23654
+ for (const name of names) {
23655
+ const resolved = column(table, name);
23656
+ if (seen.has(resolved.name)) continue;
23657
+ seen.add(resolved.name);
23658
+ columns.push(resolved);
23659
+ }
23660
+ return columns;
23661
+ }
23662
+ function selectList(columns) {
23663
+ return columns.map((item) => quoteIdentifier(item.name)).join(", ");
23664
+ }
23665
+ function returningClause(table, returning, action) {
23666
+ if (returning === void 0) return "";
23667
+ if (returning !== "*" && returning.length === 0)
23668
+ fail(invalidQuery("returning", "must name at least one column"));
23669
+ if (table.kind === "virtual" && action !== "insert")
23670
+ fail(
23671
+ invalidQuery(
23672
+ "returning",
23673
+ `is not available for ${action} on the virtual table ${table.name}`
23674
+ )
23675
+ );
23676
+ const columns = returning === "*" ? table.columns : columnList(table, returning);
23677
+ return ` RETURNING ${selectList(columns)}`;
23678
+ }
23679
+ function scalar(value, path) {
23680
+ if (Array.isArray(value) || !isValue(value))
23681
+ fail(invalidQuery(path, "must be a single value"));
23682
+ return value;
23683
+ }
23684
+ function arrayValues(value, path) {
23685
+ const reason = `must be a non-empty array of strings and numbers (at most ${String(MAX_ARRAY_VALUES)})`;
23686
+ if (!Array.isArray(value) || value.length === 0 || value.length > MAX_ARRAY_VALUES)
23687
+ fail(invalidQuery(path, reason));
23688
+ const items = [];
23689
+ for (const item of value) {
23690
+ if (typeof item !== "string" && !(typeof item === "number" && Number.isFinite(item)))
23691
+ fail(invalidQuery(path, reason));
23692
+ items.push(item);
23693
+ }
23694
+ return distinctArrayValues(items);
23695
+ }
23696
+ function filterSql(table, filter, params, path) {
23697
+ const resolved = column(table, filter.column);
23698
+ const target = quoteIdentifier(resolved.name);
23699
+ const valuePath = `${path}.value`;
23700
+ switch (filter.operator) {
23701
+ case "eq":
23702
+ case "neq":
23703
+ case "gt":
23704
+ case "gte":
23705
+ case "lt":
23706
+ case "lte": {
23707
+ const operator = {
23708
+ eq: "=",
23709
+ neq: "<>",
23710
+ gt: ">",
23711
+ gte: ">=",
23712
+ lt: "<",
23713
+ lte: "<="
23714
+ }[filter.operator];
23715
+ params.push(toBoundValue(scalar(filter.value, valuePath)));
23716
+ return `${target} ${operator} ?`;
23717
+ }
23718
+ case "in": {
23719
+ if (!Array.isArray(filter.value))
23720
+ fail(invalidQuery(valuePath, "must be an array"));
23721
+ if (filter.value.length === 0) return "0 = 1";
23722
+ for (const [index, item] of filter.value.entries())
23723
+ params.push(toBoundValue(scalar(item, `${valuePath}[${index}]`)));
23724
+ return `${target} IN (${filter.value.map(() => "?").join(", ")})`;
23725
+ }
23726
+ case "is": {
23727
+ const value = filter.value;
23728
+ if (value !== null && value !== true && value !== false)
23729
+ fail(invalidQuery(valuePath, "must be null, true or false"));
23730
+ if (value === null) {
23731
+ params.push(null);
23732
+ return `${target} IS ?`;
23733
+ }
23734
+ return isBoolean(target, value);
23735
+ }
23736
+ case "ilike":
23737
+ case "like": {
23738
+ if (typeof filter.value !== "string")
23739
+ fail(invalidQuery(valuePath, "must be a string"));
23740
+ const problem = patternProblem(filter.operator, filter.value);
23741
+ if (problem !== void 0) fail(invalidQuery(valuePath, problem));
23742
+ const ilike = filter.operator === "ilike";
23743
+ params.push(boundPattern(filter.value, ilike));
23744
+ return ilike ? `${target} LIKE ? ESCAPE '\\'` : `${target} GLOB ?`;
23745
+ }
23746
+ case "contains":
23747
+ case "containedBy":
23748
+ case "overlaps": {
23749
+ const values = arrayValues(filter.value, valuePath);
23750
+ const qualified = `${quoteIdentifier(table.name)}.${target}`;
23751
+ let next = 0;
23752
+ return arraySql(
23753
+ () => qualified,
23754
+ filter.operator,
23755
+ values.length,
23756
+ () => {
23757
+ params.push(values[next++] ?? null);
23758
+ return "?";
23759
+ }
23760
+ );
23761
+ }
23762
+ }
23763
+ }
23764
+ function nodeSql(table, node, params, path) {
23765
+ if ("operator" in node) return filterSql(table, node, params, path);
23766
+ if ("not" in node)
23767
+ return `NOT (${nodeSql(table, node.not, params, `${path}.not`)})`;
23768
+ const [key, children] = "or" in node ? ["or", node.or] : ["and", node.and];
23769
+ if (children.length === 0)
23770
+ fail(invalidQuery(`${path}.${key}`, "must not be empty"));
23771
+ const terms = children.map(
23772
+ (child, index) => nodeSql(table, child, params, `${path}.${key}[${index}]`)
23773
+ );
23774
+ return `(${terms.join(key === "or" ? " OR " : " AND ")})`;
23775
+ }
23776
+ function whereClause(table, filters, policy, params) {
23777
+ const parts = [];
23778
+ if (policy) {
23779
+ parts.push(policy.sql);
23780
+ params.push(...policy.params);
23781
+ }
23782
+ const client = filters.map(
23783
+ (node, index) => nodeSql(table, node, params, `filters[${index}]`)
23784
+ );
23785
+ if (client.length > 0) parts.push(`(${client.join(" AND ")})`);
23786
+ return parts.length === 0 ? "" : ` WHERE ${parts.join(" AND ")}`;
23787
+ }
23788
+ function orderClause(table, order) {
23789
+ if (order.length === 0) return "";
23790
+ const terms = order.map((term) => {
23791
+ const direction = term.ascending ? "ASC" : "DESC";
23792
+ const nulls = term.nullsFirst === void 0 ? "" : term.nullsFirst ? " NULLS FIRST" : " NULLS LAST";
23793
+ return `${quoteIdentifier(column(table, term.column).name)} ${direction}${nulls}`;
23794
+ });
23795
+ return ` ORDER BY ${terms.join(", ")}`;
23796
+ }
23797
+ function statement(sql, params) {
23798
+ if (params.length > MAX_PARAMETERS) fail(dataError("TOO_MANY_PARAMETERS"));
23799
+ if (utf8ByteLength(sql) > MAX_STATEMENT_BYTES)
23800
+ fail(
23801
+ invalidQuery(
23802
+ "",
23803
+ `compiles to more than ${String(MAX_STATEMENT_BYTES)} bytes of SQL, D1's limit for one statement`
23804
+ )
23805
+ );
23806
+ return { sql, params };
23807
+ }
23808
+ function assignments(table, row, path) {
23809
+ const resolved = /* @__PURE__ */ new Map();
23810
+ for (const [key, value] of Object.entries(row)) {
23811
+ const target = column(table, key);
23812
+ if (target.generated)
23813
+ fail(
23814
+ invalidQuery(
23815
+ `${path}.${key}`,
23816
+ `Column ${target.name} is generated and cannot be written.`
23817
+ )
23818
+ );
23819
+ if (resolved.has(target.name))
23820
+ fail(
23821
+ invalidQuery(
23822
+ `${path}.${key}`,
23823
+ `Column ${target.name} is given more than once.`
23824
+ )
23825
+ );
23826
+ resolved.set(target.name, { column: target, value });
23827
+ }
23828
+ return resolved;
23829
+ }
23830
+ function hasCondition(nodes) {
23831
+ return (nodes ?? []).some((node) => {
23832
+ if ("operator" in node) return true;
23833
+ if ("not" in node) return hasCondition([node.not]);
23834
+ return hasCondition("or" in node ? node.or : node.and);
23835
+ });
23836
+ }
23837
+ function requireFilter(query, context) {
23838
+ if (hasCondition(query.filters)) return;
23839
+ if (query.allowUnfiltered === true && context.role === "service") return;
23840
+ fail(dataError("FILTER_REQUIRED"));
23841
+ }
23842
+ function mustBeNumber(action) {
23843
+ return `must be a number, a boolean or null, since the ${action} policy checks it`;
23844
+ }
23845
+ function mustBeNumberFor(type) {
23846
+ return `must be a number, a boolean or null, since the column is declared ${type}`;
23847
+ }
23848
+ function mustIncludeFor(name, command) {
23849
+ return `must include ${name}, which the ${command} policy checks`;
23850
+ }
23851
+ function mustInclude(name, action) {
23852
+ return action === "select" ? `${mustIncludeFor(name, action)} before a row is returned` : mustIncludeFor(name, action);
23853
+ }
23854
+ function mustNotBeNull(action) {
23855
+ return action === "select" ? "must not be null, since the select policy checks this key column before a row is returned" : `must not be null, since the ${action} policy checks this key column`;
23856
+ }
23857
+ function keepFirst(map2, item, policy) {
23858
+ if (isNumeric(item) && !map2.has(item.name)) map2.set(item.name, policy);
23859
+ }
23860
+ function insertSource(table, group) {
23861
+ const checked = /* @__PURE__ */ new Set();
23862
+ const source = {
23863
+ table,
23864
+ names: [asciiLower(table.name)],
23865
+ used: false,
23866
+ read: (item, env, how) => {
23867
+ const check2 = env.check;
23868
+ if (!check2) return fail(dataError("INTERNAL_ERROR"));
23869
+ source.used = true;
23870
+ if (item.generated)
23871
+ check2.problem(item.name, "generated", env.blame.policy);
23872
+ if (how.textual && isNumeric(item))
23873
+ check2.problem(item.name, how.textual, env.blame.policy);
23874
+ const index = group.columns.indexOf(item);
23875
+ if (index < 0) {
23876
+ const omitted = omittedSql(item);
23877
+ if (omitted === void 0)
23878
+ fail(
23879
+ invalidQuery(
23880
+ `rows[${String(group.indexes[0] ?? 0)}]`,
23881
+ mustInclude(item.name, check2.action)
23882
+ )
23883
+ );
23884
+ return omitted;
23885
+ }
23886
+ if (!checked.has(item.name)) {
23887
+ checked.add(item.name);
23888
+ for (const [row, values] of group.rows.entries()) {
23889
+ const path = `rows[${String(group.indexes[row] ?? row)}].${item.name}`;
23890
+ if (item.primaryKey > 0 && values[index] === null)
23891
+ fail(invalidQuery(path, mustNotBeNull(check2.action)));
23892
+ const value2 = values[index];
23893
+ if (typeof value2 === "string" && refusesText(item, value2))
23894
+ fail(invalidQuery(path, mustBeNumber(check2.action)));
23895
+ }
23896
+ }
23897
+ const value = `_naro_new.column${String(index + 1)}`;
23898
+ return group.rows.every(
23899
+ (values) => readsAsIs(item, values[index] ?? null)
23900
+ ) ? value : asStored(item, value);
23901
+ },
23902
+ operand: (item) => group.columns.includes(item) ? { stable: false, newColumnish: true, newCollation: "BINARY" } : { stable: false, newColumnish: false, newCollation: null }
23903
+ };
23904
+ return source;
23905
+ }
23906
+ function updateSource(table, values) {
23907
+ const plain = storedSource(table, [asciiLower(table.name)], null);
23908
+ const source = {
23909
+ table,
23910
+ names: [asciiLower(table.name)],
23911
+ used: false,
23912
+ read: (item, env, how) => {
23913
+ const check2 = env.check;
23914
+ if (!check2) return fail(dataError("INTERNAL_ERROR"));
23915
+ source.used = true;
23916
+ if (item.generated)
23917
+ check2.problem(item.name, "generated", env.blame.policy);
23918
+ const set2 = values.get(item.name);
23919
+ if (!set2) return plain.read(item, env, how);
23920
+ if (how.textual && isNumeric(item))
23921
+ check2.problem(item.name, how.textual, env.blame.policy);
23922
+ const bound = toBoundValue(set2.value);
23923
+ if (typeof bound === "string" && refusesText(item, bound))
23924
+ fail(invalidQuery(`values.${item.name}`, mustBeNumber(check2.action)));
23925
+ env.params.push(bound);
23926
+ return readsAsIs(item, bound) ? "?" : asStored(item, "?");
23927
+ },
23928
+ operand: (item) => values.has(item.name) ? { stable: false, newColumnish: false, newCollation: null } : { stable: true, newColumnish: true, newCollation: item.collation }
23929
+ };
23930
+ return source;
23931
+ }
23932
+ function checkProblemReason(column2, problem) {
23933
+ switch (problem) {
23934
+ case "generated":
23935
+ return `${column2} is a generated column, which a WITH CHECK cannot read (an update policy without WITH CHECK checks its USING)`;
23936
+ case "collation":
23937
+ return `${column2} has a collation naro cannot read, which a WITH CHECK needs to compare it`;
23938
+ case "pattern":
23939
+ return `${column2} is a number column, which a WITH CHECK cannot compare with LIKE or ILIKE`;
23940
+ case "textual":
23941
+ return `${column2} is a number column, which a WITH CHECK cannot turn into text (lower, upper, length, ::text)`;
23942
+ }
23943
+ }
23944
+ function isTrue(term) {
23945
+ return term.sql === "1" && term.params.length === 0;
23946
+ }
23947
+ var Rls = class {
23948
+ constructor(req, snapshot, table, nonce, commands) {
23949
+ this.req = req;
23950
+ this.snapshot = snapshot;
23951
+ this.table = table;
23952
+ this.nonce = nonce;
23953
+ this.outer = storedSource(table, [asciiLower(table.name)], null);
23954
+ const all = commands.flatMap((command) => {
23955
+ const found = this.found(command);
23956
+ return [...found.permissive, ...found.restrictive];
23957
+ });
23958
+ if (hasSubqueries(all)) this.state.active.push(asciiLower(table.name));
23959
+ for (const command of commands) {
23960
+ const found = this.found(command);
23961
+ for (const policy of [...found.permissive, ...found.restrictive]) {
23962
+ if (policy.owner === null) continue;
23963
+ const owner = table.byName.get(asciiLower(policy.owner));
23964
+ if (!owner || owner.generated)
23965
+ fail(
23966
+ policyInvalid(policy, command),
23967
+ owner ? `the owner column ${owner.name} is a generated column` : `the owner column ${policy.owner} is not on ${table.name}`
23968
+ );
23969
+ }
23970
+ }
23971
+ }
23972
+ state = newState2();
23973
+ /** Restrictive policies named by the checks' markers. */
23974
+ restrictive = [];
23975
+ outer;
23976
+ /**
23977
+ * A new statement of the same query — an insert's next group of rows:
23978
+ * MAX_STATEMENT_SUBQUERIES is a cap on one statement's SQL, so its count
23979
+ * starts again (P2-7 적대적 리뷰 반영: counted across an insert's groups,
23980
+ * rows naming different columns let any caller get the owner's 500).
23981
+ */
23982
+ nextStatement() {
23983
+ this.state.subqueries = 0;
23984
+ this.state.aliases = 0;
23985
+ }
23986
+ found(command) {
23987
+ const found = applicablePolicies(
23988
+ this.req.policies,
23989
+ this.table.name,
23990
+ command,
23991
+ this.req.role
23992
+ );
23993
+ if (found.invalid)
23994
+ fail(
23995
+ policyInvalid(found.invalid, command),
23996
+ found.invalid.invalid ?? void 0
23997
+ );
23998
+ return found;
23999
+ }
24000
+ env(check2, blame, action) {
24001
+ return {
24002
+ req: this.req,
24003
+ snapshot: this.snapshot,
24004
+ scopes: [this.outer],
24005
+ params: [],
24006
+ state: this.state,
24007
+ blame: { policy: blame, action },
24008
+ check: check2
24009
+ };
24010
+ }
24011
+ /**
24012
+ * The owner column when every permissive policy of the command is an
24013
+ * owner policy of that one column (spec §10) — then a row that does not
24014
+ * hold the caller's id there fails the policy anyway, and is refused with
24015
+ * P1's POLICY_VIOLATION.
24016
+ */
24017
+ ownerOnly(command) {
24018
+ const { permissive } = this.found(command);
24019
+ const owners = new Set(
24020
+ permissive.map(
24021
+ (policy) => policy.owner === null ? null : asciiLower(policy.owner)
24022
+ )
24023
+ );
24024
+ if (owners.size !== 1) return void 0;
24025
+ const [owner] = owners;
24026
+ return owner ? this.table.byName.get(owner) : void 0;
24027
+ }
24028
+ /** The columns an insert fills with the caller's id (spec §10). */
24029
+ fills() {
24030
+ const columns = /* @__PURE__ */ new Map();
24031
+ for (const policy of this.found("insert").permissive) {
24032
+ if (policy.owner === null) continue;
24033
+ const owner = this.table.byName.get(asciiLower(policy.owner));
24034
+ if (owner) columns.set(owner.name, owner);
24035
+ }
24036
+ return [...columns.values()];
24037
+ }
24038
+ /** WHERE's policy part (spec §5): the command's USING, and SELECT's. */
24039
+ where(action) {
24040
+ const commands = action === "select" ? ["select"] : [action, "select"];
24041
+ const terms = [];
24042
+ for (const command of commands) {
24043
+ const found = this.found(command);
24044
+ const first = found.permissive[0];
24045
+ if (!first) {
24046
+ terms.push({ sql: "0", params: [] });
24047
+ continue;
24048
+ }
24049
+ const group = groupTerms(
24050
+ this.env(null, first, command),
24051
+ [this.outer],
24052
+ command,
24053
+ found.permissive,
24054
+ found.restrictive,
24055
+ (policy) => policy.usingExpr
24056
+ );
24057
+ terms.push(group.permissive, ...group.restrictive.map((e) => e.term));
24058
+ }
24059
+ const kept = uniqueTerms(terms).filter((term) => !isTrue(term));
24060
+ const [only] = kept;
24061
+ if (!only) return null;
24062
+ if (kept.length === 1) return only;
24063
+ return {
24064
+ sql: `(${kept.map((term) => term.sql).join(" AND ")})`,
24065
+ params: kept.flatMap((term) => term.params)
24066
+ };
24067
+ }
24068
+ /**
24069
+ * The checks on a new row (spec §5, §8.2), in Postgres's order: the
24070
+ * command's permissive policies together, its restrictive ones each, then
24071
+ * — for an insert with `returning`, and every update — SELECT's. `skip`
24072
+ * leaves out a group a WHERE already guarantees (§8.5).
24073
+ */
24074
+ checks(action, source, withSelect, skip, holdsCaller) {
24075
+ const groups = [
24076
+ {
24077
+ command: action,
24078
+ kind: action,
24079
+ pick: (policy) => policy.checkExpr ?? policy.usingExpr
24080
+ }
24081
+ ];
24082
+ if (withSelect)
24083
+ groups.push({
24084
+ command: "select",
24085
+ kind: "select",
24086
+ pick: (policy) => policy.usingExpr
24087
+ });
24088
+ const terms = [];
24089
+ for (const { command, kind, pick: pick2 } of groups) {
24090
+ const found = this.found(command);
24091
+ const first = found.permissive[0];
24092
+ if (!first) {
24093
+ terms.push({
24094
+ sql: "0",
24095
+ params: [],
24096
+ marker: checkMarker(kind, this.nonce),
24097
+ kind,
24098
+ policy: void 0
24099
+ });
24100
+ continue;
24101
+ }
24102
+ if (skip(command, [...found.permissive, ...found.restrictive])) continue;
24103
+ const mode = {
24104
+ action: kind,
24105
+ problem: (name, problem, policy) => kind === "select" ? fail(
24106
+ selectCheckRefusal(
24107
+ action === "insert" ? "returning" : "values",
24108
+ name,
24109
+ problem
24110
+ )
24111
+ ) : fail(
24112
+ policyInvalid(policy, action),
24113
+ checkProblemReason(name, problem)
24114
+ )
24115
+ };
24116
+ const ownerColumn = this.ownerOnly(command);
24117
+ const owned = this.req.uid !== null && ownerColumn !== void 0 && holdsCaller(ownerColumn);
24118
+ const group = groupTerms(
24119
+ { ...this.env(mode, first, command), scopes: [source] },
24120
+ [source],
24121
+ command,
24122
+ owned ? [] : found.permissive,
24123
+ found.restrictive,
24124
+ pick2
24125
+ );
24126
+ if (!owned && !isTrue(group.permissive))
24127
+ terms.push({
24128
+ ...group.permissive,
24129
+ marker: checkMarker(kind, this.nonce),
24130
+ kind,
24131
+ policy: first
24132
+ });
24133
+ for (const { policy, term } of group.restrictive) {
24134
+ if (isTrue(term)) continue;
24135
+ const index = this.restrictive.push(policy.name) - 1;
24136
+ terms.push({
24137
+ ...term,
24138
+ marker: checkMarker(kind, this.nonce, index),
24139
+ kind,
24140
+ policy
24141
+ });
24142
+ }
24143
+ }
24144
+ return terms;
24145
+ }
24146
+ /**
24147
+ * Whether an update's check group is guaranteed by its WHERE (spec §8.5):
24148
+ * no subquery, no generated column, no column the update sets, and — for
24149
+ * UPDATE's own — every policy's check the same as its USING.
24150
+ */
24151
+ implied(command, policies, set2) {
24152
+ for (const policy of policies) {
24153
+ if (command === "update") {
24154
+ if (policy.usingExpr === null) return false;
24155
+ if (policy.check !== null && policy.check !== policy.using)
24156
+ return false;
24157
+ }
24158
+ const expr = policy.usingExpr;
24159
+ if (expr === null) continue;
24160
+ const read = outerColumns(this.table, expr);
24161
+ if (!read) return false;
24162
+ if (read.some((item) => item.generated || set2.has(item.name)))
24163
+ return false;
24164
+ }
24165
+ return true;
24166
+ }
24167
+ /**
24168
+ * The number columns policies read, and those they read as true or false
24169
+ * or compare with a number, each with the first policy that does; read
24170
+ * once, on the first string.
24171
+ */
24172
+ textReaders;
24173
+ /**
24174
+ * Refuses text written to a number column that a policy reads — any
24175
+ * table's policy, whatever its command or role, and whether or not this
24176
+ * statement's checks read it (known-issues #88). Stored, `'true'` is
24177
+ * false to `NOT is_private` and turns the policy inside out; Postgres
24178
+ * would read it as a boolean. A uuid-like column (`numberClass`
24179
+ * "fallback") keeps text that is no number, so only text it would store
24180
+ * as a number is refused — unless a policy reads it as true or false
24181
+ * (`truthColumns`) or compares it with a number (`numberComparedColumns`,
24182
+ * PR #80 리뷰: `hidden <> 1` let the text 'true' through): then it takes
24183
+ * no text at all. The refusal is named after the first policy that reads
24184
+ * the column, in the order they are read (table, then name), `all` after
24185
+ * the statement.
24186
+ *
24187
+ * And text a number or boolean column (INT, REAL, FLOA, DOUB, NUM, DEC,
24188
+ * BOOL, BIT) would keep as text, whether or not a policy reads it now
24189
+ * (PR #80 리뷰): a policy written later reads what is already stored.
24190
+ * Text it stores as a number ("7") is that number, and kept; so is the
24191
+ * caller's own id in an owner policy's column, which an owner insert
24192
+ * writes there itself and the owner condition compares as text (spec
24193
+ * §10 — owner policies are left out of the refusal above too).
24194
+ */
24195
+ refuseText(action, path, target, value) {
24196
+ if (typeof value !== "string") return;
24197
+ const { numbers, truths } = this.textReaders ??= this.readersOfText();
24198
+ const reader = refusesText(target, value) ? numbers.get(target.name) : truths.get(target.name);
24199
+ if (reader)
24200
+ fail(
24201
+ invalidQuery(
24202
+ path,
24203
+ mustBeNumber(reader.command === "all" ? action : reader.command)
24204
+ )
24205
+ );
24206
+ if (numberClass(target.type) === "number" && !storesAsNumber(value) && !this.ownId(target, value))
24207
+ fail(invalidQuery(path, mustBeNumberFor(target.type)));
24208
+ }
24209
+ /** The caller's id, in a column an owner policy of this table names. */
24210
+ ownId(target, value) {
24211
+ return value === this.req.uid && this.req.policies.some(
24212
+ (policy) => policy.owner !== null && asciiLower(policy.owner) === asciiLower(target.name) && resolveTable(this.snapshot, policy.table) === this.table
24213
+ );
24214
+ }
24215
+ /**
24216
+ * The same refusal for a column an insert leaves out, whose DEFAULT is a
24217
+ * string (P2-7b 최종 리뷰 I2): SQLite stores it as it would store that
24218
+ * string sent, so `is_private BOOLEAN DEFAULT 'true'` holds the text
24219
+ * `'true'`, which `NOT is_private` reads as false. Only text SQLite keeps
24220
+ * as text — a DEFAULT `'0'` is stored as 0 — and then by the rule for a
24221
+ * sent string: a number or boolean column any policy reads, a fallback
24222
+ * one a policy reads as true or false. The client must send the value.
24223
+ * A name SQLite makes a string (`DEFAULT "true"`, `DEFAULT yes`) is that
24224
+ * text, and a blob `x'…'` — never stored as a number — is refused as text
24225
+ * that is no number (P2-7b 최종 리뷰 후속 2).
24226
+ */
24227
+ refuseOmittedText(path, target) {
24228
+ const stored = stringDefault(target);
24229
+ if (stored === void 0) return;
24230
+ if (stored.kind === "text" && storesAsNumber(stored.text)) return;
24231
+ const { numbers, truths } = this.textReaders ??= this.readersOfText();
24232
+ const refused = stored.kind === "text" ? refusesText(target, stored.text) : numberClass(target.type) === "number";
24233
+ const reader = refused ? numbers.get(target.name) : truths.get(target.name);
24234
+ if (reader)
24235
+ fail(
24236
+ invalidQuery(
24237
+ path,
24238
+ mustIncludeFor(
24239
+ target.name,
24240
+ reader.command === "all" ? "insert" : reader.command
24241
+ )
24242
+ )
24243
+ );
24244
+ }
24245
+ readersOfText() {
24246
+ const numbers = /* @__PURE__ */ new Map();
24247
+ const truths = /* @__PURE__ */ new Map();
24248
+ for (const policy of this.req.policies) {
24249
+ if (policy.owner !== null) continue;
24250
+ const on = resolveTable(this.snapshot, policy.table);
24251
+ if (!on) continue;
24252
+ for (const expr of [policy.usingExpr, policy.checkExpr]) {
24253
+ if (expr === null) continue;
24254
+ for (const read of columnsReadOn(this.snapshot, on, expr, this.table))
24255
+ keepFirst(numbers, read, policy);
24256
+ for (const read of [
24257
+ ...truthColumns(this.snapshot, on, expr),
24258
+ ...numberComparedColumns(this.snapshot, on, expr)
24259
+ ])
24260
+ if (read.table === this.table) keepFirst(truths, read.column, policy);
24261
+ }
24262
+ }
24263
+ return { numbers, truths };
24264
+ }
24265
+ /**
24266
+ * The policy part's values and SQL must leave D1's other half of each to
24267
+ * the request: 50 of 100 values, 50,000 of 100,000 bytes.
24268
+ */
24269
+ budget(parts) {
24270
+ const count2 = parts.reduce((sum, part) => sum + part.params.length, 0);
24271
+ if (count2 > MAX_POLICY_VALUES)
24272
+ fail(
24273
+ dataError("POLICY_INVALID", {
24274
+ action: "select",
24275
+ table: this.table.name,
24276
+ values: true
24277
+ }),
24278
+ `they bind ${String(count2)}`
24279
+ );
24280
+ const bytes = parts.reduce(
24281
+ (sum, part) => sum + utf8ByteLength(part.sql),
24282
+ 0
24283
+ );
24284
+ if (bytes > MAX_POLICY_STATEMENT_BYTES)
24285
+ fail(
24286
+ dataError("POLICY_INVALID", {
24287
+ action: "select",
24288
+ table: this.table.name,
24289
+ bytes: true
24290
+ }),
24291
+ `they write ${String(bytes)}`
24292
+ );
24293
+ }
24294
+ };
24295
+ function checkCase(table, terms, guard, pass, action, nonce) {
24296
+ if (terms.length === 0) return pass;
24297
+ let sql = pass.sql;
24298
+ let params = pass.params;
24299
+ for (const term of terms.toReversed()) {
24300
+ sql = `CASE WHEN ${term.sql} THEN ${sql} ELSE ${term.marker} END`;
24301
+ params = [...term.params, ...params];
24302
+ }
24303
+ if (!guard) return { sql, params };
24304
+ if (table.sql === null) {
24305
+ const first = terms.find((term) => term.policy !== void 0);
24306
+ if (first?.kind === "select" || !first?.policy)
24307
+ fail(
24308
+ invalidQuery(
24309
+ action === "insert" ? "returning" : "values",
24310
+ `is not available: naro cannot read the schema of ${table.name}, so a new row cannot be checked against its select policy`
24311
+ )
24312
+ );
24313
+ fail(policyInvalid(first.policy, action));
24314
+ }
24315
+ return {
24316
+ sql: `CASE WHEN (SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?) = ? THEN ${sql} ELSE ${schemaMarker(nonce)} END`,
24317
+ params: [table.name, table.sql, ...params]
24318
+ };
24319
+ }
24320
+ function compileSelect(query, table, rls) {
24321
+ if (query.columns !== "*" && query.columns.length === 0)
24322
+ fail(invalidQuery("columns", "must name at least one column"));
24323
+ const columns = query.columns === "*" ? table.columns : columnList(table, query.columns);
24324
+ const params = [];
24325
+ const policy = rls?.where("select") ?? null;
24326
+ if (rls && policy) rls.budget([policy]);
24327
+ const where = whereClause(table, query.filters ?? [], policy, params);
24328
+ const order = orderClause(table, query.order ?? []);
24329
+ const limit = query.limit ?? MAX_LIMIT;
24330
+ params.push(query.single ? Math.min(limit, 2) : limit, query.offset ?? 0);
24331
+ return [
24332
+ statement(
24333
+ `SELECT ${selectList(columns)} FROM ${quoteIdentifier(table.name)}${where}${order} LIMIT ? OFFSET ?`,
24334
+ params
24335
+ )
24336
+ ];
24337
+ }
24338
+ function compileInsert(query, table, rls, nonce) {
24339
+ const uid = rls?.req.uid ?? null;
24340
+ const ownerInsert = rls?.ownerOnly("insert");
24341
+ const ownerRead = rls && query.returning !== void 0 ? rls.ownerOnly("select") : void 0;
24342
+ const fills = rls && uid !== null ? rls.fills() : [];
24343
+ const groups = /* @__PURE__ */ new Map();
24344
+ for (const [index, row] of query.rows.entries()) {
24345
+ const values = assignments(table, row, `rows[${index}]`);
24346
+ if (ownerInsert) {
24347
+ const given = values.get(ownerInsert.name);
24348
+ if (given && given.value !== uid)
24349
+ fail(dataError("POLICY_VIOLATION", { column: ownerInsert.name }));
24350
+ }
24351
+ for (const owner of fills)
24352
+ if (!values.has(owner.name) && uid !== null)
24353
+ values.set(owner.name, { column: owner, value: uid });
24354
+ if (ownerRead && values.get(ownerRead.name)?.value !== uid)
24355
+ fail(dataError("POLICY_VIOLATION", { column: ownerRead.name }));
24356
+ const ordered = [...values.values()].toSorted(
24357
+ (left, right) => left.column.cid - right.column.cid
24358
+ );
24359
+ const bound = ordered.map((item) => toBoundValue(item.value));
24360
+ const key = ordered.map(
24361
+ (item, at) => rls && readsAsIs(item.column, bound[at] ?? null) ? `${String(item.column.cid)}t` : String(item.column.cid)
24362
+ ).join(",");
24363
+ let group = groups.get(key);
24364
+ if (!group) {
24365
+ group = {
24366
+ columns: ordered.map((item) => item.column),
24367
+ rows: [],
24368
+ indexes: []
24369
+ };
24370
+ groups.set(key, group);
24371
+ }
24372
+ group.rows.push(bound);
24373
+ group.indexes.push(index);
24374
+ }
24375
+ const plans = [...groups.values()].map((group) => {
24376
+ const width = group.columns.length;
24377
+ if (width > MAX_PARAMETERS) fail(dataError("TOO_MANY_PARAMETERS"));
24378
+ let where;
24379
+ if (rls) {
24380
+ rls.nextStatement();
24381
+ const source = insertSource(table, group);
24382
+ const terms = rls.checks(
24383
+ "insert",
24384
+ source,
24385
+ query.returning !== void 0,
24386
+ () => false,
24387
+ // Every row sends the caller's id there (filled in or checked).
24388
+ (owner) => {
24389
+ const index = group.columns.indexOf(owner);
24390
+ return index >= 0 && group.rows.every((row) => row[index] === uid);
24391
+ }
24392
+ );
24393
+ if (terms.length > 0) {
24394
+ where = checkCase(
24395
+ table,
24396
+ terms,
24397
+ source.used,
24398
+ { sql: width === 0 ? "0" : "1", params: [] },
24399
+ "insert",
24400
+ nonce
24401
+ );
24402
+ rls.budget([
24403
+ { sql: where.sql, params: terms.flatMap((term) => term.params) }
24404
+ ]);
24405
+ }
24406
+ }
24407
+ const checkParams = where?.params ?? [];
24408
+ const room = MAX_PARAMETERS - checkParams.length;
24409
+ if (width > 0 && room < width) fail(dataError("TOO_MANY_PARAMETERS"));
24410
+ const perStatement = width === 0 ? 1 : Math.floor(room / width);
24411
+ const needed2 = width === 0 ? group.rows.length + (where === void 0 ? 0 : 1) : Math.ceil(group.rows.length / perStatement);
24412
+ return { group, width, where, perStatement, needed: needed2 };
24413
+ });
24414
+ const needed = plans.reduce((sum, plan) => sum + plan.needed, 0);
24415
+ if (needed > MAX_INSERT_STATEMENTS)
24416
+ fail(
24417
+ invalidQuery(
24418
+ "rows",
24419
+ `would take ${String(needed)} statements; one insert may take at most ${String(MAX_INSERT_STATEMENTS)} (each binds at most ${String(MAX_PARAMETERS)} values, and a row with no values takes one of its own)`
24420
+ )
24421
+ );
24422
+ const into = `INSERT ${OR_ABORT} INTO ${quoteIdentifier(table.name)}`;
24423
+ const returning = returningClause(table, query.returning, "insert");
24424
+ if (rls) {
24425
+ for (const { group } of plans)
24426
+ for (const [row, values] of group.rows.entries())
24427
+ for (const [index, item] of group.columns.entries())
24428
+ rls.refuseText(
24429
+ "insert",
24430
+ `rows[${String(group.indexes[row] ?? row)}].${item.name}`,
24431
+ item,
24432
+ values[index] ?? null
24433
+ );
24434
+ for (const { group } of plans)
24435
+ for (const item of table.columns)
24436
+ if (!item.generated && !group.columns.includes(item))
24437
+ rls.refuseOmittedText(`rows[${String(group.indexes[0] ?? 0)}]`, item);
24438
+ }
24439
+ const statements = [];
24440
+ for (const { group, width, where, perStatement } of plans) {
24441
+ if (width === 0) {
24442
+ if (where !== void 0)
24443
+ statements.push(statement(`SELECT 1 WHERE ${where.sql}`, where.params));
24444
+ for (let count2 = 0; count2 < group.rows.length; count2++)
24445
+ statements.push(statement(`${into} DEFAULT VALUES${returning}`, []));
24446
+ continue;
24447
+ }
24448
+ const tuple2 = `(${group.columns.map(() => "?").join(", ")})`;
24449
+ const names = selectList(group.columns);
24450
+ for (let start = 0; start < group.rows.length; start += perStatement) {
24451
+ const chunk = group.rows.slice(start, start + perStatement);
24452
+ const values = chunk.map(() => tuple2).join(", ");
24453
+ statements.push(
24454
+ where === void 0 ? statement(
24455
+ `${into} (${names}) VALUES ${values}${returning}`,
24456
+ chunk.flat()
24457
+ ) : statement(
24458
+ `${into} (${names}) SELECT ${group.columns.map((_, index) => `_naro_new.column${String(index + 1)}`).join(", ")} FROM (VALUES ${values}) AS _naro_new WHERE ${where.sql}${returning}`,
24459
+ [...chunk.flat(), ...where.params]
24460
+ )
24461
+ );
24462
+ }
24463
+ }
24464
+ return statements;
24465
+ }
24466
+ function compileUpdate(query, table, context, rls, nonce) {
24467
+ requireFilter(query, context);
24468
+ const values = assignments(table, query.values, "values");
24469
+ if (values.size === 0)
24470
+ fail(invalidQuery("values", "must set at least one column"));
24471
+ const ownerUpdate = rls?.ownerOnly("update");
24472
+ if (ownerUpdate) {
24473
+ const given = values.get(ownerUpdate.name);
24474
+ if (given && given.value !== rls?.req.uid)
24475
+ fail(dataError("POLICY_VIOLATION", { column: ownerUpdate.name }));
24476
+ }
24477
+ const set2 = new Set(values.keys());
24478
+ const policy = rls?.where("update") ?? null;
24479
+ const source = rls ? updateSource(table, values) : void 0;
24480
+ const terms = rls && source ? rls.checks(
24481
+ "update",
24482
+ source,
24483
+ true,
24484
+ (command, policies) => rls.implied(command, policies, set2),
24485
+ // Kept as WHERE found it, or set to the caller's id.
24486
+ (owner) => {
24487
+ const given = values.get(owner.name);
24488
+ return !given || given.value === rls.req.uid;
24489
+ }
24490
+ ) : [];
24491
+ const params = [];
24492
+ let checkSql = "";
24493
+ const assigned = [...values.values()].map((item, index) => {
24494
+ const value = { sql: "?", params: [toBoundValue(item.value)] };
24495
+ const expression = index === 0 && terms.length > 0 ? checkCase(table, terms, source?.used ?? false, value, "update", nonce) : value;
24496
+ if (expression !== value) checkSql = expression.sql;
24497
+ params.push(...expression.params);
24498
+ return `${quoteIdentifier(item.column.name)} = ${expression.sql}`;
24499
+ });
24500
+ rls?.budget([
24501
+ { sql: checkSql, params: terms.flatMap((term) => term.params) },
24502
+ ...policy ? [policy] : []
24503
+ ]);
24504
+ const where = whereClause(table, query.filters ?? [], policy, params);
24505
+ const returning = returningClause(table, query.returning, "update");
24506
+ for (const item of values.values())
24507
+ rls?.refuseText(
24508
+ "update",
24509
+ `values.${item.column.name}`,
24510
+ item.column,
24511
+ toBoundValue(item.value)
24512
+ );
24513
+ return [
24514
+ statement(
24515
+ `UPDATE ${OR_ABORT} ${quoteIdentifier(table.name)} SET ${assigned.join(", ")}${where}${returning}`,
24516
+ params
24517
+ )
24518
+ ];
24519
+ }
24520
+ function compileDelete(query, table, context, rls) {
24521
+ requireFilter(query, context);
24522
+ const params = [];
24523
+ const policy = rls?.where("delete") ?? null;
24524
+ if (rls && policy) rls.budget([policy]);
24525
+ const where = whereClause(table, query.filters ?? [], policy, params);
24526
+ return [
24527
+ statement(
24528
+ `DELETE FROM ${quoteIdentifier(table.name)}${where}${returningClause(table, query.returning, "delete")}`,
24529
+ params
24530
+ )
24531
+ ];
24532
+ }
24533
+ function compile(query, snapshot, decision, context, options = {}) {
24534
+ const nonce = options.checkNonce ?? DEFAULT_CHECK_NONCE;
24535
+ if (!/^[0-9a-f]{1,64}$/.test(nonce))
24536
+ throw new Error("compile: checkNonce must be 1 to 64 hex digits");
24537
+ if (!decision.allow)
24538
+ return decision.reason === void 0 ? { ok: false, error: decision.error } : { ok: false, error: decision.error, reason: decision.reason };
24539
+ const table = resolveTable(snapshot, query.table);
24540
+ if (!table)
24541
+ return {
24542
+ ok: false,
24543
+ error: dataError("TABLE_NOT_FOUND", { table: query.table })
24544
+ };
24545
+ try {
24546
+ const rls = decision.rls ? new Rls(decision.rls, snapshot, table, nonce, commandsOf(query)) : void 0;
24547
+ let statements;
24548
+ switch (query.action) {
24549
+ case "select":
24550
+ statements = compileSelect(query, table, rls);
24551
+ break;
24552
+ case "insert":
24553
+ statements = compileInsert(query, table, rls, nonce);
24554
+ break;
24555
+ case "update":
24556
+ statements = compileUpdate(query, table, context, rls, nonce);
24557
+ break;
24558
+ case "delete":
24559
+ statements = compileDelete(query, table, context, rls);
24560
+ break;
24561
+ }
24562
+ return { ok: true, statements, restrictive: rls?.restrictive ?? [] };
24563
+ } catch (error51) {
24564
+ if (error51 instanceof Failure)
24565
+ return error51.reason === void 0 ? { ok: false, error: error51.error } : { ok: false, error: error51.error, reason: error51.reason };
24566
+ throw error51;
24567
+ }
24568
+ }
24569
+
24570
+ // ../../packages/data-core/src/policy-index.ts
24571
+ var INDEX_SQL = `SELECT t.name AS table_name, il.name AS index_name, il."partial" AS partial, ix.name AS column_name, ix.coll AS collation FROM pragma_table_list AS t JOIN pragma_index_list(t.name, t.schema) AS il JOIN pragma_index_xinfo(il.name, t.schema) AS ix WHERE t.schema = 'main' AND t.type = 'table' AND ${systemTablePredicate("t.name")} AND ix.seqno = 0`;
24572
+ function indexesFromRows(rows) {
24573
+ const tables = /* @__PURE__ */ new Map();
24574
+ for (const row of rows) {
24575
+ const table = row.table_name;
24576
+ const column2 = row.column_name;
24577
+ if (typeof table !== "string" || typeof column2 !== "string") continue;
24578
+ if (Number(row.partial) !== 0) continue;
24579
+ const key = asciiLower(table);
24580
+ const columns = tables.get(key) ?? /* @__PURE__ */ new Map();
24581
+ tables.set(key, columns);
24582
+ const collations = columns.get(asciiLower(column2)) ?? [];
24583
+ collations.push(
24584
+ typeof row.collation === "string" ? row.collation.toUpperCase() : "BINARY"
24585
+ );
24586
+ columns.set(asciiLower(column2), collations);
24587
+ }
24588
+ return tables;
24589
+ }
24590
+ function resolve8(scopes, qualifier, name) {
24591
+ const column2 = asciiLower(name);
24592
+ for (let index = scopes.length - 1; index >= 0; index--) {
24593
+ const scope = scopes[index];
24594
+ if (qualifier !== null && !scope.names.includes(asciiLower(qualifier)))
24595
+ continue;
24596
+ const found = scope.table.byName.get(column2);
24597
+ if (found) return { column: found, scope: index };
24598
+ if (qualifier !== null) return void 0;
24599
+ }
24600
+ return void 0;
24601
+ }
24602
+ function valueScopes(scopes, value) {
24603
+ switch (value.kind) {
24604
+ case "column": {
24605
+ const found = resolve8(scopes, value.qualifier, value.name);
24606
+ return found ? [found.scope] : [];
24607
+ }
24608
+ case "func":
24609
+ return value.args.flatMap((arg) => valueScopes(scopes, arg));
24610
+ case "cast":
24611
+ return valueScopes(scopes, value.operand);
24612
+ default:
24613
+ return [];
24614
+ }
24615
+ }
24616
+ function readsOutside(scopes, expr, depth, snapshot) {
24617
+ if (expr === null) return false;
24618
+ if (isValueExpr(expr))
24619
+ return valueScopes(scopes, expr).some((scope) => scope < depth);
24620
+ const values = (...items) => items.some(
24621
+ (item) => valueScopes(scopes, item).some((scope) => scope < depth)
24622
+ );
24623
+ switch (expr.kind) {
24624
+ case "and":
24625
+ case "or":
24626
+ return expr.args.some(
24627
+ (arg) => readsOutside(scopes, arg, depth, snapshot)
24628
+ );
24629
+ case "not":
24630
+ return readsOutside(scopes, expr.arg, depth, snapshot);
24631
+ case "compare":
24632
+ case "distinct":
24633
+ return values(expr.left, expr.right);
24634
+ case "is":
24635
+ case "like":
24636
+ return values(expr.operand);
24637
+ case "in":
24638
+ return values(expr.operand, ...expr.list);
24639
+ case "between":
24640
+ return values(expr.operand, expr.low, expr.high);
24641
+ case "array":
24642
+ return values(expr.operand);
24643
+ case "exists":
24644
+ case "inSelect": {
24645
+ if (expr.kind === "inSelect" && values(expr.operand)) return true;
24646
+ const inner = subqueryScopes2(scopes, expr.subquery, snapshot);
24647
+ if (!inner) return false;
24648
+ return expr.subquery.select !== "*" && readsOutside(inner, expr.subquery.select, depth, snapshot) || readsOutside(inner, expr.subquery.where, depth, snapshot);
24649
+ }
24650
+ }
24651
+ }
24652
+ function subqueryScopes2(scopes, subquery, snapshot) {
24653
+ const table = resolveTable(snapshot, subquery.table);
24654
+ if (!table) return void 0;
24655
+ return [
24656
+ ...scopes,
24657
+ { table, names: [asciiLower(subquery.alias ?? table.name)] }
24658
+ ];
24659
+ }
24660
+ function conjuncts(expr) {
24661
+ if (expr === null) return [];
24662
+ if (!isValueExpr(expr) && expr.kind === "and")
24663
+ return expr.args.flatMap(conjuncts);
24664
+ return [expr];
24665
+ }
24666
+ function columnAffinityOf(column2) {
24667
+ const affinity2 = columnAffinity(column2);
24668
+ if (affinity2 === "TEXT") return "text";
24669
+ if (affinity2 === "BLOB") return "blob";
24670
+ return "numeric";
24671
+ }
24672
+ function valueAffinity(scopes, value) {
24673
+ if (value.kind === "cast") return "text";
24674
+ if (value.kind !== "column") return "none";
24675
+ const found = resolve8(scopes, value.qualifier, value.name);
24676
+ return found ? columnAffinityOf(found.column) : "none";
24677
+ }
24678
+ function valueCollation(scopes, value) {
24679
+ if (value.kind === "cast") return valueCollation(scopes, value.operand);
24680
+ if (value.kind !== "column") return void 0;
24681
+ const found = resolve8(scopes, value.qualifier, value.name);
24682
+ return found ? found.column.collation : void 0;
24683
+ }
24684
+ function waysIn(scopes, where) {
24685
+ const inner = scopes.length - 1;
24686
+ const own2 = (value) => {
24687
+ if (value.kind !== "column") return void 0;
24688
+ const found = resolve8(scopes, value.qualifier, value.name);
24689
+ return found?.scope === inner ? found.column : void 0;
24690
+ };
24691
+ const outside = (value) => valueScopes(scopes, value).every((scope) => scope < inner);
24692
+ const ways = [];
24693
+ const pair2 = (left, right) => {
24694
+ for (const [mine, other, mineLeft] of [
24695
+ [left, right, true],
24696
+ [right, left, false]
24697
+ ]) {
24698
+ const column2 = own2(mine);
24699
+ if (!column2 || !outside(other)) continue;
24700
+ const first = mineLeft ? mine : other;
24701
+ const second = mineLeft ? other : mine;
24702
+ const collation = valueCollation(scopes, first) !== void 0 ? valueCollation(scopes, first) : valueCollation(scopes, second);
24703
+ const mineAffinity = columnAffinityOf(column2);
24704
+ const otherAffinity = valueAffinity(scopes, other);
24705
+ const affinity2 = otherAffinity === "none" ? mineAffinity : mineAffinity === "numeric" || otherAffinity === "numeric" ? "numeric" : "blob";
24706
+ ways.push({ column: column2, collation, affinity: affinity2 });
24707
+ }
24708
+ };
24709
+ for (const condition of conjuncts(where)) {
24710
+ if (isValueExpr(condition)) continue;
24711
+ if (condition.kind === "compare" && condition.op === "=")
24712
+ pair2(condition.left, condition.right);
24713
+ else if (condition.kind === "distinct" && condition.not)
24714
+ pair2(condition.left, condition.right);
24715
+ else if (condition.kind === "in" && !condition.not) {
24716
+ const column2 = own2(condition.operand);
24717
+ if (column2 && condition.list.every(outside))
24718
+ ways.push({
24719
+ column: column2,
24720
+ collation: column2.collation,
24721
+ affinity: columnAffinityOf(column2)
24722
+ });
24723
+ }
24724
+ }
24725
+ return ways;
24726
+ }
24727
+ function isRowid(table, column2) {
24728
+ const keys = table.columns.filter((item) => item.primaryKey > 0);
24729
+ return keys.length === 1 && keys[0] === column2 && asciiLower(column2.type.trim()) === "integer";
24730
+ }
24731
+ function blocked(table, indexes, way) {
24732
+ if (isRowid(table, way.column))
24733
+ return way.affinity === "text" ? "affinity" : void 0;
24734
+ const collations = indexes.get(asciiLower(table.name))?.get(asciiLower(way.column.name));
24735
+ if (!collations || collations.length === 0) return "none";
24736
+ const wanted = way.collation === void 0 ? way.column.collation : way.collation;
24737
+ if (wanted !== null && !collations.includes(wanted)) return "collation";
24738
+ const own2 = columnAffinityOf(way.column);
24739
+ if (way.affinity === "text" && own2 !== "text") return "affinity";
24740
+ if (way.affinity === "numeric" && own2 !== "numeric") return "affinity";
24741
+ return void 0;
24742
+ }
24743
+ function problemMessage(policy, outer, table, ways, why) {
24744
+ const start = `Policy ${policy.name} on ${outer.name}: its subquery on ${table.name} runs once for every row of ${outer.name} it looks at`;
24745
+ const override = "or send it with allow_unindexed=1 (naro policy create --allow-unindexed) to accept a scan of every row";
24746
+ const [way] = ways;
24747
+ if (!way)
24748
+ return `${start}, and no condition of its WHERE compares a column of ${table.name} with = to a value from outside it, so each run reads all of ${table.name}. Compare a column of ${table.name} with = (or IN) and index that column \u2014 ${override}.`;
24749
+ const column2 = way.column.name;
24750
+ if (why === "affinity")
24751
+ return `${start}, and it compares ${table.name}.${column2} with a value of another type (a number and text), which no index can search, so each run reads all of ${table.name}. Compare it with a value of its own type \u2014 ${override}.`;
24752
+ const wanted = way.collation === void 0 || way.collation === null ? null : way.collation;
24753
+ const collate = why === "collation" && wanted !== null && wanted !== way.column.collation ? ` COLLATE ${wanted}` : "";
24754
+ const index = quoteIdent(`${table.name}_${column2}_idx`);
24755
+ return `${start}, and no index starts with ${table.name}.${column2}${collate === "" ? "" : ` in collation ${String(wanted)}`}, so each run reads all of ${table.name}. Create one first: CREATE INDEX ${index} ON ${quoteIdent(table.name)} (${quoteIdent(column2)}${collate}); \u2014 ${override}.`;
24756
+ }
24757
+ function unindexedSubqueries(snapshot, indexes, policies) {
24758
+ const found = [];
24759
+ for (const policy of policies) {
24760
+ if (policy.invalid !== null) continue;
24761
+ const outer = resolveTable(snapshot, policy.table);
24762
+ if (!outer) continue;
24763
+ const visit = (scopes, expr) => {
24764
+ if (expr === null || isValueExpr(expr)) return;
24765
+ switch (expr.kind) {
24766
+ case "and":
24767
+ case "or":
24768
+ expr.args.forEach((arg) => visit(scopes, arg));
24769
+ return;
24770
+ case "not":
24771
+ visit(scopes, expr.arg);
24772
+ return;
24773
+ case "exists":
24774
+ case "inSelect": {
24775
+ const inner = subqueryScopes2(scopes, expr.subquery, snapshot);
24776
+ if (!inner) return;
24777
+ const depth = inner.length - 1;
24778
+ const correlated = expr.subquery.select !== "*" && readsOutside(inner, expr.subquery.select, depth, snapshot) || readsOutside(inner, expr.subquery.where, depth, snapshot);
24779
+ if (correlated) {
24780
+ const table = inner[depth].table;
24781
+ const ways = waysIn(inner, expr.subquery.where);
24782
+ const reasons = ways.map((way) => blocked(table, indexes, way));
24783
+ if (!reasons.includes(void 0)) {
24784
+ const order = ["none", "collation", "affinity"];
24785
+ const best = order.map((reason) => reasons.indexOf(reason)).find((index) => index >= 0);
24786
+ const way = best === void 0 ? void 0 : ways[best];
24787
+ found.push({
24788
+ policy,
24789
+ table: table.name,
24790
+ message: problemMessage(
24791
+ policy,
24792
+ outer,
24793
+ table,
24794
+ way ? [way] : [],
24795
+ best === void 0 ? void 0 : reasons[best]
24796
+ )
24797
+ });
24798
+ }
24799
+ }
24800
+ visit(inner, expr.subquery.where);
24801
+ return;
24802
+ }
24803
+ default:
24804
+ return;
24805
+ }
24806
+ };
24807
+ const root = [{ table: outer, names: [asciiLower(outer.name)] }];
24808
+ visit(root, policy.usingExpr);
24809
+ visit(root, policy.checkExpr);
24810
+ }
24811
+ return found;
24812
+ }
24813
+
24814
+ // ../../packages/data-core/src/policy-check.ts
24815
+ var CONTEXTS = [
24816
+ { role: "anon" },
24817
+ {
24818
+ role: "authenticated",
24819
+ userId: "naro-policy-check",
24820
+ email: "check@naro.invalid",
24821
+ emailVerified: true
24822
+ }
24823
+ ];
24824
+ function probes(table, context, owners) {
24825
+ const writable = table.columns.filter((column2) => !column2.generated);
24826
+ const [first] = table.columns;
24827
+ const filters = first ? [{ operator: "is", column: first.name, value: null }] : [];
24828
+ const row = Object.fromEntries(
24829
+ writable.map((column2) => [
24830
+ column2.name,
24831
+ context.role === "authenticated" && owners.has(asciiLower(column2.name)) ? context.userId : isNumeric(column2) ? 0 : "naro-policy-check"
24832
+ ])
24833
+ );
24834
+ const queries = [
24835
+ { version: 1, action: "select", table: table.name, columns: "*" },
24836
+ { version: 1, action: "insert", table: table.name, rows: [row] },
24837
+ {
24838
+ version: 1,
24839
+ action: "insert",
24840
+ table: table.name,
24841
+ rows: [row],
24842
+ returning: "*"
24843
+ },
24844
+ { version: 1, action: "delete", table: table.name, filters }
24845
+ ];
24846
+ if (writable.length > 0)
24847
+ queries.push({
24848
+ version: 1,
24849
+ action: "update",
24850
+ table: table.name,
24851
+ values: row,
24852
+ filters
24853
+ });
24854
+ return queries;
24855
+ }
24856
+ function namedInRefusal(message, policies) {
24857
+ let found;
24858
+ for (const policy of policies) {
24859
+ if (!message.startsWith(`Policy ${policy.name} on ${policy.table} cannot be`))
24860
+ continue;
24861
+ if (found === void 0 || policy.name.length > found.name.length)
24862
+ found = policy;
24863
+ }
24864
+ return found;
24865
+ }
24866
+ function problems(snapshot, policies, options) {
24867
+ const found = /* @__PURE__ */ new Map();
24868
+ const loaded = loadNamed(policies);
24869
+ for (const policy of loaded)
24870
+ if (policy.invalid !== null)
24871
+ found.set(`read:${policy.table}:${policy.name}`, {
24872
+ problem: {
24873
+ table: policy.table,
24874
+ message: `Policy ${policy.name} on ${policy.table} cannot be read: ${policy.invalid}.`,
24875
+ policy: policy.name,
24876
+ kind: "read"
24877
+ },
24878
+ policy: policy.name
24879
+ });
24880
+ const tables = /* @__PURE__ */ new Map();
24881
+ for (const policy of loaded) {
24882
+ const table = resolveTable(snapshot, policy.table);
24883
+ if (table) tables.set(table.name, table);
24884
+ }
24885
+ for (const policy of loaded) {
24886
+ const table = resolveTable(snapshot, policy.table);
24887
+ if (!table || policy.invalid !== null) continue;
24888
+ for (const expr of [policy.usingExpr, policy.checkExpr]) {
24889
+ if (expr === null) continue;
24890
+ for (const { written, column: column2 } of truthColumns(snapshot, table, expr)) {
24891
+ if (isNumeric(column2)) continue;
24892
+ const message = `Policy ${policy.name} on ${policy.table} cannot be applied: ${written} is a ${columnAffinity(column2)} column, whose truth value is not what its text says ('true' reads as false): compare it, ${written} = 'true'.`;
24893
+ found.set(`${table.name}:${message}`, {
24894
+ problem: {
24895
+ table: table.name,
24896
+ message,
24897
+ policy: policy.name,
24898
+ kind: "compile"
24899
+ },
24900
+ policy: policy.name
24901
+ });
24902
+ }
24903
+ }
24904
+ }
24905
+ for (const policy of loaded) {
24906
+ const table = resolveTable(snapshot, policy.table);
24907
+ if (!table || policy.invalid !== null) continue;
24908
+ for (const { written, column: column2 } of numberReadsOf(snapshot, table, policy)) {
24909
+ const stored = stringDefault(column2);
24910
+ if (stored === void 0) continue;
24911
+ if (stored.kind === "text" && storesAsNumber(stored.text)) continue;
24912
+ const kept = stored.kind === "text" ? "text" : "a blob";
24913
+ found.set(`default:${table.name}:${policy.name}:${written}`, {
24914
+ problem: {
24915
+ table: table.name,
24916
+ message: `Policy ${policy.name} on ${policy.table} reads ${written} as a boolean or a number, but its DEFAULT ${String(column2.defaultValue)} is stored as ${kept}, which the policy does not read as it says ('true' reads as false): the rows that took it \u2014 every row there when the column was added, and each service-key insert that leaves it out \u2014 hold that value. Write the default as a number (1, 0, TRUE or FALSE) and update those rows.`,
24917
+ policy: policy.name,
24918
+ kind: "default"
24919
+ },
24920
+ policy: policy.name
24921
+ });
24922
+ }
24923
+ }
24924
+ const probe = loadNamed(
24925
+ [...tables.values()].map((table) => ({
24926
+ table: table.name,
24927
+ name: "naro_policy_check",
24928
+ permissive: true,
24929
+ command: "select",
24930
+ roles: ["anon", "authenticated"],
24931
+ using: "true",
24932
+ check: null,
24933
+ owner: null
24934
+ }))
24935
+ );
24936
+ const compiled = [...loaded, ...probe];
24937
+ for (const table of tables.values()) {
24938
+ const owners = new Set(
24939
+ policiesOn(loaded, table.name).flatMap(
24940
+ (policy) => policy.owner ? [asciiLower(policy.owner)] : []
24941
+ )
24942
+ );
24943
+ for (const context of CONTEXTS)
24944
+ for (const query of probes(table, context, owners)) {
24945
+ const decision = decideQuery(context, query, compiled, table.name);
24946
+ const result = decision.allow ? compile(query, snapshot, decision, context) : {
24947
+ ok: false,
24948
+ error: decision.error,
24949
+ reason: decision.reason
24950
+ };
24951
+ if (result.ok || result.error.code !== "POLICY_INVALID") continue;
24952
+ const message = result.reason === void 0 ? result.error.message : `${result.error.message.replace(/\.$/, "")}: ${result.reason}.`;
24953
+ const named = namedInRefusal(result.error.message, loaded);
24954
+ const blamed2 = named === void 0 ? table.name : resolveTable(snapshot, named.table)?.name ?? named.table;
24955
+ const key = `${blamed2}:${message}`;
24956
+ if (!found.has(key))
24957
+ found.set(key, {
24958
+ problem: {
24959
+ table: blamed2,
24960
+ message,
24961
+ ...named === void 0 ? {} : { policy: named.name },
24962
+ kind: "compile"
24963
+ },
24964
+ policy: ""
24965
+ });
24966
+ }
24967
+ }
24968
+ if (options.indexes && options.allowUnindexed !== true)
24969
+ for (const problem of unindexedSubqueries(
24970
+ snapshot,
24971
+ options.indexes,
24972
+ loaded
24973
+ )) {
24974
+ const key = `index:${problem.policy.table}:${problem.message}`;
24975
+ if (!found.has(key))
24976
+ found.set(key, {
24977
+ problem: {
24978
+ table: problem.policy.table,
24979
+ message: problem.message,
24980
+ policy: problem.policy.name,
24981
+ kind: "unindexed"
24982
+ },
24983
+ policy: problem.policy.name
24984
+ });
24985
+ }
24986
+ return found;
24987
+ }
24988
+ function byText(a, b) {
24989
+ return a < b ? -1 : a > b ? 1 : 0;
24990
+ }
24991
+ function policySetProblems(snapshot, policies, options = {}) {
24992
+ return [...problems(snapshot, policies, options).values()].toSorted(
24993
+ (a, b) => byText(a.problem.table, b.problem.table) || byText(a.policy, b.policy) || byText(a.problem.message, b.problem.message)
24994
+ ).map(({ problem }) => problem);
24995
+ }
24996
+ function numberReadsOf(snapshot, table, policy) {
24997
+ const reads = /* @__PURE__ */ new Map();
24998
+ for (const expr of [policy.usingExpr, policy.checkExpr]) {
24999
+ if (expr === null) continue;
25000
+ for (const read of [
25001
+ ...truthColumns(snapshot, table, expr),
25002
+ ...numberComparedColumns(snapshot, table, expr)
25003
+ ]) {
25004
+ const key = JSON.stringify([read.table.name, read.column.name]);
25005
+ if (isNumeric(read.column) && !reads.has(key)) reads.set(key, read);
25006
+ }
25007
+ }
25008
+ return [...reads.values()];
25009
+ }
25010
+
25011
+ // ../../packages/data-core/src/policy-statement.ts
25012
+ function readPolicyName(stream) {
25013
+ const token = stream.peek();
25014
+ if (token.kind !== "ident")
25015
+ throw new PolicySyntaxError(
25016
+ `expected a policy name, found ${describeToken(token)}`,
25017
+ token.position
25018
+ );
25019
+ stream.next();
25020
+ return foldPolicyName(token.value, token.quoted);
25021
+ }
25022
+ function readRoleName(token) {
25023
+ if (token.kind !== "ident")
25024
+ throw new PolicySyntaxError(
25025
+ `expected a role, found ${describeToken(token)}`,
25026
+ token.position
25027
+ );
25028
+ return token.quoted ? token.value : asciiLower(token.value);
25029
+ }
25030
+ function readCommand(stream) {
25031
+ const token = stream.next();
25032
+ for (const command of [
25033
+ "all",
25034
+ "select",
25035
+ "insert",
25036
+ "update",
25037
+ "delete"
25038
+ ])
25039
+ if (isKeyword(token, command)) return command;
25040
+ throw new PolicySyntaxError(
25041
+ `expected ALL, SELECT, INSERT, UPDATE or DELETE, found ${describeToken(token)}`,
25042
+ token.position
25043
+ );
25044
+ }
25045
+ function readClause(stream) {
25046
+ const open = stream.expectOperator("(");
25047
+ const expr = readCondition(stream);
25048
+ stream.expectOperator(")");
25049
+ checkExpressionSize(expr, open.position);
25050
+ return { text: printExpr(expr), position: open.position };
25051
+ }
25052
+ function readCreate(stream, start) {
25053
+ stream.expectKeyword("policy");
25054
+ const name = readPolicyName(stream);
25055
+ stream.expectKeyword("on");
25056
+ const table = readTableName(stream);
25057
+ let permissive = true;
25058
+ if (stream.acceptKeyword("as")) {
25059
+ const token = stream.next();
25060
+ if (isKeyword(token, "restrictive")) permissive = false;
25061
+ else if (!isKeyword(token, "permissive"))
25062
+ throw new PolicySyntaxError(
25063
+ `expected PERMISSIVE or RESTRICTIVE, found ${describeToken(token)}`,
25064
+ token.position
25065
+ );
22251
25066
  }
22252
25067
  let command = "all";
22253
25068
  if (stream.acceptKeyword("for")) command = readCommand(stream);
@@ -22278,6 +25093,7 @@ function readCreate(stream, start) {
22278
25093
  position = clause.position;
22279
25094
  }
22280
25095
  if (!roles.ok) throw new Error("unreachable");
25096
+ readEnd(stream);
22281
25097
  const policy = {
22282
25098
  table,
22283
25099
  name,
@@ -22304,21 +25120,31 @@ function readDrop(stream) {
22304
25120
  stream.expectKeyword("on");
22305
25121
  const table = readTableName(stream);
22306
25122
  if (!stream.acceptKeyword("cascade")) stream.acceptKeyword("restrict");
25123
+ readEnd(stream);
22307
25124
  return { kind: "drop", name, table, ifExists };
22308
25125
  }
22309
- function parsePolicyStatement(text) {
22310
- if (policySqlTooLong(text))
25126
+ function readEnd(stream) {
25127
+ stream.acceptOperator(";");
25128
+ const rest = stream.peek();
25129
+ if (rest.kind !== "eof")
25130
+ throw new PolicySyntaxError(
25131
+ isKeyword(rest, "create") || isKeyword(rest, "drop") ? "one statement at a time" : `syntax error at or near ${describeToken(rest)}`,
25132
+ rest.position
25133
+ );
25134
+ }
25135
+ function parsePolicyStatement(text2) {
25136
+ if (policySqlTooLong(text2))
22311
25137
  return {
22312
25138
  ok: false,
22313
25139
  reason: `the statement is longer than ${String(MAX_POLICY_SQL_BYTES)} bytes`,
22314
25140
  position: 1
22315
25141
  };
22316
25142
  try {
22317
- const stream = new TokenStream(tokenize(text));
25143
+ const stream = new TokenStream(tokenize(text2));
22318
25144
  const start = stream.next();
22319
- let statement;
22320
- if (isKeyword(start, "create")) statement = readCreate(stream, start);
22321
- else if (isKeyword(start, "drop")) statement = readDrop(stream);
25145
+ let statement2;
25146
+ if (isKeyword(start, "create")) statement2 = readCreate(stream, start);
25147
+ else if (isKeyword(start, "drop")) statement2 = readDrop(stream);
22322
25148
  else if (isKeyword(start, "alter"))
22323
25149
  throw new PolicySyntaxError(
22324
25150
  "ALTER POLICY is not supported here yet: DROP POLICY and CREATE POLICY again",
@@ -22331,24 +25157,17 @@ function parsePolicyStatement(text) {
22331
25157
  "only CREATE POLICY and DROP POLICY are accepted here",
22332
25158
  start.position
22333
25159
  );
22334
- stream.acceptOperator(";");
22335
- const rest = stream.peek();
22336
- if (rest.kind !== "eof")
22337
- throw new PolicySyntaxError(
22338
- isKeyword(rest, "create") || isKeyword(rest, "drop") ? "one statement at a time" : `syntax error at or near ${describeToken(rest)}`,
22339
- rest.position
22340
- );
22341
- return { ok: true, statement };
25160
+ return { ok: true, statement: statement2 };
22342
25161
  } catch (error51) {
22343
25162
  if (error51 instanceof PolicySyntaxError)
22344
25163
  return { ok: false, reason: error51.reason, position: error51.position };
22345
25164
  throw error51;
22346
25165
  }
22347
25166
  }
22348
- function splitPolicyStatements(text) {
25167
+ function splitPolicyStatements(text2) {
22349
25168
  let tokens;
22350
25169
  try {
22351
- tokens = tokenize(text);
25170
+ tokens = tokenize(text2);
22352
25171
  } catch (error51) {
22353
25172
  if (error51 instanceof PolicySyntaxError)
22354
25173
  return { ok: false, reason: error51.reason, position: error51.position };
@@ -22360,16 +25179,428 @@ function splitPolicyStatements(text) {
22360
25179
  if (token.kind === "eof") break;
22361
25180
  start ??= token.position - 1;
22362
25181
  if (token.kind === "op" && token.value === ";") {
22363
- statements.push(text.slice(start, token.position).trim());
25182
+ statements.push(text2.slice(start, token.position).trim());
22364
25183
  start = void 0;
22365
25184
  }
22366
25185
  }
22367
25186
  if (start !== void 0) {
22368
- const last = text.slice(start).trim();
25187
+ const last = text2.slice(start).trim();
22369
25188
  if (last !== "") statements.push(last);
22370
25189
  }
22371
25190
  return { ok: true, statements };
22372
25191
  }
25192
+ var OWNER_PREFIX = "naro:owner";
25193
+ function ownerComment(text2, position) {
25194
+ if (!text2.startsWith(OWNER_PREFIX)) return void 0;
25195
+ const rest = text2.slice(OWNER_PREFIX.length);
25196
+ if (rest !== "" && !/^[ \t]/.test(rest)) return void 0;
25197
+ let column2 = rest.replace(/^[ \t]+|[ \t]+$/g, "");
25198
+ if (column2.startsWith('"')) {
25199
+ const quoted2 = /^"((?:[^"]|"")*)"$/.exec(column2);
25200
+ if (!quoted2)
25201
+ throw new PolicySyntaxError(
25202
+ "naro:owner needs one column name, bare or in double quotes",
25203
+ position
25204
+ );
25205
+ column2 = (quoted2[1] ?? "").replaceAll('""', '"');
25206
+ }
25207
+ if (column2 === "")
25208
+ throw new PolicySyntaxError("naro:owner needs a column name", position);
25209
+ return column2;
25210
+ }
25211
+ function readComment(stream) {
25212
+ stream.expectKeyword("on");
25213
+ stream.expectKeyword("policy");
25214
+ const name = readPolicyName(stream);
25215
+ stream.expectKeyword("on");
25216
+ const table = readTableName(stream);
25217
+ stream.expectKeyword("is");
25218
+ const token = stream.next();
25219
+ if (isKeyword(token, "null"))
25220
+ return { kind: "comment", name, table, owner: null, isOwner: true };
25221
+ if (token.kind !== "string")
25222
+ throw new PolicySyntaxError(
25223
+ `expected a string or NULL, found ${describeToken(token)}`,
25224
+ token.position
25225
+ );
25226
+ const owner = ownerComment(token.value, token.position);
25227
+ return owner === void 0 ? { kind: "comment", name, table, owner: null, isOwner: false } : { kind: "comment", name, table, owner, isOwner: true };
25228
+ }
25229
+ function readRowLevelSecurity(stream) {
25230
+ if (stream.acceptKeyword("if")) stream.expectKeyword("exists");
25231
+ stream.acceptKeyword("only");
25232
+ const table = readTableName(stream);
25233
+ const token = stream.next();
25234
+ let action;
25235
+ if (isKeyword(token, "enable")) action = "enable";
25236
+ else if (isKeyword(token, "disable")) action = "disable";
25237
+ else if (isKeyword(token, "force")) action = "force";
25238
+ else if (isKeyword(token, "no")) {
25239
+ stream.expectKeyword("force");
25240
+ action = "no-force";
25241
+ } else
25242
+ throw new PolicySyntaxError(
25243
+ `expected ENABLE, DISABLE, FORCE or NO FORCE, found ${describeToken(token)}`,
25244
+ token.position
25245
+ );
25246
+ stream.expectKeyword("row");
25247
+ stream.expectKeyword("level");
25248
+ stream.expectKeyword("security");
25249
+ return { kind: "rls", table, action };
25250
+ }
25251
+ function parseMigrationPolicyStatement(text2) {
25252
+ if (policySqlTooLong(text2))
25253
+ return {
25254
+ ok: false,
25255
+ reason: `the statement is longer than ${String(MAX_POLICY_SQL_BYTES)} bytes`,
25256
+ position: 1
25257
+ };
25258
+ try {
25259
+ const stream = new TokenStream(tokenize(text2));
25260
+ let statement2;
25261
+ if (stream.acceptKeyword("comment")) statement2 = readComment(stream);
25262
+ else if (isKeyword(stream.peek(), "alter") && isKeyword(stream.peek(1), "table")) {
25263
+ stream.next();
25264
+ stream.next();
25265
+ statement2 = readRowLevelSecurity(stream);
25266
+ } else return parsePolicyStatement(text2);
25267
+ readEnd(stream);
25268
+ return { ok: true, statement: statement2 };
25269
+ } catch (error51) {
25270
+ if (error51 instanceof PolicySyntaxError)
25271
+ return { ok: false, reason: error51.reason, position: error51.position };
25272
+ throw error51;
25273
+ }
25274
+ }
25275
+
25276
+ // ../../packages/data-core/src/sql-head.ts
25277
+ var HEAD_TOKENS = 12;
25278
+ var BOM2 = 65279;
25279
+ function isBlankStart(char) {
25280
+ return char === " " || char === " " || char === "\n" || char === "\f" || char === "\r";
25281
+ }
25282
+ function isBlank(char) {
25283
+ return isBlankStart(char) || char === "\v";
25284
+ }
25285
+ function isWordStart(char) {
25286
+ return /[A-Za-z_]/.test(char) || char.charCodeAt(0) >= 128;
25287
+ }
25288
+ function isWordPart(char) {
25289
+ return /[A-Za-z0-9_$]/.test(char) || char.charCodeAt(0) >= 128;
25290
+ }
25291
+ function skipBlanks(text2, from) {
25292
+ let index = from;
25293
+ for (; ; ) {
25294
+ const char = text2.charAt(index);
25295
+ if (isBlankStart(char)) {
25296
+ index++;
25297
+ while (index < text2.length && isBlank(text2.charAt(index))) index++;
25298
+ } else if (text2.charCodeAt(index) === BOM2) index++;
25299
+ else if (char === "-" && text2.charAt(index + 1) === "-") {
25300
+ const end = text2.indexOf("\n", index + 2);
25301
+ index = end < 0 ? text2.length : end;
25302
+ } else if (char === "/" && text2.charAt(index + 1) === "*") {
25303
+ const end = text2.indexOf("*/", index + 2);
25304
+ index = end < 0 ? text2.length : end + 2;
25305
+ } else return index;
25306
+ }
25307
+ }
25308
+ function readQuoted(text2, start, delimiter) {
25309
+ let value = "";
25310
+ let index = start + 1;
25311
+ for (; ; ) {
25312
+ const close = text2.indexOf(delimiter, index);
25313
+ if (close < 0) return void 0;
25314
+ value += text2.slice(index, close);
25315
+ if (text2.charAt(close + 1) === delimiter) {
25316
+ value += delimiter;
25317
+ index = close + 2;
25318
+ continue;
25319
+ }
25320
+ return { text: value, end: close + 1 };
25321
+ }
25322
+ }
25323
+ function headTokens(text2) {
25324
+ const tokens = [];
25325
+ let index = 0;
25326
+ while (tokens.length < HEAD_TOKENS) {
25327
+ index = skipBlanks(text2, index);
25328
+ const char = text2.charAt(index);
25329
+ if (index >= text2.length) break;
25330
+ if (char === '"' || char === "`" || char === "'") {
25331
+ const quoted2 = readQuoted(text2, index, char);
25332
+ if (!quoted2) break;
25333
+ tokens.push({ kind: "name", text: quoted2.text });
25334
+ index = quoted2.end;
25335
+ } else if (char === "[") {
25336
+ const close = text2.indexOf("]", index + 1);
25337
+ if (close < 0) break;
25338
+ tokens.push({ kind: "name", text: text2.slice(index + 1, close) });
25339
+ index = close + 1;
25340
+ } else if ((char === "x" || char === "X") && text2.charAt(index + 1) === "'") {
25341
+ break;
25342
+ } else if (isWordStart(char)) {
25343
+ let end = index + 1;
25344
+ while (end < text2.length && isWordPart(text2.charAt(end))) end++;
25345
+ tokens.push({ kind: "word", text: text2.slice(index, end) });
25346
+ index = end;
25347
+ } else if (char === "." && !/[0-9]/.test(text2.charAt(index + 1))) {
25348
+ tokens.push({ kind: "dot" });
25349
+ index++;
25350
+ } else break;
25351
+ }
25352
+ tokens.push({ kind: "stop" });
25353
+ return tokens;
25354
+ }
25355
+ var OTHER = { kind: "other" };
25356
+ var NO_NAME = {
25357
+ kind: "unreadable",
25358
+ reason: "the table name could not be read"
25359
+ };
25360
+ var OTHER_SCHEMA = {
25361
+ kind: "unreadable",
25362
+ reason: "a table in another schema"
25363
+ };
25364
+ var PUBLIC_SCHEMA = {
25365
+ kind: "unreadable",
25366
+ reason: 'SQLite has no schema "public": write the table name without "public."'
25367
+ };
25368
+ var Head = class {
25369
+ constructor(tokens) {
25370
+ this.tokens = tokens;
25371
+ }
25372
+ token(at) {
25373
+ return this.tokens[Math.min(at, this.tokens.length - 1)] ?? { kind: "stop" };
25374
+ }
25375
+ /** The bare word `word` (lower case), in any ASCII case. */
25376
+ is(at, word) {
25377
+ const token = this.token(at);
25378
+ return token.kind === "word" && asciiLower(token.text) === word;
25379
+ }
25380
+ nameAt(at) {
25381
+ const token = this.token(at);
25382
+ return token.kind === "word" || token.kind === "name" ? token.text : void 0;
25383
+ }
25384
+ /**
25385
+ * SQLite's `fullname`. `main.` is the database a migration writes and is
25386
+ * dropped; `temp.` (in any case, quoted or not, as sqlite3FindDbName
25387
+ * matches it) is a table that goes with the connection; `public.` is
25388
+ * what a Postgres migration writes, said in its own words; any other
25389
+ * schema is one D1 does not have.
25390
+ */
25391
+ table(at) {
25392
+ const first = this.nameAt(at);
25393
+ if (first === void 0) return { ok: false, head: NO_NAME };
25394
+ if (this.token(at + 1).kind !== "dot")
25395
+ return { ok: true, name: first, next: at + 1 };
25396
+ const second = this.nameAt(at + 2);
25397
+ if (second === void 0) return { ok: false, head: NO_NAME };
25398
+ const schema = asciiLower(first);
25399
+ if (schema === "main") return { ok: true, name: second, next: at + 3 };
25400
+ let head = OTHER_SCHEMA;
25401
+ if (schema === "temp") head = OTHER;
25402
+ else if (schema === "public") head = PUBLIC_SCHEMA;
25403
+ return { ok: false, head, next: at + 3 };
25404
+ }
25405
+ };
25406
+ function readCreate2(head) {
25407
+ if (head.is(1, "policy")) return { kind: "create-policy" };
25408
+ if (head.is(1, "temp") || head.is(1, "temporary")) return OTHER;
25409
+ let at;
25410
+ if (head.is(1, "table")) at = 2;
25411
+ else if (head.is(1, "virtual") && head.is(2, "table")) at = 3;
25412
+ else return OTHER;
25413
+ if (head.is(at, "if")) {
25414
+ if (!head.is(at + 1, "not") || !head.is(at + 2, "exists")) return NO_NAME;
25415
+ at += 3;
25416
+ }
25417
+ const table = head.table(at);
25418
+ return table.ok ? { kind: "create-table", table: table.name } : table.head;
25419
+ }
25420
+ function readDrop2(head) {
25421
+ if (head.is(1, "policy")) return { kind: "drop-policy" };
25422
+ if (!head.is(1, "table")) return OTHER;
25423
+ let at = 2;
25424
+ if (head.is(at, "if")) {
25425
+ if (!head.is(at + 1, "exists")) return NO_NAME;
25426
+ at += 2;
25427
+ }
25428
+ const table = head.table(at);
25429
+ return table.ok ? { kind: "drop-table", table: table.name } : table.head;
25430
+ }
25431
+ var RLS_ACTIONS = ["enable", "disable", "force", "no"];
25432
+ function readAlter(head) {
25433
+ if (head.is(1, "policy")) return { kind: "alter-policy" };
25434
+ if (!head.is(1, "table")) return OTHER;
25435
+ const sqlite = head.table(2);
25436
+ const action = sqlite.next;
25437
+ if (action !== void 0) {
25438
+ if (head.is(action, "rename") && head.is(action + 1, "to")) {
25439
+ if (!sqlite.ok) return sqlite.head;
25440
+ const to = head.table(action + 2);
25441
+ return to.ok ? { kind: "rename-table", table: sqlite.name, to: to.name } : to.head;
25442
+ }
25443
+ if (head.is(action, "rename") || head.is(action, "add") || head.is(action, "drop"))
25444
+ return sqlite.ok ? { kind: "alter-table", table: sqlite.name } : sqlite.head;
25445
+ }
25446
+ let at = 2;
25447
+ if (head.is(at, "if") && head.is(at + 1, "exists")) at += 2;
25448
+ if (head.is(at, "only")) at++;
25449
+ const next = head.table(at).next;
25450
+ if (next !== void 0 && RLS_ACTIONS.some((word) => head.is(next, word)))
25451
+ return { kind: "rls" };
25452
+ return sqlite.ok ? OTHER : sqlite.head;
25453
+ }
25454
+ function readSqlHead(text2) {
25455
+ if (text2.includes("\0"))
25456
+ return { kind: "unreadable", reason: "the statement has a NUL character" };
25457
+ const head = new Head(headTokens(text2));
25458
+ if (head.is(0, "create")) return readCreate2(head);
25459
+ if (head.is(0, "drop")) return readDrop2(head);
25460
+ if (head.is(0, "alter")) return readAlter(head);
25461
+ if (head.is(0, "comment") && head.is(1, "on") && head.is(2, "policy"))
25462
+ return { kind: "comment-policy" };
25463
+ if (head.is(0, "grant")) return { kind: "grant" };
25464
+ if (head.is(0, "revoke")) return { kind: "revoke" };
25465
+ return OTHER;
25466
+ }
25467
+
25468
+ // ../../packages/data-core/src/policy-migration.ts
25469
+ var GRANT_REFUSAL = "naro has no GRANT or REVOKE: every table is behind row-level security, so write a policy instead (CREATE POLICY \u2026).";
25470
+ function disableRefusal(table) {
25471
+ return `naro keeps row-level security on for every table. To let everyone through, create a policy: CREATE POLICY "open" ON ${quoteIdent(table)} USING (true) WITH CHECK (true);`;
25472
+ }
25473
+ function relationMissing(table) {
25474
+ return `relation "${table}" does not exist`;
25475
+ }
25476
+ var LATER_TABLE_CHANGE = "a later statement changes a table, so name the table of every column inside this policy's subqueries (for example members.user_id).";
25477
+ function tablesChanged(head) {
25478
+ switch (head.kind) {
25479
+ case "create-table":
25480
+ case "drop-table":
25481
+ case "alter-table":
25482
+ return [asciiLower(head.table)];
25483
+ case "rename-table":
25484
+ return [asciiLower(head.table), asciiLower(head.to)];
25485
+ case "unreadable":
25486
+ return "any";
25487
+ default:
25488
+ return [];
25489
+ }
25490
+ }
25491
+ function readsBareSubqueryColumn(policy) {
25492
+ return [policy.using, policy.check].some((text2) => {
25493
+ if (text2 === null) return false;
25494
+ const parsed = parseExpression(text2);
25495
+ return parsed.ok && unqualifiedInSubquery(parsed.expr) !== void 0;
25496
+ });
25497
+ }
25498
+ function tablesInScope(policy) {
25499
+ const tables = /* @__PURE__ */ new Set([asciiLower(policy.table)]);
25500
+ for (const text2 of [policy.using, policy.check]) {
25501
+ if (text2 === null) continue;
25502
+ const parsed = parseExpression(text2);
25503
+ if (!parsed.ok) continue;
25504
+ for (const subquery of subqueriesOf(parsed.expr))
25505
+ tables.add(asciiLower(subquery.table));
25506
+ }
25507
+ return tables;
25508
+ }
25509
+ function planPolicyMigration(statements) {
25510
+ const count2 = statements.length;
25511
+ const planned = [];
25512
+ let hasPolicyStatements = false;
25513
+ let touchesTables = false;
25514
+ const heads = statements.map((text2) => readSqlHead(text2));
25515
+ const changes = heads.map(tablesChanged);
25516
+ const changedAfter = (index, tables) => changes.slice(index + 1).some(
25517
+ (changed) => changed === "any" || changed.some((table) => tables.has(table))
25518
+ );
25519
+ for (const [index, text2] of statements.entries()) {
25520
+ const refuse = (message) => ({
25521
+ ok: false,
25522
+ index,
25523
+ message: `Statement ${String(index + 1)} of ${String(count2)}: ${message}`
25524
+ });
25525
+ const head = heads[index] ?? readSqlHead(text2);
25526
+ switch (head.kind) {
25527
+ case "grant":
25528
+ case "revoke":
25529
+ return refuse(GRANT_REFUSAL);
25530
+ case "unreadable":
25531
+ return refuse(`${head.reason}.`);
25532
+ case "create-table":
25533
+ case "drop-table":
25534
+ case "rename-table":
25535
+ touchesTables = true;
25536
+ planned.push({ kind: "verbatim", text: text2, head });
25537
+ continue;
25538
+ case "alter-table":
25539
+ case "other":
25540
+ planned.push({ kind: "verbatim", text: text2, head });
25541
+ continue;
25542
+ default:
25543
+ break;
25544
+ }
25545
+ const parsed = parseMigrationPolicyStatement(text2);
25546
+ if (!parsed.ok)
25547
+ return refuse(
25548
+ `${parsed.reason} at character ${String(parsed.position)}.`
25549
+ );
25550
+ const statement2 = parsed.statement;
25551
+ const table = statement2.kind === "create" ? statement2.policy.table : statement2.table;
25552
+ if (isSystemTable(table)) return refuse(`${relationMissing(table)}.`);
25553
+ switch (statement2.kind) {
25554
+ case "create":
25555
+ if (readsBareSubqueryColumn(statement2.policy) && changedAfter(index, tablesInScope(statement2.policy)))
25556
+ return refuse(LATER_TABLE_CHANGE);
25557
+ hasPolicyStatements = true;
25558
+ planned.push({ kind: "create-policy", policy: statement2.policy });
25559
+ break;
25560
+ case "drop":
25561
+ hasPolicyStatements = true;
25562
+ planned.push({
25563
+ kind: "drop-policy",
25564
+ name: statement2.name,
25565
+ table,
25566
+ ifExists: statement2.ifExists
25567
+ });
25568
+ break;
25569
+ case "comment":
25570
+ if (!statement2.isOwner) {
25571
+ planned.push({ kind: "noop" });
25572
+ break;
25573
+ }
25574
+ hasPolicyStatements = true;
25575
+ planned.push({
25576
+ kind: "owner",
25577
+ name: statement2.name,
25578
+ table,
25579
+ column: statement2.owner
25580
+ });
25581
+ break;
25582
+ case "rls":
25583
+ if (statement2.action === "disable")
25584
+ return refuse(disableRefusal(table));
25585
+ planned.push({ kind: "noop" });
25586
+ break;
25587
+ }
25588
+ }
25589
+ return {
25590
+ ok: true,
25591
+ plan: { statements: planned, hasPolicyStatements, touchesTables }
25592
+ };
25593
+ }
25594
+ function policyMigrationSql(policy) {
25595
+ const create = policySql({ ...policy, owner: null });
25596
+ if (policy.owner === null) return create;
25597
+ return `${create}
25598
+ COMMENT ON POLICY ${quotePolicyName(policy.name)} ON ${quoteIdent(policy.table)} IS ${sqlStringLiteral(`naro:owner ${quoteIdent(policy.owner)}`)};`;
25599
+ }
25600
+ function unreadablePolicyComment(policy) {
25601
+ return `-- Policy ${commentText(quotePolicyName(policy.name))} on ${commentText(quoteIdent(policy.table))} cannot be read (${commentText(policy.invalid)}); the requests it applies to are refused until it is dropped and created again.`;
25602
+ }
25603
+ var SYSTEM_PREDICATE = systemTablePredicate("name");
22373
25604
 
22374
25605
  // ../../packages/data-core/src/redirect-origins.ts
22375
25606
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
@@ -22386,19 +25617,19 @@ var TYPED_PATH = /^[a-z][a-z0-9+.-]*:\/\/[^/]*(\/.*)?$/i;
22386
25617
  var SEGMENT = /^[A-Za-z0-9._~-]+$/;
22387
25618
  function parseRedirectEntry(value) {
22388
25619
  if (typeof value !== "string") return null;
22389
- const text = value.trim();
22390
- if (text === "" || text.length > MAX_ENTRY_LENGTH) return null;
22391
- if (/[\s?#*%\\]/.test(text)) return null;
25620
+ const text2 = value.trim();
25621
+ if (text2 === "" || text2.length > MAX_ENTRY_LENGTH) return null;
25622
+ if (/[\s?#*%\\]/.test(text2)) return null;
22392
25623
  let url2;
22393
25624
  try {
22394
- url2 = new URL(text);
25625
+ url2 = new URL(text2);
22395
25626
  } catch {
22396
25627
  return null;
22397
25628
  }
22398
25629
  if (url2.username !== "" || url2.password !== "") return null;
22399
25630
  if (url2.search !== "" || url2.hash !== "") return null;
22400
25631
  if (!isSecureOrLoopback(url2)) return null;
22401
- const typed = TYPED_PATH.exec(text)?.[1] ?? "/";
25632
+ const typed = TYPED_PATH.exec(text2)?.[1] ?? "/";
22402
25633
  if (typed !== url2.pathname) return null;
22403
25634
  const path = url2.pathname;
22404
25635
  if (path !== "/") {
@@ -22604,7 +25835,7 @@ function tableOptions(sql) {
22604
25835
  }
22605
25836
  function rowidAlias(table) {
22606
25837
  if (tableOptions(table.sql).withoutRowid) return null;
22607
- const keys = table.columns.filter((column) => column.primaryKey);
25838
+ const keys = table.columns.filter((column2) => column2.primaryKey);
22608
25839
  const only = keys.length === 1 ? keys[0] : void 0;
22609
25840
  return only && only.type.trim().toUpperCase() === "INTEGER" ? only.name : null;
22610
25841
  }
@@ -22633,16 +25864,16 @@ async function policiesForTypes(client, projectId) {
22633
25864
  };
22634
25865
  }
22635
25866
  }
22636
- function isNullable(column, alias) {
22637
- return !column.notNull && column.name !== alias;
25867
+ function isNullable(column2, alias) {
25868
+ return !column2.notNull && column2.name !== alias;
22638
25869
  }
22639
- function rowProperty(column, { alias, strict }) {
22640
- const type = READ[affinity(column.type, strict)];
22641
- return ` ${keyLiteral(column.name)}: ${withNull(type, isNullable(column, alias))};`;
25870
+ function rowProperty(column2, { alias, strict }) {
25871
+ const type = READ[affinity(column2.type, strict)];
25872
+ return ` ${keyLiteral(column2.name)}: ${withNull(type, isNullable(column2, alias))};`;
22642
25873
  }
22643
- function writeProperty(column, { alias, strict }, optional2) {
22644
- const type = WRITE[affinity(column.type, strict)];
22645
- return ` ${keyLiteral(column.name)}${optional2 ? "?" : ""}: ${withNull(type, isNullable(column, alias))};`;
25874
+ function writeProperty(column2, { alias, strict }, optional2) {
25875
+ const type = WRITE[affinity(column2.type, strict)];
25876
+ return ` ${keyLiteral(column2.name)}${optional2 ? "?" : ""}: ${withNull(type, isNullable(column2, alias))};`;
22646
25877
  }
22647
25878
  function block(name, lines) {
22648
25879
  return lines.length === 0 ? [`export interface ${name} {}`] : [`export interface ${name} {`, ...lines, "}"];
@@ -22652,8 +25883,8 @@ function member(indent, name, lines) {
22652
25883
  }
22653
25884
  function tableEntry(described) {
22654
25885
  const { table, row, alias, owner } = described;
22655
- const writable = table.columns.filter((column) => column.generated !== true);
22656
- const optionalOnInsert = (column) => isNullable(column, alias) || column.defaultValue !== null || column.name === alias || asciiLower(column.name) === owner;
25886
+ const writable = table.columns.filter((column2) => column2.generated !== true);
25887
+ const optionalOnInsert = (column2) => isNullable(column2, alias) || column2.defaultValue !== null || column2.name === alias || asciiLower(column2.name) === owner;
22657
25888
  return [
22658
25889
  ` ${keyLiteral(table.name)}: {`,
22659
25890
  ` Row: ${row};`,
@@ -22661,13 +25892,13 @@ function tableEntry(described) {
22661
25892
  " ",
22662
25893
  "Insert",
22663
25894
  writable.map(
22664
- (column) => writeProperty(column, described, optionalOnInsert(column))
25895
+ (column2) => writeProperty(column2, described, optionalOnInsert(column2))
22665
25896
  )
22666
25897
  ),
22667
25898
  ...member(
22668
25899
  " ",
22669
25900
  "Update",
22670
- writable.map((column) => writeProperty(column, described, true))
25901
+ writable.map((column2) => writeProperty(column2, described, true))
22671
25902
  ),
22672
25903
  " };"
22673
25904
  ];
@@ -22706,7 +25937,7 @@ function generateTypes(tables, options = {}) {
22706
25937
  "",
22707
25938
  ...block(
22708
25939
  row,
22709
- table.columns.map((column) => rowProperty(column, entry))
25940
+ table.columns.map((column2) => rowProperty(column2, entry))
22710
25941
  )
22711
25942
  );
22712
25943
  }
@@ -22792,7 +26023,7 @@ function ensureGitignoreHasNaro(dir) {
22792
26023
  }
22793
26024
 
22794
26025
  // ../../packages/sdk/src/lint.ts
22795
- function byText(left, right) {
26026
+ function byText2(left, right) {
22796
26027
  return left < right ? -1 : left > right ? 1 : 0;
22797
26028
  }
22798
26029
  function lintedTables(input) {
@@ -22820,8 +26051,8 @@ function brokenForeignKeys(input) {
22820
26051
  parents.set(violation.parent, group);
22821
26052
  groups.set(violation.table, parents);
22822
26053
  }
22823
- return [...groups.entries()].toSorted(([left], [right]) => byText(left, right)).flatMap(
22824
- ([table, parents]) => [...parents.entries()].toSorted(([left], [right]) => byText(left, right)).map(([parent, group]) => ({
26054
+ return [...groups.entries()].toSorted(([left], [right]) => byText2(left, right)).flatMap(
26055
+ ([table, parents]) => [...parents.entries()].toSorted(([left], [right]) => byText2(left, right)).map(([parent, group]) => ({
22825
26056
  rule: "broken-foreign-key",
22826
26057
  severity: "error",
22827
26058
  object: table,
@@ -22846,7 +26077,7 @@ function tableIssues(input) {
22846
26077
  (index) => index.columns[0] === void 0 ? [] : [index.columns[0].toLowerCase()]
22847
26078
  )
22848
26079
  );
22849
- const keyColumns = table.columns.filter((column) => column.primaryKey);
26080
+ const keyColumns = table.columns.filter((column2) => column2.primaryKey);
22850
26081
  const declared = keyColumns.length === 1 && keyColumns[0]?.type.trim().toUpperCase() === "INTEGER" ? keyColumns[0]?.name.toLowerCase() ?? null : null;
22851
26082
  const rowidColumn = declared !== null && !leading.has(declared) ? declared : null;
22852
26083
  if (keyColumns.length === 0)
@@ -22856,12 +26087,12 @@ function tableIssues(input) {
22856
26087
  object: table.name,
22857
26088
  message: "The table has no primary key, so its rows are identified by a rowid that changes after a VACUUM or a restore."
22858
26089
  });
22859
- for (const column of keyColumns) {
22860
- if (rowidColumn !== null || column.notNull) continue;
26090
+ for (const column2 of keyColumns) {
26091
+ if (rowidColumn !== null || column2.notNull) continue;
22861
26092
  issues.push({
22862
26093
  rule: "nullable-primary-key",
22863
26094
  severity: "warn",
22864
- object: `${table.name}.${column.name}`,
26095
+ object: `${table.name}.${column2.name}`,
22865
26096
  message: "The primary key column is not INTEGER and has no NOT NULL, so SQLite accepts NULL in it."
22866
26097
  });
22867
26098
  }
@@ -22878,11 +26109,73 @@ function tableIssues(input) {
22878
26109
  }
22879
26110
  return issues;
22880
26111
  }
26112
+ var RULE_OF_KIND = {
26113
+ read: "rls_policy_invalid",
26114
+ compile: "rls_policy_invalid",
26115
+ default: "rls_policy_text_default",
26116
+ unindexed: "rls_policy_unindexed"
26117
+ };
26118
+ var POLICY_WARNING_RULES = [
26119
+ "rls_policy_text_default",
26120
+ "rls_policy_unindexed"
26121
+ ];
26122
+ function policyIssues(input) {
26123
+ const present = new Set(input.tables.map(asciiLower));
26124
+ const found = [
26125
+ ...policySetProblems(
26126
+ input.snapshot,
26127
+ input.policies,
26128
+ input.indexes === void 0 ? {} : { indexes: input.indexes }
26129
+ ).map((problem) => ({
26130
+ rule: RULE_OF_KIND[problem.kind],
26131
+ table: problem.table,
26132
+ policy: problem.policy ?? null,
26133
+ message: problem.message
26134
+ })),
26135
+ ...input.unreadable.filter((row) => present.has(asciiLower(row.table))).map((row) => ({
26136
+ rule: "rls_policy_invalid",
26137
+ table: row.table,
26138
+ policy: row.name,
26139
+ message: `Policy ${row.name} on ${row.table} cannot be read (${row.reason}); the requests it applies to are refused until it is dropped and created again.`
26140
+ }))
26141
+ ];
26142
+ const issues = found.toSorted(
26143
+ (left, right) => byText2(left.table, right.table) || byText2(left.policy ?? "", right.policy ?? "") || byText2(left.message, right.message)
26144
+ ).map((entry) => ({
26145
+ rule: entry.rule,
26146
+ severity: entry.rule === "rls_policy_invalid" ? "error" : "warn",
26147
+ object: entry.policy === null ? entry.table : `${entry.table}.${entry.policy}`,
26148
+ message: entry.message
26149
+ }));
26150
+ return {
26151
+ errors: issues.filter((issue2) => issue2.severity === "error"),
26152
+ warnings: issues.filter((issue2) => issue2.severity === "warn").toSorted(
26153
+ (left, right) => POLICY_WARNING_RULES.indexOf(left.rule) - POLICY_WARNING_RULES.indexOf(right.rule)
26154
+ )
26155
+ };
26156
+ }
26157
+ function tablesWithoutPolicy(input) {
26158
+ const covered = new Set(
26159
+ [...input.policies, ...input.unreadable].map(
26160
+ (policy) => asciiLower(policy.table)
26161
+ )
26162
+ );
26163
+ return input.tables.filter((name) => !isSystemTable(name) && !covered.has(asciiLower(name))).toSorted(byText2).map((name) => ({
26164
+ rule: "rls_no_policy",
26165
+ severity: "warn",
26166
+ object: name,
26167
+ message: `Table ${name} has no policy, so the Data API refuses every request to it. Add one in a migration: CREATE POLICY \u2026`
26168
+ }));
26169
+ }
22881
26170
  function lintSchema(input) {
26171
+ const policies = input.policies === void 0 ? void 0 : policyIssues(input.policies);
22882
26172
  return [
22883
26173
  ...brokenForeignKeys(input),
22884
26174
  ...integrity(input),
26175
+ ...policies?.errors ?? [],
22885
26176
  ...tableIssues(input),
26177
+ ...input.policies === void 0 ? [] : tablesWithoutPolicy(input.policies),
26178
+ ...policies?.warnings ?? [],
22886
26179
  ...input.triggers.filter((name) => !isSystemTable(name)).map((name) => ({
22887
26180
  rule: "trigger-not-reproducible",
22888
26181
  severity: "warn",
@@ -22900,10 +26193,10 @@ function lintSchema(input) {
22900
26193
 
22901
26194
  // ../../packages/sdk/src/migrations.ts
22902
26195
  import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
22903
- import { resolve as resolve8 } from "path";
26196
+ import { resolve as resolve9 } from "path";
22904
26197
  function resolveMigrationsDir(input) {
22905
- if (input.dir) return resolve8(input.baseDir, input.dir);
22906
- return resolve8(input.baseDir, input.rootDirectory ?? "", "migrations");
26198
+ if (input.dir) return resolve9(input.baseDir, input.dir);
26199
+ return resolve9(input.baseDir, input.rootDirectory ?? "", "migrations");
22907
26200
  }
22908
26201
  function sortMigrationNames(names) {
22909
26202
  return names.toSorted((a, b) => {
@@ -22985,6 +26278,8 @@ function migrationNameMismatch(sent, recorded) {
22985
26278
  // ../../packages/sdk/src/reset-plan.ts
22986
26279
  var RESET_DEFER_FOREIGN_KEYS = "pragma defer_foreign_keys = true;";
22987
26280
  var RESET_BATCH_STATEMENTS = 50;
26281
+ var RESET_CLEAR_POLICIES = `DELETE FROM ${NAMED_POLICY_TABLE};`;
26282
+ var RESET_BACKEND_OUTDATED = "The project backend is older than named policies. Run `naro backend enable` to update it, then try again.";
22988
26283
  function clearInternal(internal) {
22989
26284
  return [
22990
26285
  ...internal.sqliteSequence ? ["DELETE FROM sqlite_sequence;"] : [],
@@ -23087,9 +26382,9 @@ function tableGroups(names, edges) {
23087
26382
  }
23088
26383
  return order;
23089
26384
  }
23090
- function pack(groups) {
26385
+ function pack(groups, first) {
23091
26386
  const batches = [];
23092
- let current = [RESET_DEFER_FOREIGN_KEYS];
26387
+ let current = [RESET_DEFER_FOREIGN_KEYS, ...first];
23093
26388
  for (const group of groups) {
23094
26389
  if (current.length > 1 && current.length + group.length > RESET_BATCH_STATEMENTS) {
23095
26390
  batches.push(current);
@@ -23114,7 +26409,7 @@ function refuseOversizedCycle(groups) {
23114
26409
  `A foreign key cycle of ${String(cycle.length)} tables cannot be dropped in one request (the limit is ${String(room)}): ${nameList(cycle)}. Break the cycle or drop those tables by hand.`
23115
26410
  );
23116
26411
  }
23117
- function buildResetPlan(input) {
26412
+ function buildResetPlan(input, options = {}) {
23118
26413
  const objects = input.objects.filter((object2) => !isSystemTable(object2.name));
23119
26414
  const views = sorted(
23120
26415
  objects.filter((object2) => object2.type === "view").map((object2) => object2.name)
@@ -23145,10 +26440,113 @@ function buildResetPlan(input) {
23145
26440
  ...clear.length > 0 ? [clear] : []
23146
26441
  ];
23147
26442
  return {
23148
- batches: pack(groups),
26443
+ batches: pack(
26444
+ groups,
26445
+ options.clearPolicies === true ? [RESET_CLEAR_POLICIES] : []
26446
+ ),
23149
26447
  dropped: { tables: ordered.flat(), views, triggers }
23150
26448
  };
23151
26449
  }
26450
+ function sameTable(left, right) {
26451
+ return asciiLower(left) === asciiLower(right);
26452
+ }
26453
+ function policiesFromFiles(files) {
26454
+ let policies = [];
26455
+ const tables = /* @__PURE__ */ new Set();
26456
+ const forget = (table) => {
26457
+ policies = policies.filter((policy) => !sameTable(policy.table, table));
26458
+ };
26459
+ for (const [file2, sql] of files.entries()) {
26460
+ const split = splitStatements(sql);
26461
+ if (!split.ok)
26462
+ return {
26463
+ policies,
26464
+ tables,
26465
+ refused: { file: file2, reason: `sql could not be parsed: ${split.reason}.` }
26466
+ };
26467
+ const planned = planPolicyMigration(
26468
+ split.statements.map((statement2) => statement2.text)
26469
+ );
26470
+ if (!planned.ok)
26471
+ return {
26472
+ policies,
26473
+ tables,
26474
+ refused: { file: file2, reason: planned.message }
26475
+ };
26476
+ for (const statement2 of planned.plan.statements) {
26477
+ if (statement2.kind === "create-policy") {
26478
+ const { table, name } = statement2.policy;
26479
+ policies.push({ table, name });
26480
+ } else if (statement2.kind === "drop-policy") {
26481
+ policies = policies.filter(
26482
+ (policy) => !(sameTable(policy.table, statement2.table) && policy.name === statement2.name)
26483
+ );
26484
+ } else if (statement2.kind === "verbatim") {
26485
+ const head = statement2.head;
26486
+ if (head.kind === "create-table") {
26487
+ if (!tables.has(asciiLower(head.table))) forget(head.table);
26488
+ tables.add(asciiLower(head.table));
26489
+ } else if (head.kind === "drop-table") {
26490
+ forget(head.table);
26491
+ tables.delete(asciiLower(head.table));
26492
+ } else if (head.kind === "rename-table") {
26493
+ if (!tables.has(asciiLower(head.to))) forget(head.to);
26494
+ tables.delete(asciiLower(head.table));
26495
+ tables.add(asciiLower(head.to));
26496
+ }
26497
+ }
26498
+ }
26499
+ }
26500
+ return { policies, tables };
26501
+ }
26502
+ function policiesLostByReset(input) {
26503
+ const present = new Set(input.tables.map(asciiLower));
26504
+ const made = policiesFromFiles(input.files);
26505
+ if (made.refused) return { lost: [], withTables: [], refused: made.refused };
26506
+ const gone = input.current.filter(
26507
+ (policy) => present.has(asciiLower(policy.table)) && !made.policies.some(
26508
+ (other) => sameTable(other.table, policy.table) && other.name === policy.name
26509
+ )
26510
+ );
26511
+ return {
26512
+ lost: gone.filter((policy) => made.tables.has(asciiLower(policy.table))),
26513
+ withTables: gone.filter(
26514
+ (policy) => !made.tables.has(asciiLower(policy.table))
26515
+ )
26516
+ };
26517
+ }
26518
+ function resetPoliciesLostMessage(lost) {
26519
+ return `These ${String(lost.length)} policies are not in your migration files and will be gone after the reset: ${lost.map((policy) => `${policy.table}.${policy.name}`).join(", ")}. To keep them, add this to a migration first:`;
26520
+ }
26521
+ function resetPoliciesWithTablesMessage(policies) {
26522
+ return `Policies of tables your migration files do not create: ${policies.map((policy) => `${policy.table}.${policy.name}`).join(", ")} (they go with their tables).`;
26523
+ }
26524
+ function resetFileRefusedMessage(file2, reason) {
26525
+ return `${file2} would be refused: ${reason}`;
26526
+ }
26527
+ function storedPolicySql(row) {
26528
+ const policy = loadPolicy(row);
26529
+ if (policy === void 0) {
26530
+ const comment = unreadablePolicyComment({
26531
+ name: typeof row.name === "string" ? row.name : "(unnamed)",
26532
+ table: typeof row.table_name === "string" ? row.table_name : "",
26533
+ invalid: "the table name is missing"
26534
+ });
26535
+ return { sql: comment, statements: [comment] };
26536
+ }
26537
+ if (policy.invalid !== null) {
26538
+ const comment = unreadablePolicyComment({
26539
+ name: policy.name,
26540
+ table: policy.table,
26541
+ invalid: policy.invalid
26542
+ });
26543
+ return { sql: comment, statements: [comment] };
26544
+ }
26545
+ const sql = policyMigrationSql(policy);
26546
+ if (policy.owner === null) return { sql, statements: [sql] };
26547
+ const create = policyMigrationSql({ ...policy, owner: null });
26548
+ return { sql, statements: [create, sql.slice(create.length + 1)] };
26549
+ }
23152
26550
 
23153
26551
  // src/env-consent.ts
23154
26552
  import { existsSync as existsSync6 } from "fs";
@@ -23218,10 +26616,10 @@ var SUPPORTED_REMOTE = [
23218
26616
  /^ssh:\/\/[^\s]+$/,
23219
26617
  /^git@[A-Za-z0-9.-]+:[A-Za-z0-9._/-]+(\.git)?$/
23220
26618
  ];
23221
- var defaultExec = (args, cwd) => new Promise((resolve10, reject) => {
26619
+ var defaultExec = (args, cwd) => new Promise((resolve11, reject) => {
23222
26620
  execFile("git", args, { cwd }, (error51, stdout) => {
23223
26621
  if (error51) reject(error51);
23224
- else resolve10(stdout);
26622
+ else resolve11(stdout);
23225
26623
  });
23226
26624
  });
23227
26625
  async function tryExec(exec, args, cwd) {
@@ -23368,8 +26766,8 @@ async function uploadArtifact(artifact, opts) {
23368
26766
  }
23369
26767
 
23370
26768
  // src/deploy.ts
23371
- function parseDotenv2(text) {
23372
- return parseDotenv(text);
26769
+ function parseDotenv2(text2) {
26770
+ return parseDotenv(text2);
23373
26771
  }
23374
26772
  function readProjectEnv2(dir) {
23375
26773
  return readProjectEnv(dir);
@@ -23392,7 +26790,7 @@ function eventsObserver(cp3, deploymentId, token, fetchImpl = fetch, logIngestUr
23392
26790
  } catch (error51) {
23393
26791
  lastError = error51;
23394
26792
  await new Promise(
23395
- (resolve10) => setTimeout(resolve10, 500 * (attempt + 1))
26793
+ (resolve11) => setTimeout(resolve11, 500 * (attempt + 1))
23396
26794
  );
23397
26795
  }
23398
26796
  }
@@ -23528,7 +26926,7 @@ function localDependencies(sourceDir) {
23528
26926
  async function deployDirectory(sourceDir, options, log2) {
23529
26927
  const cpUrl = controlPlaneUrl();
23530
26928
  const production = options.production ?? false;
23531
- const appRoot = options.rootDirectory ? resolve9(sourceDir, options.rootDirectory) : sourceDir;
26929
+ const appRoot = options.rootDirectory ? resolve10(sourceDir, options.rootDirectory) : sourceDir;
23532
26930
  const framework = detectFramework2(appRoot);
23533
26931
  if (!framework.deployable && !options.force) {
23534
26932
  throw new NaroError(
@@ -23682,6 +27080,7 @@ export {
23682
27080
  parseGitHubRemote,
23683
27081
  schemaMigrationTitle,
23684
27082
  projectPath,
27083
+ splitStatements,
23685
27084
  NaroError,
23686
27085
  isNaroError,
23687
27086
  toNaroError,
@@ -23709,12 +27108,22 @@ export {
23709
27108
  summarizeStatement,
23710
27109
  serializeDotenv,
23711
27110
  systemTablePredicate,
27111
+ SCHEMA_SQL,
27112
+ tableNames,
27113
+ snapshotFromRows,
23712
27114
  quoteIdent,
23713
27115
  commentText,
23714
27116
  quotePolicyName,
27117
+ NAMED_POLICY_TABLE,
27118
+ NAMED_POLICY_SQL,
27119
+ loadPolicy,
23715
27120
  parsePolicyNode,
27121
+ INDEX_SQL,
27122
+ indexesFromRows,
23716
27123
  parsePolicyStatement,
23717
27124
  splitPolicyStatements,
27125
+ planPolicyMigration,
27126
+ unreadablePolicyComment,
23718
27127
  parseRedirectEntry,
23719
27128
  formatRedirectEntry,
23720
27129
  policiesForTypes,
@@ -23732,7 +27141,13 @@ export {
23732
27141
  pendingMigrationNames,
23733
27142
  migrationsPendingError,
23734
27143
  migrationNameMismatch,
27144
+ RESET_BACKEND_OUTDATED,
23735
27145
  buildResetPlan,
27146
+ policiesLostByReset,
27147
+ resetPoliciesLostMessage,
27148
+ resetPoliciesWithTablesMessage,
27149
+ resetFileRefusedMessage,
27150
+ storedPolicySql,
23736
27151
  promptForMissingEnv,
23737
27152
  detectGitInfo,
23738
27153
  parseDotenv2 as parseDotenv,