naro-cli 0.10.0 → 0.11.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 join23, resolve as resolve9 } from "path";
10
+ import { basename as basename3, dirname as dirname4, join as join24, resolve as resolve9 } from "path";
11
11
  import { fileURLToPath } from "url";
12
12
 
13
13
  // ../../packages/build/src/artifact-upload.ts
@@ -226,7 +226,22 @@ function hashAsset(data, contentType2) {
226
226
  // ../../packages/build/src/local-build-executor.ts
227
227
  import { access as access8, mkdtemp, readFile as readFile12, rm as rm2 } from "fs/promises";
228
228
  import { tmpdir } from "os";
229
- import { dirname as dirname3, join as join16, resolve as resolve7 } from "path";
229
+ import { dirname as dirname3, join as join17, resolve as resolve7 } from "path";
230
+
231
+ // ../../packages/core/src/route-patterns.ts
232
+ var ROUTE_PATTERN = /^\/[A-Za-z0-9\-._~%!$&()*+,;=:@/[\]]*$/;
233
+ var MAX_ROUTE_LENGTH = 256;
234
+ var MAX_ROUTES = 2e3;
235
+ var MAX_ROUTE_LIST_BYTES = 64 * 1024;
236
+ function isRoutePattern(value) {
237
+ return typeof value === "string" && value.length <= MAX_ROUTE_LENGTH && ROUTE_PATTERN.test(value);
238
+ }
239
+ function sanitizeRoutes(value) {
240
+ if (!Array.isArray(value)) return void 0;
241
+ const routes = [...new Set(value.filter(isRoutePattern))].sort();
242
+ if (routes.length > MAX_ROUTES) return void 0;
243
+ return JSON.stringify(routes).length > MAX_ROUTE_LIST_BYTES ? void 0 : routes;
244
+ }
230
245
 
231
246
  // ../../packages/core/src/artifact-manifest.ts
232
247
  var MAX_FILE_BYTES = 25 * 1024 * 1024;
@@ -4950,8 +4965,8 @@ function az_default() {
4950
4965
  }
4951
4966
 
4952
4967
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/be.js
4953
- function getBelarusianPlural(count, one, few, many) {
4954
- const absCount = Math.abs(count);
4968
+ function getBelarusianPlural(count2, one, few, many) {
4969
+ const absCount = Math.abs(count2);
4955
4970
  const lastDigit = absCount % 10;
4956
4971
  const lastTwoDigits = absCount % 100;
4957
4972
  if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
@@ -7029,8 +7044,8 @@ function hu_default() {
7029
7044
  }
7030
7045
 
7031
7046
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/hy.js
7032
- function getArmenianPlural(count, one, many) {
7033
- return Math.abs(count) === 1 ? one : many;
7047
+ function getArmenianPlural(count2, one, many) {
7048
+ return Math.abs(count2) === 1 ? one : many;
7034
7049
  }
7035
7050
  function withDefiniteArticle(word) {
7036
7051
  if (!word)
@@ -9158,8 +9173,8 @@ function ro_default() {
9158
9173
  }
9159
9174
 
9160
9175
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ru.js
9161
- function getRussianPlural(count, one, few, many) {
9162
- const absCount = Math.abs(count);
9176
+ function getRussianPlural(count2, one, few, many) {
9177
+ const absCount = Math.abs(count2);
9163
9178
  const lastDigit = absCount % 10;
9164
9179
  const lastTwoDigits = absCount % 100;
9165
9180
  if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
@@ -16015,6 +16030,93 @@ async function detectFramework(root, overrides = {}) {
16015
16030
  };
16016
16031
  }
16017
16032
 
16033
+ // ../../packages/build/src/next-routes.ts
16034
+ import { readdir as readdir2, stat as stat2 } from "fs/promises";
16035
+ import { join as join8 } from "path";
16036
+ var APP_PAGE_FILE = /^page\.(?:js|jsx|ts|tsx|md|mdx)$/;
16037
+ var PAGES_ROUTER_FILE = /^(.+)\.(?:js|jsx|ts|tsx)$/;
16038
+ var DECLARATION_FILE = /\.d\.ts$/;
16039
+ var PAGES_ROUTER_SPECIAL = /* @__PURE__ */ new Set(["_app", "_document", "_error", "api"]);
16040
+ var DYNAMIC_SEGMENT = /^\[.+\]$/;
16041
+ var ROUTE_GROUP = /^\(.+\)$/;
16042
+ var INTERCEPTING = /^\(\.{1,3}\)/;
16043
+ var LITERAL_IN_PATHNAME = /%(?:21|24|26|28|29|2A|2B|2C|3B|3D|3A|40)/g;
16044
+ function errorCode(error51) {
16045
+ return error51?.code;
16046
+ }
16047
+ async function firstDirectory(root, candidates) {
16048
+ for (const candidate of candidates) {
16049
+ const path = join8(root, candidate);
16050
+ try {
16051
+ if ((await stat2(path)).isDirectory()) return path;
16052
+ } catch (error51) {
16053
+ const code = errorCode(error51);
16054
+ if (code !== "ENOENT" && code !== "ENOTDIR") throw error51;
16055
+ }
16056
+ }
16057
+ return null;
16058
+ }
16059
+ function urlSegment(name, router) {
16060
+ if (DYNAMIC_SEGMENT.test(name)) return name;
16061
+ const decoded = router === "app" ? name.replace(/%5F/gi, "_") : name;
16062
+ return encodeURIComponent(decoded).replace(/'/g, "%27").replace(
16063
+ LITERAL_IN_PATHNAME,
16064
+ (escape) => String.fromCharCode(Number.parseInt(escape.slice(1), 16))
16065
+ );
16066
+ }
16067
+ function toRoute(segments) {
16068
+ return `/${segments.join("/")}`;
16069
+ }
16070
+ async function readAppRouter(dir, segments, routes) {
16071
+ const entries = await readdir2(dir, { withFileTypes: true });
16072
+ if (entries.some((entry) => entry.isFile() && APP_PAGE_FILE.test(entry.name)))
16073
+ routes.add(toRoute(segments));
16074
+ for (const entry of entries) {
16075
+ if (!entry.isDirectory()) continue;
16076
+ const name = entry.name;
16077
+ if (name.startsWith("_") || INTERCEPTING.test(name)) continue;
16078
+ const next = ROUTE_GROUP.test(name) || name.startsWith("@") ? segments : [...segments, urlSegment(name, "app")];
16079
+ await readAppRouter(join8(dir, name), next, routes);
16080
+ }
16081
+ }
16082
+ async function readPagesRouter(dir, segments, routes) {
16083
+ const entries = await readdir2(dir, { withFileTypes: true });
16084
+ for (const entry of entries) {
16085
+ if (entry.isDirectory()) {
16086
+ if (segments.length === 0 && entry.name === "api") continue;
16087
+ await readPagesRouter(
16088
+ join8(dir, entry.name),
16089
+ [...segments, urlSegment(entry.name, "pages")],
16090
+ routes
16091
+ );
16092
+ continue;
16093
+ }
16094
+ if (!entry.isFile() || DECLARATION_FILE.test(entry.name)) continue;
16095
+ const base2 = PAGES_ROUTER_FILE.exec(entry.name)?.[1];
16096
+ if (!base2) continue;
16097
+ if (segments.length === 0 && PAGES_ROUTER_SPECIAL.has(base2)) continue;
16098
+ routes.add(
16099
+ toRoute(
16100
+ base2 === "index" ? segments : [...segments, urlSegment(base2, "pages")]
16101
+ )
16102
+ );
16103
+ }
16104
+ }
16105
+ async function listNextRoutes(appRoot) {
16106
+ const app = await firstDirectory(appRoot, ["app", join8("src", "app")]);
16107
+ const pages = await firstDirectory(appRoot, ["pages", join8("src", "pages")]);
16108
+ if (!app && !pages) return null;
16109
+ const routes = /* @__PURE__ */ new Set();
16110
+ if (app) await readAppRouter(app, [], routes);
16111
+ if (pages) await readPagesRouter(pages, [], routes);
16112
+ const valid = sanitizeRoutes([...routes]);
16113
+ if (!valid)
16114
+ throw new Error(
16115
+ "the app has more than 2,000 routes or its route list is over 64 KiB"
16116
+ );
16117
+ return valid.length > 0 ? valid : null;
16118
+ }
16119
+
16018
16120
  // ../../packages/build/src/process.ts
16019
16121
  import { spawn } from "child_process";
16020
16122
  import { createInterface } from "readline";
@@ -16246,8 +16348,8 @@ function resolveAppRoot(checkout, configuredRoot) {
16246
16348
  }
16247
16349
 
16248
16350
  // ../../packages/build/src/static-artifact.ts
16249
- import { readdir as readdir2, readFile as readFile7 } from "fs/promises";
16250
- import { isAbsolute as isAbsolute3, join as join8, relative as relative3, resolve as resolve4, sep as sep2 } from "path";
16351
+ import { readdir as readdir3, readFile as readFile7 } from "fs/promises";
16352
+ import { isAbsolute as isAbsolute3, join as join9, relative as relative3, resolve as resolve4, sep as sep2 } from "path";
16251
16353
  var SHIM_WORKER_SOURCE = "export default { fetch: (request, env) => env.ASSETS.fetch(request) };\n";
16252
16354
  var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules"]);
16253
16355
  var ALLOWED_DOTFILE = ".well-known";
@@ -16270,12 +16372,12 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
16270
16372
  );
16271
16373
  const assets = [];
16272
16374
  async function visit(directory) {
16273
- const entries = await readdir2(directory, { withFileTypes: true });
16375
+ const entries = await readdir3(directory, { withFileTypes: true });
16274
16376
  for (const entry of entries.sort(
16275
16377
  (left, right) => left.name.localeCompare(right.name)
16276
16378
  )) {
16277
16379
  if (excluded(entry.name)) continue;
16278
- const path = join8(directory, entry.name);
16380
+ const path = join9(directory, entry.name);
16279
16381
  if (entry.isSymbolicLink())
16280
16382
  throw new DeploymentError(
16281
16383
  "ARTIFACT_INVALID",
@@ -16331,7 +16433,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
16331
16433
  // ../../packages/build/src/strategy/fingerprint.ts
16332
16434
  import { createHash as createHash3 } from "crypto";
16333
16435
  import { readFile as readFile8 } from "fs/promises";
16334
- import { join as join9, relative as relative4, sep as sep3 } from "path";
16436
+ import { join as join10, relative as relative4, sep as sep3 } from "path";
16335
16437
  var APP_ROOT_FILES = [
16336
16438
  "package.json",
16337
16439
  "next.config.js",
@@ -16375,11 +16477,11 @@ async function hashFile(path) {
16375
16477
  }
16376
16478
  async function hashStrategyInputs(checkout, appRoot) {
16377
16479
  const candidates = /* @__PURE__ */ new Set();
16378
- for (const name of CHECKOUT_FILES) candidates.add(join9(checkout, name));
16379
- for (const name of APP_ROOT_FILES) candidates.add(join9(appRoot, name));
16480
+ for (const name of CHECKOUT_FILES) candidates.add(join10(checkout, name));
16481
+ for (const name of APP_ROOT_FILES) candidates.add(join10(appRoot, name));
16380
16482
  for (const base2 of SOURCE_FILES) {
16381
16483
  for (const ext of SOURCE_EXTENSIONS) {
16382
- candidates.add(join9(appRoot, `${base2}.${ext}`));
16484
+ candidates.add(join10(appRoot, `${base2}.${ext}`));
16383
16485
  }
16384
16486
  }
16385
16487
  const entries = [];
@@ -16408,7 +16510,7 @@ async function computeStrategyFingerprint(input) {
16408
16510
 
16409
16511
  // ../../packages/build/src/strategy/project-facts.ts
16410
16512
  import { access as access6, readFile as readFile9 } from "fs/promises";
16411
- import { join as join10 } from "path";
16513
+ import { join as join11 } from "path";
16412
16514
  var SOURCE_EXTENSIONS2 = ["ts", "tsx", "js", "jsx", "mjs", "mts"];
16413
16515
  var SOURCE_DIRECTORIES = ["", "src"];
16414
16516
  var VITE_CONFIGS = [
@@ -16429,7 +16531,7 @@ async function findSourceFile(appRoot, basename4) {
16429
16531
  for (const dir of SOURCE_DIRECTORIES) {
16430
16532
  for (const ext of SOURCE_EXTENSIONS2) {
16431
16533
  const rel = dir ? `${dir}/${basename4}.${ext}` : `${basename4}.${ext}`;
16432
- if (await exists4(join10(appRoot, rel))) return rel;
16534
+ if (await exists4(join11(appRoot, rel))) return rel;
16433
16535
  }
16434
16536
  }
16435
16537
  return null;
@@ -16458,10 +16560,10 @@ async function collectProjectFacts(appRoot) {
16458
16560
  const proxyFile = await findSourceFile(appRoot, "proxy");
16459
16561
  const middlewareFile = await findSourceFile(appRoot, "middleware");
16460
16562
  const routeSourceFile = middlewareFile ?? proxyFile;
16461
- const routeSource = routeSourceFile ? await readIfExists(join10(appRoot, routeSourceFile)) : null;
16563
+ const routeSource = routeSourceFile ? await readIfExists(join11(appRoot, routeSourceFile)) : null;
16462
16564
  let usesVinext = false;
16463
16565
  let nextSpec = null;
16464
- const packageJsonSource = await readIfExists(join10(appRoot, "package.json"));
16566
+ const packageJsonSource = await readIfExists(join11(appRoot, "package.json"));
16465
16567
  if (packageJsonSource) {
16466
16568
  try {
16467
16569
  const packageJson = JSON.parse(packageJsonSource);
@@ -16472,7 +16574,7 @@ async function collectProjectFacts(appRoot) {
16472
16574
  nextSpec = dependency("next");
16473
16575
  if (dependency("vinext") !== null) {
16474
16576
  for (const name of VITE_CONFIGS) {
16475
- const config2 = await readIfExists(join10(appRoot, name));
16577
+ const config2 = await readIfExists(join11(appRoot, name));
16476
16578
  if (config2 && /\bvinext\s*\(/.test(config2) && /@cloudflare\/vite-plugin/.test(config2)) {
16477
16579
  usesVinext = true;
16478
16580
  break;
@@ -16726,7 +16828,7 @@ function createSmokeTester(options = {}) {
16726
16828
  const pollIntervalMs = options.pollIntervalMs ?? 1e3;
16727
16829
  async function runOnce(request, observer) {
16728
16830
  const stage = request.stage ?? "BUILDING";
16729
- const errorCode = request.errorCode ?? "BUILD_FAILED";
16831
+ const errorCode2 = request.errorCode ?? "BUILD_FAILED";
16730
16832
  const bootTimeoutMs = request.bootTimeoutMs ?? 9e4;
16731
16833
  const probeTimeoutMs = request.probeTimeoutMs ?? 15e3;
16732
16834
  const port = await allocatePort();
@@ -16734,7 +16836,7 @@ function createSmokeTester(options = {}) {
16734
16836
  const boot = request.boot(port);
16735
16837
  const recent = [];
16736
16838
  const server = start(
16737
- { ...boot, stage, errorCode },
16839
+ { ...boot, stage, errorCode: errorCode2 },
16738
16840
  // The dev server is chatty; keep its stderr from reading as build
16739
16841
  // errors in the deployment log.
16740
16842
  {
@@ -16850,11 +16952,11 @@ function defaultSmokeProbes(matcherRoutes) {
16850
16952
 
16851
16953
  // ../../packages/build/src/strategy/strategies/opennext-aws.ts
16852
16954
  import { writeFile as writeFile6 } from "fs/promises";
16853
- import { join as join12 } from "path";
16955
+ import { join as join13 } from "path";
16854
16956
 
16855
16957
  // ../../packages/build/src/strategy/strategies/shared.ts
16856
16958
  import { access as access7, readFile as readFile10, rm, writeFile as writeFile5 } from "fs/promises";
16857
- import { dirname as dirname2, join as join11, resolve as resolve5 } from "path";
16959
+ import { dirname as dirname2, join as join12, resolve as resolve5 } from "path";
16858
16960
  function capturingObserver(observer) {
16859
16961
  const lines = [];
16860
16962
  return {
@@ -16907,7 +17009,7 @@ async function findProjectBin(appRoot, checkout, name) {
16907
17009
  let dir = resolve5(appRoot);
16908
17010
  const stop = resolve5(checkout);
16909
17011
  for (; ; ) {
16910
- const candidate = join11(dir, "node_modules", ".bin", name);
17012
+ const candidate = join12(dir, "node_modules", ".bin", name);
16911
17013
  if (await pathExists2(candidate)) return candidate;
16912
17014
  if (dir === stop) return null;
16913
17015
  const parent = dirname2(dir);
@@ -16921,7 +17023,7 @@ function dotenvQuote(value) {
16921
17023
  return `"${value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r")}"`;
16922
17024
  }
16923
17025
  async function withDevVars(wranglerConfigPath, env, run) {
16924
- const path = join11(dirname2(wranglerConfigPath), ".dev.vars");
17026
+ const path = join12(dirname2(wranglerConfigPath), ".dev.vars");
16925
17027
  const keys = Object.keys(env).filter(
16926
17028
  (key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)
16927
17029
  );
@@ -16938,7 +17040,7 @@ async function withDevVars(wranglerConfigPath, env, run) {
16938
17040
  await rm(path, { force: true });
16939
17041
  }
16940
17042
  }
16941
- async function bundleWorker(context, layout, observer, errorCode) {
17043
+ async function bundleWorker(context, layout, observer, errorCode2) {
16942
17044
  return runStep(
16943
17045
  context,
16944
17046
  {
@@ -16954,7 +17056,7 @@ async function bundleWorker(context, layout, observer, errorCode) {
16954
17056
  cwd: layout.appRoot,
16955
17057
  env: {
16956
17058
  CI: "1",
16957
- WRANGLER_LOG_PATH: join11(
17059
+ WRANGLER_LOG_PATH: join12(
16958
17060
  layout.appRoot,
16959
17061
  layout.outputRoot,
16960
17062
  "wrangler.log"
@@ -16962,7 +17064,7 @@ async function bundleWorker(context, layout, observer, errorCode) {
16962
17064
  },
16963
17065
  timeoutMs: 5 * 6e4,
16964
17066
  stage: "BUILDING",
16965
- errorCode
17067
+ errorCode: errorCode2
16966
17068
  },
16967
17069
  observer
16968
17070
  );
@@ -16987,12 +17089,12 @@ async function smokeWorker(context, layout, observer) {
16987
17089
  "--show-interactive-dev-session=false",
16988
17090
  "--log-level=warn",
16989
17091
  "--persist-to",
16990
- join11(layout.appRoot, layout.outputRoot, ".naro-smoke-state")
17092
+ join12(layout.appRoot, layout.outputRoot, ".naro-smoke-state")
16991
17093
  ],
16992
17094
  cwd: layout.appRoot,
16993
17095
  env: {
16994
17096
  CI: "1",
16995
- WRANGLER_LOG_PATH: join11(
17097
+ WRANGLER_LOG_PATH: join12(
16996
17098
  layout.appRoot,
16997
17099
  layout.outputRoot,
16998
17100
  "wrangler-dev.log"
@@ -17008,7 +17110,7 @@ async function smokeWorker(context, layout, observer) {
17008
17110
  );
17009
17111
  }
17010
17112
  async function approvePnpmBuilds(checkout, packages) {
17011
- const path = join11(checkout, "pnpm-workspace.yaml");
17113
+ const path = join12(checkout, "pnpm-workspace.yaml");
17012
17114
  let source = "";
17013
17115
  try {
17014
17116
  source = await readFile10(path, "utf8");
@@ -17104,7 +17206,7 @@ function createOpenNextAwsStrategy() {
17104
17206
  } catch (error51) {
17105
17207
  return failed("prepare", describeError(error51));
17106
17208
  }
17107
- const configPath = join12(fork.appRoot, CONFIG_NAME);
17209
+ const configPath = join13(fork.appRoot, CONFIG_NAME);
17108
17210
  try {
17109
17211
  await writeFile6(
17110
17212
  configPath,
@@ -17133,7 +17235,7 @@ function createOpenNextAwsStrategy() {
17133
17235
  observer
17134
17236
  );
17135
17237
  if (!build.ok) return failed("build", describeFailure(build));
17136
- const serverFunction = join12(fork.appRoot, SERVER_FUNCTION);
17238
+ const serverFunction = join13(fork.appRoot, SERVER_FUNCTION);
17137
17239
  if (!await pathExists2(serverFunction))
17138
17240
  return failed("bundle", `OpenNext AWS produced no ${SERVER_FUNCTION}.`);
17139
17241
  const smoke = await context.ports.smoke.run(
@@ -17155,7 +17257,7 @@ function createOpenNextAwsStrategy() {
17155
17257
  status: "success",
17156
17258
  artifact: {
17157
17259
  kind: "opennext-aws",
17158
- outputRoot: join12(fork.appRoot, ".open-next"),
17260
+ outputRoot: join13(fork.appRoot, ".open-next"),
17159
17261
  serverFunction
17160
17262
  },
17161
17263
  notes: [
@@ -17168,14 +17270,14 @@ function createOpenNextAwsStrategy() {
17168
17270
 
17169
17271
  // ../../packages/build/src/strategy/strategies/opennext-cloudflare.ts
17170
17272
  import { readFile as readFile11 } from "fs/promises";
17171
- import { join as join13 } from "path";
17273
+ import { join as join14 } from "path";
17172
17274
  var PROXY_TO_MIDDLEWARE_VARIANT = "proxy-to-middleware";
17173
17275
  var NODE_MIDDLEWARE_REFUSAL = /Node\.js middleware is not (?:currently )?supported/i;
17174
17276
  async function buildDeclaresNodeMiddleware(appRoot) {
17175
17277
  try {
17176
17278
  const manifest2 = JSON.parse(
17177
17279
  await readFile11(
17178
- join13(appRoot, ".next", "server", "functions-config-manifest.json"),
17280
+ join14(appRoot, ".next", "server", "functions-config-manifest.json"),
17179
17281
  "utf8"
17180
17282
  )
17181
17283
  );
@@ -17387,7 +17489,7 @@ function createAwsMiddlewareCloudflareAppStrategy() {
17387
17489
  }
17388
17490
 
17389
17491
  // ../../packages/build/src/strategy/strategies/vinext-cloudflare.ts
17390
- import { join as join14 } from "path";
17492
+ import { join as join15 } from "path";
17391
17493
  var VINEXT_MIN_NEXT_MAJOR = 16;
17392
17494
  var VINEXT_BUILD_SCRIPTS = ["esbuild", "workerd"];
17393
17495
  var VINEXT_OUTPUT = {
@@ -17542,7 +17644,7 @@ function createVinextCloudflareStrategy(options) {
17542
17644
  context,
17543
17645
  {
17544
17646
  appRoot: fork.appRoot,
17545
- wranglerConfigPath: join14(
17647
+ wranglerConfigPath: join15(
17546
17648
  fork.appRoot,
17547
17649
  "dist",
17548
17650
  "server",
@@ -17566,7 +17668,7 @@ function createVinextCloudflareStrategy(options) {
17566
17668
 
17567
17669
  // ../../packages/build/src/strategy/workspace.ts
17568
17670
  import { cp } from "fs/promises";
17569
- import { basename as basename2, join as join15, relative as relative5, resolve as resolve6 } from "path";
17671
+ import { basename as basename2, join as join16, relative as relative5, resolve as resolve6 } from "path";
17570
17672
  var SKIPPED_ANYWHERE = /* @__PURE__ */ new Set(["node_modules"]);
17571
17673
  function shouldCopy(path) {
17572
17674
  const name = basename2(path);
@@ -17582,7 +17684,7 @@ async function copyCheckout(source, target) {
17582
17684
  });
17583
17685
  }
17584
17686
  function forkedAppRoot(checkout, appRoot, forkCheckout) {
17585
- return join15(forkCheckout, relative5(checkout, appRoot));
17687
+ return join16(forkCheckout, relative5(checkout, appRoot));
17586
17688
  }
17587
17689
 
17588
17690
  // ../../packages/build/src/local-build-executor.ts
@@ -17590,8 +17692,21 @@ function concurrencyFromEnvironment() {
17590
17692
  const raw = Number(process.env.NARO_RESOLVER_CONCURRENCY ?? "1");
17591
17693
  return Number.isInteger(raw) && raw > 0 ? raw : 1;
17592
17694
  }
17695
+ async function withRouteList(artifact, list, appRoot, observer) {
17696
+ try {
17697
+ const routes = await list(appRoot);
17698
+ return routes ? { ...artifact, routes } : artifact;
17699
+ } catch (error51) {
17700
+ await observer.onLog({
17701
+ level: "WARN",
17702
+ stage: "BUILDING",
17703
+ message: `Speed Insights route list skipped: ${error51 instanceof Error ? error51.message : String(error51)}`
17704
+ });
17705
+ return artifact;
17706
+ }
17707
+ }
17593
17708
  var defaultLocalBuildDependencies = {
17594
- createWorkspace: () => mkdtemp(join16(tmpdir(), "next-deploy-build-")),
17709
+ createWorkspace: () => mkdtemp(join17(tmpdir(), "next-deploy-build-")),
17595
17710
  removeWorkspace: (path) => rm2(path, { recursive: true, force: true }),
17596
17711
  runCommand,
17597
17712
  detectPackageManager,
@@ -17613,6 +17728,7 @@ var defaultLocalBuildDependencies = {
17613
17728
  vinextCommand: "vinext",
17614
17729
  forkWorkspace: copyCheckout,
17615
17730
  collectProjectFacts,
17731
+ listNextRoutes,
17616
17732
  hashFingerprintInputs: hashStrategyInputs,
17617
17733
  computeFingerprint: computeStrategyFingerprint,
17618
17734
  targets: { cloudflare: true },
@@ -17624,7 +17740,7 @@ async function readInstalledNextVersion(appRoot, checkout) {
17624
17740
  for (; ; ) {
17625
17741
  try {
17626
17742
  const source = await readFile12(
17627
- join16(dir, "node_modules", "next", "package.json"),
17743
+ join17(dir, "node_modules", "next", "package.json"),
17628
17744
  "utf8"
17629
17745
  );
17630
17746
  const version2 = JSON.parse(source).version;
@@ -17717,8 +17833,8 @@ var LocalBuildExecutor = class {
17717
17833
  }
17718
17834
  async build(input, observer, cache) {
17719
17835
  const workspace = await this.dependencies.createWorkspace();
17720
- const checkout = join16(workspace, "checkout");
17721
- const pmCacheDir = join16(workspace, ".pm-store");
17836
+ const checkout = join17(workspace, "checkout");
17837
+ const pmCacheDir = join17(workspace, ".pm-store");
17722
17838
  let cacheSeq = 0;
17723
17839
  const commandAvailable = this.dependencies.commandAvailable ?? hasCommand;
17724
17840
  const compressor = "zstd";
@@ -17727,7 +17843,7 @@ var LocalBuildExecutor = class {
17727
17843
  transport: cache,
17728
17844
  fetch: this.dependencies.cacheFetch ?? ((url2, init) => fetch(url2, init)),
17729
17845
  runCommand: this.dependencies.runCommand,
17730
- tmpFile: (suffix) => join16(workspace, `cache-${String(cacheSeq++)}${suffix}`),
17846
+ tmpFile: (suffix) => join17(workspace, `cache-${String(cacheSeq++)}${suffix}`),
17731
17847
  // One latch for the whole build so a disabled cache warns once.
17732
17848
  report: { unavailable: false }
17733
17849
  } : null;
@@ -17911,7 +18027,7 @@ var LocalBuildExecutor = class {
17911
18027
  }
17912
18028
  }
17913
18029
  await notifyStatus(observer, "BUILDING");
17914
- const buildCacheDir = resolved.preset.cachePath ? join16(appRoot, resolved.preset.cachePath) : null;
18030
+ const buildCacheDir = resolved.preset.cachePath ? join17(appRoot, resolved.preset.cachePath) : null;
17915
18031
  const buildCacheKey = sanitizeCacheKey(input.branch);
17916
18032
  if (cachePorts && buildCacheDir) {
17917
18033
  await tryRestore(
@@ -17935,12 +18051,12 @@ var LocalBuildExecutor = class {
17935
18051
  entries: fingerprintEntries,
17936
18052
  nextVersion: nextVersion ? `${String(nextVersion.major)}.${String(nextVersion.minor)}.${String(nextVersion.patch)}` : "unknown"
17937
18053
  });
17938
- const pristine = join16(workspace, "pristine");
18054
+ const pristine = join17(workspace, "pristine");
17939
18055
  const fork = this.dependencies.forkWorkspace ?? defaultFork;
17940
18056
  await fork(checkout, pristine);
17941
18057
  let forks = 0;
17942
18058
  const forkWorkspace = async (label2) => {
17943
- const target = join16(
18059
+ const target = join17(
17944
18060
  workspace,
17945
18061
  "forks",
17946
18062
  `${String(forks++)}-${label2.replace(/[^a-z0-9-]/gi, "-")}`
@@ -17997,7 +18113,12 @@ var LocalBuildExecutor = class {
17997
18113
  `Strategy ${resolution.strategy.id} produced a ${resolution.outcome.artifact.kind} artifact, which this control plane cannot deploy.`
17998
18114
  );
17999
18115
  }
18000
- artifact = resolution.outcome.artifact.artifact;
18116
+ artifact = await withRouteList(
18117
+ resolution.outcome.artifact.artifact,
18118
+ this.dependencies.listNextRoutes ?? listNextRoutes,
18119
+ appRoot,
18120
+ observer
18121
+ );
18001
18122
  strategyRecord = resolution.record;
18002
18123
  await observer.onMetadata?.({ strategy: resolution.record });
18003
18124
  } else {
@@ -18034,6 +18155,13 @@ var LocalBuildExecutor = class {
18034
18155
  }
18035
18156
  await notifyStatus(observer, "DEPLOYING");
18036
18157
  artifact ??= await this.readStatic(appRoot, resolved, identity, observer);
18158
+ if (resolved.preset.id === "next-export")
18159
+ artifact = await withRouteList(
18160
+ artifact,
18161
+ this.dependencies.listNextRoutes ?? listNextRoutes,
18162
+ appRoot,
18163
+ observer
18164
+ );
18037
18165
  return {
18038
18166
  gitCommitSha,
18039
18167
  artifact,
@@ -18193,12 +18321,12 @@ async function errorFromResponse(response, context) {
18193
18321
  // ../../packages/sdk/src/session.ts
18194
18322
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
18195
18323
  import { homedir } from "os";
18196
- import { join as join17 } from "path";
18324
+ import { join as join18 } from "path";
18197
18325
  function credentialsDir(home = homedir()) {
18198
- return join17(home, ".naro");
18326
+ return join18(home, ".naro");
18199
18327
  }
18200
18328
  function credentialsFile(home = homedir()) {
18201
- return join17(credentialsDir(home), "credentials.json");
18329
+ return join18(credentialsDir(home), "credentials.json");
18202
18330
  }
18203
18331
  function saveSession(session, home = homedir()) {
18204
18332
  mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
@@ -19287,7 +19415,10 @@ var DbApi = class {
19287
19415
  * `d1_migrations` — all operations or none. `dryRun` returns the SQL and
19288
19416
  * the name without applying anything. Save `sql` as `migrations/<name>` so
19289
19417
  * `naro db reset` re-applies it; a refused operation's index is in
19290
- * `details.operation`.
19418
+ * `details.operation`. Policies left under a name the change gives to a
19419
+ * table are removed before it runs and stay removed if it then fails:
19420
+ * that refusal carries them in `details.removedPolicies`, shaped as the
19421
+ * result's `removedPolicies`.
19291
19422
  */
19292
19423
  async schema(projectId, operations, options = {}) {
19293
19424
  return this.client.request(
@@ -19306,6 +19437,9 @@ var DbApi = class {
19306
19437
  }
19307
19438
  };
19308
19439
  var PROVISION_TIMEOUT_MS = 12e4;
19440
+ function writeQuery(options) {
19441
+ return options.allowUnindexed === true ? "?allow_unindexed=1" : "";
19442
+ }
19309
19443
  function backendInfo(body) {
19310
19444
  return {
19311
19445
  status: body.status,
@@ -19581,7 +19715,12 @@ var BackendApi = class {
19581
19715
  );
19582
19716
  return answer.user;
19583
19717
  }
19584
- /** Every stored policy, by table name. Tables without one deny anon and signed-in requests. */
19718
+ /**
19719
+ * Every table's policies as rules and conditions, one row per table —
19720
+ * the view `setPolicy` writes through (P2-7 §11.2). A policy that view
19721
+ * cannot show reads `{ invalid }`; `rlsPolicies` shows every one. Tables
19722
+ * without one deny anon and signed-in requests.
19723
+ */
19585
19724
  async policies(projectId) {
19586
19725
  const body = await this.client.request(
19587
19726
  this.path(projectId, "/policies"),
@@ -19605,8 +19744,9 @@ var BackendApi = class {
19605
19744
  return answer.policy;
19606
19745
  }
19607
19746
  /**
19608
- * Deletes the table's policy, so it denies anon and signed-in requests
19609
- * again. `POLICY_NOT_FOUND` when the table has none.
19747
+ * Deletes every policy on the table (P2-7: the named ones too, whose
19748
+ * names the answer's `policies` lists), so it denies anon and signed-in
19749
+ * requests again. `POLICY_NOT_FOUND` when the table has none.
19610
19750
  */
19611
19751
  async removePolicy(projectId, table) {
19612
19752
  const answer = await this.client.request(
@@ -19615,6 +19755,75 @@ var BackendApi = class {
19615
19755
  );
19616
19756
  return answer.policy;
19617
19757
  }
19758
+ /*
19759
+ * ---- Named policies (spec 2026-09-24 P2-7 §11.1) ----
19760
+ * As Postgres's CREATE POLICY: any number per table, each permissive or
19761
+ * restrictive, for a command and roles, with USING and WITH CHECK
19762
+ * expressions. The control plane checks them against the live schema
19763
+ * with the worker's own compiler; the worker picks a change up within
19764
+ * about 10 seconds.
19765
+ *
19766
+ * A write — `runPolicySql`, `replaceRlsPolicy`, `dropRlsPolicy`, and
19767
+ * `setPolicy` above — on a backend turned on before named policies is
19768
+ * `409 BACKEND_OUTDATED` "The project backend is older than named
19769
+ * policies. Run `naro backend enable` to update it, then try again."
19770
+ * (`backend.enable` also turns a backend that is off back on.) Reads
19771
+ * and `removePolicy` work there.
19772
+ */
19773
+ /** The named policies, by table then name; only one table's with `table`. */
19774
+ async rlsPolicies(projectId, options = {}) {
19775
+ const query = options.table === void 0 ? "" : `?table=${encodeURIComponent(options.table)}`;
19776
+ const body = await this.client.request(
19777
+ this.path(projectId, `/rls/policies${query}`),
19778
+ { context: "List policies" }
19779
+ );
19780
+ return body.policies;
19781
+ }
19782
+ /**
19783
+ * Runs one CREATE POLICY or DROP POLICY statement. A statement that does
19784
+ * not parse is `VALIDATION_FAILED` with Postgres's words and where
19785
+ * ("syntax error at or near … at character N."); a policy already there
19786
+ * is `POLICY_EXISTS`; DROP POLICY of none is `POLICY_NOT_FOUND` unless
19787
+ * it says IF EXISTS.
19788
+ */
19789
+ async runPolicySql(projectId, sql, options = {}) {
19790
+ const answer = await this.client.request(this.path(projectId, `/rls/policies${writeQuery(options)}`), {
19791
+ method: "POST",
19792
+ body: { sql },
19793
+ context: "Run policy statement"
19794
+ });
19795
+ return "policy" in answer ? { created: answer.policy } : { dropped: answer.dropped };
19796
+ }
19797
+ /**
19798
+ * Replaces the named policy with a CREATE POLICY on the same table,
19799
+ * which may rename it. `POLICY_NOT_FOUND` when there is none to replace.
19800
+ * `name` is the name as typed: the policy of exactly that name, else the
19801
+ * one of its ASCII lower case — what `CREATE POLICY MyPolicy` stored.
19802
+ */
19803
+ async replaceRlsPolicy(projectId, table, name, sql, options = {}) {
19804
+ const answer = await this.client.request(
19805
+ this.path(
19806
+ projectId,
19807
+ `${this.namedPath(table, name)}${writeQuery(options)}`
19808
+ ),
19809
+ { method: "PUT", body: { sql }, context: "Replace policy" }
19810
+ );
19811
+ return answer.policy;
19812
+ }
19813
+ /**
19814
+ * DROP POLICY: answers the policy dropped; `POLICY_NOT_FOUND` when none.
19815
+ * `name` is the name as typed, as for `replaceRlsPolicy`.
19816
+ */
19817
+ async dropRlsPolicy(projectId, table, name) {
19818
+ const answer = await this.client.request(
19819
+ this.path(projectId, this.namedPath(table, name)),
19820
+ { method: "DELETE", context: "Drop policy" }
19821
+ );
19822
+ return answer.policy;
19823
+ }
19824
+ namedPath(table, name) {
19825
+ return `/rls/policies/${encodeURIComponent(table)}/${encodeURIComponent(name)}`;
19826
+ }
19618
19827
  };
19619
19828
 
19620
19829
  // ../../packages/sdk/src/database.ts
@@ -19667,7 +19876,7 @@ function summarizeStatement(statement) {
19667
19876
 
19668
19877
  // ../../packages/sdk/src/dotenv.ts
19669
19878
  import { existsSync, readFileSync as readFileSync2 } from "fs";
19670
- import { join as join18 } from "path";
19879
+ import { join as join19 } from "path";
19671
19880
  function parseDotenv(text) {
19672
19881
  const out = {};
19673
19882
  for (const line of text.split(/\r?\n/)) {
@@ -19685,7 +19894,7 @@ function parseDotenv(text) {
19685
19894
  }
19686
19895
  function readProjectEnv(dir) {
19687
19896
  for (const name of [".env.local", ".env.production", ".env"]) {
19688
- const path = join18(dir, name);
19897
+ const path = join19(dir, name);
19689
19898
  if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
19690
19899
  }
19691
19900
  return {};
@@ -19700,7 +19909,7 @@ function serializeDotenv(vars) {
19700
19909
 
19701
19910
  // ../../packages/sdk/src/env-scan.ts
19702
19911
  import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
19703
- import { join as join19 } from "path";
19912
+ import { join as join20 } from "path";
19704
19913
  var SOURCE_EXTENSIONS3 = /* @__PURE__ */ new Set([
19705
19914
  ".ts",
19706
19915
  ".tsx",
@@ -19755,7 +19964,7 @@ function collect(dir, out) {
19755
19964
  for (const entry of entries) {
19756
19965
  if (out.length >= MAX_FILES) return;
19757
19966
  if (entry.isSymbolicLink()) continue;
19758
- const path = join19(dir, entry.name);
19967
+ const path = join20(dir, entry.name);
19759
19968
  if (entry.isDirectory()) {
19760
19969
  if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name))
19761
19970
  continue;
@@ -19787,7 +19996,7 @@ function scanEnvReferences(dir) {
19787
19996
  }
19788
19997
  }
19789
19998
  for (const name of EXAMPLE_FILES) {
19790
- const path = join19(dir, name);
19999
+ const path = join20(dir, name);
19791
20000
  if (!existsSync2(path)) continue;
19792
20001
  let parsed;
19793
20002
  try {
@@ -19805,11 +20014,11 @@ function scanEnvReferences(dir) {
19805
20014
 
19806
20015
  // ../../packages/sdk/src/framework.ts
19807
20016
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
19808
- import { join as join20 } from "path";
20017
+ import { join as join21 } from "path";
19809
20018
  function readPackageJson2(dir) {
19810
20019
  try {
19811
20020
  return JSON.parse(
19812
- readFileSync4(join20(dir, "package.json"), "utf8")
20021
+ readFileSync4(join21(dir, "package.json"), "utf8")
19813
20022
  );
19814
20023
  } catch {
19815
20024
  return null;
@@ -19820,7 +20029,7 @@ function hasDependency(pkg, name) {
19820
20029
  }
19821
20030
  function hasConfig(dir, base2, evidence) {
19822
20031
  for (const ext of ["js", "mjs", "cjs", "ts", "mts"]) {
19823
- if (existsSync3(join20(dir, `${base2}.${ext}`))) {
20032
+ if (existsSync3(join21(dir, `${base2}.${ext}`))) {
19824
20033
  evidence.push(`${base2}.${ext}`);
19825
20034
  return true;
19826
20035
  }
@@ -19928,8 +20137,8 @@ function detectFramework2(dir) {
19928
20137
  if (!byDependency && !byConfig) continue;
19929
20138
  if (byDependency) evidence.push(`package.json: ${detector.dependency}`);
19930
20139
  if (detector.framework === "nextjs") {
19931
- if (existsSync3(join20(dir, "app"))) evidence.push("app/");
19932
- if (existsSync3(join20(dir, "pages"))) evidence.push("pages/");
20140
+ if (existsSync3(join21(dir, "app"))) evidence.push("app/");
20141
+ if (existsSync3(join21(dir, "pages"))) evidence.push("pages/");
19933
20142
  }
19934
20143
  return {
19935
20144
  framework: detector.framework,
@@ -19940,7 +20149,7 @@ function detectFramework2(dir) {
19940
20149
  deployable: true
19941
20150
  };
19942
20151
  }
19943
- if (!pkg && existsSync3(join20(dir, "index.html"))) {
20152
+ if (!pkg && existsSync3(join21(dir, "index.html"))) {
19944
20153
  evidence.push("index.html (no package.json)");
19945
20154
  return {
19946
20155
  framework: "static",
@@ -19994,6 +20203,276 @@ function systemTablePredicate(column = "name") {
19994
20203
  }
19995
20204
  var SYSTEM_TABLE_SQL_PREDICATE = systemTablePredicate("name");
19996
20205
 
20206
+ // ../../packages/data-core/src/d1.ts
20207
+ function rowsOf(result) {
20208
+ const rows = result?.results ?? [];
20209
+ return rows.filter(
20210
+ (row) => typeof row === "object" && row !== null && !Array.isArray(row)
20211
+ );
20212
+ }
20213
+
20214
+ // ../../packages/data-core/src/policy-lexer.ts
20215
+ var PolicySyntaxError = class extends Error {
20216
+ constructor(reason, position) {
20217
+ super(`${reason} at character ${String(position)}`);
20218
+ this.reason = reason;
20219
+ this.position = position;
20220
+ }
20221
+ };
20222
+ var OPERATORS = [
20223
+ "->>",
20224
+ "->",
20225
+ "::",
20226
+ "<>",
20227
+ "!=",
20228
+ "<=",
20229
+ ">=",
20230
+ "<@",
20231
+ "@>",
20232
+ "&&",
20233
+ "(",
20234
+ ")",
20235
+ ",",
20236
+ ".",
20237
+ ";",
20238
+ "=",
20239
+ "<",
20240
+ ">",
20241
+ "[",
20242
+ "]",
20243
+ "*",
20244
+ "-"
20245
+ ];
20246
+ function isSpace(char) {
20247
+ return char === " " || char === " " || char === "\n" || char === "\r" || char === "\f";
20248
+ }
20249
+ function isIdentStart(char) {
20250
+ return /[A-Za-z_]/.test(char) || char.charCodeAt(0) >= 128;
20251
+ }
20252
+ function isIdentChar(char) {
20253
+ return /[A-Za-z0-9_$]/.test(char) || char.charCodeAt(0) >= 128;
20254
+ }
20255
+ function isDigit(char) {
20256
+ return char >= "0" && char <= "9";
20257
+ }
20258
+ var NUMBER = /^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/;
20259
+ function tokenize(text) {
20260
+ const tokens = [];
20261
+ let index = 0;
20262
+ const at = (offset = 0) => text.charAt(index + offset);
20263
+ while (index < text.length) {
20264
+ const char = at();
20265
+ const position = index + 1;
20266
+ if (char === "\0")
20267
+ throw new PolicySyntaxError("NUL characters are not allowed", position);
20268
+ if (isSpace(char)) {
20269
+ index++;
20270
+ continue;
20271
+ }
20272
+ if (char === "-" && at(1) === "-") {
20273
+ while (index < text.length && at() !== "\n" && at() !== "\r") index++;
20274
+ continue;
20275
+ }
20276
+ if (char === "/" && at(1) === "*") {
20277
+ let depth = 0;
20278
+ while (index < text.length) {
20279
+ if (at() === "/" && at(1) === "*") {
20280
+ depth++;
20281
+ index += 2;
20282
+ } else if (at() === "*" && at(1) === "/") {
20283
+ depth--;
20284
+ index += 2;
20285
+ if (depth === 0) break;
20286
+ } else index++;
20287
+ }
20288
+ if (depth !== 0)
20289
+ throw new PolicySyntaxError("unterminated /* comment", position);
20290
+ continue;
20291
+ }
20292
+ if (char === "'") {
20293
+ let value = "";
20294
+ index++;
20295
+ for (; ; ) {
20296
+ if (index >= text.length)
20297
+ throw new PolicySyntaxError("unterminated quoted string", position);
20298
+ if (at() === "'") {
20299
+ if (at(1) === "'") {
20300
+ value += "'";
20301
+ index += 2;
20302
+ continue;
20303
+ }
20304
+ index++;
20305
+ break;
20306
+ }
20307
+ value += at();
20308
+ index++;
20309
+ }
20310
+ tokens.push({ kind: "string", value, quoted: false, position });
20311
+ continue;
20312
+ }
20313
+ if (char === '"') {
20314
+ let value = "";
20315
+ index++;
20316
+ for (; ; ) {
20317
+ if (index >= text.length)
20318
+ throw new PolicySyntaxError(
20319
+ "unterminated quoted identifier",
20320
+ position
20321
+ );
20322
+ if (at() === '"') {
20323
+ if (at(1) === '"') {
20324
+ value += '"';
20325
+ index += 2;
20326
+ continue;
20327
+ }
20328
+ index++;
20329
+ break;
20330
+ }
20331
+ value += at();
20332
+ index++;
20333
+ }
20334
+ if (value === "")
20335
+ throw new PolicySyntaxError("zero-length quoted identifier", position);
20336
+ tokens.push({ kind: "ident", value, quoted: true, position });
20337
+ continue;
20338
+ }
20339
+ if (char === "$")
20340
+ throw new PolicySyntaxError(
20341
+ "dollar-quoted strings are not supported: use '\u2026'",
20342
+ position
20343
+ );
20344
+ if (isDigit(char) || char === "." && isDigit(at(1))) {
20345
+ const match = NUMBER.exec(text.slice(index));
20346
+ const value = match?.[0] ?? char;
20347
+ index += value.length;
20348
+ if (index < text.length && isIdentChar(at()))
20349
+ throw new PolicySyntaxError(
20350
+ `trailing junk after numeric literal ${value}`,
20351
+ position
20352
+ );
20353
+ tokens.push({ kind: "number", value, quoted: false, position });
20354
+ continue;
20355
+ }
20356
+ if (isIdentStart(char)) {
20357
+ 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) === "'")
20361
+ throw new PolicySyntaxError(
20362
+ `${value}'\u2026' strings are not supported: use a plain '\u2026' string`,
20363
+ position
20364
+ );
20365
+ index = end;
20366
+ tokens.push({ kind: "ident", value, quoted: false, position });
20367
+ continue;
20368
+ }
20369
+ const operator = OPERATORS.find(
20370
+ (candidate) => text.startsWith(candidate, index)
20371
+ );
20372
+ if (!operator)
20373
+ throw new PolicySyntaxError(
20374
+ `unexpected character ${JSON.stringify(char)}`,
20375
+ position
20376
+ );
20377
+ index += operator.length;
20378
+ tokens.push({ kind: "op", value: operator, quoted: false, position });
20379
+ }
20380
+ tokens.push({
20381
+ kind: "eof",
20382
+ value: "",
20383
+ quoted: false,
20384
+ position: text.length + 1
20385
+ });
20386
+ return tokens;
20387
+ }
20388
+ function isKeyword(token, word) {
20389
+ return token !== void 0 && token.kind === "ident" && !token.quoted && asciiLower(token.value) === word;
20390
+ }
20391
+ function isOperator(token, op) {
20392
+ return token !== void 0 && token.kind === "op" && token.value === op;
20393
+ }
20394
+ function describeToken(token) {
20395
+ if (token.kind === "eof") return "end of input";
20396
+ if (token.kind === "string") return `'${token.value}'`;
20397
+ if (token.kind === "ident" && token.quoted) return `"${token.value}"`;
20398
+ return `"${token.value}"`;
20399
+ }
20400
+ var TokenStream = class {
20401
+ constructor(tokens) {
20402
+ this.tokens = tokens;
20403
+ }
20404
+ #index = 0;
20405
+ peek(offset = 0) {
20406
+ const token = this.tokens[Math.min(this.#index + offset, this.tokens.length - 1)];
20407
+ if (!token) throw new Error("TokenStream: no eof token");
20408
+ return token;
20409
+ }
20410
+ next() {
20411
+ const token = this.peek();
20412
+ if (token.kind !== "eof") this.#index++;
20413
+ return token;
20414
+ }
20415
+ /** Takes the keyword when it is next. */
20416
+ acceptKeyword(word) {
20417
+ if (!isKeyword(this.peek(), word)) return false;
20418
+ this.next();
20419
+ return true;
20420
+ }
20421
+ acceptOperator(op) {
20422
+ if (!isOperator(this.peek(), op)) return false;
20423
+ this.next();
20424
+ return true;
20425
+ }
20426
+ expectKeyword(word) {
20427
+ const token = this.peek();
20428
+ if (!isKeyword(token, word))
20429
+ throw new PolicySyntaxError(
20430
+ `expected ${word.toUpperCase()}, found ${describeToken(token)}`,
20431
+ token.position
20432
+ );
20433
+ return this.next();
20434
+ }
20435
+ expectOperator(op) {
20436
+ const token = this.peek();
20437
+ if (!isOperator(token, op))
20438
+ throw new PolicySyntaxError(
20439
+ `expected ${op}, found ${describeToken(token)}`,
20440
+ token.position
20441
+ );
20442
+ return this.next();
20443
+ }
20444
+ /** Postgres's own words for anything else. */
20445
+ unexpected(token = this.peek()) {
20446
+ throw new PolicySyntaxError(
20447
+ token.kind === "eof" ? "syntax error at end of input" : `syntax error at or near ${describeToken(token)}`,
20448
+ token.position
20449
+ );
20450
+ }
20451
+ };
20452
+
20453
+ // ../../packages/data-core/src/schema.ts
20454
+ var SCHEMA_SQL = [
20455
+ `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
+ // Phase 2-2 (적대적 리뷰 반영): each table's CREATE TABLE text, where
20457
+ // alone a column's collation is written. A separate statement, so the
20458
+ // text comes once per table rather than once per column. D1 answers
20459
+ // `sqlite_master` reads (the database routes' `_tables.ts` reads it too).
20460
+ `SELECT name AS table_name, sql AS table_sql FROM sqlite_master WHERE type = 'table' AND ${systemTablePredicate("name")}`
20461
+ ];
20462
+ var TABLE_NAMES_SQL = `SELECT name AS table_name FROM pragma_table_list WHERE schema = 'main' AND type IN ('table', 'virtual') AND ${systemTablePredicate("name")}`;
20463
+ function tableNamesFromRows(rows) {
20464
+ const names = [];
20465
+ for (const row of rows) {
20466
+ const name = row.table_name;
20467
+ if (typeof name === "string" && name !== "" && !isSystemTable(name))
20468
+ names.push(name);
20469
+ }
20470
+ return names;
20471
+ }
20472
+ async function readTableNames(db) {
20473
+ return tableNamesFromRows(rowsOf(await db.prepare(TABLE_NAMES_SQL).all()));
20474
+ }
20475
+
19997
20476
  // ../../packages/data-core/src/values.ts
19998
20477
  function isPlainObject2(value) {
19999
20478
  if (typeof value !== "object" || value === null || Array.isArray(value))
@@ -20004,6 +20483,1121 @@ function isPlainObject2(value) {
20004
20483
  function isValue(value) {
20005
20484
  return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
20006
20485
  }
20486
+ function utf8ByteLength(text) {
20487
+ let bytes = 0;
20488
+ for (let index = 0; index < text.length; index++) {
20489
+ const unit = text.charCodeAt(index);
20490
+ if (unit < 128) bytes += 1;
20491
+ else if (unit < 2048) bytes += 2;
20492
+ else if (unit >= 55296 && unit <= 56319 && index + 1 < text.length) {
20493
+ const next = text.charCodeAt(index + 1);
20494
+ if (next >= 56320 && next <= 57343) {
20495
+ bytes += 4;
20496
+ index++;
20497
+ } else bytes += 3;
20498
+ } else bytes += 3;
20499
+ }
20500
+ return bytes;
20501
+ }
20502
+
20503
+ // ../../packages/data-core/src/sql-fragments.ts
20504
+ function globLiteral(char) {
20505
+ return char === "*" || char === "?" || char === "[" ? `[${char}]` : char;
20506
+ }
20507
+ function likeToGlob(pattern) {
20508
+ let glob = "";
20509
+ for (let index = 0; index < pattern.length; index++) {
20510
+ const char = pattern.charAt(index);
20511
+ if (char === "\\" && index + 1 < pattern.length) {
20512
+ index++;
20513
+ glob += globLiteral(pattern.charAt(index));
20514
+ } else if (char === "%") glob += "*";
20515
+ else if (char === "_") glob += "?";
20516
+ else glob += globLiteral(char);
20517
+ }
20518
+ return glob;
20519
+ }
20520
+ var MAX_PATTERN_BYTES = 50;
20521
+ function boundPattern(pattern, caseInsensitive) {
20522
+ return caseInsensitive ? pattern : likeToGlob(pattern);
20523
+ }
20524
+ function patternTooLong(pattern, caseInsensitive) {
20525
+ if (pattern.length > MAX_PATTERN_BYTES * 2) return true;
20526
+ return utf8ByteLength(boundPattern(pattern, caseInsensitive)) > MAX_PATTERN_BYTES;
20527
+ }
20528
+
20529
+ // ../../packages/data-core/src/policy-expr.ts
20530
+ var MAX_EXPRESSION_BYTES = 3 * 16384;
20531
+ var MAX_EXPRESSION_NODES = 1600;
20532
+ var MAX_EXPRESSION_DEPTH = 32;
20533
+ var MAX_SUBQUERIES = 4;
20534
+ var MAX_SUBQUERY_DEPTH = 2;
20535
+ var MAX_ARRAY_OPERATORS = 4;
20536
+ var MAX_POLICY_ARRAY_VALUES = 100;
20537
+ var MAX_IN_VALUES = 100;
20538
+ var MAX_POLICY_PATTERN_LENGTH = MAX_PATTERN_BYTES;
20539
+ var JWT_CLAIMS = ["sub", "role", "email", "email_verified"];
20540
+ var POLICY_FUNCTIONS = [
20541
+ "lower",
20542
+ "upper",
20543
+ "length",
20544
+ "coalesce",
20545
+ "now"
20546
+ ];
20547
+ var VALUE_KINDS = /* @__PURE__ */ new Set([
20548
+ "literal",
20549
+ "column",
20550
+ "auth",
20551
+ "jwt",
20552
+ "func",
20553
+ "cast"
20554
+ ]);
20555
+ function isValueExpr(expr) {
20556
+ return VALUE_KINDS.has(expr.kind);
20557
+ }
20558
+ function isConditionExpr(expr) {
20559
+ if (!isValueExpr(expr)) return true;
20560
+ if (expr.kind === "column") return true;
20561
+ return expr.kind === "literal" && (expr.value === null || typeof expr.value === "boolean");
20562
+ }
20563
+ var RESERVED = /* @__PURE__ */ new Set([
20564
+ "all",
20565
+ "and",
20566
+ "array",
20567
+ "as",
20568
+ "between",
20569
+ "check",
20570
+ "current_timestamp",
20571
+ "distinct",
20572
+ "exists",
20573
+ "false",
20574
+ "for",
20575
+ `from`,
20576
+ "ilike",
20577
+ "in",
20578
+ "is",
20579
+ "like",
20580
+ "not",
20581
+ "null",
20582
+ "on",
20583
+ "or",
20584
+ "select",
20585
+ "to",
20586
+ "true",
20587
+ "using",
20588
+ "where",
20589
+ "with"
20590
+ ]);
20591
+ var CLAUSE_WORDS = /* @__PURE__ */ new Set([
20592
+ "join",
20593
+ "group",
20594
+ "order",
20595
+ "limit",
20596
+ "union",
20597
+ "having",
20598
+ "left",
20599
+ "inner",
20600
+ "cross"
20601
+ ]);
20602
+ var SPECIAL_WORDS = /* @__PURE__ */ new Set([
20603
+ ...RESERVED,
20604
+ ...CLAUSE_WORDS,
20605
+ "symmetric",
20606
+ "auth"
20607
+ ]);
20608
+ var TEXT_TYPES = /* @__PURE__ */ new Set(["text", "varchar", "uuid"]);
20609
+ function count(state, token) {
20610
+ if (++state.nodes > MAX_EXPRESSION_NODES)
20611
+ throw new PolicySyntaxError(
20612
+ `the expression has more than ${String(MAX_EXPRESSION_NODES)} parts`,
20613
+ token.position
20614
+ );
20615
+ }
20616
+ function nested(state, token, read) {
20617
+ if (++state.depth > MAX_EXPRESSION_DEPTH)
20618
+ throw new PolicySyntaxError(
20619
+ `the expression nests deeper than ${String(MAX_EXPRESSION_DEPTH)} levels`,
20620
+ token.position
20621
+ );
20622
+ try {
20623
+ return read();
20624
+ } finally {
20625
+ state.depth--;
20626
+ }
20627
+ }
20628
+ function truthProblem(operand) {
20629
+ const text = "is text, where 'true' reads as false";
20630
+ switch (operand.kind) {
20631
+ case "column":
20632
+ return void 0;
20633
+ case "literal":
20634
+ return typeof operand.value === "string" ? `a '\u2026' string ${text}: write TRUE or FALSE, or compare the string with =` : void 0;
20635
+ case "jwt": {
20636
+ const claim = printValue(operand);
20637
+ return `${claim} ${text}: compare it with text, ${claim} = 'true'`;
20638
+ }
20639
+ case "auth":
20640
+ 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
+ case "cast":
20642
+ return `a ::text cast ${text}: compare it with text, (\u2026)::text = 'true'`;
20643
+ case "func":
20644
+ switch (operand.name) {
20645
+ case "lower":
20646
+ case "upper":
20647
+ return `${operand.name}(\u2026) ${text}: compare it with text, ${operand.name}(\u2026) = 'true'`;
20648
+ case "coalesce":
20649
+ return "coalesce(\u2026) is not a boolean: compare it, coalesce(\u2026) = 'true'";
20650
+ case "length":
20651
+ return "length(\u2026) is a number: compare it, length(\u2026) > 0";
20652
+ case "now":
20653
+ return "now() is a time: compare it, created_at < now()";
20654
+ }
20655
+ }
20656
+ }
20657
+ function truthTestProblem(operand) {
20658
+ const problem = truthProblem(operand);
20659
+ return problem === void 0 ? void 0 : `IS TRUE and IS FALSE test a boolean, and ${problem}`;
20660
+ }
20661
+ function operandValues(operand) {
20662
+ return operand.kind === "func" && operand.name === "coalesce" ? operand.args.flatMap(operandValues) : [operand];
20663
+ }
20664
+ function isBooleanConstant(value) {
20665
+ return value.kind === "literal" && typeof value.value === "boolean";
20666
+ }
20667
+ function isTextValue(value) {
20668
+ switch (value.kind) {
20669
+ case "literal":
20670
+ return typeof value.value === "string";
20671
+ case "auth":
20672
+ case "jwt":
20673
+ case "cast":
20674
+ return true;
20675
+ case "func":
20676
+ return value.name !== "length" && value.name !== "coalesce";
20677
+ case "column":
20678
+ return false;
20679
+ }
20680
+ }
20681
+ function pair(left, right, inner = false) {
20682
+ return { left, right, inner };
20683
+ }
20684
+ function comparedPairs(expr) {
20685
+ switch (expr.kind) {
20686
+ case "compare":
20687
+ case "distinct":
20688
+ return [pair(expr.left, expr.right)];
20689
+ case "in":
20690
+ return expr.list.map((item) => pair(expr.operand, item));
20691
+ case "inSelect":
20692
+ return expr.subquery.select === "*" ? [] : [pair(expr.operand, expr.subquery.select, true)];
20693
+ case "between":
20694
+ return [pair(expr.operand, expr.low), pair(expr.operand, expr.high)];
20695
+ case "like":
20696
+ return [pair(expr.operand, { kind: "literal", value: expr.pattern })];
20697
+ default:
20698
+ return [];
20699
+ }
20700
+ }
20701
+ function booleanComparisonProblem(expr) {
20702
+ for (const { left, right } of comparedPairs(expr)) {
20703
+ const met = [...operandValues(left), ...operandValues(right)];
20704
+ if (!met.some(isBooleanConstant)) continue;
20705
+ 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);
20709
+ return `a comparison with TRUE or FALSE compares a boolean, and ${String(problem)}`;
20710
+ }
20711
+ return void 0;
20712
+ }
20713
+ function comparable(expr, token) {
20714
+ const problem = booleanComparisonProblem(expr);
20715
+ if (problem !== void 0)
20716
+ throw new PolicySyntaxError(problem, token.position);
20717
+ return expr;
20718
+ }
20719
+ function requireCondition(expr, token) {
20720
+ if (!isConditionExpr(expr)) {
20721
+ const problem = isValueExpr(expr) ? truthProblem(expr) : void 0;
20722
+ throw new PolicySyntaxError(
20723
+ `this is a value, not a condition: ${problem ?? "compare it with =, IN, LIKE, IS \u2026 (a column alone reads as its truth value)"}`,
20724
+ token.position
20725
+ );
20726
+ }
20727
+ return expr;
20728
+ }
20729
+ function requireValue(expr, token) {
20730
+ if (!isValueExpr(expr))
20731
+ throw new PolicySyntaxError(
20732
+ "a comparison compares values: combine conditions with AND, OR and NOT",
20733
+ token.position
20734
+ );
20735
+ return expr;
20736
+ }
20737
+ function readOr(state) {
20738
+ const first = state.stream.peek();
20739
+ const args = [readAnd(state)];
20740
+ while (isKeyword(state.stream.peek(), "or")) {
20741
+ const token = state.stream.next();
20742
+ count(state, token);
20743
+ args.push(readAnd(state));
20744
+ }
20745
+ if (args.length === 1) return args[0];
20746
+ for (const arg of args) requireCondition(arg, first);
20747
+ return { kind: "or", args };
20748
+ }
20749
+ function readAnd(state) {
20750
+ const first = state.stream.peek();
20751
+ const args = [readNot(state)];
20752
+ while (isKeyword(state.stream.peek(), "and")) {
20753
+ const token = state.stream.next();
20754
+ count(state, token);
20755
+ args.push(readNot(state));
20756
+ }
20757
+ if (args.length === 1) return args[0];
20758
+ for (const arg of args) requireCondition(arg, first);
20759
+ return { kind: "and", args };
20760
+ }
20761
+ function readNot(state) {
20762
+ const token = state.stream.peek();
20763
+ if (isKeyword(token, "not")) {
20764
+ state.stream.next();
20765
+ count(state, token);
20766
+ return nested(state, token, () => {
20767
+ const inner = state.stream.peek();
20768
+ return { kind: "not", arg: requireCondition(readNot(state), inner) };
20769
+ });
20770
+ }
20771
+ return readPredicate(state);
20772
+ }
20773
+ function readPredicate(state) {
20774
+ const { stream } = state;
20775
+ const token = stream.peek();
20776
+ if (isKeyword(token, "exists")) {
20777
+ stream.next();
20778
+ count(state, token);
20779
+ stream.expectOperator("(");
20780
+ const subquery = readSubquery(state, token, "exists");
20781
+ stream.expectOperator(")");
20782
+ return { kind: "exists", subquery };
20783
+ }
20784
+ const left = readValue(state);
20785
+ const next = stream.peek();
20786
+ const compare2 = ["=", "<>", "!=", "<", "<=", ">", ">="].find(
20787
+ (op) => isOperator(next, op)
20788
+ );
20789
+ if (compare2) {
20790
+ stream.next();
20791
+ count(state, next);
20792
+ const right = readValue(state);
20793
+ return comparable(
20794
+ {
20795
+ kind: "compare",
20796
+ op: compare2 === "!=" ? "<>" : compare2,
20797
+ left: requireValue(left, token),
20798
+ right: requireValue(right, next)
20799
+ },
20800
+ next
20801
+ );
20802
+ }
20803
+ if (isKeyword(next, "is")) {
20804
+ stream.next();
20805
+ count(state, next);
20806
+ const operand = requireValue(left, token);
20807
+ const not2 = stream.acceptKeyword("not");
20808
+ if (stream.acceptKeyword("null"))
20809
+ return { kind: "is", not: not2, operand, value: null };
20810
+ const truth = stream.acceptKeyword("true") ? true : stream.acceptKeyword("false") ? false : void 0;
20811
+ if (truth !== void 0) {
20812
+ const problem = truthTestProblem(operand);
20813
+ if (problem !== void 0)
20814
+ throw new PolicySyntaxError(problem, next.position);
20815
+ return { kind: "is", not: not2, operand, value: truth };
20816
+ }
20817
+ if (stream.acceptKeyword("distinct")) {
20818
+ stream.expectKeyword(`from`);
20819
+ const right = requireValue(readValue(state), stream.peek());
20820
+ return comparable({ kind: "distinct", not: not2, left: operand, right }, next);
20821
+ }
20822
+ stream.unexpected();
20823
+ }
20824
+ if (isOperator(next, "@>") || isOperator(next, "<@") || isOperator(next, "&&"))
20825
+ return readArray(state, left, token);
20826
+ let not = false;
20827
+ let word = next;
20828
+ if (isKeyword(next, "not")) {
20829
+ const after = stream.peek(1);
20830
+ if (isKeyword(after, "in") || isKeyword(after, "like") || isKeyword(after, "ilike") || isKeyword(after, "between")) {
20831
+ stream.next();
20832
+ not = true;
20833
+ word = after;
20834
+ } else return left;
20835
+ }
20836
+ if (isKeyword(word, "in")) {
20837
+ stream.next();
20838
+ count(state, word);
20839
+ const operand = requireValue(left, token);
20840
+ stream.expectOperator("(");
20841
+ if (isKeyword(stream.peek(), "select")) {
20842
+ const subquery = readSubquery(state, word, "in");
20843
+ stream.expectOperator(")");
20844
+ return comparable({ kind: "inSelect", not, operand, subquery }, word);
20845
+ }
20846
+ const list = [];
20847
+ do {
20848
+ if (list.length >= MAX_IN_VALUES)
20849
+ throw new PolicySyntaxError(
20850
+ `IN lists at most ${String(MAX_IN_VALUES)} values`,
20851
+ stream.peek().position
20852
+ );
20853
+ const item = stream.peek();
20854
+ list.push(requireValue(readValue(state), item));
20855
+ } while (stream.acceptOperator(","));
20856
+ stream.expectOperator(")");
20857
+ return comparable({ kind: "in", not, operand, list }, word);
20858
+ }
20859
+ if (isKeyword(word, "like") || isKeyword(word, "ilike")) {
20860
+ stream.next();
20861
+ count(state, word);
20862
+ const operand = requireValue(left, token);
20863
+ const pattern = stream.peek();
20864
+ if (pattern.kind !== "string")
20865
+ throw new PolicySyntaxError(
20866
+ "a LIKE or ILIKE pattern must be a '\u2026' string",
20867
+ pattern.position
20868
+ );
20869
+ stream.next();
20870
+ const ci = isKeyword(word, "ilike");
20871
+ checkPattern(pattern, ci);
20872
+ return comparable(
20873
+ { kind: "like", not, ci, operand, pattern: pattern.value },
20874
+ word
20875
+ );
20876
+ }
20877
+ if (isKeyword(word, "between")) {
20878
+ stream.next();
20879
+ count(state, word);
20880
+ const operand = requireValue(left, token);
20881
+ if (stream.acceptKeyword("symmetric"))
20882
+ throw new PolicySyntaxError(
20883
+ "BETWEEN SYMMETRIC is not supported",
20884
+ word.position
20885
+ );
20886
+ const low = requireValue(readValue(state), stream.peek());
20887
+ stream.expectKeyword("and");
20888
+ const high = requireValue(readValue(state), stream.peek());
20889
+ return comparable({ kind: "between", not, operand, low, high }, word);
20890
+ }
20891
+ return left;
20892
+ }
20893
+ var PATTERN_SHOWN = 24;
20894
+ function policyPatternProblem(pattern, ci) {
20895
+ if (!patternTooLong(pattern, ci)) return void 0;
20896
+ const chars = [...pattern];
20897
+ const shown = `${commentText(chars.slice(0, PATTERN_SHOWN).join(""))}${chars.length > PATTERN_SHOWN ? "\u2026" : ""}`;
20898
+ const bytes = utf8ByteLength(boundPattern(pattern, ci));
20899
+ return `${ci ? "an ILIKE" : "a LIKE"} pattern may be at most ${String(MAX_POLICY_PATTERN_LENGTH)} bytes of UTF-8, D1's limit, and '${shown}' is ${String(bytes)}${ci ? "" : " (LIKE counts each *, ? and [ as 3 bytes)"}`;
20900
+ }
20901
+ function checkPattern(token, ci) {
20902
+ const pattern = token.value;
20903
+ const problem = policyPatternProblem(pattern, ci);
20904
+ if (problem !== void 0)
20905
+ throw new PolicySyntaxError(problem, token.position);
20906
+ let run = 0;
20907
+ for (let index = pattern.length - 1; index >= 0; index--) {
20908
+ if (pattern[index] !== "\\") break;
20909
+ run++;
20910
+ }
20911
+ if (run % 2 === 1)
20912
+ throw new PolicySyntaxError(
20913
+ "a LIKE pattern must not end with an unescaped backslash",
20914
+ token.position
20915
+ );
20916
+ }
20917
+ function readArray(state, left, start) {
20918
+ const { stream } = state;
20919
+ const opToken = stream.next();
20920
+ count(state, opToken);
20921
+ if (left.kind !== "column")
20922
+ throw new PolicySyntaxError(
20923
+ `the left side of ${opToken.value} must be a column`,
20924
+ start.position
20925
+ );
20926
+ if (++state.arrays > MAX_ARRAY_OPERATORS)
20927
+ throw new PolicySyntaxError(
20928
+ `an expression may use @>, <@ and && at most ${String(MAX_ARRAY_OPERATORS)} times`,
20929
+ opToken.position
20930
+ );
20931
+ stream.expectKeyword("array");
20932
+ stream.expectOperator("[");
20933
+ const values = [];
20934
+ do {
20935
+ const item = stream.peek();
20936
+ if (values.length >= MAX_POLICY_ARRAY_VALUES)
20937
+ throw new PolicySyntaxError(
20938
+ `ARRAY[\u2026] lists at most ${String(MAX_POLICY_ARRAY_VALUES)} values`,
20939
+ item.position
20940
+ );
20941
+ const value = readValue(state);
20942
+ if (value.kind === "auth" && value.fn === "uid") values.push(value);
20943
+ else if (value.kind === "literal" && (typeof value.value === "string" || typeof value.value === "number"))
20944
+ values.push(value.value);
20945
+ else
20946
+ throw new PolicySyntaxError(
20947
+ "ARRAY[\u2026] holds strings, numbers and auth.uid()",
20948
+ item.position
20949
+ );
20950
+ } while (stream.acceptOperator(","));
20951
+ stream.expectOperator("]");
20952
+ return {
20953
+ kind: "array",
20954
+ op: opToken.value,
20955
+ operand: left,
20956
+ values
20957
+ };
20958
+ }
20959
+ function readSubquery(state, start, kind) {
20960
+ const { stream } = state;
20961
+ if (++state.subqueries > MAX_SUBQUERIES)
20962
+ throw new PolicySyntaxError(
20963
+ `an expression may have at most ${String(MAX_SUBQUERIES)} subqueries`,
20964
+ start.position
20965
+ );
20966
+ if (++state.subqueryDepth > MAX_SUBQUERY_DEPTH)
20967
+ throw new PolicySyntaxError(
20968
+ `subqueries nest at most ${String(MAX_SUBQUERY_DEPTH)} deep`,
20969
+ start.position
20970
+ );
20971
+ try {
20972
+ stream.expectKeyword("select");
20973
+ let select;
20974
+ const item = stream.peek();
20975
+ if (stream.acceptOperator("*")) {
20976
+ if (kind === "in")
20977
+ throw new PolicySyntaxError(
20978
+ "IN (SELECT \u2026) selects one value, not *",
20979
+ item.position
20980
+ );
20981
+ select = "*";
20982
+ } else select = requireValue(readValue(state), item);
20983
+ if (isOperator(stream.peek(), ","))
20984
+ throw new PolicySyntaxError(
20985
+ "a subquery here selects one value",
20986
+ stream.peek().position
20987
+ );
20988
+ stream.expectKeyword(`from`);
20989
+ const table = readTableName(stream);
20990
+ let alias = null;
20991
+ if (stream.acceptKeyword("as")) alias = readIdentifier(stream, "an alias");
20992
+ else {
20993
+ const next = stream.peek();
20994
+ if (next.kind === "ident" && (next.quoted || !isReservedWord(next)))
20995
+ alias = readIdentifier(stream, "an alias");
20996
+ }
20997
+ let where = null;
20998
+ if (stream.acceptKeyword("where")) {
20999
+ const token = stream.peek();
21000
+ where = requireCondition(
21001
+ nested(state, token, () => readOr(state)),
21002
+ token
21003
+ );
21004
+ }
21005
+ const after = stream.peek();
21006
+ for (const word of ["join", "group", "order", "limit", "union", "having"])
21007
+ if (isKeyword(after, word) || isOperator(after, ","))
21008
+ throw new PolicySyntaxError(
21009
+ "a subquery in a policy reads one table: SELECT \u2026 FROM t [alias] [WHERE \u2026]",
21010
+ after.position
21011
+ );
21012
+ return { table, alias, select, where };
21013
+ } finally {
21014
+ state.subqueryDepth--;
21015
+ }
21016
+ }
21017
+ function isReservedWord(token) {
21018
+ return token.kind === "ident" && !token.quoted && (RESERVED.has(asciiLower(token.value)) || CLAUSE_WORDS.has(asciiLower(token.value)));
21019
+ }
21020
+ function readIdentifier(stream, what) {
21021
+ const token = stream.peek();
21022
+ if (token.kind !== "ident" || !token.quoted && isReservedWord(token))
21023
+ throw new PolicySyntaxError(
21024
+ `expected ${what}, found ${token.kind === "eof" ? "end of input" : JSON.stringify(token.value)}`,
21025
+ token.position
21026
+ );
21027
+ stream.next();
21028
+ return token.value;
21029
+ }
21030
+ function readTableName(stream) {
21031
+ const first = stream.peek();
21032
+ const name = readIdentifier(stream, "a table name");
21033
+ if (!stream.acceptOperator(".")) return name;
21034
+ if (first.quoted ? name !== "public" : asciiLower(name) !== "public")
21035
+ throw new PolicySyntaxError(
21036
+ `schema ${name} does not exist: naro's tables are in public`,
21037
+ first.position
21038
+ );
21039
+ return readIdentifier(stream, "a table name");
21040
+ }
21041
+ function readNumber(token, negative) {
21042
+ const value = Number(token.value) * (negative ? -1 : 1);
21043
+ if (!Number.isFinite(value))
21044
+ throw new PolicySyntaxError(
21045
+ `${token.value} is out of range`,
21046
+ token.position
21047
+ );
21048
+ if (/[.eE]/.test(token.value)) return { kind: "literal", value };
21049
+ if (!Number.isSafeInteger(value))
21050
+ throw new PolicySyntaxError(
21051
+ `${token.value} is out of range: integers are at most 9007199254740991`,
21052
+ token.position
21053
+ );
21054
+ return { kind: "literal", value: value === 0 ? 0 : value };
21055
+ }
21056
+ function readValue(state) {
21057
+ const { stream } = state;
21058
+ const token = stream.peek();
21059
+ count(state, token);
21060
+ let value = nested(state, token, () => readPrimary(state));
21061
+ while (isOperator(stream.peek(), "::")) {
21062
+ const cast = stream.next();
21063
+ count(state, cast);
21064
+ const type = stream.peek();
21065
+ if (type.kind !== "ident" || type.quoted || !TEXT_TYPES.has(asciiLower(type.value)))
21066
+ throw new PolicySyntaxError(
21067
+ "casts are ::text, ::varchar and ::uuid (all read as text)",
21068
+ type.position
21069
+ );
21070
+ stream.next();
21071
+ value = { kind: "cast", operand: requireValue(value, token) };
21072
+ }
21073
+ return value;
21074
+ }
21075
+ function readAuth(state, start, allowBareJwt) {
21076
+ const { stream } = state;
21077
+ const name = stream.next();
21078
+ const fn = name.kind === "ident" ? asciiLower(name.value) : "";
21079
+ if (!isOperator(stream.peek(), "(")) {
21080
+ if (fn === "uid" || fn === "role" || fn === "jwt")
21081
+ throw new PolicySyntaxError(
21082
+ `auth.${fn} needs parentheses: auth.${fn}()`,
21083
+ start.position
21084
+ );
21085
+ throw new PolicySyntaxError(
21086
+ `auth.${name.value} is not available: use auth.uid(), auth.role() or auth.jwt()`,
21087
+ start.position
21088
+ );
21089
+ }
21090
+ if (fn !== "uid" && fn !== "role" && fn !== "jwt")
21091
+ throw new PolicySyntaxError(
21092
+ `auth.${name.value}() is not available: use auth.uid(), auth.role() or auth.jwt()`,
21093
+ start.position
21094
+ );
21095
+ stream.expectOperator("(");
21096
+ stream.expectOperator(")");
21097
+ if (fn !== "jwt") return { kind: "auth", fn };
21098
+ const access9 = readJwtAccess(stream);
21099
+ if (access9) return access9;
21100
+ if (allowBareJwt) return "jwt";
21101
+ throw new PolicySyntaxError(
21102
+ "auth.jwt() is read one claim at a time: auth.jwt() ->> 'email'",
21103
+ start.position
21104
+ );
21105
+ }
21106
+ function readJwtAccess(stream) {
21107
+ const arrow = stream.peek();
21108
+ if (!isOperator(arrow, "->") && !isOperator(arrow, "->>")) return void 0;
21109
+ stream.next();
21110
+ const claim = stream.peek();
21111
+ if (claim.kind !== "string")
21112
+ throw new PolicySyntaxError(
21113
+ "a claim is a '\u2026' string: auth.jwt() ->> 'email'",
21114
+ claim.position
21115
+ );
21116
+ stream.next();
21117
+ if (!JWT_CLAIMS.includes(claim.value))
21118
+ throw new PolicySyntaxError(
21119
+ `auth.jwt() has ${JWT_CLAIMS.slice(0, -1).join(", ")} and ${String(JWT_CLAIMS.at(-1))}; ${JSON.stringify(claim.value)} is not one of them`,
21120
+ claim.position
21121
+ );
21122
+ const next = stream.peek();
21123
+ if (isOperator(next, "->") || isOperator(next, "->>"))
21124
+ throw new PolicySyntaxError(
21125
+ "naro's auth.jwt() claims are not nested",
21126
+ next.position
21127
+ );
21128
+ return {
21129
+ kind: "jwt",
21130
+ op: arrow.value,
21131
+ claim: claim.value
21132
+ };
21133
+ }
21134
+ function readPrimary(state) {
21135
+ const { stream } = state;
21136
+ const token = stream.next();
21137
+ switch (token.kind) {
21138
+ case "string":
21139
+ return { kind: "literal", value: token.value };
21140
+ case "number":
21141
+ return readNumber(token, false);
21142
+ case "op": {
21143
+ if (token.value === "-") {
21144
+ const number4 = stream.peek();
21145
+ if (number4.kind !== "number")
21146
+ throw new PolicySyntaxError(
21147
+ "only a number can be negated",
21148
+ token.position
21149
+ );
21150
+ stream.next();
21151
+ return readNumber(number4, true);
21152
+ }
21153
+ if (token.value === "(") {
21154
+ if (isKeyword(stream.peek(), "select")) {
21155
+ const select = stream.next();
21156
+ const auth = stream.peek();
21157
+ if (!isKeyword(auth, "auth") || !isOperator(stream.peek(1), "."))
21158
+ throw new PolicySyntaxError(
21159
+ "a scalar subquery here must be (select auth.uid()), (select auth.role()) or (select auth.jwt() ->> '\u2026'): read other tables with EXISTS (\u2026) or IN (SELECT \u2026)",
21160
+ select.position
21161
+ );
21162
+ stream.next();
21163
+ stream.next();
21164
+ const inner2 = readAuth(state, auth, true);
21165
+ stream.expectOperator(")");
21166
+ if (inner2 !== "jwt") return inner2;
21167
+ const access9 = readJwtAccess(stream);
21168
+ if (!access9)
21169
+ throw new PolicySyntaxError(
21170
+ "auth.jwt() is read one claim at a time: (select auth.jwt()) ->> 'email'",
21171
+ auth.position
21172
+ );
21173
+ return access9;
21174
+ }
21175
+ const inner = readOr(state);
21176
+ stream.expectOperator(")");
21177
+ return inner;
21178
+ }
21179
+ return stream.unexpected(token);
21180
+ }
21181
+ case "ident": {
21182
+ if (!token.quoted) {
21183
+ const word = asciiLower(token.value);
21184
+ if (word === "true") return { kind: "literal", value: true };
21185
+ if (word === "false") return { kind: "literal", value: false };
21186
+ if (word === "null") return { kind: "literal", value: null };
21187
+ if (word === "current_timestamp")
21188
+ return { kind: "func", name: "now", args: [] };
21189
+ if (word === "auth" && isOperator(stream.peek(), ".")) {
21190
+ stream.next();
21191
+ const auth = readAuth(state, token, false);
21192
+ if (auth === "jwt") return stream.unexpected();
21193
+ return auth;
21194
+ }
21195
+ if (isOperator(stream.peek(), "(")) return readCall(state, token);
21196
+ if (RESERVED.has(word)) return stream.unexpected(token);
21197
+ }
21198
+ if (isOperator(stream.peek(), ".")) {
21199
+ stream.next();
21200
+ const name = stream.peek();
21201
+ if (name.kind !== "ident" || !name.quoted && isReservedWord(name))
21202
+ return stream.unexpected(name);
21203
+ stream.next();
21204
+ if (isOperator(stream.peek(), "(") || isOperator(stream.peek(), "."))
21205
+ throw new PolicySyntaxError(
21206
+ `${token.value}.${name.value} is not a column: naro reads column or table.column`,
21207
+ token.position
21208
+ );
21209
+ return { kind: "column", qualifier: token.value, name: name.value };
21210
+ }
21211
+ return { kind: "column", qualifier: null, name: token.value };
21212
+ }
21213
+ default:
21214
+ return stream.unexpected(token);
21215
+ }
21216
+ }
21217
+ function readCall(state, name) {
21218
+ const { stream } = state;
21219
+ const word = asciiLower(name.value);
21220
+ if (!POLICY_FUNCTIONS.includes(word))
21221
+ throw new PolicySyntaxError(
21222
+ `function ${name.value} is not available in policies: use lower, upper, coalesce, length or now`,
21223
+ name.position
21224
+ );
21225
+ stream.expectOperator("(");
21226
+ const args = [];
21227
+ if (!isOperator(stream.peek(), ")"))
21228
+ do {
21229
+ const item = stream.peek();
21230
+ args.push(requireValue(readValue(state), item));
21231
+ } while (stream.acceptOperator(","));
21232
+ stream.expectOperator(")");
21233
+ const arity = {
21234
+ lower: [1, 1],
21235
+ upper: [1, 1],
21236
+ length: [1, 1],
21237
+ coalesce: [1, 16],
21238
+ now: [0, 0]
21239
+ };
21240
+ const [min, max] = arity[word];
21241
+ if (args.length < min || args.length > max)
21242
+ throw new PolicySyntaxError(
21243
+ min === max ? `${word}() takes ${String(min)} argument${min === 1 ? "" : "s"}` : `${word}() takes ${String(min)} to ${String(max)} arguments`,
21244
+ name.position
21245
+ );
21246
+ return { kind: "func", name: word, args };
21247
+ }
21248
+ function newState(stream) {
21249
+ return {
21250
+ stream,
21251
+ nodes: 0,
21252
+ depth: 0,
21253
+ subqueries: 0,
21254
+ subqueryDepth: 0,
21255
+ arrays: 0
21256
+ };
21257
+ }
21258
+ function readCondition(stream) {
21259
+ const token = stream.peek();
21260
+ const expr = readOr(newState(stream));
21261
+ return requireCondition(expr, token);
21262
+ }
21263
+ function checkExpressionSize(expr, position) {
21264
+ const text = printExpr(expr);
21265
+ if (utf8ByteLength(text) > MAX_EXPRESSION_BYTES)
21266
+ throw new PolicySyntaxError(
21267
+ `the expression is longer than ${String(MAX_EXPRESSION_BYTES)} bytes`,
21268
+ position
21269
+ );
21270
+ return text;
21271
+ }
21272
+ function parseExpression(text) {
21273
+ try {
21274
+ const stream = new TokenStream(tokenize(text));
21275
+ if (stream.peek().kind === "eof")
21276
+ throw new PolicySyntaxError("the expression is empty", 1);
21277
+ const expr = readCondition(stream);
21278
+ if (stream.peek().kind !== "eof") stream.unexpected();
21279
+ return { ok: true, expr, text: checkExpressionSize(expr, 1) };
21280
+ } catch (error51) {
21281
+ if (error51 instanceof PolicySyntaxError)
21282
+ return { ok: false, reason: error51.reason, position: error51.position };
21283
+ throw error51;
21284
+ }
21285
+ }
21286
+ function quoteIdent(name) {
21287
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !SPECIAL_WORDS.has(asciiLower(name)) ? name : `"${name.replaceAll('"', '""')}"`;
21288
+ }
21289
+ function commentText(text) {
21290
+ let out = "";
21291
+ for (const char of text) {
21292
+ const code = char.charCodeAt(0);
21293
+ const control = code < 32 || code >= 127 && code <= 159 || code === 8232 || code === 8233;
21294
+ out += control ? `\\u${code.toString(16).padStart(4, "0")}` : char;
21295
+ }
21296
+ return out;
21297
+ }
21298
+ function quoteString(text) {
21299
+ return `'${text.replaceAll("'", "''")}'`;
21300
+ }
21301
+ function printNumber(value) {
21302
+ if (Object.is(value, -0)) return "-0.0";
21303
+ const text = String(value);
21304
+ return Number.isSafeInteger(value) || /[.e]/.test(text) ? text : `${text}.0`;
21305
+ }
21306
+ function printLiteral(value) {
21307
+ if (value === null) return "NULL";
21308
+ if (typeof value === "boolean") return value ? "true" : "false";
21309
+ if (typeof value === "number") return printNumber(value);
21310
+ return quoteString(value);
21311
+ }
21312
+ function printValue(expr) {
21313
+ switch (expr.kind) {
21314
+ case "literal":
21315
+ return printLiteral(expr.value);
21316
+ case "column":
21317
+ return expr.qualifier === null ? quoteIdent(expr.name) : `${quoteIdent(expr.qualifier)}.${quoteIdent(expr.name)}`;
21318
+ case "auth":
21319
+ return `auth.${expr.fn}()`;
21320
+ case "jwt":
21321
+ return `(auth.jwt() ${expr.op} ${quoteString(expr.claim)})`;
21322
+ case "func":
21323
+ return expr.name === "now" ? "now()" : `${expr.name}(${expr.args.map(printValue).join(", ")})`;
21324
+ case "cast":
21325
+ return `(${printValue(expr.operand)})::text`;
21326
+ }
21327
+ }
21328
+ function printSubquery(subquery) {
21329
+ const select = subquery.select === "*" ? "*" : printValue(subquery.select);
21330
+ const alias = subquery.alias === null ? "" : ` ${quoteIdent(subquery.alias)}`;
21331
+ const where = subquery.where === null ? "" : ` WHERE ${printExpr(subquery.where)}`;
21332
+ return `SELECT ${select} FROM ${quoteIdent(subquery.table)}${alias}${where}`;
21333
+ }
21334
+ function printElement(element) {
21335
+ if (typeof element === "object") return printValue(element);
21336
+ return typeof element === "number" ? printNumber(element) : quoteString(element);
21337
+ }
21338
+ function printExpr(expr) {
21339
+ if (isValueExpr(expr)) return printValue(expr);
21340
+ switch (expr.kind) {
21341
+ case "and":
21342
+ case "or":
21343
+ return `(${expr.args.map(printExpr).join(expr.kind === "and" ? " AND " : " OR ")})`;
21344
+ case "not":
21345
+ return `(NOT ${printExpr(expr.arg)})`;
21346
+ case "compare":
21347
+ return `(${printValue(expr.left)} ${expr.op} ${printValue(expr.right)})`;
21348
+ case "distinct":
21349
+ return `(${printValue(expr.left)} IS ${expr.not ? "NOT " : ""}DISTINCT FROM ${printValue(expr.right)})`;
21350
+ case "is":
21351
+ return `(${printValue(expr.operand)} IS ${expr.not ? "NOT " : ""}${printLiteral(expr.value)})`;
21352
+ case "in":
21353
+ return `(${printValue(expr.operand)} ${expr.not ? "NOT " : ""}IN (${expr.list.map(printValue).join(", ")}))`;
21354
+ case "inSelect":
21355
+ return `(${printValue(expr.operand)} ${expr.not ? "NOT " : ""}IN (${printSubquery(expr.subquery)}))`;
21356
+ case "like":
21357
+ return `(${printValue(expr.operand)} ${expr.not ? "NOT " : ""}${expr.ci ? "ILIKE" : "LIKE"} ${quoteString(expr.pattern)})`;
21358
+ case "between":
21359
+ return `(${printValue(expr.operand)} ${expr.not ? "NOT " : ""}BETWEEN ${printValue(expr.low)} AND ${printValue(expr.high)})`;
21360
+ case "array":
21361
+ return `(${printValue(expr.operand)} ${expr.op} ARRAY[${expr.values.map(printElement).join(", ")}])`;
21362
+ case "exists":
21363
+ return `(EXISTS (${printSubquery(expr.subquery)}))`;
21364
+ }
21365
+ }
21366
+ function unqualifiedInSubquery(expr) {
21367
+ let found;
21368
+ function value(node, inside) {
21369
+ if (found !== void 0) return;
21370
+ switch (node.kind) {
21371
+ case "column":
21372
+ if (inside && node.qualifier === null) found = node;
21373
+ return;
21374
+ case "func":
21375
+ for (const arg of node.args) value(arg, inside);
21376
+ return;
21377
+ case "cast":
21378
+ value(node.operand, inside);
21379
+ return;
21380
+ default:
21381
+ return;
21382
+ }
21383
+ }
21384
+ function subquery(node) {
21385
+ if (node.select !== "*") value(node.select, true);
21386
+ if (node.where !== null) visit(node.where, true);
21387
+ }
21388
+ function visit(node, inside) {
21389
+ if (found !== void 0) return;
21390
+ if (isValueExpr(node)) {
21391
+ value(node, inside);
21392
+ return;
21393
+ }
21394
+ switch (node.kind) {
21395
+ case "and":
21396
+ case "or":
21397
+ for (const arg of node.args) visit(arg, inside);
21398
+ return;
21399
+ case "not":
21400
+ visit(node.arg, inside);
21401
+ return;
21402
+ case "compare":
21403
+ case "distinct":
21404
+ value(node.left, inside);
21405
+ value(node.right, inside);
21406
+ return;
21407
+ case "is":
21408
+ case "like":
21409
+ case "array":
21410
+ value(node.operand, inside);
21411
+ return;
21412
+ case "in":
21413
+ value(node.operand, inside);
21414
+ for (const item of node.list) value(item, inside);
21415
+ return;
21416
+ case "between":
21417
+ for (const item of [node.operand, node.low, node.high])
21418
+ value(item, inside);
21419
+ return;
21420
+ case "inSelect":
21421
+ value(node.operand, inside);
21422
+ subquery(node.subquery);
21423
+ return;
21424
+ case "exists":
21425
+ subquery(node.subquery);
21426
+ return;
21427
+ }
21428
+ }
21429
+ visit(expr, false);
21430
+ return found;
21431
+ }
21432
+ function unqualifiedProblem(ref) {
21433
+ const name = quoteIdent(ref.name);
21434
+ 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
+ }
21436
+
21437
+ // ../../packages/data-core/src/named-policy.ts
21438
+ var POLICY_COMMANDS = [
21439
+ "all",
21440
+ "select",
21441
+ "insert",
21442
+ "update",
21443
+ "delete"
21444
+ ];
21445
+ var POLICY_ROLES = ["anon", "authenticated"];
21446
+ var MAX_POLICY_NAME_LENGTH = 63;
21447
+ var MAX_POLICY_SQL_BYTES = 16 * 1024;
21448
+ function rolesText(roles) {
21449
+ const set2 = new Set(roles);
21450
+ if (set2.has("anon") && set2.has("authenticated")) return "public";
21451
+ return set2.has("anon") ? "anon" : "authenticated";
21452
+ }
21453
+ function readRoles(names) {
21454
+ const set2 = /* @__PURE__ */ new Set();
21455
+ if (names.length === 0)
21456
+ return { ok: false, reason: "Roles are public, anon and authenticated." };
21457
+ for (const name of names) {
21458
+ if (name === "public") {
21459
+ set2.add("anon");
21460
+ set2.add("authenticated");
21461
+ } else if (name === "anon" || name === "authenticated") set2.add(name);
21462
+ else
21463
+ return {
21464
+ ok: false,
21465
+ reason: name === "service_role" ? "Roles are public, anon and authenticated: the service key skips every policy." : "Roles are public, anon and authenticated."
21466
+ };
21467
+ }
21468
+ return { ok: true, roles: POLICY_ROLES.filter((role) => set2.has(role)) };
21469
+ }
21470
+ function quotePolicyName(name) {
21471
+ return /^[a-z_][a-z0-9_]*$/.test(name) && quoteIdent(name) === name ? name : `"${name.replaceAll('"', '""')}"`;
21472
+ }
21473
+ function policyShapeProblem(policy) {
21474
+ const { name, command, using, check: check2 } = policy;
21475
+ if (name === "" || [...name].length > MAX_POLICY_NAME_LENGTH || name.includes("\0"))
21476
+ return `Policy names are 1 to ${String(MAX_POLICY_NAME_LENGTH)} characters.`;
21477
+ const upper = command.toUpperCase();
21478
+ if ((command === "select" || command === "delete") && check2 !== null)
21479
+ return "WITH CHECK cannot be applied to SELECT or DELETE.";
21480
+ if (command === "insert" && using !== null)
21481
+ return "Only WITH CHECK expression allowed for INSERT.";
21482
+ if ((command === "select" || command === "delete") && using === null)
21483
+ return `A ${upper} policy needs USING (\u2026).`;
21484
+ if (command === "insert" && check2 === null)
21485
+ return "An INSERT policy needs WITH CHECK (\u2026).";
21486
+ if (using === null && check2 === null)
21487
+ return `${command === "all" ? "An ALL" : "An UPDATE"} policy needs USING (\u2026), WITH CHECK (\u2026) or both.`;
21488
+ return void 0;
21489
+ }
21490
+ function ownerExpression(column) {
21491
+ return printExpr({
21492
+ kind: "compare",
21493
+ op: "=",
21494
+ left: { kind: "column", qualifier: null, name: column },
21495
+ right: { kind: "auth", fn: "uid" }
21496
+ });
21497
+ }
21498
+ function ownerClauses(column, command) {
21499
+ const text = ownerExpression(column);
21500
+ return command === "insert" ? { using: null, check: text } : { using: text, check: null };
21501
+ }
21502
+ var NAMED_POLICY_TABLE = "_naro_policies_v2";
21503
+ 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))`;
21504
+ var NAMED_POLICY_SQL = {
21505
+ list: `SELECT table_name, name, kind, command, roles, using_expr, check_expr, owner_column FROM ${NAMED_POLICY_TABLE} ORDER BY table_name, name`,
21506
+ insert: `INSERT INTO ${NAMED_POLICY_TABLE} (table_name, name, kind, command, roles, using_expr, check_expr, owner_column) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
21507
+ replace: `UPDATE ${NAMED_POLICY_TABLE} SET table_name = ?, name = ?, kind = ?, command = ?, roles = ?, using_expr = ?, check_expr = ?, owner_column = ?, updated_at = datetime('now') WHERE table_name = ? AND name = ?`,
21508
+ remove: `DELETE FROM ${NAMED_POLICY_TABLE} WHERE table_name = ? COLLATE NOCASE AND name = ?`,
21509
+ removeTable: `DELETE FROM ${NAMED_POLICY_TABLE} WHERE table_name = ? COLLATE NOCASE`
21510
+ };
21511
+ function policyValues(policy) {
21512
+ return [
21513
+ policy.table,
21514
+ policy.name,
21515
+ policy.permissive ? "permissive" : "restrictive",
21516
+ policy.command,
21517
+ rolesText(policy.roles),
21518
+ policy.using,
21519
+ policy.check,
21520
+ policy.owner
21521
+ ];
21522
+ }
21523
+ function storedExpression(value, field) {
21524
+ if (value === null || value === void 0) return { expr: null, text: null };
21525
+ if (typeof value !== "string")
21526
+ return { expr: null, text: null, problem: `${field} is not text` };
21527
+ const parsed = parseExpression(value);
21528
+ if (!parsed.ok)
21529
+ return {
21530
+ expr: null,
21531
+ text: value,
21532
+ problem: `${field}: ${parsed.reason} at character ${String(parsed.position)}`
21533
+ };
21534
+ const unbound = unqualifiedInSubquery(parsed.expr);
21535
+ if (unbound !== void 0)
21536
+ return {
21537
+ expr: null,
21538
+ text: value,
21539
+ problem: `${field}: ${unqualifiedProblem(unbound)}`
21540
+ };
21541
+ return { expr: parsed.expr, text: parsed.text };
21542
+ }
21543
+ function loadPolicy(row, log2) {
21544
+ const table = row.table_name;
21545
+ if (typeof table !== "string" || table === "") return void 0;
21546
+ const problems = [];
21547
+ const name = typeof row.name === "string" && row.name !== "" ? row.name : "(unnamed)";
21548
+ if (name === "(unnamed)") problems.push("the name is missing");
21549
+ const kind = row.kind;
21550
+ if (kind !== "permissive" && kind !== "restrictive")
21551
+ problems.push("kind is not permissive or restrictive");
21552
+ const command = POLICY_COMMANDS.includes(row.command) ? row.command : void 0;
21553
+ if (!command)
21554
+ problems.push("command is not one of all, select, insert, update, delete");
21555
+ 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");
21557
+ const using = storedExpression(row.using_expr, "using");
21558
+ const check2 = storedExpression(row.check_expr, "check");
21559
+ if (using.problem) problems.push(using.problem);
21560
+ if (check2.problem) problems.push(check2.problem);
21561
+ const owner = row.owner_column;
21562
+ const policy = {
21563
+ table,
21564
+ name,
21565
+ permissive: kind !== "restrictive",
21566
+ // What cannot be read applies everywhere: it refuses, never allows.
21567
+ command: command ?? "all",
21568
+ roles: roles?.ok ? roles.roles : [...POLICY_ROLES],
21569
+ using: using.text,
21570
+ check: check2.text,
21571
+ owner: typeof owner === "string" ? owner : null,
21572
+ invalid: null,
21573
+ usingExpr: using.expr,
21574
+ checkExpr: check2.expr
21575
+ };
21576
+ if (command && problems.length === 0) {
21577
+ const shape = policyShapeProblem(policy);
21578
+ if (shape) problems.push(shape);
21579
+ }
21580
+ if (policy.owner !== null && policy.owner !== "" && command && problems.length === 0) {
21581
+ const expected = ownerClauses(policy.owner, command);
21582
+ if (policy.using !== expected.using || policy.check !== expected.check)
21583
+ problems.push(
21584
+ "an owner policy's expressions are not its owner condition"
21585
+ );
21586
+ }
21587
+ if (problems.length > 0) {
21588
+ policy.invalid = problems.join("; ");
21589
+ log2?.(
21590
+ `naro data-core: policy ${name} on ${table} cannot be read: ${policy.invalid}`
21591
+ );
21592
+ }
21593
+ return policy;
21594
+ }
21595
+ function foldPolicyName(name, quoted) {
21596
+ return quoted ? name : asciiLower(name);
21597
+ }
21598
+ function policySqlTooLong(text) {
21599
+ return utf8ByteLength(text) > MAX_POLICY_SQL_BYTES;
21600
+ }
20007
21601
 
20008
21602
  // ../../packages/data-core/src/query.ts
20009
21603
  var FILTER_OPERATORS = [
@@ -20035,7 +21629,11 @@ var ARRAY_OPERATORS = [
20035
21629
  "containedBy",
20036
21630
  "overlaps"
20037
21631
  ];
20038
- var MAX_PATTERN_LENGTH = 1024;
21632
+ function patternProblem(operator, pattern) {
21633
+ if (!patternTooLong(pattern, operator === "ilike")) return void 0;
21634
+ const limit = `must be at most ${String(MAX_PATTERN_BYTES)} bytes of UTF-8, D1's limit for a pattern`;
21635
+ return operator === "ilike" ? limit : `${limit} (like counts each *, ? and [ as 3 bytes)`;
21636
+ }
20039
21637
  var MAX_IDENTIFIER_LENGTH = 128;
20040
21638
  var Invalid = class extends Error {
20041
21639
  constructor(path, reason) {
@@ -20050,7 +21648,7 @@ function invalid(path, reason) {
20050
21648
  function own(object2, key) {
20051
21649
  return Object.hasOwn(object2, key) ? object2[key] : void 0;
20052
21650
  }
20053
- function isOperator(value) {
21651
+ function isOperator2(value) {
20054
21652
  return FILTER_OPERATORS.includes(value);
20055
21653
  }
20056
21654
  function readObject(value, path) {
@@ -20075,7 +21673,7 @@ function readName(value, path) {
20075
21673
  );
20076
21674
  return value;
20077
21675
  }
20078
- function readValue(value, path, auth = false) {
21676
+ function readValue2(value, path, auth = false) {
20079
21677
  if (auth && isAuthUid(value)) return { auth: "uid" };
20080
21678
  if (!isValue(value))
20081
21679
  invalid(
@@ -20087,8 +21685,8 @@ function readValue(value, path, auth = false) {
20087
21685
  function longerThan(text, max) {
20088
21686
  if (text.length <= max) return false;
20089
21687
  if (text.length > max * 2) return true;
20090
- let count = 0;
20091
- for (const _ of text) if (++count > max) return true;
21688
+ let count2 = 0;
21689
+ for (const _ of text) if (++count2 > max) return true;
20092
21690
  return false;
20093
21691
  }
20094
21692
  function endsWithLoneEscape(pattern) {
@@ -20103,7 +21701,7 @@ function readFilter(value, path, auth) {
20103
21701
  const object2 = readObject(value, path);
20104
21702
  checkKeys(object2, ["operator", "column", "value"], path);
20105
21703
  const operator = own(object2, "operator");
20106
- if (!isOperator(operator))
21704
+ if (!isOperator2(operator))
20107
21705
  invalid(
20108
21706
  `${path}.operator`,
20109
21707
  `must be one of ${FILTER_OPERATORS.join(", ")}`
@@ -20116,7 +21714,7 @@ function readFilter(value, path, auth) {
20116
21714
  case "in": {
20117
21715
  if (!Array.isArray(raw)) invalid(valuePath, "must be an array");
20118
21716
  const values = raw.map(
20119
- (item, index) => readValue(item, `${valuePath}[${index}]`, auth)
21717
+ (item, index) => readValue2(item, `${valuePath}[${index}]`, auth)
20120
21718
  );
20121
21719
  return { operator, column, value: values };
20122
21720
  }
@@ -20125,18 +21723,16 @@ function readFilter(value, path, auth) {
20125
21723
  invalid(valuePath, "must be null, true or false");
20126
21724
  return { operator, column, value: raw };
20127
21725
  case "like":
20128
- case "ilike":
21726
+ case "ilike": {
20129
21727
  if (typeof raw !== "string") invalid(valuePath, "must be a string");
20130
- if (longerThan(raw, MAX_PATTERN_LENGTH))
20131
- invalid(
20132
- valuePath,
20133
- `must be at most ${String(MAX_PATTERN_LENGTH)} characters`
20134
- );
21728
+ const tooLong = patternProblem(operator, raw);
21729
+ if (tooLong !== void 0) invalid(valuePath, tooLong);
20135
21730
  if (raw.includes("\0"))
20136
21731
  invalid(valuePath, "must not contain a NUL character");
20137
21732
  if (endsWithLoneEscape(raw))
20138
21733
  invalid(valuePath, "must not end with an unescaped backslash");
20139
21734
  return { operator, column, value: raw };
21735
+ }
20140
21736
  case "contains":
20141
21737
  case "containedBy":
20142
21738
  case "overlaps":
@@ -20155,7 +21751,7 @@ function readFilter(value, path, auth) {
20155
21751
  )
20156
21752
  };
20157
21753
  default:
20158
- return { operator, column, value: readValue(raw, valuePath, auth) };
21754
+ return { operator, column, value: readValue2(raw, valuePath, auth) };
20159
21755
  }
20160
21756
  }
20161
21757
  var NODE_KEYS = ["operator", "or", "and", "not"];
@@ -20201,18 +21797,579 @@ function parsePolicyNode(value, path) {
20201
21797
  }
20202
21798
  }
20203
21799
 
20204
- // ../../packages/data-core/src/schema.ts
20205
- var SCHEMA_SQL = [
20206
- `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`,
20207
- // Phase 2-2 (적대적 리뷰 반영): each table's CREATE TABLE text, where
20208
- // alone a column's collation is written. A separate statement, so the
20209
- // text comes once per table rather than once per column. D1 answers
20210
- // `sqlite_master` reads (the database routes' `_tables.ts` reads it too).
20211
- `SELECT name AS table_name, sql AS table_sql FROM sqlite_master WHERE type = 'table' AND ${systemTablePredicate("name")}`
20212
- ];
20213
-
20214
21800
  // ../../packages/data-core/src/condition.ts
21801
+ var CONDITION_RULE = "condition";
20215
21802
  var MAX_CONDITION_BYTES = 16 * 1024;
21803
+ var TREES = {
21804
+ select: ["using"],
21805
+ delete: ["using"],
21806
+ insert: ["check"],
21807
+ update: ["using", "check"]
21808
+ };
21809
+ function parseCondition(action, value) {
21810
+ if (!isPlainObject2(value))
21811
+ return { ok: false, path: "", reason: "must be an object" };
21812
+ const trees = TREES[action];
21813
+ for (const key of Object.keys(value))
21814
+ if (key !== "role" && !trees.includes(key))
21815
+ return key.length > MAX_IDENTIFIER_LENGTH ? {
21816
+ ok: false,
21817
+ path: "",
21818
+ reason: "has a field that is not a known field"
21819
+ } : { ok: false, path: key, reason: "is not a known field" };
21820
+ const condition = {};
21821
+ if (Object.hasOwn(value, "role")) {
21822
+ const role = value.role;
21823
+ if (role !== "any" && role !== "authenticated")
21824
+ return {
21825
+ ok: false,
21826
+ path: "role",
21827
+ reason: 'must be "any" or "authenticated"'
21828
+ };
21829
+ if (role === "authenticated") condition.role = role;
21830
+ }
21831
+ const [required2] = trees;
21832
+ for (const key of trees) {
21833
+ if (!Object.hasOwn(value, key)) {
21834
+ if (key === required2)
21835
+ return { ok: false, path: key, reason: "is required" };
21836
+ continue;
21837
+ }
21838
+ const parsed = parsePolicyNode(value[key], key);
21839
+ if (!parsed.ok) return parsed;
21840
+ condition[key] = parsed.node;
21841
+ }
21842
+ return { ok: true, condition };
21843
+ }
21844
+ function conditionsTooLarge(texts) {
21845
+ let total = 0;
21846
+ for (const text of texts)
21847
+ if (typeof text === "string") total += utf8ByteLength(text);
21848
+ return total > MAX_CONDITION_BYTES;
21849
+ }
21850
+ function isInvalidCondition(value) {
21851
+ return isPlainObject2(value) && typeof value.invalid === "string";
21852
+ }
21853
+ function checkOf(action, condition) {
21854
+ if (action === "insert") return condition.check;
21855
+ if (action === "update") return condition.check ?? condition.using;
21856
+ return void 0;
21857
+ }
21858
+ var MAX_CONDITION_VALUES = 50;
21859
+ var ARRAY_OPERATORS2 = ["contains", "containedBy", "overlaps"];
21860
+ function treeValues(node, rebinds) {
21861
+ if ("not" in node) return treeValues(node.not, rebinds);
21862
+ if ("or" in node || "and" in node)
21863
+ return ("or" in node ? node.or : node.and).reduce(
21864
+ (sum, child) => sum + treeValues(child, rebinds),
21865
+ 0
21866
+ );
21867
+ const value = node.value;
21868
+ const own2 = Array.isArray(value) ? ARRAY_OPERATORS2.includes(node.operator) ? new Set(value.map((item) => JSON.stringify(item))).size : value.length : node.operator === "is" && value !== null ? 0 : 1;
21869
+ if (!rebinds || Array.isArray(value) && value.length === 0) return own2;
21870
+ return own2 + (ARRAY_OPERATORS2.includes(node.operator) ? 5 : 1);
21871
+ }
21872
+ function conditionValueCount(action, condition) {
21873
+ let count2 = 0;
21874
+ if (action !== "insert" && condition.using)
21875
+ count2 += treeValues(condition.using, false);
21876
+ const check2 = checkOf(action, condition);
21877
+ if (check2) count2 += treeValues(check2, action === "update");
21878
+ return count2;
21879
+ }
21880
+
21881
+ // ../../packages/data-core/src/legacy-policy.ts
21882
+ var RULES = ["none", "public", "authenticated", "owner"];
21883
+ var POLICY_ACTIONS2 = ["select", "insert", "update", "delete"];
21884
+ function isRule(value) {
21885
+ return RULES.includes(value);
21886
+ }
21887
+ function sugarName(action) {
21888
+ return `naro_${action}`;
21889
+ }
21890
+ var CONVERTED_RULE = "rls";
21891
+ var ROW_AS_READ = "table_name = ? AND select_rule IS ? AND insert_rule IS ? AND update_rule IS ? AND delete_rule IS ? AND owner_column IS ? AND select_condition IS ? AND insert_condition IS ? AND update_condition IS ? AND delete_condition IS ? AND updated_at IS ?";
21892
+ var LEGACY_ROW_COLUMNS = [
21893
+ "table_name",
21894
+ "select_rule",
21895
+ "insert_rule",
21896
+ "update_rule",
21897
+ "delete_rule",
21898
+ "owner_column",
21899
+ "select_condition",
21900
+ "insert_condition",
21901
+ "update_condition",
21902
+ "delete_condition",
21903
+ "updated_at"
21904
+ ];
21905
+ function legacyRowAsRead(row) {
21906
+ const values = [];
21907
+ for (const column of LEGACY_ROW_COLUMNS) {
21908
+ const value = row[column];
21909
+ if (value === null || value === void 0) values.push(null);
21910
+ else if (typeof value === "string") values.push(value);
21911
+ else return void 0;
21912
+ }
21913
+ return values;
21914
+ }
21915
+ var POLICY_SQL = {
21916
+ list: "SELECT * FROM _naro_policies",
21917
+ upsert: "INSERT INTO _naro_policies (table_name, select_rule, insert_rule, update_rule, delete_rule, owner_column, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now')) ON CONFLICT(table_name) DO UPDATE SET select_rule = excluded.select_rule, insert_rule = excluded.insert_rule, update_rule = excluded.update_rule, delete_rule = excluded.delete_rule, owner_column = excluded.owner_column, updated_at = excluded.updated_at",
21918
+ upsertWithConditions: "INSERT INTO _naro_policies (table_name, select_rule, insert_rule, update_rule, delete_rule, owner_column, select_condition, insert_condition, update_condition, delete_condition, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now')) ON CONFLICT(table_name) DO UPDATE SET select_rule = excluded.select_rule, insert_rule = excluded.insert_rule, update_rule = excluded.update_rule, delete_rule = excluded.delete_rule, owner_column = excluded.owner_column, select_condition = excluded.select_condition, insert_condition = excluded.insert_condition, update_condition = excluded.update_condition, delete_condition = excluded.delete_condition, updated_at = excluded.updated_at",
21919
+ remove: "DELETE FROM _naro_policies WHERE table_name = ?",
21920
+ /**
21921
+ * Migration 4: the rules of a converted row, if it is still as read —
21922
+ * binds `legacyRowAsRead(row)`.
21923
+ */
21924
+ markConverted: `UPDATE _naro_policies SET select_rule = '${CONVERTED_RULE}', insert_rule = '${CONVERTED_RULE}', update_rule = '${CONVERTED_RULE}', delete_rule = '${CONVERTED_RULE}' WHERE ${ROW_AS_READ}`,
21925
+ /**
21926
+ * Migration 4: one named policy a row converts to, written only if the
21927
+ * row is still as read — the same gate as `markConverted`, so a row the
21928
+ * old control plane changed in between is neither converted nor marked.
21929
+ * Binds `policyValues` and then `legacyRowAsRead(row)`.
21930
+ */
21931
+ 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
+ };
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";
21936
+ if (tooLarge)
21937
+ return `the conditions of the policy are larger than ${String(MAX_CONDITION_BYTES)} bytes`;
21938
+ let value;
21939
+ try {
21940
+ value = JSON.parse(text);
21941
+ } catch {
21942
+ return "is not JSON";
21943
+ }
21944
+ const parsed = parseCondition(action, value);
21945
+ if (parsed.ok) return parsed.condition;
21946
+ return parsed.path === "" ? parsed.reason : `${parsed.path} ${parsed.reason}`;
21947
+ }
21948
+ function policyFromRow(row, log2) {
21949
+ const table = row.table_name;
21950
+ if (typeof table !== "string" || table === "") return void 0;
21951
+ const texts = POLICY_ACTIONS2.map(
21952
+ (action) => row[`${action}_rule`] === CONDITION_RULE ? row[`${action}_condition`] : void 0
21953
+ );
21954
+ const tooLarge = conditionsTooLarge(
21955
+ texts.filter((text) => typeof text === "string")
21956
+ );
21957
+ const read = POLICY_ACTIONS2.map(
21958
+ (action, index) => {
21959
+ const value = row[`${action}_rule`];
21960
+ if (value !== CONDITION_RULE) return isRule(value) ? value : "none";
21961
+ return readStoredCondition(action, texts[index], tooLarge);
21962
+ }
21963
+ );
21964
+ const values = POLICY_ACTIONS2.reduce((sum, action, index) => {
21965
+ const item = read[index];
21966
+ return typeof item === "object" ? sum + conditionValueCount(action, item) : sum;
21967
+ }, 0);
21968
+ const rule = (action, index) => {
21969
+ let item = read[index] ?? "none";
21970
+ if (typeof item === "object" && values > MAX_CONDITION_VALUES)
21971
+ item = `the conditions of the policy use more than ${String(MAX_CONDITION_VALUES)} values`;
21972
+ if (typeof item === "object" || isRule(item)) return item;
21973
+ log2?.(
21974
+ `naro data-core: the ${action} condition of ${table} cannot be read: ${item}`
21975
+ );
21976
+ return { invalid: item };
21977
+ };
21978
+ const owner = row.owner_column;
21979
+ return {
21980
+ table,
21981
+ select: rule("select", 0),
21982
+ insert: rule("insert", 1),
21983
+ update: rule("update", 2),
21984
+ delete: rule("delete", 3),
21985
+ ownerColumn: typeof owner === "string" && owner !== "" ? owner : null
21986
+ };
21987
+ }
21988
+ function findPolicy(policies, table) {
21989
+ const exact = policies.find((policy) => policy.table === table);
21990
+ if (exact) return exact;
21991
+ const folded = asciiLower(table);
21992
+ const matches = policies.filter(
21993
+ (policy) => asciiLower(policy.table) === folded
21994
+ );
21995
+ return matches.length === 1 ? matches[0] : void 0;
21996
+ }
21997
+ function legacyInForce(policies, tables) {
21998
+ const picked = /* @__PURE__ */ new Set();
21999
+ for (const table of tables) {
22000
+ const policy = findPolicy(policies, table);
22001
+ if (policy) picked.add(policy);
22002
+ }
22003
+ return policies.filter((policy) => picked.has(policy));
22004
+ }
22005
+ function valueExpr(value) {
22006
+ if (typeof value === "object" && value !== null)
22007
+ return { kind: "auth", fn: "uid" };
22008
+ return { kind: "literal", value };
22009
+ }
22010
+ var COMPARISONS = {
22011
+ eq: "=",
22012
+ neq: "<>",
22013
+ gt: ">",
22014
+ gte: ">=",
22015
+ lt: "<",
22016
+ lte: "<="
22017
+ };
22018
+ var ARRAYS = { contains: "@>", containedBy: "<@", overlaps: "&&" };
22019
+ var UID = { kind: "auth", fn: "uid" };
22020
+ function uidIsNull(not) {
22021
+ return { kind: "is", not, operand: UID, value: null };
22022
+ }
22023
+ function anonDiffers(expr) {
22024
+ if (expr.kind === "array")
22025
+ return expr.values.some((value) => typeof value === "object");
22026
+ if (expr.kind !== "in") return false;
22027
+ const uids = expr.list.filter((item) => item.kind === "auth").length;
22028
+ return uids > 0 && uids < expr.list.length;
22029
+ }
22030
+ function filterNodeToExpr(node, negated = false) {
22031
+ if ("not" in node)
22032
+ return { kind: "not", arg: filterNodeToExpr(node.not, !negated) };
22033
+ if ("or" in node || "and" in node) {
22034
+ const children = ("or" in node ? node.or : node.and).map(
22035
+ (child) => filterNodeToExpr(child, negated)
22036
+ );
22037
+ if (children.length === 1) return children[0];
22038
+ return { kind: "or" in node ? "or" : "and", args: children };
22039
+ }
22040
+ const leaf = leafToExpr(node);
22041
+ if (!anonDiffers(leaf)) return leaf;
22042
+ return negated ? { kind: "or", args: [leaf, uidIsNull(false)] } : { kind: "and", args: [leaf, uidIsNull(true)] };
22043
+ }
22044
+ function leafToExpr(node) {
22045
+ const column = {
22046
+ kind: "column",
22047
+ qualifier: null,
22048
+ name: node.column
22049
+ };
22050
+ const value = node.value;
22051
+ switch (node.operator) {
22052
+ case "eq":
22053
+ case "neq":
22054
+ case "gt":
22055
+ case "gte":
22056
+ case "lt":
22057
+ case "lte":
22058
+ return {
22059
+ kind: "compare",
22060
+ op: COMPARISONS[node.operator],
22061
+ left: column,
22062
+ right: valueExpr(Array.isArray(value) ? null : value)
22063
+ };
22064
+ case "in": {
22065
+ const list = Array.isArray(value) ? value : [value];
22066
+ if (list.length === 0) return { kind: "literal", value: false };
22067
+ return {
22068
+ kind: "in",
22069
+ not: false,
22070
+ operand: column,
22071
+ list: list.map(valueExpr)
22072
+ };
22073
+ }
22074
+ case "is":
22075
+ return {
22076
+ kind: "is",
22077
+ not: false,
22078
+ operand: column,
22079
+ value: value === true || value === false ? value : null
22080
+ };
22081
+ case "like":
22082
+ case "ilike":
22083
+ return {
22084
+ kind: "like",
22085
+ not: false,
22086
+ ci: node.operator === "ilike",
22087
+ operand: column,
22088
+ pattern: typeof value === "string" ? value : ""
22089
+ };
22090
+ case "contains":
22091
+ case "containedBy":
22092
+ case "overlaps":
22093
+ return {
22094
+ kind: "array",
22095
+ op: ARRAYS[node.operator],
22096
+ operand: { kind: "column", qualifier: null, name: node.column },
22097
+ values: (Array.isArray(value) ? value : []).map(
22098
+ (item) => typeof item === "object" && item !== null ? { kind: "auth", fn: "uid" } : typeof item === "number" ? item : String(item)
22099
+ )
22100
+ };
22101
+ }
22102
+ }
22103
+ var TRUE_TEXT = printExpr({ kind: "literal", value: true });
22104
+ function treeText(node) {
22105
+ return node === void 0 ? null : printExpr(filterNodeToExpr(node));
22106
+ }
22107
+ function namedFromRule(table, action, rule, ownerColumn) {
22108
+ const base2 = {
22109
+ table,
22110
+ name: sugarName(action),
22111
+ permissive: true,
22112
+ command: action,
22113
+ owner: null
22114
+ };
22115
+ const everyone = [...POLICY_ROLES];
22116
+ if (rule === "none" || typeof rule === "string" && !isRule(rule))
22117
+ return null;
22118
+ if (rule === "public" || rule === "authenticated")
22119
+ return {
22120
+ ...base2,
22121
+ roles: rule === "public" ? everyone : ["authenticated"],
22122
+ using: action === "insert" ? null : TRUE_TEXT,
22123
+ check: action === "insert" ? TRUE_TEXT : null
22124
+ };
22125
+ if (rule === "owner") {
22126
+ const column = ownerColumn ?? "";
22127
+ return {
22128
+ ...base2,
22129
+ roles: ["authenticated"],
22130
+ ...column === "" ? { using: null, check: null } : ownerClauses(column, action),
22131
+ owner: column
22132
+ };
22133
+ }
22134
+ if (isInvalidCondition(rule) || (action === "insert" ? rule.check : rule.using) === void 0)
22135
+ return { ...base2, roles: everyone, using: null, check: null };
22136
+ return {
22137
+ ...base2,
22138
+ roles: rule.role === "authenticated" ? ["authenticated"] : everyone,
22139
+ using: action === "insert" ? null : treeText(rule.using),
22140
+ check: action === "insert" || action === "update" ? treeText(rule.check) : null
22141
+ };
22142
+ }
22143
+ function namedFromLegacy(policy) {
22144
+ return POLICY_ACTIONS2.flatMap((action) => {
22145
+ const named = namedFromRule(
22146
+ policy.table,
22147
+ action,
22148
+ policy[action],
22149
+ policy.ownerColumn
22150
+ );
22151
+ return named ? [named] : [];
22152
+ });
22153
+ }
22154
+ function loadNamed(policies, log2) {
22155
+ const columns = [
22156
+ "table_name",
22157
+ "name",
22158
+ "kind",
22159
+ "command",
22160
+ "roles",
22161
+ "using_expr",
22162
+ "check_expr",
22163
+ "owner_column"
22164
+ ];
22165
+ return policies.flatMap((policy) => {
22166
+ const values = policyValues(policy);
22167
+ const loaded = loadPolicy(
22168
+ Object.fromEntries(
22169
+ columns.map((column, index) => [column, values[index]])
22170
+ ),
22171
+ log2
22172
+ );
22173
+ return loaded ? [loaded] : [];
22174
+ });
22175
+ }
22176
+ function unreadableConversions(policy) {
22177
+ const found = [];
22178
+ for (const action of POLICY_ACTIONS2) {
22179
+ const rule = policy[action];
22180
+ const named = namedFromRule(policy.table, action, rule, policy.ownerColumn);
22181
+ if (!named) continue;
22182
+ const [loaded] = loadNamed([named]);
22183
+ if (!loaded || loaded.invalid === null) continue;
22184
+ found.push({
22185
+ table: named.table,
22186
+ policy: named.name,
22187
+ reason: isInvalidCondition(rule) ? `the old ${action} condition could not be read: ${rule.invalid}` : rule === "owner" && named.owner === "" ? `the old ${action} rule is owner, and the row has no owner column` : loaded.invalid
22188
+ });
22189
+ }
22190
+ return found;
22191
+ }
22192
+
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);
22206
+ }
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);
22214
+ }
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
+ );
22229
+ }
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 };
22236
+ }
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
+ );
22251
+ }
22252
+ let command = "all";
22253
+ if (stream.acceptKeyword("for")) command = readCommand(stream);
22254
+ let roles = readRoles(["public"]);
22255
+ if (isKeyword(stream.peek(), "to")) {
22256
+ const to = stream.next();
22257
+ const names = [];
22258
+ do
22259
+ names.push(readRoleName(stream.next()));
22260
+ while (stream.acceptOperator(","));
22261
+ roles = readRoles(names);
22262
+ if (!roles.ok)
22263
+ throw new PolicySyntaxError(roles.reason.replace(/\.$/, ""), to.position);
22264
+ }
22265
+ let using = null;
22266
+ let check2 = null;
22267
+ let position = start.position;
22268
+ if (stream.acceptKeyword("using")) {
22269
+ const clause = readClause(stream);
22270
+ using = clause.text;
22271
+ position = clause.position;
22272
+ }
22273
+ if (isKeyword(stream.peek(), "with")) {
22274
+ stream.next();
22275
+ stream.expectKeyword("check");
22276
+ const clause = readClause(stream);
22277
+ check2 = clause.text;
22278
+ position = clause.position;
22279
+ }
22280
+ if (!roles.ok) throw new Error("unreachable");
22281
+ const policy = {
22282
+ table,
22283
+ name,
22284
+ permissive,
22285
+ command,
22286
+ roles: roles.roles,
22287
+ using,
22288
+ check: check2,
22289
+ owner: null
22290
+ };
22291
+ const problem = policyShapeProblem(policy);
22292
+ if (problem)
22293
+ throw new PolicySyntaxError(problem.replace(/\.$/, ""), position);
22294
+ return { kind: "create", policy };
22295
+ }
22296
+ function readDrop(stream) {
22297
+ stream.expectKeyword("policy");
22298
+ let ifExists = false;
22299
+ if (stream.acceptKeyword("if")) {
22300
+ stream.expectKeyword("exists");
22301
+ ifExists = true;
22302
+ }
22303
+ const name = readPolicyName(stream);
22304
+ stream.expectKeyword("on");
22305
+ const table = readTableName(stream);
22306
+ if (!stream.acceptKeyword("cascade")) stream.acceptKeyword("restrict");
22307
+ return { kind: "drop", name, table, ifExists };
22308
+ }
22309
+ function parsePolicyStatement(text) {
22310
+ if (policySqlTooLong(text))
22311
+ return {
22312
+ ok: false,
22313
+ reason: `the statement is longer than ${String(MAX_POLICY_SQL_BYTES)} bytes`,
22314
+ position: 1
22315
+ };
22316
+ try {
22317
+ const stream = new TokenStream(tokenize(text));
22318
+ 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);
22322
+ else if (isKeyword(start, "alter"))
22323
+ throw new PolicySyntaxError(
22324
+ "ALTER POLICY is not supported here yet: DROP POLICY and CREATE POLICY again",
22325
+ start.position
22326
+ );
22327
+ else if (start.kind === "eof")
22328
+ throw new PolicySyntaxError("the statement is empty", 1);
22329
+ else
22330
+ throw new PolicySyntaxError(
22331
+ "only CREATE POLICY and DROP POLICY are accepted here",
22332
+ start.position
22333
+ );
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 };
22342
+ } catch (error51) {
22343
+ if (error51 instanceof PolicySyntaxError)
22344
+ return { ok: false, reason: error51.reason, position: error51.position };
22345
+ throw error51;
22346
+ }
22347
+ }
22348
+ function splitPolicyStatements(text) {
22349
+ let tokens;
22350
+ try {
22351
+ tokens = tokenize(text);
22352
+ } catch (error51) {
22353
+ if (error51 instanceof PolicySyntaxError)
22354
+ return { ok: false, reason: error51.reason, position: error51.position };
22355
+ throw error51;
22356
+ }
22357
+ const statements = [];
22358
+ let start;
22359
+ for (const token of tokens) {
22360
+ if (token.kind === "eof") break;
22361
+ start ??= token.position - 1;
22362
+ if (token.kind === "op" && token.value === ";") {
22363
+ statements.push(text.slice(start, token.position).trim());
22364
+ start = void 0;
22365
+ }
22366
+ }
22367
+ if (start !== void 0) {
22368
+ const last = text.slice(start).trim();
22369
+ if (last !== "") statements.push(last);
22370
+ }
22371
+ return { ok: true, statements };
22372
+ }
20216
22373
 
20217
22374
  // ../../packages/data-core/src/redirect-origins.ts
20218
22375
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
@@ -20259,6 +22416,34 @@ function formatRedirectEntry(entry) {
20259
22416
  }
20260
22417
 
20261
22418
  // ../../packages/data-core/src/system-sql.ts
22419
+ async function convertLegacyPolicies(db, found) {
22420
+ const rows = rowsOf(await db.prepare(POLICY_SQL.list).all());
22421
+ if (rows.length === 0) return [];
22422
+ const read = rows.map((row) => ({
22423
+ policy: policyFromRow(row),
22424
+ asRead: legacyRowAsRead(row)
22425
+ }));
22426
+ const inForce = new Set(
22427
+ legacyInForce(
22428
+ read.flatMap(({ policy }) => policy ? [policy] : []),
22429
+ await readTableNames(db)
22430
+ )
22431
+ );
22432
+ const statements = [];
22433
+ for (const { policy, asRead } of read) {
22434
+ if (!policy || !asRead) continue;
22435
+ if (inForce.has(policy)) {
22436
+ for (const named of namedFromLegacy(policy))
22437
+ statements.push({
22438
+ sql: POLICY_SQL.insertConverted,
22439
+ params: [...policyValues(named), ...asRead]
22440
+ });
22441
+ found.unreadable.push(...unreadableConversions(policy));
22442
+ }
22443
+ statements.push({ sql: POLICY_SQL.markConverted, params: asRead });
22444
+ }
22445
+ return statements;
22446
+ }
20262
22447
  var MIGRATIONS_TABLE = "CREATE TABLE IF NOT EXISTS _naro_migrations (version INTEGER PRIMARY KEY, name TEXT NOT NULL, applied_at TEXT NOT NULL DEFAULT (datetime('now')))";
20263
22448
  var SYSTEM_MIGRATIONS = [
20264
22449
  {
@@ -20300,6 +22485,15 @@ var SYSTEM_MIGRATIONS = [
20300
22485
  "CREATE INDEX IF NOT EXISTS _naro_auth_email_log_address ON _naro_auth_email_log (address_hash, sent_at)",
20301
22486
  "CREATE TABLE IF NOT EXISTS _naro_auth_contested (user_id TEXT PRIMARY KEY, contested_at INTEGER NOT NULL)"
20302
22487
  ]
22488
+ },
22489
+ {
22490
+ // P2-7 §9: named policies, many per table. The old rows are converted
22491
+ // in the same batch and their rules set to a word old workers read as
22492
+ // `none`.
22493
+ version: 4,
22494
+ name: "named_policies",
22495
+ statements: [NAMED_POLICY_TABLE_SQL],
22496
+ compute: convertLegacyPolicies
20303
22497
  }
20304
22498
  ];
20305
22499
 
@@ -20365,12 +22559,12 @@ function sqlSkeleton(sql) {
20365
22559
  let index = 0;
20366
22560
  while (index < sql.length) {
20367
22561
  const char = sql[index] ?? "";
20368
- const pair = sql.slice(index, index + 2);
20369
- if (pair === "--") {
22562
+ const pair2 = sql.slice(index, index + 2);
22563
+ if (pair2 === "--") {
20370
22564
  const end = sql.indexOf("\n", index);
20371
22565
  index = end < 0 ? sql.length : end;
20372
22566
  out += " ";
20373
- } else if (pair === "/*") {
22567
+ } else if (pair2 === "/*") {
20374
22568
  const end = sql.indexOf("*/", index + 2);
20375
22569
  index = end < 0 ? sql.length : end + 2;
20376
22570
  out += " ";
@@ -20548,12 +22742,12 @@ import {
20548
22742
  rmSync as rmSync2,
20549
22743
  writeFileSync as writeFileSync2
20550
22744
  } from "fs";
20551
- import { join as join21 } from "path";
22745
+ import { join as join22 } from "path";
20552
22746
  function linkDir(dir) {
20553
- return join21(dir, ".naro");
22747
+ return join22(dir, ".naro");
20554
22748
  }
20555
22749
  function linkFile(dir) {
20556
- return join21(linkDir(dir), "project.json");
22750
+ return join22(linkDir(dir), "project.json");
20557
22751
  }
20558
22752
  function readProjectLink(dir) {
20559
22753
  try {
@@ -20575,7 +22769,7 @@ function writeProjectLink(dir, link) {
20575
22769
  writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
20576
22770
  `);
20577
22771
  writeFileSync2(
20578
- join21(linkDir(dir), "README.txt"),
22772
+ join22(linkDir(dir), "README.txt"),
20579
22773
  "This directory links the project to Naro and should not be committed.\n"
20580
22774
  );
20581
22775
  }
@@ -20585,9 +22779,9 @@ function removeProjectLink(dir) {
20585
22779
  return true;
20586
22780
  }
20587
22781
  function ensureGitignoreHasNaro(dir) {
20588
- const gitignorePath = join21(dir, ".gitignore");
22782
+ const gitignorePath = join22(dir, ".gitignore");
20589
22783
  const hasGitignore = existsSync4(gitignorePath);
20590
- if (!hasGitignore && !existsSync4(join21(dir, ".git"))) return false;
22784
+ if (!hasGitignore && !existsSync4(join22(dir, ".git"))) return false;
20591
22785
  const current = hasGitignore ? readFileSync5(gitignorePath, "utf8") : "";
20592
22786
  const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".naro" || line === ".naro/" || line === "/.naro");
20593
22787
  if (covered) return false;
@@ -20928,10 +23122,10 @@ function buildResetPlan(input) {
20928
23122
  const triggers = sorted(
20929
23123
  objects.filter((object2) => object2.type === "trigger").map((object2) => object2.name)
20930
23124
  );
20931
- const tableNames = sorted(
23125
+ const tableNames2 = sorted(
20932
23126
  objects.filter((object2) => object2.type === "table" || object2.type === "virtual").map((object2) => object2.name)
20933
23127
  );
20934
- const canonical = new Map(tableNames.map((name) => [asciiLower(name), name]));
23128
+ const canonical = new Map(tableNames2.map((name) => [asciiLower(name), name]));
20935
23129
  const edges = [];
20936
23130
  for (const edge of input.foreignKeys) {
20937
23131
  const child = canonical.get(asciiLower(edge.child));
@@ -20939,7 +23133,7 @@ function buildResetPlan(input) {
20939
23133
  if (child !== void 0 && parent !== void 0 && child !== parent)
20940
23134
  edges.push({ child, parent });
20941
23135
  }
20942
- const ordered = tableGroups(tableNames, edges);
23136
+ const ordered = tableGroups(tableNames2, edges);
20943
23137
  refuseOversizedCycle(ordered);
20944
23138
  const clear = clearInternal(input.internal);
20945
23139
  const groups = [
@@ -20958,7 +23152,7 @@ function buildResetPlan(input) {
20958
23152
 
20959
23153
  // src/env-consent.ts
20960
23154
  import { existsSync as existsSync6 } from "fs";
20961
- import { join as join22 } from "path";
23155
+ import { join as join23 } from "path";
20962
23156
  var DOTENV_FILES = [".env.local", ".env.production", ".env"];
20963
23157
  var PUBLIC_PREFIXES = ["NEXT_PUBLIC_", "VITE_", "REACT_APP_", "PUBLIC_"];
20964
23158
  function maskEnvValue(key, value) {
@@ -20967,7 +23161,7 @@ function maskEnvValue(key, value) {
20967
23161
  return value.length <= 8 ? "****" : `${value.slice(0, 4)}****`;
20968
23162
  }
20969
23163
  function findDotenvFile(dir) {
20970
- for (const name of DOTENV_FILES) if (existsSync6(join22(dir, name))) return name;
23164
+ for (const name of DOTENV_FILES) if (existsSync6(join23(dir, name))) return name;
20971
23165
  return null;
20972
23166
  }
20973
23167
  function envAddGuidance(keys) {
@@ -21038,6 +23232,24 @@ async function tryExec(exec, args, cwd) {
21038
23232
  return null;
21039
23233
  }
21040
23234
  }
23235
+ var COMMIT_SHA = /^[0-9a-f]{40}$/;
23236
+ var MAX_COMMIT_MESSAGE = 300;
23237
+ async function detectCommit(exec, dir) {
23238
+ const sha = (await tryExec(exec, ["rev-parse", "HEAD"], dir))?.toLowerCase();
23239
+ if (!sha || !COMMIT_SHA.test(sha)) return null;
23240
+ const subject = await tryExec(exec, ["log", "-1", "--format=%s"], dir);
23241
+ let dirty;
23242
+ try {
23243
+ dirty = (await exec(["status", "--porcelain"], dir)).trim().length > 0;
23244
+ } catch {
23245
+ dirty = true;
23246
+ }
23247
+ return {
23248
+ sha,
23249
+ message: subject === null ? null : [...subject].slice(0, MAX_COMMIT_MESSAGE).join(""),
23250
+ dirty
23251
+ };
23252
+ }
21041
23253
  async function detectGitInfo(dir, exec = defaultExec) {
21042
23254
  const remote = await tryExec(exec, ["remote", "get-url", "origin"], dir);
21043
23255
  const branch = await tryExec(
@@ -21046,10 +23258,12 @@ async function detectGitInfo(dir, exec = defaultExec) {
21046
23258
  dir
21047
23259
  );
21048
23260
  const prefix = await tryExec(exec, ["rev-parse", "--show-prefix"], dir);
23261
+ const commit = await detectCommit(exec, dir);
21049
23262
  return {
21050
23263
  repositoryUrl: remote !== null && SUPPORTED_REMOTE.some((re) => re.test(remote)) ? remote : null,
21051
23264
  branch: branch !== null && branch !== "HEAD" ? branch : null,
21052
- prefix: prefix === null ? null : prefix.replace(/\/+$/, "") || null
23265
+ prefix: prefix === null ? null : prefix.replace(/\/+$/, "") || null,
23266
+ commit
21053
23267
  };
21054
23268
  }
21055
23269
 
@@ -21133,7 +23347,8 @@ function manifest(artifact) {
21133
23347
  compatibilityDate: artifact.compatibilityDate,
21134
23348
  compatibilityFlags: artifact.compatibilityFlags,
21135
23349
  assetsBinding: artifact.assetsBinding,
21136
- ...artifact.assetsConfig ? { assetsConfig: artifact.assetsConfig } : {}
23350
+ ...artifact.assetsConfig ? { assetsConfig: artifact.assetsConfig } : {},
23351
+ ...artifact.routes ? { routes: artifact.routes } : {}
21137
23352
  };
21138
23353
  }
21139
23354
  async function uploadArtifact(artifact, opts) {
@@ -21215,8 +23430,14 @@ function eventsObserver(cp3, deploymentId, token, fetchImpl = fetch, logIngestUr
21215
23430
  onLog: (log2) => batcher.add(log2).catch(() => {
21216
23431
  }),
21217
23432
  onMetadata: async (metadata) => {
23433
+ const {
23434
+ gitCommitSha: _sha,
23435
+ gitCommitMessage: _message,
23436
+ ...rest
23437
+ } = metadata;
23438
+ if (Object.keys(rest).length === 0) return;
21218
23439
  await flushLogs();
21219
- await post({ type: "metadata", metadata }).catch(() => {
23440
+ await post({ type: "metadata", metadata: rest }).catch(() => {
21220
23441
  });
21221
23442
  }
21222
23443
  };
@@ -21228,7 +23449,7 @@ function slugify2(value) {
21228
23449
  async function projectName(sourceDir) {
21229
23450
  try {
21230
23451
  const pkg = JSON.parse(
21231
- await readFile13(join23(sourceDir, "package.json"), "utf8")
23452
+ await readFile13(join24(sourceDir, "package.json"), "utf8")
21232
23453
  );
21233
23454
  if (pkg.name) return pkg.name;
21234
23455
  } catch {
@@ -21239,7 +23460,7 @@ var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.tu
21239
23460
  function resolveBin(name) {
21240
23461
  let dir = dirname4(fileURLToPath(import.meta.url));
21241
23462
  for (; ; ) {
21242
- const candidate = join23(dir, "node_modules", ".bin", name);
23463
+ const candidate = join24(dir, "node_modules", ".bin", name);
21243
23464
  if (existsSync7(candidate)) return candidate;
21244
23465
  const parent = dirname4(dir);
21245
23466
  if (parent === dir) return name;
@@ -21266,7 +23487,7 @@ async function detectPackageManagerLenient(root) {
21266
23487
  }
21267
23488
  function localDependencies(sourceDir) {
21268
23489
  return {
21269
- createWorkspace: () => mkdtemp2(join23(tmpdir2(), "naro-build-")),
23490
+ createWorkspace: () => mkdtemp2(join24(tmpdir2(), "naro-build-")),
21270
23491
  removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
21271
23492
  async runCommand(spec, observer) {
21272
23493
  if (spec.command === "git" && spec.args[0] === "clone") {
@@ -21371,7 +23592,8 @@ async function deployDirectory(sourceDir, options, log2) {
21371
23592
  ...git.repositoryUrl != null && { repositoryUrl: git.repositoryUrl },
21372
23593
  ...branch != null && { branch },
21373
23594
  ...repositoryRoot != null && { rootDirectory: repositoryRoot },
21374
- ...Object.keys(envToSend).length > 0 && { env: envToSend }
23595
+ ...Object.keys(envToSend).length > 0 && { env: envToSend },
23596
+ ...git.commit != null && { commit: git.commit }
21375
23597
  });
21376
23598
  if (ctx.envSkipped?.length)
21377
23599
  log2(`\u26A0 skipped env: ${ctx.envSkipped.join(", ")}`);
@@ -21487,7 +23709,12 @@ export {
21487
23709
  summarizeStatement,
21488
23710
  serializeDotenv,
21489
23711
  systemTablePredicate,
23712
+ quoteIdent,
23713
+ commentText,
23714
+ quotePolicyName,
21490
23715
  parsePolicyNode,
23716
+ parsePolicyStatement,
23717
+ splitPolicyStatements,
21491
23718
  parseRedirectEntry,
21492
23719
  formatRedirectEntry,
21493
23720
  policiesForTypes,