naro-cli 0.9.1 → 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 });
@@ -18598,6 +18726,15 @@ var TERMINAL_DEPLOYMENT_STATUSES = [
18598
18726
  function isTerminalStatus(status) {
18599
18727
  return TERMINAL_DEPLOYMENT_STATUSES.includes(status);
18600
18728
  }
18729
+ var BACKEND_STEPS = [
18730
+ "database",
18731
+ "keys",
18732
+ "secrets",
18733
+ "worker",
18734
+ "routes",
18735
+ "migrate",
18736
+ "health"
18737
+ ];
18601
18738
  var STALLED_GRACE_MS = 2 * 6e4;
18602
18739
  function isStalledBackend(info3, now = Date.now()) {
18603
18740
  if (typeof info3.stalled === "boolean") return info3.stalled;
@@ -19278,7 +19415,10 @@ var DbApi = class {
19278
19415
  * `d1_migrations` — all operations or none. `dryRun` returns the SQL and
19279
19416
  * the name without applying anything. Save `sql` as `migrations/<name>` so
19280
19417
  * `naro db reset` re-applies it; a refused operation's index is in
19281
- * `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`.
19282
19422
  */
19283
19423
  async schema(projectId, operations, options = {}) {
19284
19424
  return this.client.request(
@@ -19297,6 +19437,9 @@ var DbApi = class {
19297
19437
  }
19298
19438
  };
19299
19439
  var PROVISION_TIMEOUT_MS = 12e4;
19440
+ function writeQuery(options) {
19441
+ return options.allowUnindexed === true ? "?allow_unindexed=1" : "";
19442
+ }
19300
19443
  function backendInfo(body) {
19301
19444
  return {
19302
19445
  status: body.status,
@@ -19315,6 +19458,35 @@ function backendInfo(body) {
19315
19458
  turningOff: body.turningOff === true
19316
19459
  };
19317
19460
  }
19461
+ function authConfig(body) {
19462
+ return {
19463
+ ...body,
19464
+ signupsEnabled: typeof body.signupsEnabled === "boolean" ? body.signupsEnabled : true,
19465
+ passwordMinLength: typeof body.passwordMinLength === "number" ? body.passwordMinLength : 6
19466
+ };
19467
+ }
19468
+ function isRecord(value) {
19469
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19470
+ }
19471
+ async function authChange(answer) {
19472
+ let body;
19473
+ try {
19474
+ body = await answer;
19475
+ } catch (error51) {
19476
+ const details = isNaroError(error51) ? error51.details : void 0;
19477
+ if (isNaroError(error51) && error51.code === "BACKEND_PROVISION_FAILED" && isRecord(details?.auth) && isRecord(details.backend))
19478
+ throw new NaroError(error51.code, error51.message, {
19479
+ ...error51.status !== void 0 ? { status: error51.status } : {},
19480
+ details: {
19481
+ ...details,
19482
+ auth: authConfig(details.auth),
19483
+ backend: backendInfo(details.backend)
19484
+ }
19485
+ });
19486
+ throw error51;
19487
+ }
19488
+ return { auth: authConfig(body.auth), backend: backendInfo(body.backend) };
19489
+ }
19318
19490
  var BackendApi = class {
19319
19491
  constructor(client) {
19320
19492
  this.client = client;
@@ -19447,11 +19619,13 @@ var BackendApi = class {
19447
19619
  * and the callback URLs to register with the providers. Never a secret.
19448
19620
  */
19449
19621
  async auth(projectId) {
19450
- return this.client.request(
19451
- this.path(projectId, "/auth"),
19452
- {
19453
- context: "Read sign-in settings"
19454
- }
19622
+ return authConfig(
19623
+ await this.client.request(
19624
+ this.path(projectId, "/auth"),
19625
+ {
19626
+ context: "Read sign-in settings"
19627
+ }
19628
+ )
19455
19629
  );
19456
19630
  }
19457
19631
  /**
@@ -19460,34 +19634,93 @@ var BackendApi = class {
19460
19634
  * (nothing is saved), `BACKEND_NOT_READY` before the first provision,
19461
19635
  * `BACKEND_OFF` while the backend is turned off (nothing is saved),
19462
19636
  * `VERIFICATION_NEEDS_EMAIL` for a state where nobody could sign in. A
19463
- * failed upload is not an error — `backend.status` says `error` and the
19464
- * settings are saved; `enable` finishes. Two minutes, like enable.
19637
+ * failed upload throws `BACKEND_PROVISION_FAILED` (502): the settings are
19638
+ * saved, the message says whether the worker runs them yet, `details`
19639
+ * holds `{ auth, backend }`, and `enable` finishes. (A control plane from
19640
+ * before that resolves with `backend.status` `error` instead.) Two
19641
+ * minutes, like enable.
19465
19642
  */
19466
19643
  async setAuth(projectId, patch) {
19467
- const answer = await this.client.request(
19468
- this.path(projectId, "/auth"),
19469
- {
19644
+ return authChange(
19645
+ this.client.request(this.path(projectId, "/auth"), {
19470
19646
  method: "PUT",
19471
19647
  body: patch,
19472
19648
  context: "Change sign-in settings",
19473
19649
  timeoutMs: PROVISION_TIMEOUT_MS
19474
- }
19650
+ })
19475
19651
  );
19476
- return { auth: answer.auth, backend: backendInfo(answer.backend) };
19477
19652
  }
19478
19653
  /** Removes email or one provider; the same answer as `setAuth`. */
19479
19654
  async removeAuth(projectId, target) {
19655
+ return authChange(
19656
+ this.client.request(
19657
+ this.path(projectId, `/auth?target=${encodeURIComponent(target)}`),
19658
+ {
19659
+ method: "DELETE",
19660
+ context: "Change sign-in settings",
19661
+ timeoutMs: PROVISION_TIMEOUT_MS
19662
+ }
19663
+ )
19664
+ );
19665
+ }
19666
+ /**
19667
+ * The project's users, newest first (spec 2026-09-26 §2.2): page 1 and 50
19668
+ * a page unless given, at most 1000. `database.read`. `BACKEND_OFF` while
19669
+ * the backend is turned off, `BACKEND_NOT_READY` before it is ready,
19670
+ * `BACKEND_OUTDATED` while its Naro worker predates user management
19671
+ * (`enable` updates it), `BACKEND_UNAVAILABLE` when it cannot be reached.
19672
+ */
19673
+ async listUsers(projectId, options = {}) {
19674
+ const query = new URLSearchParams();
19675
+ if (options.page !== void 0) query.set("page", String(options.page));
19676
+ if (options.perPage !== void 0)
19677
+ query.set("perPage", String(options.perPage));
19678
+ const suffix = query.size > 0 ? `?${query.toString()}` : "";
19679
+ const body = await this.client.request(
19680
+ this.path(projectId, `/users${suffix}`),
19681
+ { context: "List users" }
19682
+ );
19683
+ return { users: body.users, total: body.total };
19684
+ }
19685
+ /**
19686
+ * Creates a user with a password, confirmed unless `emailConfirm: false`
19687
+ * — also while sign-ups are closed and while sign-in waits for a
19688
+ * confirmed email. A username-only user answers `email: null`.
19689
+ * `database.write`. The Naro worker's refusals come through as it said
19690
+ * them: `USERNAME_TAKEN` (409), `USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL`
19691
+ * (422), `PASSWORD_TOO_SHORT` ("Use at least {n} characters."),
19692
+ * `VALIDATION_ERROR`, `WRITE_QUOTA_EXCEEDED` (429, `details.retryAfter`).
19693
+ */
19694
+ async createUser(projectId, input) {
19695
+ const body = { password: input.password };
19696
+ if (input.email !== void 0) body.email = input.email;
19697
+ if (input.username !== void 0) body.username = input.username;
19698
+ if (input.emailConfirm !== void 0)
19699
+ body.emailConfirm = input.emailConfirm;
19480
19700
  const answer = await this.client.request(
19481
- this.path(projectId, `/auth?target=${encodeURIComponent(target)}`),
19482
- {
19483
- method: "DELETE",
19484
- context: "Change sign-in settings",
19485
- timeoutMs: PROVISION_TIMEOUT_MS
19486
- }
19701
+ this.path(projectId, "/users"),
19702
+ { method: "POST", body, context: "Create user" }
19703
+ );
19704
+ return answer.user;
19705
+ }
19706
+ /**
19707
+ * Deletes a user with its sign-in methods, and ends its sessions. Answers
19708
+ * the user it was. `USER_NOT_FOUND` (404) for an id that is not one.
19709
+ * `database.write`.
19710
+ */
19711
+ async deleteUser(projectId, userId) {
19712
+ const answer = await this.client.request(
19713
+ this.path(projectId, `/users/${encodeURIComponent(userId)}`),
19714
+ { method: "DELETE", context: "Delete user" }
19487
19715
  );
19488
- return { auth: answer.auth, backend: backendInfo(answer.backend) };
19716
+ return answer.user;
19489
19717
  }
19490
- /** 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
+ */
19491
19724
  async policies(projectId) {
19492
19725
  const body = await this.client.request(
19493
19726
  this.path(projectId, "/policies"),
@@ -19511,8 +19744,9 @@ var BackendApi = class {
19511
19744
  return answer.policy;
19512
19745
  }
19513
19746
  /**
19514
- * Deletes the table's policy, so it denies anon and signed-in requests
19515
- * 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.
19516
19750
  */
19517
19751
  async removePolicy(projectId, table) {
19518
19752
  const answer = await this.client.request(
@@ -19521,6 +19755,75 @@ var BackendApi = class {
19521
19755
  );
19522
19756
  return answer.policy;
19523
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
+ }
19524
19827
  };
19525
19828
 
19526
19829
  // ../../packages/sdk/src/database.ts
@@ -19573,7 +19876,7 @@ function summarizeStatement(statement) {
19573
19876
 
19574
19877
  // ../../packages/sdk/src/dotenv.ts
19575
19878
  import { existsSync, readFileSync as readFileSync2 } from "fs";
19576
- import { join as join18 } from "path";
19879
+ import { join as join19 } from "path";
19577
19880
  function parseDotenv(text) {
19578
19881
  const out = {};
19579
19882
  for (const line of text.split(/\r?\n/)) {
@@ -19591,7 +19894,7 @@ function parseDotenv(text) {
19591
19894
  }
19592
19895
  function readProjectEnv(dir) {
19593
19896
  for (const name of [".env.local", ".env.production", ".env"]) {
19594
- const path = join18(dir, name);
19897
+ const path = join19(dir, name);
19595
19898
  if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
19596
19899
  }
19597
19900
  return {};
@@ -19606,7 +19909,7 @@ function serializeDotenv(vars) {
19606
19909
 
19607
19910
  // ../../packages/sdk/src/env-scan.ts
19608
19911
  import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
19609
- import { join as join19 } from "path";
19912
+ import { join as join20 } from "path";
19610
19913
  var SOURCE_EXTENSIONS3 = /* @__PURE__ */ new Set([
19611
19914
  ".ts",
19612
19915
  ".tsx",
@@ -19661,7 +19964,7 @@ function collect(dir, out) {
19661
19964
  for (const entry of entries) {
19662
19965
  if (out.length >= MAX_FILES) return;
19663
19966
  if (entry.isSymbolicLink()) continue;
19664
- const path = join19(dir, entry.name);
19967
+ const path = join20(dir, entry.name);
19665
19968
  if (entry.isDirectory()) {
19666
19969
  if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name))
19667
19970
  continue;
@@ -19693,7 +19996,7 @@ function scanEnvReferences(dir) {
19693
19996
  }
19694
19997
  }
19695
19998
  for (const name of EXAMPLE_FILES) {
19696
- const path = join19(dir, name);
19999
+ const path = join20(dir, name);
19697
20000
  if (!existsSync2(path)) continue;
19698
20001
  let parsed;
19699
20002
  try {
@@ -19711,11 +20014,11 @@ function scanEnvReferences(dir) {
19711
20014
 
19712
20015
  // ../../packages/sdk/src/framework.ts
19713
20016
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
19714
- import { join as join20 } from "path";
20017
+ import { join as join21 } from "path";
19715
20018
  function readPackageJson2(dir) {
19716
20019
  try {
19717
20020
  return JSON.parse(
19718
- readFileSync4(join20(dir, "package.json"), "utf8")
20021
+ readFileSync4(join21(dir, "package.json"), "utf8")
19719
20022
  );
19720
20023
  } catch {
19721
20024
  return null;
@@ -19726,7 +20029,7 @@ function hasDependency(pkg, name) {
19726
20029
  }
19727
20030
  function hasConfig(dir, base2, evidence) {
19728
20031
  for (const ext of ["js", "mjs", "cjs", "ts", "mts"]) {
19729
- if (existsSync3(join20(dir, `${base2}.${ext}`))) {
20032
+ if (existsSync3(join21(dir, `${base2}.${ext}`))) {
19730
20033
  evidence.push(`${base2}.${ext}`);
19731
20034
  return true;
19732
20035
  }
@@ -19834,8 +20137,8 @@ function detectFramework2(dir) {
19834
20137
  if (!byDependency && !byConfig) continue;
19835
20138
  if (byDependency) evidence.push(`package.json: ${detector.dependency}`);
19836
20139
  if (detector.framework === "nextjs") {
19837
- if (existsSync3(join20(dir, "app"))) evidence.push("app/");
19838
- 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/");
19839
20142
  }
19840
20143
  return {
19841
20144
  framework: detector.framework,
@@ -19846,7 +20149,7 @@ function detectFramework2(dir) {
19846
20149
  deployable: true
19847
20150
  };
19848
20151
  }
19849
- if (!pkg && existsSync3(join20(dir, "index.html"))) {
20152
+ if (!pkg && existsSync3(join21(dir, "index.html"))) {
19850
20153
  evidence.push("index.html (no package.json)");
19851
20154
  return {
19852
20155
  framework: "static",
@@ -19900,6 +20203,276 @@ function systemTablePredicate(column = "name") {
19900
20203
  }
19901
20204
  var SYSTEM_TABLE_SQL_PREDICATE = systemTablePredicate("name");
19902
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
+
19903
20476
  // ../../packages/data-core/src/values.ts
19904
20477
  function isPlainObject2(value) {
19905
20478
  if (typeof value !== "object" || value === null || Array.isArray(value))
@@ -19910,6 +20483,1121 @@ function isPlainObject2(value) {
19910
20483
  function isValue(value) {
19911
20484
  return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
19912
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
+ }
19913
21601
 
19914
21602
  // ../../packages/data-core/src/query.ts
19915
21603
  var FILTER_OPERATORS = [
@@ -19941,7 +21629,11 @@ var ARRAY_OPERATORS = [
19941
21629
  "containedBy",
19942
21630
  "overlaps"
19943
21631
  ];
19944
- 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
+ }
19945
21637
  var MAX_IDENTIFIER_LENGTH = 128;
19946
21638
  var Invalid = class extends Error {
19947
21639
  constructor(path, reason) {
@@ -19956,7 +21648,7 @@ function invalid(path, reason) {
19956
21648
  function own(object2, key) {
19957
21649
  return Object.hasOwn(object2, key) ? object2[key] : void 0;
19958
21650
  }
19959
- function isOperator(value) {
21651
+ function isOperator2(value) {
19960
21652
  return FILTER_OPERATORS.includes(value);
19961
21653
  }
19962
21654
  function readObject(value, path) {
@@ -19981,7 +21673,7 @@ function readName(value, path) {
19981
21673
  );
19982
21674
  return value;
19983
21675
  }
19984
- function readValue(value, path, auth = false) {
21676
+ function readValue2(value, path, auth = false) {
19985
21677
  if (auth && isAuthUid(value)) return { auth: "uid" };
19986
21678
  if (!isValue(value))
19987
21679
  invalid(
@@ -19993,8 +21685,8 @@ function readValue(value, path, auth = false) {
19993
21685
  function longerThan(text, max) {
19994
21686
  if (text.length <= max) return false;
19995
21687
  if (text.length > max * 2) return true;
19996
- let count = 0;
19997
- for (const _ of text) if (++count > max) return true;
21688
+ let count2 = 0;
21689
+ for (const _ of text) if (++count2 > max) return true;
19998
21690
  return false;
19999
21691
  }
20000
21692
  function endsWithLoneEscape(pattern) {
@@ -20009,7 +21701,7 @@ function readFilter(value, path, auth) {
20009
21701
  const object2 = readObject(value, path);
20010
21702
  checkKeys(object2, ["operator", "column", "value"], path);
20011
21703
  const operator = own(object2, "operator");
20012
- if (!isOperator(operator))
21704
+ if (!isOperator2(operator))
20013
21705
  invalid(
20014
21706
  `${path}.operator`,
20015
21707
  `must be one of ${FILTER_OPERATORS.join(", ")}`
@@ -20022,7 +21714,7 @@ function readFilter(value, path, auth) {
20022
21714
  case "in": {
20023
21715
  if (!Array.isArray(raw)) invalid(valuePath, "must be an array");
20024
21716
  const values = raw.map(
20025
- (item, index) => readValue(item, `${valuePath}[${index}]`, auth)
21717
+ (item, index) => readValue2(item, `${valuePath}[${index}]`, auth)
20026
21718
  );
20027
21719
  return { operator, column, value: values };
20028
21720
  }
@@ -20031,18 +21723,16 @@ function readFilter(value, path, auth) {
20031
21723
  invalid(valuePath, "must be null, true or false");
20032
21724
  return { operator, column, value: raw };
20033
21725
  case "like":
20034
- case "ilike":
21726
+ case "ilike": {
20035
21727
  if (typeof raw !== "string") invalid(valuePath, "must be a string");
20036
- if (longerThan(raw, MAX_PATTERN_LENGTH))
20037
- invalid(
20038
- valuePath,
20039
- `must be at most ${String(MAX_PATTERN_LENGTH)} characters`
20040
- );
21728
+ const tooLong = patternProblem(operator, raw);
21729
+ if (tooLong !== void 0) invalid(valuePath, tooLong);
20041
21730
  if (raw.includes("\0"))
20042
21731
  invalid(valuePath, "must not contain a NUL character");
20043
21732
  if (endsWithLoneEscape(raw))
20044
21733
  invalid(valuePath, "must not end with an unescaped backslash");
20045
21734
  return { operator, column, value: raw };
21735
+ }
20046
21736
  case "contains":
20047
21737
  case "containedBy":
20048
21738
  case "overlaps":
@@ -20061,7 +21751,7 @@ function readFilter(value, path, auth) {
20061
21751
  )
20062
21752
  };
20063
21753
  default:
20064
- return { operator, column, value: readValue(raw, valuePath, auth) };
21754
+ return { operator, column, value: readValue2(raw, valuePath, auth) };
20065
21755
  }
20066
21756
  }
20067
21757
  var NODE_KEYS = ["operator", "or", "and", "not"];
@@ -20107,18 +21797,579 @@ function parsePolicyNode(value, path) {
20107
21797
  }
20108
21798
  }
20109
21799
 
20110
- // ../../packages/data-core/src/schema.ts
20111
- var SCHEMA_SQL = [
20112
- `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`,
20113
- // Phase 2-2 (적대적 리뷰 반영): each table's CREATE TABLE text, where
20114
- // alone a column's collation is written. A separate statement, so the
20115
- // text comes once per table rather than once per column. D1 answers
20116
- // `sqlite_master` reads (the database routes' `_tables.ts` reads it too).
20117
- `SELECT name AS table_name, sql AS table_sql FROM sqlite_master WHERE type = 'table' AND ${systemTablePredicate("name")}`
20118
- ];
20119
-
20120
21800
  // ../../packages/data-core/src/condition.ts
21801
+ var CONDITION_RULE = "condition";
20121
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
+ }
20122
22373
 
20123
22374
  // ../../packages/data-core/src/redirect-origins.ts
20124
22375
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
@@ -20165,6 +22416,34 @@ function formatRedirectEntry(entry) {
20165
22416
  }
20166
22417
 
20167
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
+ }
20168
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')))";
20169
22448
  var SYSTEM_MIGRATIONS = [
20170
22449
  {
@@ -20206,6 +22485,15 @@ var SYSTEM_MIGRATIONS = [
20206
22485
  "CREATE INDEX IF NOT EXISTS _naro_auth_email_log_address ON _naro_auth_email_log (address_hash, sent_at)",
20207
22486
  "CREATE TABLE IF NOT EXISTS _naro_auth_contested (user_id TEXT PRIMARY KEY, contested_at INTEGER NOT NULL)"
20208
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
20209
22497
  }
20210
22498
  ];
20211
22499
 
@@ -20271,12 +22559,12 @@ function sqlSkeleton(sql) {
20271
22559
  let index = 0;
20272
22560
  while (index < sql.length) {
20273
22561
  const char = sql[index] ?? "";
20274
- const pair = sql.slice(index, index + 2);
20275
- if (pair === "--") {
22562
+ const pair2 = sql.slice(index, index + 2);
22563
+ if (pair2 === "--") {
20276
22564
  const end = sql.indexOf("\n", index);
20277
22565
  index = end < 0 ? sql.length : end;
20278
22566
  out += " ";
20279
- } else if (pair === "/*") {
22567
+ } else if (pair2 === "/*") {
20280
22568
  const end = sql.indexOf("*/", index + 2);
20281
22569
  index = end < 0 ? sql.length : end + 2;
20282
22570
  out += " ";
@@ -20454,12 +22742,12 @@ import {
20454
22742
  rmSync as rmSync2,
20455
22743
  writeFileSync as writeFileSync2
20456
22744
  } from "fs";
20457
- import { join as join21 } from "path";
22745
+ import { join as join22 } from "path";
20458
22746
  function linkDir(dir) {
20459
- return join21(dir, ".naro");
22747
+ return join22(dir, ".naro");
20460
22748
  }
20461
22749
  function linkFile(dir) {
20462
- return join21(linkDir(dir), "project.json");
22750
+ return join22(linkDir(dir), "project.json");
20463
22751
  }
20464
22752
  function readProjectLink(dir) {
20465
22753
  try {
@@ -20481,7 +22769,7 @@ function writeProjectLink(dir, link) {
20481
22769
  writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
20482
22770
  `);
20483
22771
  writeFileSync2(
20484
- join21(linkDir(dir), "README.txt"),
22772
+ join22(linkDir(dir), "README.txt"),
20485
22773
  "This directory links the project to Naro and should not be committed.\n"
20486
22774
  );
20487
22775
  }
@@ -20491,9 +22779,9 @@ function removeProjectLink(dir) {
20491
22779
  return true;
20492
22780
  }
20493
22781
  function ensureGitignoreHasNaro(dir) {
20494
- const gitignorePath = join21(dir, ".gitignore");
22782
+ const gitignorePath = join22(dir, ".gitignore");
20495
22783
  const hasGitignore = existsSync4(gitignorePath);
20496
- if (!hasGitignore && !existsSync4(join21(dir, ".git"))) return false;
22784
+ if (!hasGitignore && !existsSync4(join22(dir, ".git"))) return false;
20497
22785
  const current = hasGitignore ? readFileSync5(gitignorePath, "utf8") : "";
20498
22786
  const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".naro" || line === ".naro/" || line === "/.naro");
20499
22787
  if (covered) return false;
@@ -20834,10 +23122,10 @@ function buildResetPlan(input) {
20834
23122
  const triggers = sorted(
20835
23123
  objects.filter((object2) => object2.type === "trigger").map((object2) => object2.name)
20836
23124
  );
20837
- const tableNames = sorted(
23125
+ const tableNames2 = sorted(
20838
23126
  objects.filter((object2) => object2.type === "table" || object2.type === "virtual").map((object2) => object2.name)
20839
23127
  );
20840
- const canonical = new Map(tableNames.map((name) => [asciiLower(name), name]));
23128
+ const canonical = new Map(tableNames2.map((name) => [asciiLower(name), name]));
20841
23129
  const edges = [];
20842
23130
  for (const edge of input.foreignKeys) {
20843
23131
  const child = canonical.get(asciiLower(edge.child));
@@ -20845,7 +23133,7 @@ function buildResetPlan(input) {
20845
23133
  if (child !== void 0 && parent !== void 0 && child !== parent)
20846
23134
  edges.push({ child, parent });
20847
23135
  }
20848
- const ordered = tableGroups(tableNames, edges);
23136
+ const ordered = tableGroups(tableNames2, edges);
20849
23137
  refuseOversizedCycle(ordered);
20850
23138
  const clear = clearInternal(input.internal);
20851
23139
  const groups = [
@@ -20864,7 +23152,7 @@ function buildResetPlan(input) {
20864
23152
 
20865
23153
  // src/env-consent.ts
20866
23154
  import { existsSync as existsSync6 } from "fs";
20867
- import { join as join22 } from "path";
23155
+ import { join as join23 } from "path";
20868
23156
  var DOTENV_FILES = [".env.local", ".env.production", ".env"];
20869
23157
  var PUBLIC_PREFIXES = ["NEXT_PUBLIC_", "VITE_", "REACT_APP_", "PUBLIC_"];
20870
23158
  function maskEnvValue(key, value) {
@@ -20873,7 +23161,7 @@ function maskEnvValue(key, value) {
20873
23161
  return value.length <= 8 ? "****" : `${value.slice(0, 4)}****`;
20874
23162
  }
20875
23163
  function findDotenvFile(dir) {
20876
- 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;
20877
23165
  return null;
20878
23166
  }
20879
23167
  function envAddGuidance(keys) {
@@ -20944,6 +23232,24 @@ async function tryExec(exec, args, cwd) {
20944
23232
  return null;
20945
23233
  }
20946
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
+ }
20947
23253
  async function detectGitInfo(dir, exec = defaultExec) {
20948
23254
  const remote = await tryExec(exec, ["remote", "get-url", "origin"], dir);
20949
23255
  const branch = await tryExec(
@@ -20952,10 +23258,12 @@ async function detectGitInfo(dir, exec = defaultExec) {
20952
23258
  dir
20953
23259
  );
20954
23260
  const prefix = await tryExec(exec, ["rev-parse", "--show-prefix"], dir);
23261
+ const commit = await detectCommit(exec, dir);
20955
23262
  return {
20956
23263
  repositoryUrl: remote !== null && SUPPORTED_REMOTE.some((re) => re.test(remote)) ? remote : null,
20957
23264
  branch: branch !== null && branch !== "HEAD" ? branch : null,
20958
- prefix: prefix === null ? null : prefix.replace(/\/+$/, "") || null
23265
+ prefix: prefix === null ? null : prefix.replace(/\/+$/, "") || null,
23266
+ commit
20959
23267
  };
20960
23268
  }
20961
23269
 
@@ -21039,7 +23347,8 @@ function manifest(artifact) {
21039
23347
  compatibilityDate: artifact.compatibilityDate,
21040
23348
  compatibilityFlags: artifact.compatibilityFlags,
21041
23349
  assetsBinding: artifact.assetsBinding,
21042
- ...artifact.assetsConfig ? { assetsConfig: artifact.assetsConfig } : {}
23350
+ ...artifact.assetsConfig ? { assetsConfig: artifact.assetsConfig } : {},
23351
+ ...artifact.routes ? { routes: artifact.routes } : {}
21043
23352
  };
21044
23353
  }
21045
23354
  async function uploadArtifact(artifact, opts) {
@@ -21121,8 +23430,14 @@ function eventsObserver(cp3, deploymentId, token, fetchImpl = fetch, logIngestUr
21121
23430
  onLog: (log2) => batcher.add(log2).catch(() => {
21122
23431
  }),
21123
23432
  onMetadata: async (metadata) => {
23433
+ const {
23434
+ gitCommitSha: _sha,
23435
+ gitCommitMessage: _message,
23436
+ ...rest
23437
+ } = metadata;
23438
+ if (Object.keys(rest).length === 0) return;
21124
23439
  await flushLogs();
21125
- await post({ type: "metadata", metadata }).catch(() => {
23440
+ await post({ type: "metadata", metadata: rest }).catch(() => {
21126
23441
  });
21127
23442
  }
21128
23443
  };
@@ -21134,7 +23449,7 @@ function slugify2(value) {
21134
23449
  async function projectName(sourceDir) {
21135
23450
  try {
21136
23451
  const pkg = JSON.parse(
21137
- await readFile13(join23(sourceDir, "package.json"), "utf8")
23452
+ await readFile13(join24(sourceDir, "package.json"), "utf8")
21138
23453
  );
21139
23454
  if (pkg.name) return pkg.name;
21140
23455
  } catch {
@@ -21145,7 +23460,7 @@ var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.tu
21145
23460
  function resolveBin(name) {
21146
23461
  let dir = dirname4(fileURLToPath(import.meta.url));
21147
23462
  for (; ; ) {
21148
- const candidate = join23(dir, "node_modules", ".bin", name);
23463
+ const candidate = join24(dir, "node_modules", ".bin", name);
21149
23464
  if (existsSync7(candidate)) return candidate;
21150
23465
  const parent = dirname4(dir);
21151
23466
  if (parent === dir) return name;
@@ -21172,7 +23487,7 @@ async function detectPackageManagerLenient(root) {
21172
23487
  }
21173
23488
  function localDependencies(sourceDir) {
21174
23489
  return {
21175
- createWorkspace: () => mkdtemp2(join23(tmpdir2(), "naro-build-")),
23490
+ createWorkspace: () => mkdtemp2(join24(tmpdir2(), "naro-build-")),
21176
23491
  removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
21177
23492
  async runCommand(spec, observer) {
21178
23493
  if (spec.command === "git" && spec.args[0] === "clone") {
@@ -21277,7 +23592,8 @@ async function deployDirectory(sourceDir, options, log2) {
21277
23592
  ...git.repositoryUrl != null && { repositoryUrl: git.repositoryUrl },
21278
23593
  ...branch != null && { branch },
21279
23594
  ...repositoryRoot != null && { rootDirectory: repositoryRoot },
21280
- ...Object.keys(envToSend).length > 0 && { env: envToSend }
23595
+ ...Object.keys(envToSend).length > 0 && { env: envToSend },
23596
+ ...git.commit != null && { commit: git.commit }
21281
23597
  });
21282
23598
  if (ctx.envSkipped?.length)
21283
23599
  log2(`\u26A0 skipped env: ${ctx.envSkipped.join(", ")}`);
@@ -21380,6 +23696,7 @@ export {
21380
23696
  resolveAuth,
21381
23697
  openBrowser,
21382
23698
  isTerminalStatus,
23699
+ BACKEND_STEPS,
21383
23700
  isStalledBackend,
21384
23701
  POLICY_RULES,
21385
23702
  POLICY_ACTIONS,
@@ -21392,7 +23709,12 @@ export {
21392
23709
  summarizeStatement,
21393
23710
  serializeDotenv,
21394
23711
  systemTablePredicate,
23712
+ quoteIdent,
23713
+ commentText,
23714
+ quotePolicyName,
21395
23715
  parsePolicyNode,
23716
+ parsePolicyStatement,
23717
+ splitPolicyStatements,
21396
23718
  parseRedirectEntry,
21397
23719
  formatRedirectEntry,
21398
23720
  policiesForTypes,