naro-cli 0.19.0 → 0.19.1
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.
- package/README.md +196 -9
- package/dist/{chunk-DEEFHVDZ.js → chunk-ZO2BEC46.js} +1935 -449
- package/dist/deploy.d.ts +3 -3
- package/dist/deploy.js +2 -2
- package/dist/index.js +261 -28
- package/package.json +4 -4
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/deploy.ts
|
|
7
7
|
import { existsSync as existsSync7 } from "fs";
|
|
8
|
-
import { mkdtemp as mkdtemp2, readFile as
|
|
8
|
+
import { mkdtemp as mkdtemp2, readFile as readFile15, rm as rm5 } from "fs/promises";
|
|
9
9
|
import { tmpdir as tmpdir2 } from "os";
|
|
10
|
-
import { basename as basename4, dirname as dirname6, join as
|
|
10
|
+
import { basename as basename4, dirname as dirname6, join as join26, resolve as resolve11 } from "path";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
12
|
|
|
13
13
|
// ../../packages/build/src/artifact-upload.ts
|
|
@@ -287,9 +287,9 @@ function hashAsset(data, contentType2) {
|
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
// ../../packages/build/src/local-build-executor.ts
|
|
290
|
-
import { access as access8, mkdtemp, readFile as
|
|
290
|
+
import { access as access8, mkdtemp, readFile as readFile13, rm as rm3 } from "fs/promises";
|
|
291
291
|
import { tmpdir } from "os";
|
|
292
|
-
import { dirname as dirname4, join as
|
|
292
|
+
import { dirname as dirname4, join as join18, resolve as resolve7 } from "path";
|
|
293
293
|
|
|
294
294
|
// ../../packages/core/src/route-patterns.ts
|
|
295
295
|
var ROUTE_PATTERN = /^\/[A-Za-z0-9\-._~%!$&()*+,;=:@/[\]]*$/;
|
|
@@ -15613,7 +15613,7 @@ import { access as access5, readFile as readFile5, writeFile as writeFile4 } fro
|
|
|
15613
15613
|
import { join as join6, resolve as resolve2 } from "path";
|
|
15614
15614
|
|
|
15615
15615
|
// ../../packages/build/src/open-next.ts
|
|
15616
|
-
import { access as access3, readdir, readFile as readFile4 } from "fs/promises";
|
|
15616
|
+
import { access as access3, readdir, readFile as readFile4, realpath } from "fs/promises";
|
|
15617
15617
|
import {
|
|
15618
15618
|
basename,
|
|
15619
15619
|
dirname,
|
|
@@ -15729,7 +15729,33 @@ function contentType(path) {
|
|
|
15729
15729
|
return "application/octet-stream";
|
|
15730
15730
|
}
|
|
15731
15731
|
}
|
|
15732
|
-
async function
|
|
15732
|
+
async function assertInsideOnDisk(appRoot, directory, label2) {
|
|
15733
|
+
let realRoot;
|
|
15734
|
+
let realDirectory;
|
|
15735
|
+
try {
|
|
15736
|
+
[realRoot, realDirectory] = await Promise.all([
|
|
15737
|
+
realpath(appRoot),
|
|
15738
|
+
realpath(directory)
|
|
15739
|
+
]);
|
|
15740
|
+
} catch {
|
|
15741
|
+
return;
|
|
15742
|
+
}
|
|
15743
|
+
const relation = relative(realRoot, realDirectory);
|
|
15744
|
+
if (relation === ".." || relation.startsWith(`..${sep}`) || isAbsolute(relation)) {
|
|
15745
|
+
throw new DeploymentError(
|
|
15746
|
+
"ARTIFACT_INVALID",
|
|
15747
|
+
`${label2} output leads out of the application root through a symbolic link.`
|
|
15748
|
+
);
|
|
15749
|
+
}
|
|
15750
|
+
if (relation.split(sep).includes(".git")) {
|
|
15751
|
+
throw new DeploymentError(
|
|
15752
|
+
"ARTIFACT_INVALID",
|
|
15753
|
+
`${label2} output leads into .git through a symbolic link.`
|
|
15754
|
+
);
|
|
15755
|
+
}
|
|
15756
|
+
}
|
|
15757
|
+
async function listArtifactFiles(root, label2 = "OpenNext", appRoot) {
|
|
15758
|
+
if (appRoot !== void 0) await assertInsideOnDisk(appRoot, root, label2);
|
|
15733
15759
|
const files = [];
|
|
15734
15760
|
async function visit(directory) {
|
|
15735
15761
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
@@ -15786,7 +15812,6 @@ var OPEN_NEXT_LAYOUT = {
|
|
|
15786
15812
|
outputRoot: ".open-next",
|
|
15787
15813
|
bundleDir: ".open-next/worker-bundle"
|
|
15788
15814
|
};
|
|
15789
|
-
var listFiles = (root) => listArtifactFiles(root);
|
|
15790
15815
|
async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
|
|
15791
15816
|
const configPath = await findWranglerConfig(appRoot, wranglerConfigPath);
|
|
15792
15817
|
return readWorkerArtifact(appRoot, identity, {
|
|
@@ -15795,6 +15820,7 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
|
|
|
15795
15820
|
});
|
|
15796
15821
|
}
|
|
15797
15822
|
async function readWorkerArtifact(appRoot, identity, layout) {
|
|
15823
|
+
const listFiles = (root) => listArtifactFiles(root, "OpenNext", appRoot);
|
|
15798
15824
|
try {
|
|
15799
15825
|
const configPath = await findWranglerConfig(
|
|
15800
15826
|
appRoot,
|
|
@@ -15839,12 +15865,13 @@ async function readWorkerArtifact(appRoot, identity, layout) {
|
|
|
15839
15865
|
);
|
|
15840
15866
|
}
|
|
15841
15867
|
const bundleRoot = confinedPath(appRoot, layout.bundleDir, "Bundle dir");
|
|
15842
|
-
let
|
|
15868
|
+
let bundleExists = true;
|
|
15843
15869
|
try {
|
|
15844
15870
|
await access3(bundleRoot);
|
|
15845
|
-
bundleFiles = await listFiles(bundleRoot);
|
|
15846
15871
|
} catch {
|
|
15872
|
+
bundleExists = false;
|
|
15847
15873
|
}
|
|
15874
|
+
const bundleFiles = bundleExists ? await listFiles(bundleRoot) : null;
|
|
15848
15875
|
const outputFiles = bundleFiles ?? await listFiles(outputRoot);
|
|
15849
15876
|
const entrypointName = bundleFiles ? basename(entrypointPath) : relative(outputRoot, entrypointPath).split(sep).join("/");
|
|
15850
15877
|
const assetsPrefix = `${relative(outputRoot, configuredAssets).split(sep).join("/")}/`;
|
|
@@ -16192,7 +16219,7 @@ async function readVinextArtifact(appRoot, identity) {
|
|
|
16192
16219
|
const entrypointName = metadata.main.split(/[\\/]/).join("/");
|
|
16193
16220
|
const matchers = moduleMatchers(metadata.rules);
|
|
16194
16221
|
const isModule = (name) => matchers.length === 0 || matchers.some((matcher) => matcher.test(name));
|
|
16195
|
-
const serverFiles = await listArtifactFiles(serverRoot, "vinext");
|
|
16222
|
+
const serverFiles = await listArtifactFiles(serverRoot, "vinext", appRoot);
|
|
16196
16223
|
const entrypoint = serverFiles.find((file2) => file2.name === entrypointName);
|
|
16197
16224
|
if (!entrypoint) {
|
|
16198
16225
|
throw new DeploymentError(
|
|
@@ -16206,7 +16233,7 @@ async function readVinextArtifact(appRoot, identity) {
|
|
|
16206
16233
|
modules: serverFiles.filter(
|
|
16207
16234
|
(file2) => file2.name !== entrypointName && file2.name !== WRANGLER_OUTPUT_NAME && !file2.name.endsWith(".map") && isModule(file2.name)
|
|
16208
16235
|
),
|
|
16209
|
-
assets: await listArtifactFiles(clientRoot, "vinext"),
|
|
16236
|
+
assets: await listArtifactFiles(clientRoot, "vinext", appRoot),
|
|
16210
16237
|
compatibilityDate: metadata.compatibility_date,
|
|
16211
16238
|
compatibilityFlags: Array.isArray(metadata.compatibility_flags) ? metadata.compatibility_flags.filter(
|
|
16212
16239
|
(flag) => typeof flag === "string"
|
|
@@ -16879,7 +16906,8 @@ async function runCommand(spec, observer) {
|
|
|
16879
16906
|
}
|
|
16880
16907
|
|
|
16881
16908
|
// ../../packages/build/src/repository.ts
|
|
16882
|
-
import {
|
|
16909
|
+
import { realpath as realpath2 } from "fs/promises";
|
|
16910
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
|
|
16883
16911
|
function resolveAppRoot(checkout, configuredRoot) {
|
|
16884
16912
|
const checkoutRoot = resolve3(checkout);
|
|
16885
16913
|
if (!configuredRoot) return checkoutRoot;
|
|
@@ -16891,12 +16919,34 @@ function resolveAppRoot(checkout, configuredRoot) {
|
|
|
16891
16919
|
if (relativeRoot === ".." || relativeRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute2(relativeRoot)) {
|
|
16892
16920
|
throw new Error("Configured root directory escapes repository");
|
|
16893
16921
|
}
|
|
16922
|
+
if (relativeRoot.split(/[\\/]/).includes(".git")) {
|
|
16923
|
+
throw new Error("Configured root directory cannot be inside .git");
|
|
16924
|
+
}
|
|
16894
16925
|
return appRoot;
|
|
16895
16926
|
}
|
|
16927
|
+
async function assertAppRootInsideCheckout(checkout, appRoot) {
|
|
16928
|
+
let realCheckout;
|
|
16929
|
+
let realApp;
|
|
16930
|
+
try {
|
|
16931
|
+
[realCheckout, realApp] = await Promise.all([
|
|
16932
|
+
realpath2(checkout),
|
|
16933
|
+
realpath2(appRoot)
|
|
16934
|
+
]);
|
|
16935
|
+
} catch {
|
|
16936
|
+
return;
|
|
16937
|
+
}
|
|
16938
|
+
const relation = relative2(realCheckout, realApp);
|
|
16939
|
+
if (relation === ".." || relation.startsWith(`..${sep2}`) || isAbsolute2(relation)) {
|
|
16940
|
+
throw new Error("Configured root directory escapes repository");
|
|
16941
|
+
}
|
|
16942
|
+
if (relation.split(sep2).includes(".git")) {
|
|
16943
|
+
throw new Error("Configured root directory cannot be inside .git");
|
|
16944
|
+
}
|
|
16945
|
+
}
|
|
16896
16946
|
|
|
16897
16947
|
// ../../packages/build/src/static-artifact.ts
|
|
16898
16948
|
import { readdir as readdir3, readFile as readFile7 } from "fs/promises";
|
|
16899
|
-
import { isAbsolute as isAbsolute3, join as join9, relative as relative3, resolve as resolve4, sep as
|
|
16949
|
+
import { isAbsolute as isAbsolute3, join as join9, relative as relative3, resolve as resolve4, sep as sep3 } from "path";
|
|
16900
16950
|
var SHIM_WORKER_SOURCE = "export default { fetch: (request, env) => env.ASSETS.fetch(request) };\n";
|
|
16901
16951
|
var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules"]);
|
|
16902
16952
|
var ALLOWED_DOTFILE = ".well-known";
|
|
@@ -16912,11 +16962,16 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16912
16962
|
);
|
|
16913
16963
|
const outputRoot = resolve4(appRoot, outputDirectory);
|
|
16914
16964
|
const relation = relative3(appRoot, outputRoot);
|
|
16915
|
-
if (relation === ".." || relation.startsWith(`..${
|
|
16965
|
+
if (relation === ".." || relation.startsWith(`..${sep3}`) || isAbsolute3(relation))
|
|
16916
16966
|
throw new DeploymentError(
|
|
16917
16967
|
"ARTIFACT_INVALID",
|
|
16918
16968
|
"Output directory escapes the application root."
|
|
16919
16969
|
);
|
|
16970
|
+
if (relation.split(sep3).includes(".git"))
|
|
16971
|
+
throw new DeploymentError(
|
|
16972
|
+
"ARTIFACT_INVALID",
|
|
16973
|
+
"Output directory cannot be inside .git."
|
|
16974
|
+
);
|
|
16920
16975
|
const assets = [];
|
|
16921
16976
|
async function visit(directory) {
|
|
16922
16977
|
const entries = await readdir3(directory, { withFileTypes: true });
|
|
@@ -16933,7 +16988,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16933
16988
|
if (entry.isDirectory()) await visit(path);
|
|
16934
16989
|
else if (entry.isFile()) {
|
|
16935
16990
|
assets.push({
|
|
16936
|
-
name: relative3(outputRoot, path).split(
|
|
16991
|
+
name: relative3(outputRoot, path).split(sep3).join("/"),
|
|
16937
16992
|
data: await readFile7(path),
|
|
16938
16993
|
contentType: contentType(path)
|
|
16939
16994
|
});
|
|
@@ -16941,6 +16996,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16941
16996
|
}
|
|
16942
16997
|
}
|
|
16943
16998
|
try {
|
|
16999
|
+
await assertInsideOnDisk(appRoot, outputRoot, "Static");
|
|
16944
17000
|
await visit(outputRoot);
|
|
16945
17001
|
} catch (error51) {
|
|
16946
17002
|
if (error51 instanceof DeploymentError) throw error51;
|
|
@@ -16980,7 +17036,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16980
17036
|
// ../../packages/build/src/strategy/fingerprint.ts
|
|
16981
17037
|
import { createHash as createHash3 } from "crypto";
|
|
16982
17038
|
import { readFile as readFile8 } from "fs/promises";
|
|
16983
|
-
import { join as join10, relative as relative4, sep as
|
|
17039
|
+
import { join as join10, relative as relative4, sep as sep4 } from "path";
|
|
16984
17040
|
var APP_ROOT_FILES = [
|
|
16985
17041
|
"package.json",
|
|
16986
17042
|
"next.config.js",
|
|
@@ -17035,7 +17091,7 @@ async function hashStrategyInputs(checkout, appRoot) {
|
|
|
17035
17091
|
for (const path of candidates) {
|
|
17036
17092
|
const digest = await hashFile(path);
|
|
17037
17093
|
if (digest === null) continue;
|
|
17038
|
-
entries.push([relative4(checkout, path).split(
|
|
17094
|
+
entries.push([relative4(checkout, path).split(sep4).join("/"), digest]);
|
|
17039
17095
|
}
|
|
17040
17096
|
return entries.toSorted(
|
|
17041
17097
|
([left], [right]) => left < right ? -1 : left > right ? 1 : 0
|
|
@@ -17056,8 +17112,76 @@ async function computeStrategyFingerprint(input) {
|
|
|
17056
17112
|
}
|
|
17057
17113
|
|
|
17058
17114
|
// ../../packages/build/src/strategy/project-facts.ts
|
|
17059
|
-
import { access as access6, readFile as
|
|
17115
|
+
import { access as access6, readFile as readFile10 } from "fs/promises";
|
|
17116
|
+
import { join as join12 } from "path";
|
|
17117
|
+
|
|
17118
|
+
// ../../packages/build/src/strategy/user-adapter.ts
|
|
17119
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
17060
17120
|
import { join as join11 } from "path";
|
|
17121
|
+
var ADAPTER_PATH = /\badapterPath\b/;
|
|
17122
|
+
var EXPERIMENTAL_ADAPTER_PATH = /\bexperimental\s*:\s*\{[^{}]*\badapterPath\b/;
|
|
17123
|
+
function adapterKeyFromConfigSource(source) {
|
|
17124
|
+
const code = stripComments(source);
|
|
17125
|
+
if (!ADAPTER_PATH.test(code)) return null;
|
|
17126
|
+
return EXPERIMENTAL_ADAPTER_PATH.test(code) ? "experimental.adapterPath" : "adapterPath";
|
|
17127
|
+
}
|
|
17128
|
+
function keysNextReads(nextVersion) {
|
|
17129
|
+
const version2 = parseNextVersion(nextVersion);
|
|
17130
|
+
if (!version2) return ["adapterPath", "experimental.adapterPath"];
|
|
17131
|
+
if (version2.major < 15 || version2.major === 15 && version2.minor < 4)
|
|
17132
|
+
return [];
|
|
17133
|
+
if (version2.major > 16 || version2.major === 16 && version2.minor >= 2)
|
|
17134
|
+
return ["adapterPath", "experimental.adapterPath"];
|
|
17135
|
+
return ["experimental.adapterPath"];
|
|
17136
|
+
}
|
|
17137
|
+
function userAdapterOf(input) {
|
|
17138
|
+
const keys = keysNextReads(input.nextVersion);
|
|
17139
|
+
if (keys.length === 0) return null;
|
|
17140
|
+
const config2 = input.facts.adapterConfig;
|
|
17141
|
+
if (config2 && keys.includes(config2.key))
|
|
17142
|
+
return { source: "next-config", file: config2.file, key: config2.key };
|
|
17143
|
+
if (input.env.NEXT_ADAPTER_PATH) return { source: "env" };
|
|
17144
|
+
return null;
|
|
17145
|
+
}
|
|
17146
|
+
async function adapterInBuildOutput(appRoot, nextVersion) {
|
|
17147
|
+
const keys = keysNextReads(nextVersion);
|
|
17148
|
+
if (keys.length === 0) return null;
|
|
17149
|
+
let manifest2;
|
|
17150
|
+
try {
|
|
17151
|
+
const parsed = JSON.parse(
|
|
17152
|
+
await readFile9(
|
|
17153
|
+
join11(appRoot, ".next", "required-server-files.json"),
|
|
17154
|
+
"utf8"
|
|
17155
|
+
)
|
|
17156
|
+
);
|
|
17157
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
17158
|
+
manifest2 = parsed;
|
|
17159
|
+
} catch {
|
|
17160
|
+
return null;
|
|
17161
|
+
}
|
|
17162
|
+
const topLevel = manifest2.config?.adapterPath;
|
|
17163
|
+
if (keys.includes("adapterPath") && typeof topLevel === "string" && topLevel)
|
|
17164
|
+
return { source: "build-output", key: "adapterPath" };
|
|
17165
|
+
const experimental = manifest2.config?.experimental?.adapterPath;
|
|
17166
|
+
if (keys.includes("experimental.adapterPath") && typeof experimental === "string" && experimental)
|
|
17167
|
+
return { source: "build-output", key: "experimental.adapterPath" };
|
|
17168
|
+
return null;
|
|
17169
|
+
}
|
|
17170
|
+
function describeUserAdapter(adapter) {
|
|
17171
|
+
switch (adapter.source) {
|
|
17172
|
+
case "next-config":
|
|
17173
|
+
return `${adapter.file} ${adapter.key}`;
|
|
17174
|
+
case "env":
|
|
17175
|
+
return "NEXT_ADAPTER_PATH";
|
|
17176
|
+
case "build-output":
|
|
17177
|
+
return `${adapter.key} in the Next.js build output`;
|
|
17178
|
+
}
|
|
17179
|
+
}
|
|
17180
|
+
function openNextAdapterWarning(adapter) {
|
|
17181
|
+
return `P2 opennext-cloudflare: The project sets its own Next.js adapter (${describeUserAdapter(adapter)}); its modifyConfig and onBuildComplete also run in the OpenNext build.`;
|
|
17182
|
+
}
|
|
17183
|
+
|
|
17184
|
+
// ../../packages/build/src/strategy/project-facts.ts
|
|
17061
17185
|
var SOURCE_EXTENSIONS2 = ["ts", "tsx", "js", "jsx", "mjs", "mts"];
|
|
17062
17186
|
var SOURCE_DIRECTORIES = ["", "src"];
|
|
17063
17187
|
var VITE_CONFIGS = [
|
|
@@ -17078,7 +17202,7 @@ async function findSourceFile(appRoot, basename5) {
|
|
|
17078
17202
|
for (const dir of SOURCE_DIRECTORIES) {
|
|
17079
17203
|
for (const ext of SOURCE_EXTENSIONS2) {
|
|
17080
17204
|
const rel = dir ? `${dir}/${basename5}.${ext}` : `${basename5}.${ext}`;
|
|
17081
|
-
if (await exists4(
|
|
17205
|
+
if (await exists4(join12(appRoot, rel))) return rel;
|
|
17082
17206
|
}
|
|
17083
17207
|
}
|
|
17084
17208
|
return null;
|
|
@@ -17098,19 +17222,28 @@ function matcherRoutesFromSource(source) {
|
|
|
17098
17222
|
}
|
|
17099
17223
|
async function readIfExists(path) {
|
|
17100
17224
|
try {
|
|
17101
|
-
return await
|
|
17225
|
+
return await readFile10(path, "utf8");
|
|
17102
17226
|
} catch {
|
|
17103
17227
|
return null;
|
|
17104
17228
|
}
|
|
17105
17229
|
}
|
|
17230
|
+
async function findAdapterConfig(appRoot) {
|
|
17231
|
+
for (const file2 of NEXT_CONFIG_FILES) {
|
|
17232
|
+
const source = await readIfExists(join12(appRoot, file2));
|
|
17233
|
+
if (source === null) continue;
|
|
17234
|
+
const key = adapterKeyFromConfigSource(source);
|
|
17235
|
+
return key ? { file: file2, key } : null;
|
|
17236
|
+
}
|
|
17237
|
+
return null;
|
|
17238
|
+
}
|
|
17106
17239
|
async function collectProjectFacts(appRoot) {
|
|
17107
17240
|
const proxyFile = await findSourceFile(appRoot, "proxy");
|
|
17108
17241
|
const middlewareFile = await findSourceFile(appRoot, "middleware");
|
|
17109
17242
|
const routeSourceFile = middlewareFile ?? proxyFile;
|
|
17110
|
-
const routeSource = routeSourceFile ? await readIfExists(
|
|
17243
|
+
const routeSource = routeSourceFile ? await readIfExists(join12(appRoot, routeSourceFile)) : null;
|
|
17111
17244
|
let usesVinext = false;
|
|
17112
17245
|
let nextSpec = null;
|
|
17113
|
-
const packageJsonSource = await readIfExists(
|
|
17246
|
+
const packageJsonSource = await readIfExists(join12(appRoot, "package.json"));
|
|
17114
17247
|
if (packageJsonSource) {
|
|
17115
17248
|
try {
|
|
17116
17249
|
const packageJson = JSON.parse(packageJsonSource);
|
|
@@ -17121,7 +17254,7 @@ async function collectProjectFacts(appRoot) {
|
|
|
17121
17254
|
nextSpec = dependency("next");
|
|
17122
17255
|
if (dependency("vinext") !== null) {
|
|
17123
17256
|
for (const name of VITE_CONFIGS) {
|
|
17124
|
-
const config2 = await readIfExists(
|
|
17257
|
+
const config2 = await readIfExists(join12(appRoot, name));
|
|
17125
17258
|
if (config2 && /\bvinext\s*\(/.test(config2) && /@cloudflare\/vite-plugin/.test(config2)) {
|
|
17126
17259
|
usesVinext = true;
|
|
17127
17260
|
break;
|
|
@@ -17136,7 +17269,8 @@ async function collectProjectFacts(appRoot) {
|
|
|
17136
17269
|
middlewareFile,
|
|
17137
17270
|
matcherRoutes: routeSource ? matcherRoutesFromSource(routeSource) : [],
|
|
17138
17271
|
usesVinext,
|
|
17139
|
-
nextSpec
|
|
17272
|
+
nextSpec,
|
|
17273
|
+
adapterConfig: await findAdapterConfig(appRoot)
|
|
17140
17274
|
};
|
|
17141
17275
|
}
|
|
17142
17276
|
|
|
@@ -17523,11 +17657,11 @@ function defaultSmokeProbes(matcherRoutes) {
|
|
|
17523
17657
|
|
|
17524
17658
|
// ../../packages/build/src/strategy/strategies/opennext-aws.ts
|
|
17525
17659
|
import { writeFile as writeFile6 } from "fs/promises";
|
|
17526
|
-
import { join as
|
|
17660
|
+
import { join as join14 } from "path";
|
|
17527
17661
|
|
|
17528
17662
|
// ../../packages/build/src/strategy/strategies/shared.ts
|
|
17529
|
-
import { access as access7, readFile as
|
|
17530
|
-
import { dirname as dirname2, join as
|
|
17663
|
+
import { access as access7, readFile as readFile11, rm, writeFile as writeFile5 } from "fs/promises";
|
|
17664
|
+
import { dirname as dirname2, join as join13, resolve as resolve5 } from "path";
|
|
17531
17665
|
function capturingObserver(observer) {
|
|
17532
17666
|
const lines = [];
|
|
17533
17667
|
return {
|
|
@@ -17584,7 +17718,7 @@ async function findProjectBin(appRoot, checkout, name) {
|
|
|
17584
17718
|
let dir = resolve5(appRoot);
|
|
17585
17719
|
const stop = resolve5(checkout);
|
|
17586
17720
|
for (; ; ) {
|
|
17587
|
-
const candidate =
|
|
17721
|
+
const candidate = join13(dir, "node_modules", ".bin", name);
|
|
17588
17722
|
if (await pathExists2(candidate)) return candidate;
|
|
17589
17723
|
if (dir === stop) return null;
|
|
17590
17724
|
const parent = dirname2(dir);
|
|
@@ -17598,7 +17732,7 @@ function dotenvQuote(value) {
|
|
|
17598
17732
|
return `"${value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r")}"`;
|
|
17599
17733
|
}
|
|
17600
17734
|
async function withDevVars(wranglerConfigPath, env, run) {
|
|
17601
|
-
const path =
|
|
17735
|
+
const path = join13(dirname2(wranglerConfigPath), ".dev.vars");
|
|
17602
17736
|
const keys = Object.keys(env).filter(
|
|
17603
17737
|
(key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)
|
|
17604
17738
|
);
|
|
@@ -17631,7 +17765,7 @@ async function bundleWorker(context, layout, observer, errorCode2) {
|
|
|
17631
17765
|
cwd: layout.appRoot,
|
|
17632
17766
|
env: {
|
|
17633
17767
|
CI: "1",
|
|
17634
|
-
WRANGLER_LOG_PATH:
|
|
17768
|
+
WRANGLER_LOG_PATH: join13(
|
|
17635
17769
|
layout.appRoot,
|
|
17636
17770
|
layout.outputRoot,
|
|
17637
17771
|
"wrangler.log"
|
|
@@ -17664,12 +17798,12 @@ async function smokeWorker(context, layout, observer) {
|
|
|
17664
17798
|
"--show-interactive-dev-session=false",
|
|
17665
17799
|
"--log-level=warn",
|
|
17666
17800
|
"--persist-to",
|
|
17667
|
-
|
|
17801
|
+
join13(layout.appRoot, layout.outputRoot, ".naro-smoke-state")
|
|
17668
17802
|
],
|
|
17669
17803
|
cwd: layout.appRoot,
|
|
17670
17804
|
env: {
|
|
17671
17805
|
CI: "1",
|
|
17672
|
-
WRANGLER_LOG_PATH:
|
|
17806
|
+
WRANGLER_LOG_PATH: join13(
|
|
17673
17807
|
layout.appRoot,
|
|
17674
17808
|
layout.outputRoot,
|
|
17675
17809
|
"wrangler-dev.log"
|
|
@@ -17685,10 +17819,10 @@ async function smokeWorker(context, layout, observer) {
|
|
|
17685
17819
|
);
|
|
17686
17820
|
}
|
|
17687
17821
|
async function approvePnpmBuilds(checkout, packages) {
|
|
17688
|
-
const path =
|
|
17822
|
+
const path = join13(checkout, "pnpm-workspace.yaml");
|
|
17689
17823
|
let source = "";
|
|
17690
17824
|
try {
|
|
17691
|
-
source = await
|
|
17825
|
+
source = await readFile11(path, "utf8");
|
|
17692
17826
|
} catch {
|
|
17693
17827
|
}
|
|
17694
17828
|
let next = source;
|
|
@@ -17781,7 +17915,7 @@ function createOpenNextAwsStrategy() {
|
|
|
17781
17915
|
} catch (error51) {
|
|
17782
17916
|
return failed("prepare", describeError(error51));
|
|
17783
17917
|
}
|
|
17784
|
-
const configPath =
|
|
17918
|
+
const configPath = join14(fork.appRoot, CONFIG_NAME);
|
|
17785
17919
|
try {
|
|
17786
17920
|
await writeFile6(
|
|
17787
17921
|
configPath,
|
|
@@ -17810,7 +17944,7 @@ function createOpenNextAwsStrategy() {
|
|
|
17810
17944
|
observer
|
|
17811
17945
|
);
|
|
17812
17946
|
if (!build.ok) return failed("build", describeFailure(build));
|
|
17813
|
-
const serverFunction =
|
|
17947
|
+
const serverFunction = join14(fork.appRoot, SERVER_FUNCTION);
|
|
17814
17948
|
if (!await pathExists2(serverFunction))
|
|
17815
17949
|
return failed("bundle", `OpenNext AWS produced no ${SERVER_FUNCTION}.`);
|
|
17816
17950
|
const smoke = await context.ports.smoke.run(
|
|
@@ -17832,7 +17966,7 @@ function createOpenNextAwsStrategy() {
|
|
|
17832
17966
|
status: "success",
|
|
17833
17967
|
artifact: {
|
|
17834
17968
|
kind: "opennext-aws",
|
|
17835
|
-
outputRoot:
|
|
17969
|
+
outputRoot: join14(fork.appRoot, ".open-next"),
|
|
17836
17970
|
serverFunction
|
|
17837
17971
|
},
|
|
17838
17972
|
notes: [
|
|
@@ -17844,8 +17978,8 @@ function createOpenNextAwsStrategy() {
|
|
|
17844
17978
|
}
|
|
17845
17979
|
|
|
17846
17980
|
// ../../packages/build/src/strategy/strategies/opennext-cloudflare.ts
|
|
17847
|
-
import { readFile as
|
|
17848
|
-
import { join as
|
|
17981
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
17982
|
+
import { join as join15 } from "path";
|
|
17849
17983
|
var PROXY_TO_MIDDLEWARE_VARIANT = "proxy-to-middleware";
|
|
17850
17984
|
var NODE_MIDDLEWARE_REFUSAL = /Node\.js middleware is not (?:currently )?supported/i;
|
|
17851
17985
|
var NODE_MIDDLEWARE_EXPERIMENTAL = /Node\.js middleware support is experimental/i;
|
|
@@ -17853,8 +17987,8 @@ var NODE_MIDDLEWARE_NOT_DEPLOYED = 'Node.js-runtime middleware (runtime: "nodejs
|
|
|
17853
17987
|
async function buildDeclaresNodeMiddleware(appRoot) {
|
|
17854
17988
|
try {
|
|
17855
17989
|
const manifest2 = JSON.parse(
|
|
17856
|
-
await
|
|
17857
|
-
|
|
17990
|
+
await readFile12(
|
|
17991
|
+
join15(appRoot, ".next", "server", "functions-config-manifest.json"),
|
|
17858
17992
|
"utf8"
|
|
17859
17993
|
)
|
|
17860
17994
|
);
|
|
@@ -17864,7 +17998,7 @@ async function buildDeclaresNodeMiddleware(appRoot) {
|
|
|
17864
17998
|
}
|
|
17865
17999
|
}
|
|
17866
18000
|
function createOpenNextCloudflareStrategy(options) {
|
|
17867
|
-
async function attempt(context, roots, observer, ownBuild) {
|
|
18001
|
+
async function attempt(context, roots, observer, ownBuild, adapterNotice) {
|
|
17868
18002
|
let platform;
|
|
17869
18003
|
try {
|
|
17870
18004
|
platform = await options.createPlatformBuildConfig(
|
|
@@ -17902,6 +18036,13 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
17902
18036
|
outcome: ownBuild ? appBuildFailed(describeFailure(nextBuild)) : failed("build", describeFailure(nextBuild))
|
|
17903
18037
|
};
|
|
17904
18038
|
}
|
|
18039
|
+
if (!adapterNotice.warned) {
|
|
18040
|
+
const adapter = userAdapterOf(context) ?? await adapterInBuildOutput(roots.appRoot, context.nextVersion);
|
|
18041
|
+
if (adapter) {
|
|
18042
|
+
adapterNotice.warned = true;
|
|
18043
|
+
await warn(observer, openNextAdapterWarning(adapter));
|
|
18044
|
+
}
|
|
18045
|
+
}
|
|
17905
18046
|
if (await buildDeclaresNodeMiddleware(roots.appRoot)) {
|
|
17906
18047
|
return {
|
|
17907
18048
|
ok: false,
|
|
@@ -17997,6 +18138,7 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
17997
18138
|
target: "cloudflare",
|
|
17998
18139
|
supports: () => ({ supported: true }),
|
|
17999
18140
|
async evaluate(context, observer) {
|
|
18141
|
+
const adapterNotice = { warned: false };
|
|
18000
18142
|
const rememberedRewrite = context.pinnedVariant === PROXY_TO_MIDDLEWARE_VARIANT && context.facts.proxyFile !== null;
|
|
18001
18143
|
if (rememberedRewrite) {
|
|
18002
18144
|
await info(
|
|
@@ -18008,7 +18150,13 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
18008
18150
|
observer,
|
|
18009
18151
|
"P2 opennext-cloudflare: building the project as written."
|
|
18010
18152
|
);
|
|
18011
|
-
const first = await attempt(
|
|
18153
|
+
const first = await attempt(
|
|
18154
|
+
context,
|
|
18155
|
+
context.workspace,
|
|
18156
|
+
observer,
|
|
18157
|
+
true,
|
|
18158
|
+
adapterNotice
|
|
18159
|
+
);
|
|
18012
18160
|
if (first.ok) {
|
|
18013
18161
|
return {
|
|
18014
18162
|
status: "success",
|
|
@@ -18040,7 +18188,13 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
18040
18188
|
observer,
|
|
18041
18189
|
`Converted ${conversion.file ?? context.facts.proxyFile} to edge middleware for this build only. Next.js 16 proxy runs on the Node.js runtime, which Cloudflare does not support; middleware runs on the Edge runtime, so Node.js-only APIs are unavailable. Your repository is unchanged.`
|
|
18042
18190
|
);
|
|
18043
|
-
const second = await attempt(
|
|
18191
|
+
const second = await attempt(
|
|
18192
|
+
context,
|
|
18193
|
+
fork,
|
|
18194
|
+
observer,
|
|
18195
|
+
rememberedRewrite,
|
|
18196
|
+
adapterNotice
|
|
18197
|
+
);
|
|
18044
18198
|
if (!second.ok) {
|
|
18045
18199
|
if (!rememberedRewrite || second.outcome.status !== "failed" || !second.outcome.appBuild)
|
|
18046
18200
|
return second.outcome;
|
|
@@ -18052,7 +18206,8 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
18052
18206
|
context,
|
|
18053
18207
|
context.workspace,
|
|
18054
18208
|
observer,
|
|
18055
|
-
true
|
|
18209
|
+
true,
|
|
18210
|
+
adapterNotice
|
|
18056
18211
|
);
|
|
18057
18212
|
if (asWritten.ok) {
|
|
18058
18213
|
await info(
|
|
@@ -18121,7 +18276,7 @@ function createAwsMiddlewareCloudflareAppStrategy() {
|
|
|
18121
18276
|
}
|
|
18122
18277
|
|
|
18123
18278
|
// ../../packages/build/src/strategy/strategies/vinext-cloudflare.ts
|
|
18124
|
-
import { join as
|
|
18279
|
+
import { join as join16 } from "path";
|
|
18125
18280
|
var VINEXT_MIN_NEXT_MAJOR = 16;
|
|
18126
18281
|
var VINEXT_BUILD_SCRIPTS = ["esbuild", "workerd"];
|
|
18127
18282
|
var VINEXT_OUTPUT = {
|
|
@@ -18277,7 +18432,7 @@ function createVinextCloudflareStrategy(options) {
|
|
|
18277
18432
|
context,
|
|
18278
18433
|
{
|
|
18279
18434
|
appRoot: fork.appRoot,
|
|
18280
|
-
wranglerConfigPath:
|
|
18435
|
+
wranglerConfigPath: join16(
|
|
18281
18436
|
fork.appRoot,
|
|
18282
18437
|
"dist",
|
|
18283
18438
|
"server",
|
|
@@ -18302,7 +18457,7 @@ function createVinextCloudflareStrategy(options) {
|
|
|
18302
18457
|
// ../../packages/build/src/strategy/workspace.ts
|
|
18303
18458
|
import { execFile } from "child_process";
|
|
18304
18459
|
import { cp, mkdir as mkdir2, readdir as readdir4, rm as rm2 } from "fs/promises";
|
|
18305
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
18460
|
+
import { basename as basename2, dirname as dirname3, join as join17, relative as relative5, resolve as resolve6 } from "path";
|
|
18306
18461
|
import { promisify } from "util";
|
|
18307
18462
|
var execFileAsync = promisify(execFile);
|
|
18308
18463
|
var SKIPPED_ANYWHERE = /* @__PURE__ */ new Set(["node_modules"]);
|
|
@@ -18325,14 +18480,14 @@ async function copyInstalledPackages(source, target2) {
|
|
|
18325
18480
|
const walk2 = async (dir) => {
|
|
18326
18481
|
for (const entry of await readdir4(dir, { withFileTypes: true })) {
|
|
18327
18482
|
if (!entry.isDirectory() || entry.name === ".git") continue;
|
|
18328
|
-
const path =
|
|
18483
|
+
const path = join17(dir, entry.name);
|
|
18329
18484
|
if (entry.name === "node_modules") installs.push(path);
|
|
18330
18485
|
else await walk2(path);
|
|
18331
18486
|
}
|
|
18332
18487
|
};
|
|
18333
18488
|
await walk2(origin);
|
|
18334
18489
|
for (const install of installs)
|
|
18335
|
-
await copyTree(install,
|
|
18490
|
+
await copyTree(install, join17(target2, relative5(origin, install)));
|
|
18336
18491
|
}
|
|
18337
18492
|
async function copyTree(from, to) {
|
|
18338
18493
|
await mkdir2(dirname3(to), { recursive: true });
|
|
@@ -18347,7 +18502,7 @@ async function copyTree(from, to) {
|
|
|
18347
18502
|
await cp(from, to, { recursive: true, verbatimSymlinks: true });
|
|
18348
18503
|
}
|
|
18349
18504
|
function forkedAppRoot(checkout, appRoot, forkCheckout) {
|
|
18350
|
-
return
|
|
18505
|
+
return join17(forkCheckout, relative5(checkout, appRoot));
|
|
18351
18506
|
}
|
|
18352
18507
|
|
|
18353
18508
|
// ../../packages/build/src/local-build-executor.ts
|
|
@@ -18369,7 +18524,7 @@ async function withRouteList(artifact, list, appRoot, observer) {
|
|
|
18369
18524
|
}
|
|
18370
18525
|
}
|
|
18371
18526
|
var defaultLocalBuildDependencies = {
|
|
18372
|
-
createWorkspace: () => mkdtemp(
|
|
18527
|
+
createWorkspace: () => mkdtemp(join18(tmpdir(), "next-deploy-build-")),
|
|
18373
18528
|
removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
|
|
18374
18529
|
runCommand,
|
|
18375
18530
|
detectPackageManager,
|
|
@@ -18403,8 +18558,8 @@ async function readInstalledNextVersion(appRoot, checkout) {
|
|
|
18403
18558
|
const stop = resolve7(checkout);
|
|
18404
18559
|
for (; ; ) {
|
|
18405
18560
|
try {
|
|
18406
|
-
const source = await
|
|
18407
|
-
|
|
18561
|
+
const source = await readFile13(
|
|
18562
|
+
join18(dir, "node_modules", "next", "package.json"),
|
|
18408
18563
|
"utf8"
|
|
18409
18564
|
);
|
|
18410
18565
|
const version2 = JSON.parse(source).version;
|
|
@@ -18497,8 +18652,8 @@ var LocalBuildExecutor = class {
|
|
|
18497
18652
|
}
|
|
18498
18653
|
async build(input, observer, cache) {
|
|
18499
18654
|
const workspace = await this.dependencies.createWorkspace();
|
|
18500
|
-
const checkout =
|
|
18501
|
-
const pmCacheDir =
|
|
18655
|
+
const checkout = join18(workspace, "checkout");
|
|
18656
|
+
const pmCacheDir = join18(workspace, ".pm-store");
|
|
18502
18657
|
let cacheSeq = 0;
|
|
18503
18658
|
const commandAvailable = this.dependencies.commandAvailable ?? hasCommand;
|
|
18504
18659
|
const compressor = "zstd";
|
|
@@ -18507,7 +18662,7 @@ var LocalBuildExecutor = class {
|
|
|
18507
18662
|
transport: cache,
|
|
18508
18663
|
fetch: this.dependencies.cacheFetch ?? ((url2, init) => fetch(url2, init)),
|
|
18509
18664
|
runCommand: this.dependencies.runCommand,
|
|
18510
|
-
tmpFile: (suffix) =>
|
|
18665
|
+
tmpFile: (suffix) => join18(workspace, `cache-${String(cacheSeq++)}${suffix}`),
|
|
18511
18666
|
// One latch for the whole build so a disabled cache warns once.
|
|
18512
18667
|
report: { unavailable: false }
|
|
18513
18668
|
} : null;
|
|
@@ -18586,6 +18741,7 @@ var LocalBuildExecutor = class {
|
|
|
18586
18741
|
checkout,
|
|
18587
18742
|
input.rootDirectory
|
|
18588
18743
|
);
|
|
18744
|
+
await assertAppRootInsideCheckout(checkout, appRoot);
|
|
18589
18745
|
const stacks = await import("./stack-PDV3NPWC.js");
|
|
18590
18746
|
const stack = await (this.dependencies.detectStack ?? stacks.detectStack)(
|
|
18591
18747
|
appRoot
|
|
@@ -18698,7 +18854,7 @@ var LocalBuildExecutor = class {
|
|
|
18698
18854
|
}
|
|
18699
18855
|
}
|
|
18700
18856
|
await notifyStatus(observer, "BUILDING");
|
|
18701
|
-
const buildCacheDir = resolved.preset.cachePath ?
|
|
18857
|
+
const buildCacheDir = resolved.preset.cachePath ? join18(appRoot, resolved.preset.cachePath) : null;
|
|
18702
18858
|
const buildCacheKey = sanitizeCacheKey(input.branch);
|
|
18703
18859
|
if (cachePorts && buildCacheDir) {
|
|
18704
18860
|
await tryRestore(
|
|
@@ -18722,13 +18878,13 @@ var LocalBuildExecutor = class {
|
|
|
18722
18878
|
entries: fingerprintEntries,
|
|
18723
18879
|
nextVersion: nextVersion ? `${String(nextVersion.major)}.${String(nextVersion.minor)}.${String(nextVersion.patch)}` : "unknown"
|
|
18724
18880
|
});
|
|
18725
|
-
const pristine =
|
|
18881
|
+
const pristine = join18(workspace, "pristine");
|
|
18726
18882
|
const fork = this.dependencies.forkWorkspace ?? defaultFork;
|
|
18727
18883
|
await fork(checkout, pristine);
|
|
18728
18884
|
let forks = 0;
|
|
18729
18885
|
const resolverConcurrency = this.dependencies.resolverConcurrency ?? 1;
|
|
18730
18886
|
const forkWorkspace = async (label2) => {
|
|
18731
|
-
const target2 =
|
|
18887
|
+
const target2 = join18(
|
|
18732
18888
|
workspace,
|
|
18733
18889
|
"forks",
|
|
18734
18890
|
`${String(forks++)}-${label2.replace(/[^a-z0-9-]/gi, "-")}`
|
|
@@ -19016,12 +19172,12 @@ async function errorFromResponse(response, context) {
|
|
|
19016
19172
|
// ../../packages/sdk/src/session.ts
|
|
19017
19173
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
19018
19174
|
import { homedir } from "os";
|
|
19019
|
-
import { join as
|
|
19175
|
+
import { join as join19 } from "path";
|
|
19020
19176
|
function credentialsDir(home = homedir()) {
|
|
19021
|
-
return
|
|
19177
|
+
return join19(home, ".naro");
|
|
19022
19178
|
}
|
|
19023
19179
|
function credentialsFile(home = homedir()) {
|
|
19024
|
-
return
|
|
19180
|
+
return join19(credentialsDir(home), "credentials.json");
|
|
19025
19181
|
}
|
|
19026
19182
|
function saveSession(session, home = homedir()) {
|
|
19027
19183
|
mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
|
|
@@ -19848,6 +20004,8 @@ var DeploymentsApi = class {
|
|
|
19848
20004
|
}
|
|
19849
20005
|
}
|
|
19850
20006
|
};
|
|
20007
|
+
var STATIC_ASSETS_BINDING = "ASSETS";
|
|
20008
|
+
var MANAGED_DATABASE_BINDING = "DB";
|
|
19851
20009
|
var EnvApi = class {
|
|
19852
20010
|
constructor(client) {
|
|
19853
20011
|
this.client = client;
|
|
@@ -19877,6 +20035,37 @@ var EnvApi = class {
|
|
|
19877
20035
|
}
|
|
19878
20036
|
);
|
|
19879
20037
|
}
|
|
20038
|
+
/**
|
|
20039
|
+
* The names the env route refuses as a key — `409 VALIDATION_FAILED`
|
|
20040
|
+
* `<KEY> is already used by a storage binding.` — read the way it reads
|
|
20041
|
+
* them: `ASSETS` (the static files), every binding of the project's storage
|
|
20042
|
+
* and, on a project using the managed database (`database_provider` is
|
|
20043
|
+
* `d1`), `DB`, which may not be a storage row yet. For a caller that sends
|
|
20044
|
+
* many entries in several requests (`naro env import`) and has to refuse
|
|
20045
|
+
* before the first one is saved. A control plane whose project row has no
|
|
20046
|
+
* `database_provider` reads as having no managed database, so `DB` is only
|
|
20047
|
+
* left out there.
|
|
20048
|
+
*/
|
|
20049
|
+
async takenBindingNames(projectId) {
|
|
20050
|
+
const base2 = `/api/projects/${encodeURIComponent(projectId)}`;
|
|
20051
|
+
const [storage, project] = await Promise.all([
|
|
20052
|
+
this.client.request(
|
|
20053
|
+
`${base2}/storage`,
|
|
20054
|
+
{ context: "List storage" }
|
|
20055
|
+
),
|
|
20056
|
+
this.client.request(
|
|
20057
|
+
base2,
|
|
20058
|
+
{ context: "Read project" }
|
|
20059
|
+
)
|
|
20060
|
+
]);
|
|
20061
|
+
const names = /* @__PURE__ */ new Set([
|
|
20062
|
+
STATIC_ASSETS_BINDING,
|
|
20063
|
+
...storage.storage.map((row) => row.bindingName)
|
|
20064
|
+
]);
|
|
20065
|
+
if (project.project.database_provider === "d1")
|
|
20066
|
+
names.add(MANAGED_DATABASE_BINDING);
|
|
20067
|
+
return [...names];
|
|
20068
|
+
}
|
|
19880
20069
|
async removeById(projectId, envVarId) {
|
|
19881
20070
|
await this.client.request(
|
|
19882
20071
|
`/api/projects/${encodeURIComponent(projectId)}/env/${encodeURIComponent(envVarId)}`,
|
|
@@ -19887,13 +20076,28 @@ var EnvApi = class {
|
|
|
19887
20076
|
* Removes a key's rows: the one `target` names, or, with no target, the
|
|
19888
20077
|
* `production`, `preview` and `all` rows. An environment's value goes only
|
|
19889
20078
|
* when its slug is the target, so removing a key everywhere else does not
|
|
19890
|
-
* take a QA or staging value with it.
|
|
20079
|
+
* take a QA or staging value with it. A key only environments hold fails
|
|
20080
|
+
* ENV_NOT_FOUND naming them (`details.environments`), not as if there were
|
|
20081
|
+
* none.
|
|
19891
20082
|
*/
|
|
19892
20083
|
async remove(projectId, key, target2) {
|
|
19893
20084
|
const rows = await this.list(projectId);
|
|
19894
20085
|
const matches = rows.filter(
|
|
19895
20086
|
(row) => row.key === key && (target2 === void 0 ? isReservedEnvTarget(row.target) : row.target === target2)
|
|
19896
20087
|
);
|
|
20088
|
+
if (matches.length === 0 && target2 === void 0) {
|
|
20089
|
+
const environments = [
|
|
20090
|
+
...new Set(
|
|
20091
|
+
rows.filter((row) => row.key === key).map((row) => row.target).filter((slug) => !isReservedEnvTarget(slug))
|
|
20092
|
+
)
|
|
20093
|
+
].toSorted();
|
|
20094
|
+
if (environments.length > 0)
|
|
20095
|
+
throw new NaroError(
|
|
20096
|
+
"ENV_NOT_FOUND",
|
|
20097
|
+
`No ${key} variable found outside named environments. Pass the environment to remove it: ${environments.join(", ")}.`,
|
|
20098
|
+
{ details: { key, environments } }
|
|
20099
|
+
);
|
|
20100
|
+
}
|
|
19897
20101
|
if (matches.length === 0) {
|
|
19898
20102
|
throw new NaroError(
|
|
19899
20103
|
"ENV_NOT_FOUND",
|
|
@@ -21167,40 +21371,92 @@ function summarizeStatement(statement2) {
|
|
|
21167
21371
|
|
|
21168
21372
|
// ../../packages/sdk/src/dotenv.ts
|
|
21169
21373
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
21170
|
-
import { join as
|
|
21374
|
+
import { join as join20 } from "path";
|
|
21375
|
+
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/dgm;
|
|
21376
|
+
var QUOTES = /* @__PURE__ */ new Set(["'", '"', "`"]);
|
|
21377
|
+
function parseDotenvDetailed(text2) {
|
|
21378
|
+
const vars = {};
|
|
21379
|
+
const unclosedQuotes = [];
|
|
21380
|
+
const lines = text2.replace(/\r\n?/g, "\n");
|
|
21381
|
+
let line = 1;
|
|
21382
|
+
let counted = 0;
|
|
21383
|
+
for (const match of lines.matchAll(LINE)) {
|
|
21384
|
+
const key = match[1];
|
|
21385
|
+
const raw = match[2] ?? "";
|
|
21386
|
+
let value = raw.trim();
|
|
21387
|
+
const maybeQuote = value[0];
|
|
21388
|
+
if (maybeQuote !== void 0 && QUOTES.has(maybeQuote) && !value.includes(maybeQuote, 1)) {
|
|
21389
|
+
const at = (match.indices?.[2]?.[0] ?? match.index) + (raw.length - raw.trimStart().length);
|
|
21390
|
+
for (; counted < at; counted += 1) if (lines[counted] === "\n") line += 1;
|
|
21391
|
+
unclosedQuotes.push(line);
|
|
21392
|
+
}
|
|
21393
|
+
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
|
|
21394
|
+
if (maybeQuote === '"')
|
|
21395
|
+
value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r");
|
|
21396
|
+
vars[key] = value;
|
|
21397
|
+
}
|
|
21398
|
+
return { vars, unclosedQuotes };
|
|
21399
|
+
}
|
|
21171
21400
|
function parseDotenv(text2) {
|
|
21172
|
-
|
|
21173
|
-
for (const line of text2.split(/\r?\n/)) {
|
|
21174
|
-
const trimmed = line.trim().replace(/^export\s+(?=[^=\s])/, "");
|
|
21175
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
21176
|
-
const eq = trimmed.indexOf("=");
|
|
21177
|
-
if (eq <= 0) continue;
|
|
21178
|
-
const key = trimmed.slice(0, eq).trim();
|
|
21179
|
-
let value = trimmed.slice(eq + 1).trim();
|
|
21180
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))
|
|
21181
|
-
value = value.slice(1, -1);
|
|
21182
|
-
out[key] = value;
|
|
21183
|
-
}
|
|
21184
|
-
return out;
|
|
21401
|
+
return parseDotenvDetailed(text2).vars;
|
|
21185
21402
|
}
|
|
21186
21403
|
function readProjectEnv(dir) {
|
|
21187
21404
|
for (const name of [".env.local", ".env.production", ".env"]) {
|
|
21188
|
-
const path =
|
|
21405
|
+
const path = join20(dir, name);
|
|
21189
21406
|
if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
|
|
21190
21407
|
}
|
|
21191
21408
|
return {};
|
|
21192
21409
|
}
|
|
21410
|
+
var PLAIN = /^[^\s"'#$`\\]*$/;
|
|
21411
|
+
function bareReadsBack(value) {
|
|
21412
|
+
return !/[#\r\n\u2028\u2029]/.test(value) && value.trim() === value && !QUOTES.has(value[0] ?? "");
|
|
21413
|
+
}
|
|
21414
|
+
function dotenvValue(value) {
|
|
21415
|
+
if (PLAIN.test(value)) return value;
|
|
21416
|
+
const endsInBackslash = value.endsWith("\\");
|
|
21417
|
+
if (!value.includes("\r") && !endsInBackslash) {
|
|
21418
|
+
if (!value.includes("'")) return `'${value}'`;
|
|
21419
|
+
if (!value.includes("`")) return `\`${value}\``;
|
|
21420
|
+
}
|
|
21421
|
+
if (!value.includes('"') && !/\\[nr]/.test(value) && !endsInBackslash)
|
|
21422
|
+
return `"${value.replaceAll("\r", "\\r")}"`;
|
|
21423
|
+
if (bareReadsBack(value)) return value;
|
|
21424
|
+
return void 0;
|
|
21425
|
+
}
|
|
21426
|
+
var UNWRITABLE_HINT = "Store them without carriage returns or one of the quote characters (for a file: tr -d '\\r' < sa.json | naro env add KEY), or read them with naro env list --reveal.";
|
|
21193
21427
|
function serializeDotenv(vars) {
|
|
21194
|
-
const
|
|
21195
|
-
|
|
21196
|
-
|
|
21197
|
-
|
|
21428
|
+
const lines = [];
|
|
21429
|
+
const allQuotes = [];
|
|
21430
|
+
const other = [];
|
|
21431
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
21432
|
+
const written = dotenvValue(value);
|
|
21433
|
+
if (written !== void 0) lines.push(`${key}=${written}`);
|
|
21434
|
+
else if (value.includes("'") && value.includes('"') && value.includes("`"))
|
|
21435
|
+
allQuotes.push(key);
|
|
21436
|
+
else other.push(key);
|
|
21437
|
+
}
|
|
21438
|
+
const problems2 = [];
|
|
21439
|
+
if (allQuotes.length > 0)
|
|
21440
|
+
problems2.push(
|
|
21441
|
+
`Cannot write ${allQuotes.join(", ")} to a dotenv file: their values hold ', " and \` together.`
|
|
21442
|
+
);
|
|
21443
|
+
if (other.length > 0)
|
|
21444
|
+
problems2.push(
|
|
21445
|
+
`Cannot write ${other.join(", ")} to a dotenv file: dotenv would not read their values back unchanged.`
|
|
21446
|
+
);
|
|
21447
|
+
if (problems2.length > 0)
|
|
21448
|
+
throw new NaroError(
|
|
21449
|
+
"VALIDATION_FAILED",
|
|
21450
|
+
`${problems2.join(" ")} ${UNWRITABLE_HINT}`,
|
|
21451
|
+
{ details: { keys: [...allQuotes, ...other] } }
|
|
21452
|
+
);
|
|
21453
|
+
return `${lines.join("\n")}
|
|
21198
21454
|
`;
|
|
21199
21455
|
}
|
|
21200
21456
|
|
|
21201
21457
|
// ../../packages/sdk/src/env-scan.ts
|
|
21202
21458
|
import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
|
|
21203
|
-
import { join as
|
|
21459
|
+
import { join as join21 } from "path";
|
|
21204
21460
|
var SOURCE_EXTENSIONS3 = /* @__PURE__ */ new Set([
|
|
21205
21461
|
".ts",
|
|
21206
21462
|
".tsx",
|
|
@@ -21255,7 +21511,7 @@ function collect(dir, out) {
|
|
|
21255
21511
|
for (const entry of entries) {
|
|
21256
21512
|
if (out.length >= MAX_FILES) return;
|
|
21257
21513
|
if (entry.isSymbolicLink()) continue;
|
|
21258
|
-
const path =
|
|
21514
|
+
const path = join21(dir, entry.name);
|
|
21259
21515
|
if (entry.isDirectory()) {
|
|
21260
21516
|
if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name))
|
|
21261
21517
|
continue;
|
|
@@ -21287,7 +21543,7 @@ function scanEnvReferences(dir) {
|
|
|
21287
21543
|
}
|
|
21288
21544
|
}
|
|
21289
21545
|
for (const name of EXAMPLE_FILES) {
|
|
21290
|
-
const path =
|
|
21546
|
+
const path = join21(dir, name);
|
|
21291
21547
|
if (!existsSync2(path)) continue;
|
|
21292
21548
|
let parsed;
|
|
21293
21549
|
try {
|
|
@@ -21305,11 +21561,11 @@ function scanEnvReferences(dir) {
|
|
|
21305
21561
|
|
|
21306
21562
|
// ../../packages/sdk/src/framework.ts
|
|
21307
21563
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
21308
|
-
import { join as
|
|
21564
|
+
import { join as join22 } from "path";
|
|
21309
21565
|
function readPackageJson2(dir) {
|
|
21310
21566
|
try {
|
|
21311
21567
|
return JSON.parse(
|
|
21312
|
-
readFileSync4(
|
|
21568
|
+
readFileSync4(join22(dir, "package.json"), "utf8")
|
|
21313
21569
|
);
|
|
21314
21570
|
} catch {
|
|
21315
21571
|
return null;
|
|
@@ -21320,7 +21576,7 @@ function hasDependency(pkg, name) {
|
|
|
21320
21576
|
}
|
|
21321
21577
|
function hasConfig(dir, base2, evidence) {
|
|
21322
21578
|
for (const ext of ["js", "mjs", "cjs", "ts", "mts"]) {
|
|
21323
|
-
if (existsSync3(
|
|
21579
|
+
if (existsSync3(join22(dir, `${base2}.${ext}`))) {
|
|
21324
21580
|
evidence.push(`${base2}.${ext}`);
|
|
21325
21581
|
return true;
|
|
21326
21582
|
}
|
|
@@ -21428,8 +21684,8 @@ function detectFramework2(dir) {
|
|
|
21428
21684
|
if (!byDependency && !byConfig) continue;
|
|
21429
21685
|
if (byDependency) evidence.push(`package.json: ${detector.dependency}`);
|
|
21430
21686
|
if (detector.framework === "nextjs") {
|
|
21431
|
-
if (existsSync3(
|
|
21432
|
-
if (existsSync3(
|
|
21687
|
+
if (existsSync3(join22(dir, "app"))) evidence.push("app/");
|
|
21688
|
+
if (existsSync3(join22(dir, "pages"))) evidence.push("pages/");
|
|
21433
21689
|
}
|
|
21434
21690
|
return {
|
|
21435
21691
|
framework: detector.framework,
|
|
@@ -21440,7 +21696,7 @@ function detectFramework2(dir) {
|
|
|
21440
21696
|
deployable: true
|
|
21441
21697
|
};
|
|
21442
21698
|
}
|
|
21443
|
-
if (!pkg && existsSync3(
|
|
21699
|
+
if (!pkg && existsSync3(join22(dir, "index.html"))) {
|
|
21444
21700
|
evidence.push("index.html (no package.json)");
|
|
21445
21701
|
return {
|
|
21446
21702
|
framework: "static",
|
|
@@ -21461,8 +21717,26 @@ function detectFramework2(dir) {
|
|
|
21461
21717
|
};
|
|
21462
21718
|
}
|
|
21463
21719
|
|
|
21720
|
+
// ../../packages/data-core/src/unicode-controls.ts
|
|
21721
|
+
function hasControlCharacter(text2) {
|
|
21722
|
+
for (let i = 0; i < text2.length; i += 1) {
|
|
21723
|
+
const code = text2.charCodeAt(i);
|
|
21724
|
+
if (code <= 31 || code >= 127 && code <= 159) return true;
|
|
21725
|
+
}
|
|
21726
|
+
return false;
|
|
21727
|
+
}
|
|
21728
|
+
function hasBidiControl(text2) {
|
|
21729
|
+
for (let i = 0; i < text2.length; i += 1) {
|
|
21730
|
+
const code = text2.charCodeAt(i);
|
|
21731
|
+
if (code === 1564 || code === 8206 || code === 8207 || code >= 8234 && code <= 8238 || code >= 8294 && code <= 8297)
|
|
21732
|
+
return true;
|
|
21733
|
+
}
|
|
21734
|
+
return false;
|
|
21735
|
+
}
|
|
21736
|
+
|
|
21464
21737
|
// ../../packages/data-core/src/storage-path.ts
|
|
21465
21738
|
var MAX_SINGLE_UPLOAD_BYTES = 52428800;
|
|
21739
|
+
var MAX_OBJECT_NAME_BYTES = 900;
|
|
21466
21740
|
var STORAGE_RESERVED_WORDS = [
|
|
21467
21741
|
"authenticated",
|
|
21468
21742
|
"copy",
|
|
@@ -21478,6 +21752,32 @@ var RESERVED = new Set(STORAGE_RESERVED_WORDS);
|
|
|
21478
21752
|
var ENCODER = new TextEncoder();
|
|
21479
21753
|
var LONE_SURROGATE2 = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
|
|
21480
21754
|
var LONE_SURROGATES = new RegExp(LONE_SURROGATE2.source, "g");
|
|
21755
|
+
function hasLoneSurrogate(text2) {
|
|
21756
|
+
return LONE_SURROGATE2.test(text2);
|
|
21757
|
+
}
|
|
21758
|
+
function refuse(reason) {
|
|
21759
|
+
return { ok: false, reason };
|
|
21760
|
+
}
|
|
21761
|
+
function checkWellFormed(name) {
|
|
21762
|
+
if (name === "") return refuse("is empty");
|
|
21763
|
+
if (name.length > MAX_OBJECT_NAME_BYTES || ENCODER.encode(name).length > MAX_OBJECT_NAME_BYTES)
|
|
21764
|
+
return refuse(`is longer than ${String(MAX_OBJECT_NAME_BYTES)} bytes`);
|
|
21765
|
+
if (name.startsWith("/") || name.endsWith("/"))
|
|
21766
|
+
return refuse("starts or ends with /");
|
|
21767
|
+
if (name.includes("//")) return refuse("has an empty segment (//)");
|
|
21768
|
+
for (const segment of name.split("/"))
|
|
21769
|
+
if (segment === "." || segment === "..")
|
|
21770
|
+
return refuse("has a . or .. segment");
|
|
21771
|
+
if (hasControlCharacter(name)) return refuse("has a control character");
|
|
21772
|
+
if (name.includes("\\")) return refuse("has a backslash");
|
|
21773
|
+
if (hasBidiControl(name))
|
|
21774
|
+
return refuse("has a bidirectional control character");
|
|
21775
|
+
return { ok: true, name };
|
|
21776
|
+
}
|
|
21777
|
+
function checkObjectName(name) {
|
|
21778
|
+
if (hasLoneSurrogate(name)) return refuse("is not valid UTF-8");
|
|
21779
|
+
return checkWellFormed(name);
|
|
21780
|
+
}
|
|
21481
21781
|
function objectParent(name) {
|
|
21482
21782
|
const slash = name.lastIndexOf("/");
|
|
21483
21783
|
return slash < 0 ? "" : name.slice(0, slash);
|
|
@@ -21507,6 +21807,7 @@ var STORAGE_OBJECTS = "storage.objects";
|
|
|
21507
21807
|
var STORAGE_OBJECTS_TABLE = "_naro_storage_objects";
|
|
21508
21808
|
var STORAGE_OBJECTS_QUALIFIER = "objects";
|
|
21509
21809
|
var STORAGE_UNAVAILABLE_MESSAGE = "storage.objects is not available on this backend yet. Run `naro backend enable` to update it.";
|
|
21810
|
+
var STORAGE_UNAVAILABLE_REASON = `${STORAGE_OBJECTS} is not available on this backend yet`;
|
|
21510
21811
|
var STORAGE_BUCKETS_TABLE = "_naro_storage_buckets";
|
|
21511
21812
|
var STORAGE_POLICY_COLUMNS = [
|
|
21512
21813
|
"id",
|
|
@@ -21674,7 +21975,8 @@ var CATALOG = {
|
|
|
21674
21975
|
policy,
|
|
21675
21976
|
recursion,
|
|
21676
21977
|
values,
|
|
21677
|
-
bytes
|
|
21978
|
+
bytes,
|
|
21979
|
+
depth
|
|
21678
21980
|
}) => {
|
|
21679
21981
|
if (recursion)
|
|
21680
21982
|
return `Infinite recursion detected in policy for relation ${table}.`;
|
|
@@ -21682,6 +21984,8 @@ var CATALOG = {
|
|
|
21682
21984
|
return `The policies of ${table} bind more than ${values === true ? "50" : String(values)} values in one statement.`;
|
|
21683
21985
|
if (bytes)
|
|
21684
21986
|
return `The policies of ${table} write more than ${bytes === true ? "50000" : String(bytes)} bytes of SQL into one statement.`;
|
|
21987
|
+
if (depth)
|
|
21988
|
+
return `The policies of ${table} nest an expression more than 100 deep into one statement.`;
|
|
21685
21989
|
if (policy !== void 0)
|
|
21686
21990
|
return `Policy ${policy} on ${table} cannot be applied.`;
|
|
21687
21991
|
return condition ? `The ${action} policy of ${table} has a condition that cannot be applied.` : `The ${action} policy of ${table} is owner but has no valid owner column.`;
|
|
@@ -21739,7 +22043,7 @@ var CATALOG = {
|
|
|
21739
22043
|
},
|
|
21740
22044
|
OBJECT_TOO_LARGE: {
|
|
21741
22045
|
status: 413,
|
|
21742
|
-
message: (vars) => "single" in vars ? "Uploads over 50 MiB go in parts: use the multipart upload." : `The object is ${String(vars.size)} bytes; bucket ${storageName(vars.bucket)} takes objects up to ${String(vars.max)} bytes.`
|
|
22046
|
+
message: (vars) => "single" in vars ? "Uploads over 50 MiB go in parts: use the multipart upload." : "transfer" in vars ? "Objects over 5,363,466,240 bytes (5 GiB \u2212 5 MiB) cannot be copied or moved." : `The object is ${String(vars.size)} bytes; bucket ${storageName(vars.bucket)} takes objects up to ${String(vars.max)} bytes.`
|
|
21743
22047
|
},
|
|
21744
22048
|
MIME_TYPE_NOT_ALLOWED: {
|
|
21745
22049
|
status: 415,
|
|
@@ -21751,7 +22055,7 @@ var CATALOG = {
|
|
|
21751
22055
|
},
|
|
21752
22056
|
OBJECT_EXISTS: {
|
|
21753
22057
|
status: 409,
|
|
21754
|
-
message: ({ bucket, path }) => `Object ${storageName(path)} already exists in bucket ${storageName(bucket)}. Upload with upsert to replace it
|
|
22058
|
+
message: ({ bucket, path, move }) => `Object ${storageName(path)} already exists in bucket ${storageName(bucket)}. ${move === true ? "A move never replaces an object: remove it or choose another name." : "Upload with upsert to replace it."}`
|
|
21755
22059
|
},
|
|
21756
22060
|
OBJECT_BUSY: {
|
|
21757
22061
|
status: 409,
|
|
@@ -22429,6 +22733,15 @@ function resolvePolicyTable(snapshot, name) {
|
|
|
22429
22733
|
if (folded === REALTIME_MESSAGES) return REALTIME_MESSAGES_TABLE;
|
|
22430
22734
|
return resolveTable(snapshot, name);
|
|
22431
22735
|
}
|
|
22736
|
+
function resolveSubqueryTable(snapshot, name) {
|
|
22737
|
+
if (asciiLower(name) === STORAGE_OBJECTS)
|
|
22738
|
+
return snapshot.storageSubqueries === false ? void 0 : snapshot.storage;
|
|
22739
|
+
return resolveTable(snapshot, name);
|
|
22740
|
+
}
|
|
22741
|
+
function subqueryName(subquery) {
|
|
22742
|
+
if (subquery.alias !== null) return subquery.alias;
|
|
22743
|
+
return asciiLower(subquery.table) === STORAGE_OBJECTS ? STORAGE_OBJECTS_QUALIFIER : subquery.table;
|
|
22744
|
+
}
|
|
22432
22745
|
async function readTableNames(db) {
|
|
22433
22746
|
return tableNamesFromRows(rowsOf(await db.prepare(TABLE_NAMES_SQL).all()));
|
|
22434
22747
|
}
|
|
@@ -22470,6 +22783,14 @@ function utf8ByteLength(text2) {
|
|
|
22470
22783
|
function quoteIdentifier(name) {
|
|
22471
22784
|
return `"${name.replaceAll('"', '""')}"`;
|
|
22472
22785
|
}
|
|
22786
|
+
var SIDE_BY_SIDE = 8;
|
|
22787
|
+
function joinConditions(terms, op) {
|
|
22788
|
+
if (terms.length <= SIDE_BY_SIDE) return `(${terms.join(` ${op} `)})`;
|
|
22789
|
+
const groups = [];
|
|
22790
|
+
for (let at = 0; at < terms.length; at += SIDE_BY_SIDE)
|
|
22791
|
+
groups.push(joinConditions(terms.slice(at, at + SIDE_BY_SIDE), op));
|
|
22792
|
+
return joinConditions(groups, op);
|
|
22793
|
+
}
|
|
22473
22794
|
var Failure = class extends Error {
|
|
22474
22795
|
constructor(error51, reason) {
|
|
22475
22796
|
super(error51.code);
|
|
@@ -22653,6 +22974,12 @@ var VALUE_KINDS = /* @__PURE__ */ new Set([
|
|
|
22653
22974
|
function isValueExpr(expr) {
|
|
22654
22975
|
return VALUE_KINDS.has(expr.kind);
|
|
22655
22976
|
}
|
|
22977
|
+
function comparedValue(value) {
|
|
22978
|
+
let current = value;
|
|
22979
|
+
while (current.kind === "func" && current.name === "coalesce" && current.args.length === 1 && current.args[0] !== void 0)
|
|
22980
|
+
current = current.args[0];
|
|
22981
|
+
return current;
|
|
22982
|
+
}
|
|
22656
22983
|
function isConditionExpr(expr) {
|
|
22657
22984
|
if (!isValueExpr(expr)) return true;
|
|
22658
22985
|
if (expr.kind === "column") return true;
|
|
@@ -22704,7 +23031,6 @@ var SPECIAL_WORDS = /* @__PURE__ */ new Set([
|
|
|
22704
23031
|
"auth"
|
|
22705
23032
|
]);
|
|
22706
23033
|
var TEXT_TYPES = /* @__PURE__ */ new Set(["text", "varchar", "uuid"]);
|
|
22707
|
-
var STORAGE_SUBQUERY_PROBLEM = "A policy cannot read storage.objects in a subquery yet";
|
|
22708
23034
|
var FOLDERNAME_INDEX_PROBLEM = `storage.foldername(name) is available only as (storage.foldername(name))[n] with n from 1 to ${String(MAX_FOLDERNAME_INDEX)}`;
|
|
22709
23035
|
function storageHelperColumnProblem(fn) {
|
|
22710
23036
|
return `storage.${fn}() takes the name column of storage.objects`;
|
|
@@ -23155,8 +23481,12 @@ function atRealtimeMessages(stream) {
|
|
|
23155
23481
|
}
|
|
23156
23482
|
function readTableName(stream) {
|
|
23157
23483
|
const first = stream.peek();
|
|
23158
|
-
if (atStorageObjects(stream))
|
|
23159
|
-
|
|
23484
|
+
if (atStorageObjects(stream)) {
|
|
23485
|
+
stream.next();
|
|
23486
|
+
stream.next();
|
|
23487
|
+
stream.next();
|
|
23488
|
+
return STORAGE_OBJECTS;
|
|
23489
|
+
}
|
|
23160
23490
|
if (atRealtimeMessages(stream))
|
|
23161
23491
|
throw new PolicySyntaxError(REALTIME_SUBQUERY_PROBLEM, first.position);
|
|
23162
23492
|
let token = first;
|
|
@@ -23183,12 +23513,6 @@ function readTableName(stream) {
|
|
|
23183
23513
|
return name;
|
|
23184
23514
|
}
|
|
23185
23515
|
function readPolicyTarget(stream) {
|
|
23186
|
-
if (atStorageObjects(stream)) {
|
|
23187
|
-
stream.next();
|
|
23188
|
-
stream.next();
|
|
23189
|
-
stream.next();
|
|
23190
|
-
return STORAGE_OBJECTS;
|
|
23191
|
-
}
|
|
23192
23516
|
if (atRealtimeMessages(stream)) {
|
|
23193
23517
|
stream.next();
|
|
23194
23518
|
stream.next();
|
|
@@ -23608,9 +23932,10 @@ function printValue(expr) {
|
|
|
23608
23932
|
}
|
|
23609
23933
|
function printSubquery(subquery) {
|
|
23610
23934
|
const select = subquery.select === "*" ? "*" : printValue(subquery.select);
|
|
23935
|
+
const table = asciiLower(subquery.table) === STORAGE_OBJECTS ? STORAGE_OBJECTS : quoteIdent(subquery.table);
|
|
23611
23936
|
const alias = subquery.alias === null ? "" : ` ${quoteIdent(subquery.alias)}`;
|
|
23612
23937
|
const where = subquery.where === null ? "" : ` WHERE ${printExpr(subquery.where)}`;
|
|
23613
|
-
return `SELECT ${select} FROM ${
|
|
23938
|
+
return `SELECT ${select} FROM ${table}${alias}${where}`;
|
|
23614
23939
|
}
|
|
23615
23940
|
function printElement(element) {
|
|
23616
23941
|
if (typeof element === "object") return printValue(element);
|
|
@@ -23963,6 +24288,9 @@ var FILTER_OPERATORS = [
|
|
|
23963
24288
|
"containedBy",
|
|
23964
24289
|
"overlaps"
|
|
23965
24290
|
];
|
|
24291
|
+
function leastFilters(firstColumn) {
|
|
24292
|
+
return firstColumn === void 0 ? [] : [{ operator: "is", column: firstColumn, value: null }];
|
|
24293
|
+
}
|
|
23966
24294
|
function isAuthUid(value) {
|
|
23967
24295
|
return isPlainObject2(value) && Object.keys(value).length === 1 && Object.hasOwn(value, "auth") && value.auth === "uid";
|
|
23968
24296
|
}
|
|
@@ -24576,12 +24904,21 @@ function storedSource(table, names, alias) {
|
|
|
24576
24904
|
function tableLabel(table) {
|
|
24577
24905
|
return table.name === STORAGE_OBJECTS_TABLE ? STORAGE_OBJECTS : table.name;
|
|
24578
24906
|
}
|
|
24907
|
+
function storageReadySql(alias) {
|
|
24908
|
+
return `(${quoteIdentifier(alias)}.${quoteIdentifier("status")} = 'ready')`;
|
|
24909
|
+
}
|
|
24910
|
+
function ownNames(table) {
|
|
24911
|
+
return table.name === STORAGE_OBJECTS_TABLE ? [STORAGE_OBJECTS_QUALIFIER] : [asciiLower(table.name)];
|
|
24912
|
+
}
|
|
24579
24913
|
function missingColumnReason(name, tables) {
|
|
24580
24914
|
return `column ${name} does not exist on ${tables.join(" or ")}`;
|
|
24581
24915
|
}
|
|
24582
24916
|
function missingRelationReason(table) {
|
|
24583
24917
|
return `relation ${table} does not exist (a system table cannot be read either)`;
|
|
24584
24918
|
}
|
|
24919
|
+
function unresolvedSubqueryReason(table) {
|
|
24920
|
+
return asciiLower(table) === STORAGE_OBJECTS ? STORAGE_UNAVAILABLE_REASON : missingRelationReason(table);
|
|
24921
|
+
}
|
|
24585
24922
|
function resolveColumn(env, ref) {
|
|
24586
24923
|
const name = asciiLower(ref.name);
|
|
24587
24924
|
if (ref.qualifier !== null) {
|
|
@@ -24691,10 +25028,10 @@ function storageHelperSql(fn, index, operand) {
|
|
|
24691
25028
|
}
|
|
24692
25029
|
function storageCallSql(env, expr) {
|
|
24693
25030
|
const problem = storageHelperColumnProblem(expr.fn);
|
|
24694
|
-
if (
|
|
25031
|
+
if (!env.scopes.some((scope) => scope.table.name === STORAGE_OBJECTS_TABLE))
|
|
24695
25032
|
return blamed(env, problem);
|
|
24696
25033
|
const { source, column: column2 } = resolveColumn(env, expr.column);
|
|
24697
|
-
if (source
|
|
25034
|
+
if (source.table.name !== STORAGE_OBJECTS_TABLE || column2.name !== "name")
|
|
24698
25035
|
return blamed(env, problem);
|
|
24699
25036
|
const index = expr.fn === "foldername" ? expr.index : null;
|
|
24700
25037
|
if (expr.fn === "foldername" ? index === null || !Number.isInteger(index) || index < 1 || index > MAX_FOLDERNAME_INDEX : expr.index !== null)
|
|
@@ -24708,7 +25045,8 @@ function storageCallSql(env, expr) {
|
|
|
24708
25045
|
})
|
|
24709
25046
|
);
|
|
24710
25047
|
}
|
|
24711
|
-
function operandSide(env,
|
|
25048
|
+
function operandSide(env, value) {
|
|
25049
|
+
const expr = comparedValue(value);
|
|
24712
25050
|
if (expr.kind === "cast") return operandSide(env, expr.operand);
|
|
24713
25051
|
if (expr.kind !== "column" && expr.kind !== "realtime")
|
|
24714
25052
|
return {
|
|
@@ -24769,7 +25107,10 @@ function conditionSql(env, expr) {
|
|
|
24769
25107
|
switch (expr.kind) {
|
|
24770
25108
|
case "and":
|
|
24771
25109
|
case "or":
|
|
24772
|
-
return
|
|
25110
|
+
return joinConditions(
|
|
25111
|
+
expr.args.map((arg) => conditionSql(env, arg)),
|
|
25112
|
+
expr.kind === "and" ? "AND" : "OR"
|
|
25113
|
+
);
|
|
24773
25114
|
case "not":
|
|
24774
25115
|
return `(NOT ${conditionSql(env, expr.arg)})`;
|
|
24775
25116
|
default:
|
|
@@ -24852,8 +25193,8 @@ function conditionSql(env, expr) {
|
|
|
24852
25193
|
}
|
|
24853
25194
|
}
|
|
24854
25195
|
function subquerySql(env, subquery, select) {
|
|
24855
|
-
const table =
|
|
24856
|
-
if (!table) return blamed(env,
|
|
25196
|
+
const table = resolveSubqueryTable(env.snapshot, subquery.table);
|
|
25197
|
+
if (!table) return blamed(env, unresolvedSubqueryReason(subquery.table));
|
|
24857
25198
|
const { state } = env;
|
|
24858
25199
|
if (++state.subqueries > MAX_STATEMENT_SUBQUERIES)
|
|
24859
25200
|
return blamed(env, STATEMENT_SUBQUERIES_REASON);
|
|
@@ -24867,7 +25208,7 @@ function subquerySql(env, subquery, select) {
|
|
|
24867
25208
|
const alias = `_naro_s${String(++state.aliases)}`;
|
|
24868
25209
|
const source = storedSource(
|
|
24869
25210
|
table,
|
|
24870
|
-
[asciiLower(subquery
|
|
25211
|
+
[asciiLower(subqueryName(subquery))],
|
|
24871
25212
|
alias
|
|
24872
25213
|
);
|
|
24873
25214
|
const inner = { ...env, scopes: [...env.scopes, source] };
|
|
@@ -24879,11 +25220,12 @@ function subquerySql(env, subquery, select) {
|
|
|
24879
25220
|
selected = valueSql(inner, subquery.select);
|
|
24880
25221
|
if (env.check) item = operandSide(inner, subquery.select);
|
|
24881
25222
|
}
|
|
24882
|
-
const own2 = storedSource(table,
|
|
25223
|
+
const own2 = storedSource(table, ownNames(table), alias);
|
|
24883
25224
|
const rls = inlinedPolicies(env, table, own2);
|
|
25225
|
+
const ready = table.name === STORAGE_OBJECTS_TABLE ? `${storageReadySql(alias)} AND ` : "";
|
|
24884
25226
|
const where = subquery.where === null ? "" : ` AND ${conditionSql(inner, subquery.where)}`;
|
|
24885
25227
|
return {
|
|
24886
|
-
sql: `SELECT ${selected} FROM ${quoteIdentifier(table.name)} AS ${quoteIdentifier(alias)} WHERE ${rls}${where}`,
|
|
25228
|
+
sql: `SELECT ${selected} FROM ${quoteIdentifier(table.name)} AS ${quoteIdentifier(alias)} WHERE ${ready}${rls}${where}`,
|
|
24887
25229
|
item
|
|
24888
25230
|
};
|
|
24889
25231
|
} finally {
|
|
@@ -24936,7 +25278,10 @@ function groupTerms(env, sources, action, permissive, restrictive, pick2) {
|
|
|
24936
25278
|
terms.push(term);
|
|
24937
25279
|
}
|
|
24938
25280
|
const group = terms.length === 0 ? { sql: "0", params: [] } : terms.length === 1 ? terms[0] : {
|
|
24939
|
-
sql:
|
|
25281
|
+
sql: joinConditions(
|
|
25282
|
+
terms.map((term) => term.sql),
|
|
25283
|
+
"OR"
|
|
25284
|
+
),
|
|
24940
25285
|
params: terms.flatMap((term) => term.params)
|
|
24941
25286
|
};
|
|
24942
25287
|
const restricted = restrictive.flatMap((policy) => {
|
|
@@ -24946,9 +25291,10 @@ function groupTerms(env, sources, action, permissive, restrictive, pick2) {
|
|
|
24946
25291
|
return { permissive: group, restrictive: restricted };
|
|
24947
25292
|
}
|
|
24948
25293
|
function inlinedPolicies(env, table, source) {
|
|
25294
|
+
const label2 = tableLabel(table);
|
|
24949
25295
|
const found = applicablePolicies(
|
|
24950
25296
|
env.req.policies,
|
|
24951
|
-
|
|
25297
|
+
label2,
|
|
24952
25298
|
"select",
|
|
24953
25299
|
env.req.role
|
|
24954
25300
|
);
|
|
@@ -24960,7 +25306,7 @@ function inlinedPolicies(env, table, source) {
|
|
|
24960
25306
|
if (found.permissive.length === 0) return "0";
|
|
24961
25307
|
return withTableActive(
|
|
24962
25308
|
env.state,
|
|
24963
|
-
|
|
25309
|
+
label2,
|
|
24964
25310
|
[...found.permissive, ...found.restrictive],
|
|
24965
25311
|
() => {
|
|
24966
25312
|
const inner = { ...env, check: null, params: [] };
|
|
@@ -24979,7 +25325,10 @@ function inlinedPolicies(env, table, source) {
|
|
|
24979
25325
|
for (const part of parts) env.params.push(...part.params);
|
|
24980
25326
|
const [only] = parts;
|
|
24981
25327
|
if (parts.length === 1 && only) return only.sql;
|
|
24982
|
-
return
|
|
25328
|
+
return joinConditions(
|
|
25329
|
+
parts.map((part) => part.sql),
|
|
25330
|
+
"AND"
|
|
25331
|
+
);
|
|
24983
25332
|
}
|
|
24984
25333
|
);
|
|
24985
25334
|
}
|
|
@@ -25074,11 +25423,8 @@ function scopedValues(operand, scopes) {
|
|
|
25074
25423
|
return operandValues(operand).map((value) => ({ value, scopes }));
|
|
25075
25424
|
}
|
|
25076
25425
|
function subqueryScopes(snapshot, subquery, scopes) {
|
|
25077
|
-
const inner =
|
|
25078
|
-
return inner ? [
|
|
25079
|
-
...scopes,
|
|
25080
|
-
{ names: [asciiLower(subquery.alias ?? inner.name)], table: inner }
|
|
25081
|
-
] : void 0;
|
|
25426
|
+
const inner = resolveSubqueryTable(snapshot, subquery.table);
|
|
25427
|
+
return inner ? [...scopes, { names: [asciiLower(subqueryName(subquery))], table: inner }] : void 0;
|
|
25082
25428
|
}
|
|
25083
25429
|
function columnsReadOn(snapshot, table, expr, written) {
|
|
25084
25430
|
const found = [];
|
|
@@ -25413,7 +25759,7 @@ function nodeSql(table, node, params, path) {
|
|
|
25413
25759
|
const terms = children.map(
|
|
25414
25760
|
(child, index) => nodeSql(table, child, params, `${path}.${key}[${index}]`)
|
|
25415
25761
|
);
|
|
25416
|
-
return
|
|
25762
|
+
return joinConditions(terms, key === "or" ? "OR" : "AND");
|
|
25417
25763
|
}
|
|
25418
25764
|
function whereClause(table, filters, policy, params) {
|
|
25419
25765
|
const parts = [];
|
|
@@ -25424,7 +25770,7 @@ function whereClause(table, filters, policy, params) {
|
|
|
25424
25770
|
const client = filters.map(
|
|
25425
25771
|
(node, index) => nodeSql(table, node, params, `filters[${index}]`)
|
|
25426
25772
|
);
|
|
25427
|
-
if (client.length > 0) parts.push(
|
|
25773
|
+
if (client.length > 0) parts.push(joinConditions(client, "AND"));
|
|
25428
25774
|
return parts.length === 0 ? "" : ` WHERE ${parts.join(" AND ")}`;
|
|
25429
25775
|
}
|
|
25430
25776
|
function orderClause(table, order) {
|
|
@@ -25626,7 +25972,7 @@ var Rls = class {
|
|
|
25626
25972
|
const found = this.found(command);
|
|
25627
25973
|
return [...found.permissive, ...found.restrictive];
|
|
25628
25974
|
});
|
|
25629
|
-
const key = asciiLower(
|
|
25975
|
+
const key = asciiLower(this.policyTable);
|
|
25630
25976
|
if (hasSubqueries(all) && !this.state.active.includes(key))
|
|
25631
25977
|
this.state.active.push(key);
|
|
25632
25978
|
for (const command of commands) {
|
|
@@ -26432,138 +26778,916 @@ function compile(query, snapshot, decision, context, options = {}) {
|
|
|
26432
26778
|
}
|
|
26433
26779
|
}
|
|
26434
26780
|
|
|
26435
|
-
// ../../packages/data-core/src/
|
|
26436
|
-
var
|
|
26437
|
-
|
|
26438
|
-
|
|
26439
|
-
|
|
26440
|
-
|
|
26441
|
-
const column2 = row.column_name;
|
|
26442
|
-
if (typeof table !== "string" || typeof column2 !== "string") continue;
|
|
26443
|
-
if (Number(row.partial) !== 0) continue;
|
|
26444
|
-
const key = asciiLower(table);
|
|
26445
|
-
const columns = tables.get(key) ?? /* @__PURE__ */ new Map();
|
|
26446
|
-
tables.set(key, columns);
|
|
26447
|
-
const collations = columns.get(asciiLower(column2)) ?? [];
|
|
26448
|
-
collations.push(
|
|
26449
|
-
typeof row.collation === "string" ? row.collation.toUpperCase() : "BINARY"
|
|
26450
|
-
);
|
|
26451
|
-
columns.set(asciiLower(column2), collations);
|
|
26452
|
-
}
|
|
26453
|
-
return tables;
|
|
26781
|
+
// ../../packages/data-core/src/sql-depth.ts
|
|
26782
|
+
var D1_EXPRESSION_DEPTH = 100;
|
|
26783
|
+
var Unreadable = class extends Error {
|
|
26784
|
+
};
|
|
26785
|
+
function isWordStart(code) {
|
|
26786
|
+
return code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95 || code >= 128;
|
|
26454
26787
|
}
|
|
26455
|
-
function
|
|
26456
|
-
|
|
26457
|
-
for (let index = scopes.length - 1; index >= 0; index--) {
|
|
26458
|
-
const scope = scopes[index];
|
|
26459
|
-
if (qualifier !== null && !scope.names.includes(asciiLower(qualifier)))
|
|
26460
|
-
continue;
|
|
26461
|
-
const found = scope.table.byName.get(column2);
|
|
26462
|
-
if (found) return { column: found, scope: index };
|
|
26463
|
-
if (qualifier !== null) return void 0;
|
|
26464
|
-
}
|
|
26465
|
-
return void 0;
|
|
26788
|
+
function isDigit2(code) {
|
|
26789
|
+
return code >= 48 && code <= 57;
|
|
26466
26790
|
}
|
|
26467
|
-
function
|
|
26468
|
-
|
|
26469
|
-
if (root?.table.name !== REALTIME_MESSAGES) return void 0;
|
|
26470
|
-
const column2 = root.table.byName.get("topic");
|
|
26471
|
-
return column2 ? { column: column2, scope: 0 } : void 0;
|
|
26791
|
+
function isWordPart(code) {
|
|
26792
|
+
return isWordStart(code) || isDigit2(code) || code === 36;
|
|
26472
26793
|
}
|
|
26473
|
-
function
|
|
26474
|
-
|
|
26475
|
-
case "column": {
|
|
26476
|
-
const found = resolve8(scopes, value.qualifier, value.name);
|
|
26477
|
-
return found ? [found.scope] : [];
|
|
26478
|
-
}
|
|
26479
|
-
case "func":
|
|
26480
|
-
return value.args.flatMap((arg) => valueScopes(scopes, arg));
|
|
26481
|
-
case "cast":
|
|
26482
|
-
return valueScopes(scopes, value.operand);
|
|
26483
|
-
case "storage":
|
|
26484
|
-
return valueScopes(scopes, value.column);
|
|
26485
|
-
case "realtime": {
|
|
26486
|
-
const found = realtimeTopic2(scopes);
|
|
26487
|
-
return found ? [found.scope] : [];
|
|
26488
|
-
}
|
|
26489
|
-
default:
|
|
26490
|
-
return [];
|
|
26491
|
-
}
|
|
26794
|
+
function isSpace3(code) {
|
|
26795
|
+
return code === 32 || code >= 9 && code <= 13;
|
|
26492
26796
|
}
|
|
26493
|
-
|
|
26494
|
-
|
|
26495
|
-
|
|
26496
|
-
|
|
26497
|
-
|
|
26498
|
-
|
|
26499
|
-
|
|
26500
|
-
|
|
26501
|
-
|
|
26502
|
-
|
|
26503
|
-
|
|
26504
|
-
|
|
26797
|
+
var OPERATORS_2 = /* @__PURE__ */ new Set([
|
|
26798
|
+
"->",
|
|
26799
|
+
"||",
|
|
26800
|
+
"<=",
|
|
26801
|
+
">=",
|
|
26802
|
+
"<>",
|
|
26803
|
+
"!=",
|
|
26804
|
+
"==",
|
|
26805
|
+
"<<",
|
|
26806
|
+
">>"
|
|
26807
|
+
]);
|
|
26808
|
+
var OPERATORS_1 = "=<>+-*/%&|~(),.;";
|
|
26809
|
+
var NUMBER2 = /^(?:0[xX][0-9A-Fa-f_]+|[0-9_]*\.?[0-9_]*(?:[eE][+-]?[0-9_]+)?)/;
|
|
26810
|
+
function tokenize3(sql) {
|
|
26811
|
+
const tokens = [];
|
|
26812
|
+
const length = sql.length;
|
|
26813
|
+
let at = 0;
|
|
26814
|
+
const closing = (close) => {
|
|
26815
|
+
let from = at + 1;
|
|
26816
|
+
for (; ; ) {
|
|
26817
|
+
const found = sql.indexOf(close, from);
|
|
26818
|
+
if (found < 0) throw new Unreadable("an unclosed quote");
|
|
26819
|
+
if (close === "]" || sql[found + 1] !== close) return found;
|
|
26820
|
+
from = found + 2;
|
|
26821
|
+
}
|
|
26822
|
+
};
|
|
26823
|
+
while (at < length) {
|
|
26824
|
+
const code = sql.charCodeAt(at);
|
|
26825
|
+
const next = sql.charCodeAt(at + 1);
|
|
26826
|
+
if (isSpace3(code)) {
|
|
26827
|
+
at++;
|
|
26828
|
+
} else if (code === 45 && next === 45) {
|
|
26829
|
+
const end = sql.indexOf("\n", at);
|
|
26830
|
+
at = end < 0 ? length : end + 1;
|
|
26831
|
+
} else if (code === 47 && next === 42) {
|
|
26832
|
+
const end = sql.indexOf("*/", at + 2);
|
|
26833
|
+
at = end < 0 ? length : end + 2;
|
|
26834
|
+
} else if (code === 39) {
|
|
26835
|
+
at = closing("'") + 1;
|
|
26836
|
+
tokens.push({ kind: "literal" });
|
|
26837
|
+
} else if (code === 34 || code === 96 || code === 91) {
|
|
26838
|
+
at = closing(code === 91 ? "]" : String.fromCharCode(code)) + 1;
|
|
26839
|
+
tokens.push({ kind: "name" });
|
|
26840
|
+
} else if ((code === 88 || code === 120) && next === 39) {
|
|
26841
|
+
at++;
|
|
26842
|
+
at = closing("'") + 1;
|
|
26843
|
+
tokens.push({ kind: "literal" });
|
|
26844
|
+
} else if (isDigit2(code) || code === 46 && isDigit2(next)) {
|
|
26845
|
+
const text2 = NUMBER2.exec(sql.slice(at, at + 64))?.[0] || sql[at] || "";
|
|
26846
|
+
tokens.push(
|
|
26847
|
+
/^[0-9]+$/.test(text2) ? { kind: "literal", integer: Number(text2) } : { kind: "literal" }
|
|
26505
26848
|
);
|
|
26506
|
-
|
|
26507
|
-
|
|
26508
|
-
|
|
26509
|
-
|
|
26510
|
-
|
|
26511
|
-
|
|
26512
|
-
|
|
26513
|
-
|
|
26514
|
-
|
|
26515
|
-
|
|
26516
|
-
|
|
26517
|
-
|
|
26518
|
-
|
|
26519
|
-
|
|
26520
|
-
|
|
26521
|
-
|
|
26522
|
-
|
|
26523
|
-
|
|
26524
|
-
|
|
26525
|
-
|
|
26526
|
-
|
|
26527
|
-
}
|
|
26528
|
-
|
|
26529
|
-
|
|
26530
|
-
|
|
26531
|
-
|
|
26532
|
-
|
|
26533
|
-
|
|
26534
|
-
|
|
26535
|
-
|
|
26536
|
-
|
|
26537
|
-
|
|
26538
|
-
|
|
26539
|
-
|
|
26540
|
-
|
|
26541
|
-
|
|
26542
|
-
|
|
26543
|
-
|
|
26544
|
-
|
|
26545
|
-
|
|
26546
|
-
|
|
26547
|
-
|
|
26548
|
-
|
|
26549
|
-
|
|
26550
|
-
|
|
26551
|
-
|
|
26552
|
-
|
|
26553
|
-
|
|
26554
|
-
|
|
26555
|
-
|
|
26556
|
-
|
|
26557
|
-
|
|
26849
|
+
at += text2.length;
|
|
26850
|
+
} else if (code === 63 || code === 58 || code === 64 || code === 36) {
|
|
26851
|
+
at++;
|
|
26852
|
+
while (at < length && isWordPart(sql.charCodeAt(at))) at++;
|
|
26853
|
+
tokens.push({ kind: "param" });
|
|
26854
|
+
} else if (isWordStart(code)) {
|
|
26855
|
+
const start = at;
|
|
26856
|
+
while (at < length && isWordPart(sql.charCodeAt(at))) at++;
|
|
26857
|
+
const text2 = sql.slice(start, at);
|
|
26858
|
+
tokens.push({ kind: "word", text: text2, upper: text2.toUpperCase() });
|
|
26859
|
+
} else if (sql.startsWith("->>", at)) {
|
|
26860
|
+
tokens.push({ kind: "op", text: "->>" });
|
|
26861
|
+
at += 3;
|
|
26862
|
+
} else if (OPERATORS_2.has(sql.slice(at, at + 2))) {
|
|
26863
|
+
tokens.push({ kind: "op", text: sql.slice(at, at + 2) });
|
|
26864
|
+
at += 2;
|
|
26865
|
+
} else if (OPERATORS_1.includes(sql[at] ?? "")) {
|
|
26866
|
+
tokens.push({ kind: "op", text: sql[at] ?? "" });
|
|
26867
|
+
at++;
|
|
26868
|
+
} else throw new Unreadable(`an unexpected ${sql[at] ?? ""}`);
|
|
26869
|
+
}
|
|
26870
|
+
tokens.push({ kind: "end" });
|
|
26871
|
+
return tokens;
|
|
26872
|
+
}
|
|
26873
|
+
var CLAUSE_WORDS2 = /* @__PURE__ */ new Set([
|
|
26874
|
+
"AS",
|
|
26875
|
+
"CROSS",
|
|
26876
|
+
"DEFAULT",
|
|
26877
|
+
"DO",
|
|
26878
|
+
"EXCEPT",
|
|
26879
|
+
"FROM",
|
|
26880
|
+
"FULL",
|
|
26881
|
+
"GROUP",
|
|
26882
|
+
"HAVING",
|
|
26883
|
+
"INDEXED",
|
|
26884
|
+
"INNER",
|
|
26885
|
+
"INTERSECT",
|
|
26886
|
+
"JOIN",
|
|
26887
|
+
"LEFT",
|
|
26888
|
+
"LIMIT",
|
|
26889
|
+
"NATURAL",
|
|
26890
|
+
"NOT",
|
|
26891
|
+
"OFFSET",
|
|
26892
|
+
"ON",
|
|
26893
|
+
"ORDER",
|
|
26894
|
+
"OUTER",
|
|
26895
|
+
"RETURNING",
|
|
26896
|
+
"RIGHT",
|
|
26897
|
+
"SET",
|
|
26898
|
+
"UNION",
|
|
26899
|
+
"USING",
|
|
26900
|
+
"VALUES",
|
|
26901
|
+
"WHERE",
|
|
26902
|
+
"WINDOW"
|
|
26903
|
+
]);
|
|
26904
|
+
var NOT_CONSTANT = /* @__PURE__ */ new Set([
|
|
26905
|
+
"avg",
|
|
26906
|
+
"changes",
|
|
26907
|
+
"count",
|
|
26908
|
+
"group_concat",
|
|
26909
|
+
"json_group_array",
|
|
26910
|
+
"json_group_object",
|
|
26911
|
+
"jsonb_group_array",
|
|
26912
|
+
"jsonb_group_object",
|
|
26913
|
+
"last_insert_rowid",
|
|
26914
|
+
"random",
|
|
26915
|
+
"randomblob",
|
|
26916
|
+
"sqlite_offset",
|
|
26917
|
+
"string_agg",
|
|
26918
|
+
"sum",
|
|
26919
|
+
"total",
|
|
26920
|
+
"total_changes"
|
|
26921
|
+
]);
|
|
26922
|
+
var OR = 1;
|
|
26923
|
+
var AND = 2;
|
|
26924
|
+
var NOT = 3;
|
|
26925
|
+
var COMPARE = 4;
|
|
26926
|
+
var ORDER = 5;
|
|
26927
|
+
var ESCAPE = 6;
|
|
26928
|
+
var BITS = 7;
|
|
26929
|
+
var SUM = 8;
|
|
26930
|
+
var PRODUCT = 9;
|
|
26931
|
+
var CONCAT = 10;
|
|
26932
|
+
var COLLATE = 11;
|
|
26933
|
+
var UNARY = 12;
|
|
26934
|
+
var BINARY = {
|
|
26935
|
+
"=": COMPARE,
|
|
26936
|
+
"==": COMPARE,
|
|
26937
|
+
"!=": COMPARE,
|
|
26938
|
+
"<>": COMPARE,
|
|
26939
|
+
"<": ORDER,
|
|
26940
|
+
"<=": ORDER,
|
|
26941
|
+
">": ORDER,
|
|
26942
|
+
">=": ORDER,
|
|
26943
|
+
"&": BITS,
|
|
26944
|
+
"|": BITS,
|
|
26945
|
+
"<<": BITS,
|
|
26946
|
+
">>": BITS,
|
|
26947
|
+
"+": SUM,
|
|
26948
|
+
"-": SUM,
|
|
26949
|
+
"*": PRODUCT,
|
|
26950
|
+
"/": PRODUCT,
|
|
26951
|
+
"%": PRODUCT,
|
|
26952
|
+
"||": CONCAT,
|
|
26953
|
+
"->": CONCAT,
|
|
26954
|
+
"->>": CONCAT
|
|
26955
|
+
};
|
|
26956
|
+
var LIKE_WORDS = /* @__PURE__ */ new Set(["LIKE", "GLOB", "REGEXP", "MATCH"]);
|
|
26957
|
+
var USABLE_OPERATORS = /* @__PURE__ */ new Set(["=", "==", "<", "<=", ">", ">="]);
|
|
26958
|
+
var Reader = class {
|
|
26959
|
+
constructor(tokens, planner) {
|
|
26960
|
+
this.tokens = tokens;
|
|
26961
|
+
this.planner = planner;
|
|
26962
|
+
}
|
|
26963
|
+
at = 0;
|
|
26964
|
+
/** The deepest node built, kept or not: each is checked as it is built. */
|
|
26965
|
+
deepest = 0;
|
|
26966
|
+
peek(offset = 0) {
|
|
26967
|
+
return this.tokens[this.at + offset] ?? { kind: "end" };
|
|
26968
|
+
}
|
|
26969
|
+
isWord(word, offset = 0) {
|
|
26970
|
+
const token = this.peek(offset);
|
|
26971
|
+
return token.kind === "word" && token.upper === word;
|
|
26972
|
+
}
|
|
26973
|
+
isOp(op, offset = 0) {
|
|
26974
|
+
const token = this.peek(offset);
|
|
26975
|
+
return token.kind === "op" && token.text === op;
|
|
26976
|
+
}
|
|
26977
|
+
isName(offset = 0) {
|
|
26978
|
+
const kind = this.peek(offset).kind;
|
|
26979
|
+
return kind === "word" || kind === "name";
|
|
26980
|
+
}
|
|
26981
|
+
takeWord(word) {
|
|
26982
|
+
if (!this.isWord(word)) return false;
|
|
26983
|
+
this.at++;
|
|
26984
|
+
return true;
|
|
26985
|
+
}
|
|
26986
|
+
takeOp(op) {
|
|
26987
|
+
if (!this.isOp(op)) return false;
|
|
26988
|
+
this.at++;
|
|
26989
|
+
return true;
|
|
26990
|
+
}
|
|
26991
|
+
expectWord(word) {
|
|
26992
|
+
if (!this.takeWord(word)) throw new Unreadable(`no ${word}`);
|
|
26993
|
+
}
|
|
26994
|
+
expectOp(op) {
|
|
26995
|
+
if (!this.takeOp(op)) throw new Unreadable(`no ${op}`);
|
|
26996
|
+
}
|
|
26997
|
+
name() {
|
|
26998
|
+
if (!this.isName()) throw new Unreadable("no name");
|
|
26999
|
+
this.at++;
|
|
27000
|
+
}
|
|
27001
|
+
/** `name` or `schema.name`. */
|
|
27002
|
+
qualifiedName() {
|
|
27003
|
+
this.name();
|
|
27004
|
+
if (this.isOp(".") && this.isName(1)) this.at += 2;
|
|
27005
|
+
}
|
|
27006
|
+
atEnd() {
|
|
27007
|
+
this.takeOp(";");
|
|
27008
|
+
return this.peek().kind === "end";
|
|
27009
|
+
}
|
|
27010
|
+
// --- nodes, as expr.c builds them ---
|
|
27011
|
+
made(node) {
|
|
27012
|
+
this.deepest = Math.max(this.deepest, node.height);
|
|
27013
|
+
return node;
|
|
27014
|
+
}
|
|
27015
|
+
leaf(extra = {}) {
|
|
27016
|
+
return this.made({ height: 1, constant: true, subqueries: [], ...extra });
|
|
27017
|
+
}
|
|
27018
|
+
/** sqlite3PExpr, exprSetHeight: one deeper than the deepest part. */
|
|
27019
|
+
over(parts, extra = {}) {
|
|
27020
|
+
return this.made(this.unchecked(parts, extra));
|
|
27021
|
+
}
|
|
27022
|
+
/**
|
|
27023
|
+
* sqlite3ExprAttachSubtrees alone, as for a CAST: its height is set and
|
|
27024
|
+
* not checked, so it counts only through what holds it.
|
|
27025
|
+
*/
|
|
27026
|
+
unchecked(parts, extra = {}) {
|
|
27027
|
+
let height = 0;
|
|
27028
|
+
let constant = true;
|
|
27029
|
+
let hasFunc = false;
|
|
27030
|
+
let hasSubquery = false;
|
|
27031
|
+
let subqueries = [];
|
|
27032
|
+
for (const part of parts) {
|
|
27033
|
+
if (part.height > height) height = part.height;
|
|
27034
|
+
if (!part.constant) constant = false;
|
|
27035
|
+
if (part.hasFunc) hasFunc = true;
|
|
27036
|
+
if (part.hasSubquery) hasSubquery = true;
|
|
27037
|
+
if (part.subqueries.length > 0)
|
|
27038
|
+
subqueries = subqueries.length === 0 ? part.subqueries : [...subqueries, ...part.subqueries];
|
|
27039
|
+
}
|
|
27040
|
+
const node = { height: height + 1, constant, subqueries, ...extra };
|
|
27041
|
+
if (hasFunc) node.hasFunc ??= true;
|
|
27042
|
+
if (hasSubquery) node.hasSubquery ??= true;
|
|
27043
|
+
return node;
|
|
27044
|
+
}
|
|
27045
|
+
/** A node holding a SELECT: EXISTS, `(SELECT …)`, `x IN (SELECT …)`. */
|
|
27046
|
+
holding(left, select) {
|
|
27047
|
+
const node = {
|
|
27048
|
+
height: Math.max(select.height, left?.height ?? 0) + 1,
|
|
27049
|
+
constant: false,
|
|
27050
|
+
subqueries: [...left?.subqueries ?? [], select],
|
|
27051
|
+
hasSubquery: true
|
|
27052
|
+
};
|
|
27053
|
+
if (left?.hasFunc) node.hasFunc = true;
|
|
27054
|
+
if (left?.column) node.usable = true;
|
|
27055
|
+
return this.made(node);
|
|
27056
|
+
}
|
|
27057
|
+
// --- expressions, by parse.y's precedence ---
|
|
27058
|
+
expr(min = OR) {
|
|
27059
|
+
let left = this.prefix();
|
|
27060
|
+
for (; ; ) {
|
|
27061
|
+
const token = this.peek();
|
|
27062
|
+
if (token.kind === "op") {
|
|
27063
|
+
const level = BINARY[token.text];
|
|
27064
|
+
if (level === void 0 || level < min) return left;
|
|
27065
|
+
this.at++;
|
|
27066
|
+
const right = this.expr(level + 1);
|
|
27067
|
+
if (token.text === "->" || token.text === "->>") {
|
|
27068
|
+
left = this.over([left, right], { hasFunc: true });
|
|
27069
|
+
continue;
|
|
27070
|
+
}
|
|
27071
|
+
const compared = USABLE_OPERATORS.has(token.text);
|
|
27072
|
+
left = this.over(
|
|
27073
|
+
[left, right],
|
|
27074
|
+
compared && (left.column || right.column) ? { usable: true } : {}
|
|
27075
|
+
);
|
|
27076
|
+
continue;
|
|
27077
|
+
}
|
|
27078
|
+
if (token.kind !== "word") return left;
|
|
27079
|
+
const word = token.upper;
|
|
27080
|
+
if (word === "OR" || word === "AND") {
|
|
27081
|
+
if ((word === "OR" ? OR : AND) < min) return left;
|
|
27082
|
+
this.at++;
|
|
27083
|
+
const right = this.expr((word === "OR" ? OR : AND) + 1);
|
|
27084
|
+
if (word === "OR")
|
|
27085
|
+
left = this.over([left, right], { or: [left, right] });
|
|
27086
|
+
else if ((left.isFalse || right.isFalse) && !left.hasFunc && !right.hasFunc)
|
|
27087
|
+
left = this.leaf({ isFalse: true, literal: true });
|
|
27088
|
+
else left = this.over([left, right], { and: [left, right] });
|
|
27089
|
+
continue;
|
|
27090
|
+
}
|
|
27091
|
+
if (word === "COLLATE") {
|
|
27092
|
+
if (COLLATE < min) return left;
|
|
27093
|
+
this.at++;
|
|
27094
|
+
this.name();
|
|
27095
|
+
left = this.made({
|
|
27096
|
+
height: 1,
|
|
27097
|
+
constant: left.constant,
|
|
27098
|
+
subqueries: left.subqueries,
|
|
27099
|
+
...left.column ? { column: true } : {}
|
|
27100
|
+
});
|
|
27101
|
+
continue;
|
|
27102
|
+
}
|
|
27103
|
+
if (COMPARE < min) return left;
|
|
27104
|
+
const negated = word === "NOT" ? 1 : 0;
|
|
27105
|
+
const after = this.peek(negated);
|
|
27106
|
+
const verb = after.kind === "word" ? after.upper : "";
|
|
27107
|
+
if (word === "IS") {
|
|
27108
|
+
this.at++;
|
|
27109
|
+
let not = this.takeWord("NOT");
|
|
27110
|
+
if (this.takeWord("DISTINCT")) {
|
|
27111
|
+
this.expectWord("FROM");
|
|
27112
|
+
not = !not;
|
|
27113
|
+
}
|
|
27114
|
+
const right = this.expr(COMPARE + 1);
|
|
27115
|
+
left = right.isNull ? this.nullTest(left, !not) : this.over(
|
|
27116
|
+
[left, right],
|
|
27117
|
+
!not && (left.column || right.column) ? { usable: true } : {}
|
|
27118
|
+
);
|
|
27119
|
+
} else if (word === "ISNULL" || word === "NOTNULL") {
|
|
27120
|
+
this.at++;
|
|
27121
|
+
left = this.nullTest(left, word === "ISNULL");
|
|
27122
|
+
} else if (negated && verb === "NULL") {
|
|
27123
|
+
this.at += 2;
|
|
27124
|
+
left = this.nullTest(left, false);
|
|
27125
|
+
} else if (LIKE_WORDS.has(verb)) {
|
|
27126
|
+
this.at += negated + 1;
|
|
27127
|
+
const args = [this.expr(COMPARE + 1), left];
|
|
27128
|
+
if (this.takeWord("ESCAPE")) args.push(this.expr(ESCAPE + 1));
|
|
27129
|
+
left = this.over(args, { hasFunc: true });
|
|
27130
|
+
if (negated) left = this.over([left]);
|
|
27131
|
+
} else if (verb === "BETWEEN") {
|
|
27132
|
+
this.at += negated + 1;
|
|
27133
|
+
const low = this.expr(NOT);
|
|
27134
|
+
this.expectWord("AND");
|
|
27135
|
+
const high = this.expr(COMPARE + 1);
|
|
27136
|
+
left = this.over([left, low, high]);
|
|
27137
|
+
if (negated) left = this.over([left]);
|
|
27138
|
+
} else if (verb === "IN") {
|
|
27139
|
+
this.at += negated + 1;
|
|
27140
|
+
left = this.inList(left, negated === 1);
|
|
27141
|
+
} else return left;
|
|
27142
|
+
}
|
|
27143
|
+
}
|
|
27144
|
+
/**
|
|
27145
|
+
* IS NULL (`isNull`) or IS NOT NULL: on a literal, the answer itself; on
|
|
27146
|
+
* a column, a term the planner can use when it is IS NULL.
|
|
27147
|
+
*/
|
|
27148
|
+
nullTest(left, isNull) {
|
|
27149
|
+
if (left.literal)
|
|
27150
|
+
return this.leaf(
|
|
27151
|
+
isNull ? { isFalse: true, literal: true } : { literal: true }
|
|
27152
|
+
);
|
|
27153
|
+
return this.over([left], isNull && left.column ? { usable: true } : {});
|
|
27154
|
+
}
|
|
27155
|
+
inList(left, negated) {
|
|
27156
|
+
this.expectOp("(");
|
|
27157
|
+
if (this.takeOp(")"))
|
|
27158
|
+
return left.hasFunc ? this.over([left]) : this.leaf(negated ? {} : { isFalse: true });
|
|
27159
|
+
let node;
|
|
27160
|
+
if (this.startsSelect()) {
|
|
27161
|
+
node = this.holding(left, this.select());
|
|
27162
|
+
} else {
|
|
27163
|
+
const items = this.list();
|
|
27164
|
+
const [only] = items;
|
|
27165
|
+
const usable = left.column ? { usable: true } : {};
|
|
27166
|
+
if (items.length === 1 && only?.select)
|
|
27167
|
+
node = this.holding(left, only.select);
|
|
27168
|
+
else if (items.length === 1 && only?.constant)
|
|
27169
|
+
node = this.over([left, this.over([only])], usable);
|
|
27170
|
+
else node = this.over([left, ...items], usable);
|
|
27171
|
+
}
|
|
27172
|
+
this.expectOp(")");
|
|
27173
|
+
return negated ? this.over([node]) : node;
|
|
27174
|
+
}
|
|
27175
|
+
list() {
|
|
27176
|
+
const items = [this.expr()];
|
|
27177
|
+
while (this.takeOp(",")) items.push(this.expr());
|
|
27178
|
+
return items;
|
|
27179
|
+
}
|
|
27180
|
+
prefix() {
|
|
27181
|
+
const token = this.peek();
|
|
27182
|
+
this.at++;
|
|
27183
|
+
switch (token.kind) {
|
|
27184
|
+
case "literal":
|
|
27185
|
+
return this.leaf(
|
|
27186
|
+
token.integer === 0 ? { isFalse: true, literal: true } : { literal: true }
|
|
27187
|
+
);
|
|
27188
|
+
case "param":
|
|
27189
|
+
return this.leaf();
|
|
27190
|
+
case "name":
|
|
27191
|
+
return this.column();
|
|
27192
|
+
case "end":
|
|
27193
|
+
throw new Unreadable("no expression");
|
|
27194
|
+
case "op":
|
|
27195
|
+
if (token.text === "(") {
|
|
27196
|
+
if (this.startsSelect()) {
|
|
27197
|
+
const select = this.select();
|
|
27198
|
+
this.expectOp(")");
|
|
27199
|
+
return { ...this.holding(null, select), select };
|
|
27200
|
+
}
|
|
27201
|
+
const items = this.list();
|
|
27202
|
+
this.expectOp(")");
|
|
27203
|
+
if (items.length === 1 && items[0]) return items[0];
|
|
27204
|
+
throw new Unreadable("a row value");
|
|
27205
|
+
}
|
|
27206
|
+
if (token.text === "-" || token.text === "+") {
|
|
27207
|
+
const operand = this.expr(UNARY);
|
|
27208
|
+
return this.over([operand], operand.literal ? { literal: true } : {});
|
|
27209
|
+
}
|
|
27210
|
+
if (token.text === "~") return this.over([this.expr(UNARY)]);
|
|
27211
|
+
throw new Unreadable(`an unexpected ${token.text}`);
|
|
27212
|
+
case "word":
|
|
27213
|
+
break;
|
|
27214
|
+
}
|
|
27215
|
+
switch (token.upper) {
|
|
27216
|
+
case "NULL":
|
|
27217
|
+
return this.leaf({ isNull: true });
|
|
27218
|
+
case "TRUE":
|
|
27219
|
+
case "FALSE":
|
|
27220
|
+
return this.leaf();
|
|
27221
|
+
case "NOT":
|
|
27222
|
+
return this.over([this.expr(NOT)]);
|
|
27223
|
+
case "EXISTS": {
|
|
27224
|
+
this.expectOp("(");
|
|
27225
|
+
const select = this.select();
|
|
27226
|
+
this.expectOp(")");
|
|
27227
|
+
return this.holding(null, select);
|
|
27228
|
+
}
|
|
27229
|
+
case "CAST": {
|
|
27230
|
+
this.expectOp("(");
|
|
27231
|
+
const value = this.expr();
|
|
27232
|
+
this.expectWord("AS");
|
|
27233
|
+
this.typeName();
|
|
27234
|
+
this.expectOp(")");
|
|
27235
|
+
return this.unchecked([value]);
|
|
27236
|
+
}
|
|
27237
|
+
case "CASE": {
|
|
27238
|
+
const parts = [];
|
|
27239
|
+
if (!this.isWord("WHEN")) parts.push(this.expr());
|
|
27240
|
+
while (this.takeWord("WHEN")) {
|
|
27241
|
+
parts.push(this.expr());
|
|
27242
|
+
this.expectWord("THEN");
|
|
27243
|
+
parts.push(this.expr());
|
|
27244
|
+
}
|
|
27245
|
+
if (this.takeWord("ELSE")) parts.push(this.expr());
|
|
27246
|
+
this.expectWord("END");
|
|
27247
|
+
return this.over(parts);
|
|
27248
|
+
}
|
|
27249
|
+
default:
|
|
27250
|
+
break;
|
|
27251
|
+
}
|
|
27252
|
+
if (!this.takeOp("(")) return this.column();
|
|
27253
|
+
let args = [];
|
|
27254
|
+
if (!this.takeOp("*") && !this.isOp(")")) {
|
|
27255
|
+
if (!this.takeWord("DISTINCT")) this.takeWord("ALL");
|
|
27256
|
+
args = this.list();
|
|
27257
|
+
}
|
|
27258
|
+
this.expectOp(")");
|
|
27259
|
+
if (this.isWord("FILTER") || this.isWord("OVER"))
|
|
27260
|
+
throw new Unreadable("a window function");
|
|
27261
|
+
return this.over(args, {
|
|
27262
|
+
hasFunc: true,
|
|
27263
|
+
constant: !NOT_CONSTANT.has(token.text.toLowerCase()) && args.every((arg) => arg.constant)
|
|
27264
|
+
});
|
|
27265
|
+
}
|
|
27266
|
+
/**
|
|
27267
|
+
* `a`, `a.b` or `a.b.c`, its first name just read: TK_ID, and a TK_DOT a
|
|
27268
|
+
* dot — a.b.c is DOT(a, DOT(b, c)).
|
|
27269
|
+
*/
|
|
27270
|
+
column() {
|
|
27271
|
+
let node = this.leaf({ constant: false, column: true });
|
|
27272
|
+
while (this.isOp(".") && this.isName(1)) {
|
|
27273
|
+
this.at += 2;
|
|
27274
|
+
node = this.over([this.leaf({ constant: false }), node], {
|
|
27275
|
+
column: true
|
|
27276
|
+
});
|
|
27277
|
+
}
|
|
27278
|
+
return node;
|
|
27279
|
+
}
|
|
27280
|
+
typeName() {
|
|
27281
|
+
do
|
|
27282
|
+
this.name();
|
|
27283
|
+
while (this.isName());
|
|
27284
|
+
if (this.takeOp("(")) {
|
|
27285
|
+
while (!this.takeOp(")")) {
|
|
27286
|
+
if (this.peek().kind === "end") throw new Unreadable("no )");
|
|
27287
|
+
this.at++;
|
|
27288
|
+
}
|
|
27289
|
+
}
|
|
27290
|
+
}
|
|
27291
|
+
/**
|
|
27292
|
+
* A WHERE, and the chain an OR in it may bring when each of its branches
|
|
27293
|
+
* could be read through an index — a column compared, or an AND holding
|
|
27294
|
+
* one (exprAnalyzeOrTerm) — whether or not the table has that index: the
|
|
27295
|
+
* other terms the planner can use, without a subquery, ANDed one by one
|
|
27296
|
+
* from the first, under one more AND (wherecode.c, WHERE_MULTI_OR).
|
|
27297
|
+
*/
|
|
27298
|
+
where() {
|
|
27299
|
+
const where = this.expr();
|
|
27300
|
+
if (!this.planner) return where;
|
|
27301
|
+
const terms = split(where, "and");
|
|
27302
|
+
for (const or of terms) {
|
|
27303
|
+
if (!or.or || !split(or, "or").every(
|
|
27304
|
+
(branch) => branch.usable || split(branch, "and").some((term) => term.usable)
|
|
27305
|
+
))
|
|
27306
|
+
continue;
|
|
27307
|
+
let chain = 0;
|
|
27308
|
+
for (const term of terms)
|
|
27309
|
+
if (term !== or && (term.usable || term.or) && !term.hasSubquery)
|
|
27310
|
+
chain = chain === 0 ? term.height : Math.max(chain, term.height) + 1;
|
|
27311
|
+
if (chain > 0) this.deepest = Math.max(this.deepest, chain + 1);
|
|
27312
|
+
}
|
|
27313
|
+
return where;
|
|
27314
|
+
}
|
|
27315
|
+
// --- statements ---
|
|
27316
|
+
startsSelect() {
|
|
27317
|
+
return this.isWord("SELECT") || this.isWord("VALUES") || this.isWord("WITH");
|
|
27318
|
+
}
|
|
27319
|
+
/** A SELECT, compound or not, with its ORDER BY and LIMIT. */
|
|
27320
|
+
select() {
|
|
27321
|
+
if (this.isWord("WITH")) throw new Unreadable("WITH");
|
|
27322
|
+
const whole = { height: 0, resolved: [], from: [] };
|
|
27323
|
+
const counted = (node) => {
|
|
27324
|
+
whole.height = Math.max(whole.height, node.height);
|
|
27325
|
+
whole.resolved.push(node);
|
|
27326
|
+
};
|
|
27327
|
+
do {
|
|
27328
|
+
if (this.takeWord("VALUES")) {
|
|
27329
|
+
do {
|
|
27330
|
+
this.expectOp("(");
|
|
27331
|
+
for (const item of this.list()) counted(item);
|
|
27332
|
+
this.expectOp(")");
|
|
27333
|
+
} while (this.takeOp(","));
|
|
27334
|
+
continue;
|
|
27335
|
+
}
|
|
27336
|
+
this.expectWord("SELECT");
|
|
27337
|
+
if (!this.takeWord("DISTINCT")) this.takeWord("ALL");
|
|
27338
|
+
const stars = [];
|
|
27339
|
+
do {
|
|
27340
|
+
if (this.takeOp("*")) stars.push(this.leaf());
|
|
27341
|
+
else if (this.isName() && this.isOp(".", 1) && this.isOp("*", 2)) {
|
|
27342
|
+
this.at += 3;
|
|
27343
|
+
stars.push(this.over([this.leaf(), this.leaf()]));
|
|
27344
|
+
} else {
|
|
27345
|
+
counted(this.expr());
|
|
27346
|
+
this.alias();
|
|
27347
|
+
}
|
|
27348
|
+
} while (this.takeOp(","));
|
|
27349
|
+
const sources = this.takeWord("FROM") ? this.from(whole) : 0;
|
|
27350
|
+
for (const star of stars) {
|
|
27351
|
+
whole.height = Math.max(whole.height, star.height);
|
|
27352
|
+
whole.resolved.push(
|
|
27353
|
+
sources > 1 ? this.made({ height: 3, constant: false, subqueries: [] }) : star
|
|
27354
|
+
);
|
|
27355
|
+
}
|
|
27356
|
+
if (this.takeWord("WHERE")) counted(this.where());
|
|
27357
|
+
if (this.takeWord("GROUP")) {
|
|
27358
|
+
this.expectWord("BY");
|
|
27359
|
+
for (const item of this.list()) counted(item);
|
|
27360
|
+
if (this.takeWord("HAVING")) counted(this.expr());
|
|
27361
|
+
}
|
|
27362
|
+
} while (this.compound());
|
|
27363
|
+
if (this.takeWord("ORDER")) {
|
|
27364
|
+
this.expectWord("BY");
|
|
27365
|
+
do {
|
|
27366
|
+
counted(this.expr());
|
|
27367
|
+
if (!this.takeWord("ASC")) this.takeWord("DESC");
|
|
27368
|
+
if (this.takeWord("NULLS") && !this.takeWord("FIRST"))
|
|
27369
|
+
this.expectWord("LAST");
|
|
27370
|
+
} while (this.takeOp(","));
|
|
27371
|
+
}
|
|
27372
|
+
if (this.takeWord("LIMIT")) {
|
|
27373
|
+
const parts = [this.expr()];
|
|
27374
|
+
if (this.takeWord("OFFSET") || this.takeOp(",")) parts.push(this.expr());
|
|
27375
|
+
counted(this.over(parts));
|
|
27376
|
+
}
|
|
27377
|
+
return whole;
|
|
27378
|
+
}
|
|
27379
|
+
compound() {
|
|
27380
|
+
if (this.takeWord("UNION")) {
|
|
27381
|
+
this.takeWord("ALL");
|
|
27382
|
+
return true;
|
|
27383
|
+
}
|
|
27384
|
+
return this.takeWord("INTERSECT") || this.takeWord("EXCEPT");
|
|
27385
|
+
}
|
|
27386
|
+
alias() {
|
|
27387
|
+
if (this.takeWord("AS")) {
|
|
27388
|
+
this.name();
|
|
27389
|
+
return;
|
|
27390
|
+
}
|
|
27391
|
+
const token = this.peek();
|
|
27392
|
+
if (token.kind === "name" || token.kind === "word" && !CLAUSE_WORDS2.has(token.upper))
|
|
27393
|
+
this.at++;
|
|
27394
|
+
}
|
|
27395
|
+
/** The FROM items, into `into`; how many there are. */
|
|
27396
|
+
from(into) {
|
|
27397
|
+
let sources = 0;
|
|
27398
|
+
do {
|
|
27399
|
+
sources++;
|
|
27400
|
+
if (this.takeOp("(")) {
|
|
27401
|
+
if (!this.startsSelect()) throw new Unreadable("a parenthesized join");
|
|
27402
|
+
into.from.push(this.select());
|
|
27403
|
+
this.expectOp(")");
|
|
27404
|
+
} else {
|
|
27405
|
+
this.qualifiedName();
|
|
27406
|
+
if (this.takeOp("(")) {
|
|
27407
|
+
if (!this.isOp(")"))
|
|
27408
|
+
for (const arg of this.list()) {
|
|
27409
|
+
into.resolved.push(arg);
|
|
27410
|
+
this.deepest = Math.max(this.deepest, arg.height + 2);
|
|
27411
|
+
}
|
|
27412
|
+
this.expectOp(")");
|
|
27413
|
+
}
|
|
27414
|
+
}
|
|
27415
|
+
this.alias();
|
|
27416
|
+
if (this.takeWord("INDEXED")) {
|
|
27417
|
+
this.expectWord("BY");
|
|
27418
|
+
this.name();
|
|
27419
|
+
} else if (this.isWord("NOT") && this.isWord("INDEXED", 1)) this.at += 2;
|
|
27420
|
+
if (this.isWord("ON") || this.isWord("USING"))
|
|
27421
|
+
throw new Unreadable("a join constraint");
|
|
27422
|
+
} while (this.join());
|
|
27423
|
+
return sources;
|
|
27424
|
+
}
|
|
27425
|
+
join() {
|
|
27426
|
+
if (this.takeOp(",")) return true;
|
|
27427
|
+
const start = this.at;
|
|
27428
|
+
this.takeWord("NATURAL");
|
|
27429
|
+
if (this.takeWord("LEFT") || this.takeWord("RIGHT") || this.takeWord("FULL"))
|
|
27430
|
+
this.takeWord("OUTER");
|
|
27431
|
+
else if (!this.takeWord("INNER")) this.takeWord("CROSS");
|
|
27432
|
+
if (this.takeWord("JOIN")) return true;
|
|
27433
|
+
this.at = start;
|
|
27434
|
+
return false;
|
|
27435
|
+
}
|
|
27436
|
+
names() {
|
|
27437
|
+
this.expectOp("(");
|
|
27438
|
+
do
|
|
27439
|
+
this.name();
|
|
27440
|
+
while (this.takeOp(","));
|
|
27441
|
+
this.expectOp(")");
|
|
27442
|
+
}
|
|
27443
|
+
returning(into) {
|
|
27444
|
+
if (!this.takeWord("RETURNING")) return;
|
|
27445
|
+
do {
|
|
27446
|
+
if (this.takeOp("*")) into.push(this.leaf());
|
|
27447
|
+
else {
|
|
27448
|
+
into.push(this.expr());
|
|
27449
|
+
this.alias();
|
|
27450
|
+
}
|
|
27451
|
+
} while (this.takeOp(","));
|
|
27452
|
+
}
|
|
27453
|
+
assignments(into) {
|
|
27454
|
+
do {
|
|
27455
|
+
if (this.isOp("(")) this.names();
|
|
27456
|
+
else this.name();
|
|
27457
|
+
this.expectOp("=");
|
|
27458
|
+
into.push(this.expr());
|
|
27459
|
+
} while (this.takeOp(","));
|
|
27460
|
+
}
|
|
27461
|
+
/** The statement: each of its expressions resolved from 0. */
|
|
27462
|
+
statement() {
|
|
27463
|
+
const top = { height: 0, resolved: [], from: [] };
|
|
27464
|
+
if (this.startsSelect()) {
|
|
27465
|
+
top.from.push(this.select());
|
|
27466
|
+
} else if (this.takeWord("INSERT") || this.takeWord("REPLACE")) {
|
|
27467
|
+
if (this.takeWord("OR")) this.name();
|
|
27468
|
+
this.expectWord("INTO");
|
|
27469
|
+
this.qualifiedName();
|
|
27470
|
+
if (this.takeWord("AS")) this.name();
|
|
27471
|
+
if (this.isOp("(")) this.names();
|
|
27472
|
+
if (this.takeWord("DEFAULT")) this.expectWord("VALUES");
|
|
27473
|
+
else top.from.push(this.select());
|
|
27474
|
+
while (this.takeWord("ON")) {
|
|
27475
|
+
this.expectWord("CONFLICT");
|
|
27476
|
+
if (this.takeOp("(")) {
|
|
27477
|
+
top.resolved.push(...this.list());
|
|
27478
|
+
this.expectOp(")");
|
|
27479
|
+
if (this.takeWord("WHERE")) top.resolved.push(this.expr());
|
|
27480
|
+
}
|
|
27481
|
+
this.expectWord("DO");
|
|
27482
|
+
if (this.takeWord("NOTHING")) continue;
|
|
27483
|
+
this.expectWord("UPDATE");
|
|
27484
|
+
this.expectWord("SET");
|
|
27485
|
+
this.assignments(top.resolved);
|
|
27486
|
+
if (this.takeWord("WHERE")) top.resolved.push(this.expr());
|
|
27487
|
+
}
|
|
27488
|
+
this.returning(top.resolved);
|
|
27489
|
+
} else if (this.takeWord("UPDATE")) {
|
|
27490
|
+
if (this.takeWord("OR")) this.name();
|
|
27491
|
+
this.qualifiedName();
|
|
27492
|
+
this.alias();
|
|
27493
|
+
if (this.takeWord("INDEXED")) {
|
|
27494
|
+
this.expectWord("BY");
|
|
27495
|
+
this.name();
|
|
27496
|
+
}
|
|
27497
|
+
this.expectWord("SET");
|
|
27498
|
+
this.assignments(top.resolved);
|
|
27499
|
+
if (this.isWord("FROM")) throw new Unreadable("UPDATE \u2026 FROM");
|
|
27500
|
+
if (this.takeWord("WHERE")) top.resolved.push(this.where());
|
|
27501
|
+
this.returning(top.resolved);
|
|
27502
|
+
} else if (this.takeWord("DELETE")) {
|
|
27503
|
+
this.expectWord("FROM");
|
|
27504
|
+
this.qualifiedName();
|
|
27505
|
+
this.alias();
|
|
27506
|
+
if (this.takeWord("INDEXED")) {
|
|
27507
|
+
this.expectWord("BY");
|
|
27508
|
+
this.name();
|
|
27509
|
+
}
|
|
27510
|
+
if (this.takeWord("WHERE")) top.resolved.push(this.where());
|
|
27511
|
+
this.returning(top.resolved);
|
|
27512
|
+
} else throw new Unreadable("not a statement naro compiles");
|
|
27513
|
+
return top;
|
|
27514
|
+
}
|
|
27515
|
+
};
|
|
27516
|
+
function split(node, at) {
|
|
27517
|
+
const found = [];
|
|
27518
|
+
const pending = [node];
|
|
27519
|
+
for (let next = pending.pop(); next; next = pending.pop()) {
|
|
27520
|
+
const sides = next[at];
|
|
27521
|
+
if (sides) pending.push(sides[1], sides[0]);
|
|
27522
|
+
else found.push(next);
|
|
27523
|
+
}
|
|
27524
|
+
return found;
|
|
27525
|
+
}
|
|
27526
|
+
function nodeNeed(node, base2) {
|
|
27527
|
+
const at = base2 + node.height;
|
|
27528
|
+
let need = at;
|
|
27529
|
+
for (const select of node.subqueries)
|
|
27530
|
+
need = Math.max(need, selectNeed(select, at));
|
|
27531
|
+
return need;
|
|
27532
|
+
}
|
|
27533
|
+
function selectNeed(select, base2) {
|
|
27534
|
+
let need = base2;
|
|
27535
|
+
for (const node of select.resolved)
|
|
27536
|
+
need = Math.max(need, nodeNeed(node, base2));
|
|
27537
|
+
for (const inner of select.from)
|
|
27538
|
+
need = Math.max(need, selectNeed(inner, base2));
|
|
27539
|
+
return need;
|
|
27540
|
+
}
|
|
27541
|
+
function expressionDepth(sql, { planner = true } = {}) {
|
|
27542
|
+
try {
|
|
27543
|
+
const reader = new Reader(tokenize3(sql), planner);
|
|
27544
|
+
const top = reader.statement();
|
|
27545
|
+
if (!reader.atEnd()) return void 0;
|
|
27546
|
+
return Math.max(reader.deepest, selectNeed(top, 0));
|
|
27547
|
+
} catch (error51) {
|
|
27548
|
+
if (error51 instanceof Unreadable) return void 0;
|
|
27549
|
+
throw error51;
|
|
27550
|
+
}
|
|
27551
|
+
}
|
|
27552
|
+
|
|
27553
|
+
// ../../packages/data-core/src/policy-index.ts
|
|
27554
|
+
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`;
|
|
27555
|
+
function indexesFromRows(rows) {
|
|
27556
|
+
const tables = /* @__PURE__ */ new Map();
|
|
27557
|
+
for (const row of rows) {
|
|
27558
|
+
const table = row.table_name;
|
|
27559
|
+
const column2 = row.column_name;
|
|
27560
|
+
if (typeof table !== "string" || typeof column2 !== "string") continue;
|
|
27561
|
+
if (Number(row.partial) !== 0) continue;
|
|
27562
|
+
const key = asciiLower(table);
|
|
27563
|
+
const columns = tables.get(key) ?? /* @__PURE__ */ new Map();
|
|
27564
|
+
tables.set(key, columns);
|
|
27565
|
+
const collations = columns.get(asciiLower(column2)) ?? [];
|
|
27566
|
+
collations.push(
|
|
27567
|
+
typeof row.collation === "string" ? row.collation.toUpperCase() : "BINARY"
|
|
27568
|
+
);
|
|
27569
|
+
columns.set(asciiLower(column2), collations);
|
|
27570
|
+
}
|
|
27571
|
+
return tables;
|
|
27572
|
+
}
|
|
27573
|
+
var STORAGE_OBJECTS_INDEXES = /* @__PURE__ */ new Map(
|
|
27574
|
+
[
|
|
27575
|
+
["id", ["BINARY"]],
|
|
27576
|
+
["bucket_id", ["BINARY"]]
|
|
27577
|
+
]
|
|
27578
|
+
);
|
|
27579
|
+
function resolve8(scopes, qualifier, name) {
|
|
27580
|
+
const column2 = asciiLower(name);
|
|
27581
|
+
for (let index = scopes.length - 1; index >= 0; index--) {
|
|
27582
|
+
const scope = scopes[index];
|
|
27583
|
+
if (qualifier !== null && !scope.names.includes(asciiLower(qualifier)))
|
|
27584
|
+
continue;
|
|
27585
|
+
const found = scope.table.byName.get(column2);
|
|
27586
|
+
if (found) return { column: found, scope: index };
|
|
27587
|
+
if (qualifier !== null) return void 0;
|
|
27588
|
+
}
|
|
27589
|
+
return void 0;
|
|
27590
|
+
}
|
|
27591
|
+
function realtimeTopic2(scopes) {
|
|
27592
|
+
const [root] = scopes;
|
|
27593
|
+
if (root?.table.name !== REALTIME_MESSAGES) return void 0;
|
|
27594
|
+
const column2 = root.table.byName.get("topic");
|
|
27595
|
+
return column2 ? { column: column2, scope: 0 } : void 0;
|
|
27596
|
+
}
|
|
27597
|
+
function valueScopes(scopes, value) {
|
|
27598
|
+
switch (value.kind) {
|
|
27599
|
+
case "column": {
|
|
27600
|
+
const found = resolve8(scopes, value.qualifier, value.name);
|
|
27601
|
+
return found ? [found.scope] : [];
|
|
27602
|
+
}
|
|
27603
|
+
case "func":
|
|
27604
|
+
return value.args.flatMap((arg) => valueScopes(scopes, arg));
|
|
27605
|
+
case "cast":
|
|
27606
|
+
return valueScopes(scopes, value.operand);
|
|
27607
|
+
case "storage":
|
|
27608
|
+
return valueScopes(scopes, value.column);
|
|
27609
|
+
case "realtime": {
|
|
27610
|
+
const found = realtimeTopic2(scopes);
|
|
27611
|
+
return found ? [found.scope] : [];
|
|
27612
|
+
}
|
|
27613
|
+
default:
|
|
27614
|
+
return [];
|
|
27615
|
+
}
|
|
27616
|
+
}
|
|
27617
|
+
function readsOutside(scopes, expr, depth, snapshot) {
|
|
27618
|
+
if (expr === null) return false;
|
|
27619
|
+
if (isValueExpr(expr))
|
|
27620
|
+
return valueScopes(scopes, expr).some((scope) => scope < depth);
|
|
27621
|
+
const values = (...items) => items.some(
|
|
27622
|
+
(item) => valueScopes(scopes, item).some((scope) => scope < depth)
|
|
27623
|
+
);
|
|
27624
|
+
switch (expr.kind) {
|
|
27625
|
+
case "and":
|
|
27626
|
+
case "or":
|
|
27627
|
+
return expr.args.some(
|
|
27628
|
+
(arg) => readsOutside(scopes, arg, depth, snapshot)
|
|
27629
|
+
);
|
|
27630
|
+
case "not":
|
|
27631
|
+
return readsOutside(scopes, expr.arg, depth, snapshot);
|
|
27632
|
+
case "compare":
|
|
27633
|
+
case "distinct":
|
|
27634
|
+
return values(expr.left, expr.right);
|
|
27635
|
+
case "is":
|
|
27636
|
+
case "like":
|
|
27637
|
+
return values(expr.operand);
|
|
27638
|
+
case "in":
|
|
27639
|
+
return values(expr.operand, ...expr.list);
|
|
27640
|
+
case "between":
|
|
27641
|
+
return values(expr.operand, expr.low, expr.high);
|
|
27642
|
+
case "array":
|
|
27643
|
+
return values(expr.operand);
|
|
27644
|
+
case "exists":
|
|
27645
|
+
case "inSelect": {
|
|
27646
|
+
if (expr.kind === "inSelect" && values(expr.operand)) return true;
|
|
27647
|
+
const inner = subqueryScopes2(scopes, expr.subquery, snapshot);
|
|
27648
|
+
if (!inner) return false;
|
|
27649
|
+
return expr.subquery.select !== "*" && readsOutside(inner, expr.subquery.select, depth, snapshot) || readsOutside(inner, expr.subquery.where, depth, snapshot);
|
|
27650
|
+
}
|
|
27651
|
+
}
|
|
27652
|
+
}
|
|
27653
|
+
function subqueryScopes2(scopes, subquery, snapshot) {
|
|
27654
|
+
const table = resolveSubqueryTable(snapshot, subquery.table);
|
|
27655
|
+
if (!table) return void 0;
|
|
27656
|
+
return [...scopes, { table, names: [asciiLower(subqueryName(subquery))] }];
|
|
27657
|
+
}
|
|
27658
|
+
function conjuncts(expr) {
|
|
27659
|
+
if (expr === null) return [];
|
|
27660
|
+
if (!isValueExpr(expr) && expr.kind === "and")
|
|
27661
|
+
return expr.args.flatMap(conjuncts);
|
|
27662
|
+
return [expr];
|
|
27663
|
+
}
|
|
27664
|
+
function columnAffinityOf(column2) {
|
|
27665
|
+
const affinity2 = columnAffinity(column2);
|
|
27666
|
+
if (affinity2 === "TEXT") return "text";
|
|
27667
|
+
if (affinity2 === "BLOB") return "blob";
|
|
27668
|
+
return "numeric";
|
|
27669
|
+
}
|
|
27670
|
+
function operandColumn(scopes, operand) {
|
|
27671
|
+
const value = comparedValue(operand);
|
|
27672
|
+
if (value.kind === "realtime") return realtimeTopic2(scopes);
|
|
27673
|
+
if (value.kind !== "column") return void 0;
|
|
27674
|
+
return resolve8(scopes, value.qualifier, value.name);
|
|
27675
|
+
}
|
|
27676
|
+
function valueAffinity(scopes, value) {
|
|
27677
|
+
if (comparedValue(value).kind === "cast") return "text";
|
|
27678
|
+
const found = operandColumn(scopes, value);
|
|
27679
|
+
return found ? columnAffinityOf(found.column) : "none";
|
|
26558
27680
|
}
|
|
26559
27681
|
function valueCollation(scopes, value) {
|
|
26560
|
-
|
|
27682
|
+
const compared = comparedValue(value);
|
|
27683
|
+
if (compared.kind === "cast") return valueCollation(scopes, compared.operand);
|
|
26561
27684
|
const found = operandColumn(scopes, value);
|
|
26562
27685
|
return found ? found.column.collation : void 0;
|
|
26563
27686
|
}
|
|
26564
27687
|
function waysIn(scopes, where) {
|
|
26565
27688
|
const inner = scopes.length - 1;
|
|
26566
|
-
const own2 = (
|
|
27689
|
+
const own2 = (operand) => {
|
|
27690
|
+
const value = comparedValue(operand);
|
|
26567
27691
|
if (value.kind !== "column") return void 0;
|
|
26568
27692
|
const found = resolve8(scopes, value.qualifier, value.name);
|
|
26569
27693
|
return found?.scope === inner ? found.column : void 0;
|
|
@@ -26611,7 +27735,7 @@ function isRowid(table, column2) {
|
|
|
26611
27735
|
function blocked(table, indexes, way) {
|
|
26612
27736
|
if (isRowid(table, way.column))
|
|
26613
27737
|
return way.affinity === "text" ? "affinity" : void 0;
|
|
26614
|
-
const collations = indexes.get(asciiLower(table.name))?.get(asciiLower(way.column.name));
|
|
27738
|
+
const collations = (table.name === STORAGE_OBJECTS_TABLE ? STORAGE_OBJECTS_INDEXES : indexes.get(asciiLower(table.name)))?.get(asciiLower(way.column.name));
|
|
26615
27739
|
if (!collations || collations.length === 0) return "none";
|
|
26616
27740
|
const wanted = way.collation === void 0 ? way.column.collation : way.collation;
|
|
26617
27741
|
if (wanted !== null && !collations.includes(wanted)) return "collation";
|
|
@@ -26622,9 +27746,17 @@ function blocked(table, indexes, way) {
|
|
|
26622
27746
|
}
|
|
26623
27747
|
function problemMessage(policy, outer, table, ways, why) {
|
|
26624
27748
|
const runs = outer.name === REALTIME_MESSAGES ? "runs once for every channel join it checks" : `runs once for every row of ${tableLabel(outer)} it looks at`;
|
|
26625
|
-
const start = `Policy ${policy.name} on ${tableLabel(outer)}: its subquery on ${table
|
|
27749
|
+
const start = `Policy ${policy.name} on ${tableLabel(outer)}: its subquery on ${tableLabel(table)} ${runs}`;
|
|
26626
27750
|
const override = "or send it with allow_unindexed=1 (naro policy create --allow-unindexed) to accept a scan of every row";
|
|
26627
27751
|
const [way] = ways;
|
|
27752
|
+
if (table.name === STORAGE_OBJECTS_TABLE) {
|
|
27753
|
+
const column3 = way ? `${STORAGE_OBJECTS}.${way.column.name}` : "";
|
|
27754
|
+
if (way && why === "collation")
|
|
27755
|
+
return `${start}, and it compares ${column3} in collation ${String(way.collation ?? way.column.collation)}, which its index (BINARY) cannot search, so each run reads all of ${STORAGE_OBJECTS}. Write ${column3} on the left of = so that the comparison is BINARY \u2014 ${override}.`;
|
|
27756
|
+
if (way && why === "affinity")
|
|
27757
|
+
return `${start}, and it compares ${column3} with a number, which its index (text) cannot search, so each run reads all of ${STORAGE_OBJECTS}. Compare it with text, as in <value>::text \u2014 ${override}.`;
|
|
27758
|
+
return `${start}, and no condition of its WHERE compares ${STORAGE_OBJECTS}.bucket_id or ${STORAGE_OBJECTS}.id with = to a value from outside it, so each run reads all of ${STORAGE_OBJECTS}. Add bucket_id = '<bucket>' to it \u2014 ${override}.`;
|
|
27759
|
+
}
|
|
26628
27760
|
if (!way)
|
|
26629
27761
|
return `${start}, and no condition of its WHERE compares a column of ${table.name} with = to a value from outside it, so each run reads all of ${table.name}. Compare a column of ${table.name} with = (or IN) and index that column \u2014 ${override}.`;
|
|
26630
27762
|
const column2 = way.column.name;
|
|
@@ -26664,12 +27796,12 @@ function unindexedSubqueries(snapshot, indexes, policies) {
|
|
|
26664
27796
|
const ways = waysIn(inner, expr.subquery.where);
|
|
26665
27797
|
const reasons = ways.map((way) => blocked(table, indexes, way));
|
|
26666
27798
|
if (!reasons.includes(void 0)) {
|
|
26667
|
-
const order = ["none", "collation", "affinity"];
|
|
27799
|
+
const order = table.name === STORAGE_OBJECTS_TABLE ? ["collation", "affinity", "none"] : ["none", "collation", "affinity"];
|
|
26668
27800
|
const best = order.map((reason) => reasons.indexOf(reason)).find((index) => index >= 0);
|
|
26669
27801
|
const way = best === void 0 ? void 0 : ways[best];
|
|
26670
27802
|
found.push({
|
|
26671
27803
|
policy,
|
|
26672
|
-
table: table
|
|
27804
|
+
table: tableLabel(table),
|
|
26673
27805
|
message: problemMessage(
|
|
26674
27806
|
policy,
|
|
26675
27807
|
outer,
|
|
@@ -26863,6 +27995,14 @@ function privateChannelAccess(input) {
|
|
|
26863
27995
|
return { ok: true, statement: { sql, params } };
|
|
26864
27996
|
}
|
|
26865
27997
|
|
|
27998
|
+
// ../../packages/data-core/src/storage-transfer.ts
|
|
27999
|
+
function moveToPending(to) {
|
|
28000
|
+
return JSON.stringify({ moveTo: to });
|
|
28001
|
+
}
|
|
28002
|
+
function moveFromPending(from) {
|
|
28003
|
+
return JSON.stringify({ moveFrom: from });
|
|
28004
|
+
}
|
|
28005
|
+
|
|
26866
28006
|
// ../../packages/data-core/src/storage-sql.ts
|
|
26867
28007
|
var STORAGE_NONCE_MAX_DIGITS = 64;
|
|
26868
28008
|
function nonceOf(nonce) {
|
|
@@ -26992,6 +28132,73 @@ function claimOverwrite(i) {
|
|
|
26992
28132
|
[[i.lockId, i.expiresAt, i.pending, i.bucket, i.name], room.values]
|
|
26993
28133
|
);
|
|
26994
28134
|
}
|
|
28135
|
+
var MOVE_KEPT_COLUMNS = [
|
|
28136
|
+
"id",
|
|
28137
|
+
"bucket_id",
|
|
28138
|
+
"owner",
|
|
28139
|
+
"owner_id",
|
|
28140
|
+
"version",
|
|
28141
|
+
"created_at",
|
|
28142
|
+
"updated_at",
|
|
28143
|
+
"metadata"
|
|
28144
|
+
];
|
|
28145
|
+
function moveCheckRow(i) {
|
|
28146
|
+
return {
|
|
28147
|
+
row: { name: i.to },
|
|
28148
|
+
stored: { alias: STORAGE_OBJECT_ALIAS, columns: MOVE_KEPT_COLUMNS }
|
|
28149
|
+
};
|
|
28150
|
+
}
|
|
28151
|
+
function claimMove(i) {
|
|
28152
|
+
const nonce = nonceOf(i.nonce);
|
|
28153
|
+
const slotNonce = nonceOf(i.slotNonce);
|
|
28154
|
+
const source = compose(
|
|
28155
|
+
[
|
|
28156
|
+
`UPDATE ${STORAGE_OBJECTS_TABLE} AS o SET lock_id = ?, lock_expires_at = ?, pending = ? WHERE o.id = ? AND o.name = ? AND CASE WHEN NOT (`,
|
|
28157
|
+
i.selectUsing,
|
|
28158
|
+
`) THEN ${marker("hidden", nonce)} WHEN NOT EXISTS (SELECT 1 FROM ${STORAGE_BUCKETS_TABLE} AS b WHERE b.id = o.bucket_id AND b.${LIVE_BUCKET}) THEN ${marker("bucket", nonce)} WHEN o.lock_id IS NOT NULL OR o.version IS NOT ? THEN ${marker("busy", nonce)} WHEN NOT (`,
|
|
28159
|
+
i.updateUsing,
|
|
28160
|
+
`) THEN ${marker("denied", nonce)} WHEN (`,
|
|
28161
|
+
i.check,
|
|
28162
|
+
// The check is 1 or fails with its marker; anything else is no
|
|
28163
|
+
// check that held — refused as the update policy refuses.
|
|
28164
|
+
`) <> 1 THEN ${marker("denied", nonce)} WHEN ${PROJECT_BYTES} + o.size > ? THEN ${marker("quota", nonce)} ELSE 1 END RETURNING id, size`
|
|
28165
|
+
],
|
|
28166
|
+
[
|
|
28167
|
+
[i.lockId, i.expiresAt, moveToPending(i.to), i.id, i.from],
|
|
28168
|
+
[i.version],
|
|
28169
|
+
[i.limitBytes]
|
|
28170
|
+
]
|
|
28171
|
+
);
|
|
28172
|
+
const slot = compose(
|
|
28173
|
+
[
|
|
28174
|
+
`INSERT INTO ${STORAGE_OBJECTS_TABLE} (id, bucket_id, name, owner, owner_id, metadata, version, parent, size, status, lock_id, lock_expires_at, pending) SELECT ?, s.bucket_id, ?, NULL, NULL, '{}', NULL, ?, s.size, 'uploading', s.lock_id, s.lock_expires_at, ? FROM ${STORAGE_OBJECTS_TABLE} AS s WHERE s.id = ? AND s.lock_id = ? AND CASE WHEN EXISTS (SELECT 1 FROM ${STORAGE_OBJECTS_TABLE} AS d WHERE d.bucket_id = s.bucket_id AND d.name = ? AND d.status = 'ready') THEN ${marker("exists", slotNonce)} WHEN EXISTS (SELECT 1 FROM ${STORAGE_OBJECTS_TABLE} AS d WHERE d.bucket_id = s.bucket_id AND d.name = ?) THEN ${marker("busy", slotNonce)} ELSE 1 END RETURNING id`
|
|
28175
|
+
],
|
|
28176
|
+
[
|
|
28177
|
+
[
|
|
28178
|
+
i.slotId,
|
|
28179
|
+
i.to,
|
|
28180
|
+
objectParent(i.to),
|
|
28181
|
+
moveFromPending(i.from),
|
|
28182
|
+
i.id,
|
|
28183
|
+
i.lockId,
|
|
28184
|
+
i.to,
|
|
28185
|
+
i.to
|
|
28186
|
+
]
|
|
28187
|
+
]
|
|
28188
|
+
);
|
|
28189
|
+
return [source, slot];
|
|
28190
|
+
}
|
|
28191
|
+
function copySourceGuard(i) {
|
|
28192
|
+
const nonce = nonceOf(i.nonce);
|
|
28193
|
+
return compose(
|
|
28194
|
+
[
|
|
28195
|
+
`SELECT CASE WHEN EXISTS (SELECT 1 FROM ${STORAGE_OBJECTS_TABLE} AS o WHERE o.id = ? AND o.name = ? AND (`,
|
|
28196
|
+
i.using,
|
|
28197
|
+
`)) THEN 1 ELSE ${marker("source", nonce)} END AS ok`
|
|
28198
|
+
],
|
|
28199
|
+
[[i.id, i.name]]
|
|
28200
|
+
);
|
|
28201
|
+
}
|
|
26995
28202
|
function claimDelete(i) {
|
|
26996
28203
|
return compose(
|
|
26997
28204
|
[
|
|
@@ -27023,6 +28230,16 @@ function readObjects(i) {
|
|
|
27023
28230
|
[[i.bucket, JSON.stringify([...new Set(i.names)])]]
|
|
27024
28231
|
);
|
|
27025
28232
|
}
|
|
28233
|
+
function readTransferSource(i) {
|
|
28234
|
+
return compose(
|
|
28235
|
+
[
|
|
28236
|
+
`SELECT o.id, o.version, o.size, o.metadata FROM ${STORAGE_OBJECTS_TABLE} AS o WHERE o.bucket_id = ? AND o.name = ? AND (`,
|
|
28237
|
+
i.using,
|
|
28238
|
+
")"
|
|
28239
|
+
],
|
|
28240
|
+
[[i.bucket, i.name]]
|
|
28241
|
+
);
|
|
28242
|
+
}
|
|
27026
28243
|
var SORT_COLUMNS = {
|
|
27027
28244
|
name: "name",
|
|
27028
28245
|
created_at: "created_at",
|
|
@@ -27114,7 +28331,7 @@ function storageUsing(input) {
|
|
|
27114
28331
|
assertFragmentAlias(alias, "storageUsing");
|
|
27115
28332
|
const on = target(snapshot);
|
|
27116
28333
|
if ("ok" in on) return on;
|
|
27117
|
-
const status =
|
|
28334
|
+
const status = storageReadySql(alias);
|
|
27118
28335
|
if (context.role === "service")
|
|
27119
28336
|
return { ok: true, fragment: { sql: status, params: [] } };
|
|
27120
28337
|
try {
|
|
@@ -27204,12 +28421,12 @@ var CONTEXTS = [
|
|
|
27204
28421
|
userId: "naro-policy-check",
|
|
27205
28422
|
email: "check@naro.invalid",
|
|
27206
28423
|
emailVerified: true
|
|
27207
|
-
}
|
|
28424
|
+
},
|
|
28425
|
+
{ role: "authenticated", userId: "naro-policy-check" }
|
|
27208
28426
|
];
|
|
27209
28427
|
function probes(table, context, owners) {
|
|
27210
28428
|
const writable = table.columns.filter((column2) => !column2.generated);
|
|
27211
|
-
const
|
|
27212
|
-
const filters = first ? [{ operator: "is", column: first.name, value: null }] : [];
|
|
28429
|
+
const filters = leastFilters(table.columns[0]?.name);
|
|
27213
28430
|
const row = Object.fromEntries(
|
|
27214
28431
|
writable.map((column2) => [
|
|
27215
28432
|
column2.name,
|
|
@@ -27217,7 +28434,7 @@ function probes(table, context, owners) {
|
|
|
27217
28434
|
])
|
|
27218
28435
|
);
|
|
27219
28436
|
const queries = [
|
|
27220
|
-
{ version: 1, action: "select", table: table.name, columns: "*" },
|
|
28437
|
+
{ version: 1, action: "select", table: table.name, columns: "*", filters },
|
|
27221
28438
|
{ version: 1, action: "insert", table: table.name, rows: [row] },
|
|
27222
28439
|
{
|
|
27223
28440
|
version: 1,
|
|
@@ -27240,16 +28457,35 @@ function probes(table, context, owners) {
|
|
|
27240
28457
|
}
|
|
27241
28458
|
var STORAGE_PROBE_ALIAS = "o";
|
|
27242
28459
|
var STORAGE_PROBE_NONCE = "f".repeat(STORAGE_NONCE_MAX_DIGITS);
|
|
27243
|
-
var
|
|
27244
|
-
|
|
27245
|
-
|
|
27246
|
-
|
|
27247
|
-
|
|
28460
|
+
var UNVERIFIED = {
|
|
28461
|
+
role: "authenticated",
|
|
28462
|
+
userId: "naro-policy-check",
|
|
28463
|
+
emailVerified: false
|
|
28464
|
+
};
|
|
28465
|
+
function depthContexts(contexts, claims) {
|
|
28466
|
+
if (claims) return [...contexts, UNVERIFIED];
|
|
28467
|
+
return contexts.filter(
|
|
28468
|
+
(context) => context.role !== "authenticated" || context.emailVerified === true
|
|
28469
|
+
);
|
|
28470
|
+
}
|
|
28471
|
+
function readsClaims(policies) {
|
|
28472
|
+
return policies.some(
|
|
28473
|
+
(policy) => [policy.using, policy.check].some(
|
|
28474
|
+
(text2) => text2 !== null && /auth\s*\.\s*jwt\s*\(/i.test(text2)
|
|
28475
|
+
)
|
|
28476
|
+
);
|
|
28477
|
+
}
|
|
28478
|
+
function callerName(context) {
|
|
28479
|
+
if (context.role !== "authenticated") return context.role;
|
|
28480
|
+
if (context.emailVerified === true) return "authenticated";
|
|
28481
|
+
return context.emailVerified === false ? "authenticated with no verified email" : "authenticated with no email claims";
|
|
28482
|
+
}
|
|
28483
|
+
function storageRefusals(snapshot, table, policies, measuring, measured) {
|
|
27248
28484
|
const refusals = [];
|
|
27249
28485
|
const row = Object.fromEntries(
|
|
27250
28486
|
table.columns.map((column2) => [column2.name, ""])
|
|
27251
28487
|
);
|
|
27252
|
-
for (const context of
|
|
28488
|
+
for (const context of /* @__PURE__ */ new Set([...CONTEXTS, ...measuring])) {
|
|
27253
28489
|
const using = (commands) => {
|
|
27254
28490
|
const result = storageUsing({
|
|
27255
28491
|
snapshot,
|
|
@@ -27285,25 +28521,33 @@ function storageRefusals(snapshot, table, policies) {
|
|
|
27285
28521
|
overwriteCheckRow({ metadata: "", now: "", version: "" }),
|
|
27286
28522
|
true
|
|
27287
28523
|
);
|
|
27288
|
-
|
|
28524
|
+
const moveCheck = check2("update", moveCheckRow({ to: "" }), true);
|
|
28525
|
+
for (const { label: label2, statement: statement2 } of storageStatements(
|
|
27289
28526
|
select,
|
|
27290
28527
|
update,
|
|
27291
28528
|
remove,
|
|
27292
28529
|
[insertCheck, upsertCheck],
|
|
27293
|
-
updateCheck
|
|
28530
|
+
updateCheck,
|
|
28531
|
+
moveCheck
|
|
27294
28532
|
)) {
|
|
27295
28533
|
const problem = storageStatementProblem(statement2, "select");
|
|
27296
28534
|
if (problem) refusals.push(problem);
|
|
28535
|
+
else if (measuring.includes(context))
|
|
28536
|
+
measured(label2, statement2.sql, callerName(context));
|
|
27297
28537
|
}
|
|
27298
28538
|
}
|
|
27299
28539
|
return refusals;
|
|
27300
28540
|
}
|
|
27301
|
-
function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
28541
|
+
function storageStatements(select, update, remove, insertChecks, updateCheck, moveCheck) {
|
|
27302
28542
|
const statements = [];
|
|
28543
|
+
const add = (label2, ...made) => {
|
|
28544
|
+
for (const statement2 of made) statements.push({ label: label2, statement: statement2 });
|
|
28545
|
+
};
|
|
27303
28546
|
const value = "";
|
|
27304
|
-
for (const insertCheck of insertChecks)
|
|
28547
|
+
for (const [index, insertCheck] of insertChecks.entries())
|
|
27305
28548
|
if (insertCheck)
|
|
27306
|
-
|
|
28549
|
+
add(
|
|
28550
|
+
index === 0 ? "an upload's claim" : "an upsert's claim",
|
|
27307
28551
|
claimUpload({
|
|
27308
28552
|
id: value,
|
|
27309
28553
|
bucket: value,
|
|
@@ -27319,7 +28563,8 @@ function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
|
27319
28563
|
})
|
|
27320
28564
|
);
|
|
27321
28565
|
if (select && update && updateCheck)
|
|
27322
|
-
|
|
28566
|
+
add(
|
|
28567
|
+
"an overwrite's claim",
|
|
27323
28568
|
claimOverwrite({
|
|
27324
28569
|
bucket: value,
|
|
27325
28570
|
name: value,
|
|
@@ -27334,8 +28579,28 @@ function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
|
27334
28579
|
nonce: STORAGE_PROBE_NONCE
|
|
27335
28580
|
})
|
|
27336
28581
|
);
|
|
28582
|
+
if (select && update && moveCheck)
|
|
28583
|
+
add(
|
|
28584
|
+
"a move's claim",
|
|
28585
|
+
...claimMove({
|
|
28586
|
+
id: value,
|
|
28587
|
+
version: value,
|
|
28588
|
+
from: value,
|
|
28589
|
+
to: value,
|
|
28590
|
+
slotId: value,
|
|
28591
|
+
lockId: value,
|
|
28592
|
+
expiresAt: value,
|
|
28593
|
+
limitBytes: 0,
|
|
28594
|
+
selectUsing: select,
|
|
28595
|
+
updateUsing: update,
|
|
28596
|
+
check: moveCheck,
|
|
28597
|
+
nonce: STORAGE_PROBE_NONCE,
|
|
28598
|
+
slotNonce: STORAGE_PROBE_NONCE
|
|
28599
|
+
})
|
|
28600
|
+
);
|
|
27337
28601
|
if (remove)
|
|
27338
|
-
|
|
28602
|
+
add(
|
|
28603
|
+
"a delete's claim",
|
|
27339
28604
|
claimDelete({
|
|
27340
28605
|
bucket: value,
|
|
27341
28606
|
names: [],
|
|
@@ -27344,13 +28609,27 @@ function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
|
27344
28609
|
expiresAt: value
|
|
27345
28610
|
})
|
|
27346
28611
|
);
|
|
27347
|
-
if (select)
|
|
27348
|
-
|
|
27349
|
-
|
|
27350
|
-
|
|
27351
|
-
|
|
27352
|
-
|
|
27353
|
-
|
|
28612
|
+
if (select) {
|
|
28613
|
+
add("a read", readObject2({ bucket: value, name: value, using: select }));
|
|
28614
|
+
add(
|
|
28615
|
+
"a read of several objects",
|
|
28616
|
+
readObjects({ bucket: value, names: [], using: select })
|
|
28617
|
+
);
|
|
28618
|
+
add(
|
|
28619
|
+
"a move's or a copy's read",
|
|
28620
|
+
readTransferSource({ bucket: value, name: value, using: select })
|
|
28621
|
+
);
|
|
28622
|
+
add(
|
|
28623
|
+
"a copy's guard",
|
|
28624
|
+
copySourceGuard({
|
|
28625
|
+
id: value,
|
|
28626
|
+
name: value,
|
|
28627
|
+
using: select,
|
|
28628
|
+
nonce: STORAGE_PROBE_NONCE
|
|
28629
|
+
})
|
|
28630
|
+
);
|
|
28631
|
+
add(
|
|
28632
|
+
"a listing",
|
|
27354
28633
|
listObjects({
|
|
27355
28634
|
bucket: value,
|
|
27356
28635
|
prefix: "p",
|
|
@@ -27361,6 +28640,7 @@ function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
|
27361
28640
|
using: select
|
|
27362
28641
|
})
|
|
27363
28642
|
);
|
|
28643
|
+
}
|
|
27364
28644
|
return statements;
|
|
27365
28645
|
}
|
|
27366
28646
|
var REALTIME_PROBE_CHANNEL = "naro-policy-check";
|
|
@@ -27373,13 +28653,13 @@ function joinReason(reason) {
|
|
|
27373
28653
|
return JOIN_STATEMENT;
|
|
27374
28654
|
return reason;
|
|
27375
28655
|
}
|
|
27376
|
-
function realtimeRefusals(snapshot, policies) {
|
|
28656
|
+
function realtimeRefusals(snapshot, policies, measuring, measured) {
|
|
27377
28657
|
const refusals = [];
|
|
27378
28658
|
const applying = (command) => policies.filter(
|
|
27379
28659
|
(policy) => asciiLower(policy.table) !== REALTIME_MESSAGES || policy.command === command || policy.command === "all"
|
|
27380
28660
|
);
|
|
27381
28661
|
for (const set2 of [policies, applying("select"), applying("insert")])
|
|
27382
|
-
for (const context of
|
|
28662
|
+
for (const context of /* @__PURE__ */ new Set([...CONTEXTS, ...measuring])) {
|
|
27383
28663
|
const plan = privateChannelAccess({
|
|
27384
28664
|
snapshot,
|
|
27385
28665
|
context,
|
|
@@ -27387,7 +28667,11 @@ function realtimeRefusals(snapshot, policies) {
|
|
|
27387
28667
|
channel: REALTIME_PROBE_CHANNEL,
|
|
27388
28668
|
nonce: REALTIME_PROBE_NONCE
|
|
27389
28669
|
});
|
|
27390
|
-
if (plan.ok)
|
|
28670
|
+
if (plan.ok) {
|
|
28671
|
+
if ("statement" in plan && measuring.includes(context))
|
|
28672
|
+
measured("a join", plan.statement.sql, callerName(context));
|
|
28673
|
+
continue;
|
|
28674
|
+
}
|
|
27391
28675
|
const reason = joinReason(plan.reason);
|
|
27392
28676
|
refusals.push(
|
|
27393
28677
|
reason === void 0 ? { error: plan.error } : { error: plan.error, reason }
|
|
@@ -27429,9 +28713,48 @@ function namedInRefusal(message, policies) {
|
|
|
27429
28713
|
}
|
|
27430
28714
|
return found;
|
|
27431
28715
|
}
|
|
27432
|
-
function
|
|
28716
|
+
function probeLabel(query) {
|
|
28717
|
+
switch (query.action) {
|
|
28718
|
+
case "select":
|
|
28719
|
+
return "a select";
|
|
28720
|
+
case "insert":
|
|
28721
|
+
return query.returning === void 0 ? "an insert" : "an insert with returning";
|
|
28722
|
+
case "update":
|
|
28723
|
+
return "an update";
|
|
28724
|
+
case "delete":
|
|
28725
|
+
return "a delete";
|
|
28726
|
+
}
|
|
28727
|
+
}
|
|
28728
|
+
function inlinedTables(sql, probed) {
|
|
28729
|
+
const names = /* @__PURE__ */ new Set();
|
|
28730
|
+
for (const [, written] of sql.matchAll(
|
|
28731
|
+
/FROM ("(?:[^"]|"")*"|[A-Za-z_][A-Za-z0-9_$]*) AS "?_naro_s\d+"?/g
|
|
28732
|
+
)) {
|
|
28733
|
+
if (written === void 0) continue;
|
|
28734
|
+
const name = written.startsWith('"') ? written.slice(1, -1).replaceAll('""', '"') : written;
|
|
28735
|
+
const label2 = name === STORAGE_OBJECTS_TABLE ? STORAGE_OBJECTS : name;
|
|
28736
|
+
if (label2 !== probed) names.add(label2);
|
|
28737
|
+
}
|
|
28738
|
+
return [...names];
|
|
28739
|
+
}
|
|
28740
|
+
function listed(names) {
|
|
28741
|
+
if (names.length < 2) return names.join("");
|
|
28742
|
+
return `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
|
|
28743
|
+
}
|
|
28744
|
+
function tooDeepMessage({
|
|
28745
|
+
table,
|
|
28746
|
+
label: label2,
|
|
28747
|
+
caller,
|
|
28748
|
+
depth,
|
|
28749
|
+
inlined
|
|
28750
|
+
}) {
|
|
28751
|
+
const others = inlined.length === 0 ? "" : `, with those of ${listed(inlined)} that they read,`;
|
|
28752
|
+
return `The policies of ${table}${others} nest an expression more than ${String(D1_EXPRESSION_DEPTH)} deep into one statement: ${label2} as ${caller} is ${String(depth)} deep.`;
|
|
28753
|
+
}
|
|
28754
|
+
function problems(snapshot, policies, options, claims) {
|
|
27433
28755
|
const found = /* @__PURE__ */ new Map();
|
|
27434
28756
|
const loaded = loadNamed(policies);
|
|
28757
|
+
const measuring = depthContexts(CONTEXTS, claims ?? readsClaims(loaded));
|
|
27435
28758
|
for (const policy of loaded)
|
|
27436
28759
|
if (policy.invalid !== null)
|
|
27437
28760
|
found.set(`read:${policy.table}:${policy.name}`, {
|
|
@@ -27493,24 +28816,30 @@ function problems(snapshot, policies, options) {
|
|
|
27493
28816
|
}
|
|
27494
28817
|
}
|
|
27495
28818
|
const probe = loadNamed(
|
|
27496
|
-
[...tables.values()].
|
|
27497
|
-
|
|
27498
|
-
|
|
27499
|
-
|
|
27500
|
-
|
|
27501
|
-
|
|
27502
|
-
|
|
27503
|
-
|
|
27504
|
-
|
|
27505
|
-
|
|
28819
|
+
[...tables.values()].flatMap((table) => {
|
|
28820
|
+
const roles = POLICY_ROLES.filter(
|
|
28821
|
+
(role) => applicablePolicies(loaded, table.name, "select", role).permissive.length === 0
|
|
28822
|
+
);
|
|
28823
|
+
return roles.length === 0 ? [] : [
|
|
28824
|
+
{
|
|
28825
|
+
table: table.name,
|
|
28826
|
+
name: "naro_policy_check",
|
|
28827
|
+
permissive: true,
|
|
28828
|
+
command: "select",
|
|
28829
|
+
roles,
|
|
28830
|
+
using: "true",
|
|
28831
|
+
check: null,
|
|
28832
|
+
owner: null
|
|
28833
|
+
}
|
|
28834
|
+
];
|
|
28835
|
+
})
|
|
27506
28836
|
);
|
|
27507
|
-
const compiled = [...loaded, ...probe];
|
|
27508
28837
|
const refused = (probed, error51, reason) => {
|
|
27509
28838
|
if (error51.code !== "POLICY_INVALID") return;
|
|
27510
|
-
const message = reason === void 0 ? error51.message : `${error51.message.replace(/\.$/, "")}: ${reason}.`;
|
|
28839
|
+
const message = reason === STORAGE_UNAVAILABLE_REASON ? STORAGE_UNAVAILABLE_MESSAGE : reason === void 0 ? error51.message : `${error51.message.replace(/\.$/, "")}: ${reason}.`;
|
|
27511
28840
|
const named = namedInRefusal(error51.message, loaded);
|
|
27512
28841
|
const blamed2 = named === void 0 ? probed : tableLabelOf(snapshot, named.table);
|
|
27513
|
-
const key = `${blamed2}:${message}`;
|
|
28842
|
+
const key = reason === STORAGE_UNAVAILABLE_REASON ? `${blamed2}:${named?.name ?? ""}:${message}` : `${blamed2}:${message}`;
|
|
27514
28843
|
if (!found.has(key))
|
|
27515
28844
|
found.set(key, {
|
|
27516
28845
|
problem: {
|
|
@@ -27522,12 +28851,29 @@ function problems(snapshot, policies, options) {
|
|
|
27522
28851
|
policy: ""
|
|
27523
28852
|
});
|
|
27524
28853
|
};
|
|
28854
|
+
const deepest = /* @__PURE__ */ new Map();
|
|
28855
|
+
const measured = (table) => (label2, sql, caller) => {
|
|
28856
|
+
const depth = expressionDepth(sql);
|
|
28857
|
+
const key = `depth:${table}:${label2}:${caller}`;
|
|
28858
|
+
if (depth !== void 0 && depth > (deepest.get(key)?.depth ?? D1_EXPRESSION_DEPTH))
|
|
28859
|
+
deepest.set(key, {
|
|
28860
|
+
table,
|
|
28861
|
+
label: label2,
|
|
28862
|
+
caller,
|
|
28863
|
+
depth,
|
|
28864
|
+
inlined: inlinedTables(sql, table)
|
|
28865
|
+
});
|
|
28866
|
+
};
|
|
27525
28867
|
for (const table of tables.values()) {
|
|
27526
28868
|
const owners = new Set(
|
|
27527
28869
|
policiesOn(loaded, table.name).flatMap(
|
|
27528
28870
|
(policy) => policy.owner ? [asciiLower(policy.owner)] : []
|
|
27529
28871
|
)
|
|
27530
28872
|
);
|
|
28873
|
+
const compiled = [
|
|
28874
|
+
...loaded,
|
|
28875
|
+
...probe.filter((policy) => policy.table === table.name)
|
|
28876
|
+
];
|
|
27531
28877
|
for (const context of CONTEXTS)
|
|
27532
28878
|
for (const query of probes(table, context, owners)) {
|
|
27533
28879
|
const decision = decideQuery(context, query, compiled, table.name);
|
|
@@ -27538,13 +28884,27 @@ function problems(snapshot, policies, options) {
|
|
|
27538
28884
|
};
|
|
27539
28885
|
if (!result.ok) refused(table.name, result.error, result.reason);
|
|
27540
28886
|
}
|
|
28887
|
+
for (const context of measuring)
|
|
28888
|
+
for (const query of probes(table, context, owners)) {
|
|
28889
|
+
const real = decideQuery(context, query, loaded, table.name);
|
|
28890
|
+
const made = real.allow ? compile(query, snapshot, real, context) : void 0;
|
|
28891
|
+
if (made?.ok)
|
|
28892
|
+
for (const statement2 of made.statements)
|
|
28893
|
+
measured(table.name)(
|
|
28894
|
+
probeLabel(query),
|
|
28895
|
+
statement2.sql,
|
|
28896
|
+
callerName(context)
|
|
28897
|
+
);
|
|
28898
|
+
}
|
|
27541
28899
|
}
|
|
27542
28900
|
if (policiesOn(loaded, STORAGE_OBJECTS).length > 0) {
|
|
27543
28901
|
if (snapshot.storage)
|
|
27544
28902
|
for (const { error: error51, reason } of storageRefusals(
|
|
27545
28903
|
snapshot,
|
|
27546
28904
|
snapshot.storage,
|
|
27547
|
-
loaded
|
|
28905
|
+
loaded,
|
|
28906
|
+
measuring,
|
|
28907
|
+
measured(STORAGE_OBJECTS)
|
|
27548
28908
|
))
|
|
27549
28909
|
refused(STORAGE_OBJECTS, error51, reason);
|
|
27550
28910
|
else
|
|
@@ -27558,8 +28918,22 @@ function problems(snapshot, policies, options) {
|
|
|
27558
28918
|
});
|
|
27559
28919
|
}
|
|
27560
28920
|
if (policiesOn(loaded, REALTIME_MESSAGES).length > 0)
|
|
27561
|
-
for (const { error: error51, reason } of realtimeRefusals(
|
|
28921
|
+
for (const { error: error51, reason } of realtimeRefusals(
|
|
28922
|
+
snapshot,
|
|
28923
|
+
loaded,
|
|
28924
|
+
measuring,
|
|
28925
|
+
measured(REALTIME_MESSAGES)
|
|
28926
|
+
))
|
|
27562
28927
|
refused(REALTIME_MESSAGES, error51, reason);
|
|
28928
|
+
for (const [key, tooDeep] of deepest)
|
|
28929
|
+
found.set(key, {
|
|
28930
|
+
problem: {
|
|
28931
|
+
table: tooDeep.table,
|
|
28932
|
+
message: tooDeepMessage(tooDeep),
|
|
28933
|
+
kind: "compile"
|
|
28934
|
+
},
|
|
28935
|
+
policy: ""
|
|
28936
|
+
});
|
|
27563
28937
|
if (options.indexes && options.allowUnindexed !== true)
|
|
27564
28938
|
for (const problem of unindexedSubqueries(
|
|
27565
28939
|
snapshot,
|
|
@@ -27877,10 +29251,10 @@ function isBlankStart(char) {
|
|
|
27877
29251
|
function isBlank(char) {
|
|
27878
29252
|
return isBlankStart(char) || char === "\v";
|
|
27879
29253
|
}
|
|
27880
|
-
function
|
|
29254
|
+
function isWordStart2(char) {
|
|
27881
29255
|
return /[A-Za-z_]/.test(char) || char.charCodeAt(0) >= 128;
|
|
27882
29256
|
}
|
|
27883
|
-
function
|
|
29257
|
+
function isWordPart2(char) {
|
|
27884
29258
|
return /[A-Za-z0-9_$]/.test(char) || char.charCodeAt(0) >= 128;
|
|
27885
29259
|
}
|
|
27886
29260
|
function skipBlanks(text2, from) {
|
|
@@ -27934,9 +29308,9 @@ function headTokens(text2) {
|
|
|
27934
29308
|
index = close + 1;
|
|
27935
29309
|
} else if ((char === "x" || char === "X") && text2.charAt(index + 1) === "'") {
|
|
27936
29310
|
break;
|
|
27937
|
-
} else if (
|
|
29311
|
+
} else if (isWordStart2(char)) {
|
|
27938
29312
|
let end = index + 1;
|
|
27939
|
-
while (end < text2.length &&
|
|
29313
|
+
while (end < text2.length && isWordPart2(text2.charAt(end))) end++;
|
|
27940
29314
|
tokens.push({ kind: "word", text: text2.slice(index, end) });
|
|
27941
29315
|
index = end;
|
|
27942
29316
|
} else if (char === "." && !/[0-9]/.test(text2.charAt(index + 1))) {
|
|
@@ -27952,6 +29326,10 @@ var NO_NAME = {
|
|
|
27952
29326
|
kind: "unreadable",
|
|
27953
29327
|
reason: "the table name could not be read"
|
|
27954
29328
|
};
|
|
29329
|
+
var NO_COLUMN = {
|
|
29330
|
+
kind: "unreadable",
|
|
29331
|
+
reason: "the column name could not be read"
|
|
29332
|
+
};
|
|
27955
29333
|
var OTHER_SCHEMA = {
|
|
27956
29334
|
kind: "unreadable",
|
|
27957
29335
|
reason: "a table in another schema"
|
|
@@ -27977,8 +29355,9 @@ var Head = class {
|
|
|
27977
29355
|
return token.kind === "word" || token.kind === "name" ? token.text : void 0;
|
|
27978
29356
|
}
|
|
27979
29357
|
/**
|
|
27980
|
-
* SQLite's `fullname`. `main.` is the database a migration writes
|
|
27981
|
-
*
|
|
29358
|
+
* SQLite's `fullname`. `main.` is the database a migration writes: the
|
|
29359
|
+
* name goes without it, and `main` says it was written (known-issues
|
|
29360
|
+
* #226); `temp.` (in any case, quoted or not, as sqlite3FindDbName
|
|
27982
29361
|
* matches it) is a table that goes with the connection; `public.` is
|
|
27983
29362
|
* what a Postgres migration writes, said in its own words; any other
|
|
27984
29363
|
* schema is one D1 does not have.
|
|
@@ -27987,17 +29366,21 @@ var Head = class {
|
|
|
27987
29366
|
const first = this.nameAt(at);
|
|
27988
29367
|
if (first === void 0) return { ok: false, head: NO_NAME };
|
|
27989
29368
|
if (this.token(at + 1).kind !== "dot")
|
|
27990
|
-
return { ok: true, name: first, next: at + 1 };
|
|
29369
|
+
return { ok: true, name: first, next: at + 1, main: false };
|
|
27991
29370
|
const second = this.nameAt(at + 2);
|
|
27992
29371
|
if (second === void 0) return { ok: false, head: NO_NAME };
|
|
27993
29372
|
const schema = asciiLower(first);
|
|
27994
|
-
if (schema === "main")
|
|
29373
|
+
if (schema === "main")
|
|
29374
|
+
return { ok: true, name: second, next: at + 3, main: true };
|
|
27995
29375
|
let head = OTHER_SCHEMA;
|
|
27996
29376
|
if (schema === "temp") head = OTHER;
|
|
27997
29377
|
else if (schema === "public") head = PUBLIC_SCHEMA;
|
|
27998
29378
|
return { ok: false, head, next: at + 3 };
|
|
27999
29379
|
}
|
|
28000
29380
|
};
|
|
29381
|
+
function inMain(read) {
|
|
29382
|
+
return read.main ? { main: true } : {};
|
|
29383
|
+
}
|
|
28001
29384
|
function readCreate2(head) {
|
|
28002
29385
|
if (head.is(1, "policy")) return { kind: "create-policy" };
|
|
28003
29386
|
if (head.is(1, "temp") || head.is(1, "temporary")) return OTHER;
|
|
@@ -28021,9 +29404,24 @@ function readDrop2(head) {
|
|
|
28021
29404
|
at += 2;
|
|
28022
29405
|
}
|
|
28023
29406
|
const table = head.table(at);
|
|
28024
|
-
return table.ok ? { kind: "drop-table", table: table.name } : table.head;
|
|
29407
|
+
return table.ok ? { kind: "drop-table", table: table.name, ...inMain(table) } : table.head;
|
|
28025
29408
|
}
|
|
28026
29409
|
var RLS_ACTIONS = ["enable", "disable", "force", "no"];
|
|
29410
|
+
function readRenameColumn(head, table, at) {
|
|
29411
|
+
let from = at;
|
|
29412
|
+
if (head.is(from, "column") && head.nameAt(from + 1) !== void 0 && head.is(from + 2, "to"))
|
|
29413
|
+
from += 1;
|
|
29414
|
+
const column2 = head.nameAt(from);
|
|
29415
|
+
if (column2 === void 0 || !head.is(from + 1, "to")) return NO_COLUMN;
|
|
29416
|
+
const to = head.nameAt(from + 2);
|
|
29417
|
+
return to === void 0 ? NO_COLUMN : {
|
|
29418
|
+
kind: "rename-column",
|
|
29419
|
+
table: table.name,
|
|
29420
|
+
column: column2,
|
|
29421
|
+
to,
|
|
29422
|
+
...inMain(table)
|
|
29423
|
+
};
|
|
29424
|
+
}
|
|
28027
29425
|
function readAlter(head) {
|
|
28028
29426
|
if (head.is(1, "policy")) return { kind: "alter-policy" };
|
|
28029
29427
|
if (!head.is(1, "table")) return OTHER;
|
|
@@ -28033,9 +29431,18 @@ function readAlter(head) {
|
|
|
28033
29431
|
if (head.is(action, "rename") && head.is(action + 1, "to")) {
|
|
28034
29432
|
if (!sqlite.ok) return sqlite.head;
|
|
28035
29433
|
const to = head.table(action + 2);
|
|
28036
|
-
return to.ok ? {
|
|
29434
|
+
return to.ok ? {
|
|
29435
|
+
kind: "rename-table",
|
|
29436
|
+
table: sqlite.name,
|
|
29437
|
+
to: to.name,
|
|
29438
|
+
...inMain(sqlite)
|
|
29439
|
+
} : to.head;
|
|
29440
|
+
}
|
|
29441
|
+
if (head.is(action, "rename")) {
|
|
29442
|
+
if (!sqlite.ok) return sqlite.head;
|
|
29443
|
+
return readRenameColumn(head, sqlite, action + 1);
|
|
28037
29444
|
}
|
|
28038
|
-
if (head.is(action, "
|
|
29445
|
+
if (head.is(action, "add") || head.is(action, "drop"))
|
|
28039
29446
|
return sqlite.ok ? { kind: "alter-table", table: sqlite.name } : sqlite.head;
|
|
28040
29447
|
}
|
|
28041
29448
|
let at = 2;
|
|
@@ -28060,6 +29467,130 @@ function readSqlHead(text2) {
|
|
|
28060
29467
|
return OTHER;
|
|
28061
29468
|
}
|
|
28062
29469
|
|
|
29470
|
+
// ../../packages/data-core/src/system-sql.ts
|
|
29471
|
+
async function convertLegacyPolicies(db, found) {
|
|
29472
|
+
const rows = rowsOf(await db.prepare(POLICY_SQL.list).all());
|
|
29473
|
+
if (rows.length === 0) return [];
|
|
29474
|
+
const read = rows.map((row) => ({
|
|
29475
|
+
policy: policyFromRow(row),
|
|
29476
|
+
asRead: legacyRowAsRead(row)
|
|
29477
|
+
}));
|
|
29478
|
+
const inForce = new Set(
|
|
29479
|
+
legacyInForce(
|
|
29480
|
+
read.flatMap(({ policy }) => policy ? [policy] : []),
|
|
29481
|
+
await readTableNames(db)
|
|
29482
|
+
)
|
|
29483
|
+
);
|
|
29484
|
+
const statements = [];
|
|
29485
|
+
for (const { policy, asRead } of read) {
|
|
29486
|
+
if (!policy || !asRead) continue;
|
|
29487
|
+
if (inForce.has(policy)) {
|
|
29488
|
+
for (const named of namedFromLegacy(policy))
|
|
29489
|
+
statements.push({
|
|
29490
|
+
sql: POLICY_SQL.insertConverted,
|
|
29491
|
+
params: [...policyValues(named), ...asRead]
|
|
29492
|
+
});
|
|
29493
|
+
found.unreadable.push(...unreadableConversions(policy));
|
|
29494
|
+
}
|
|
29495
|
+
statements.push({ sql: POLICY_SQL.markConverted, params: asRead });
|
|
29496
|
+
}
|
|
29497
|
+
return statements;
|
|
29498
|
+
}
|
|
29499
|
+
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')))";
|
|
29500
|
+
var STORAGE_SUBQUERIES_VERSION = 7;
|
|
29501
|
+
var STORAGE_SUBQUERIES_NAME = "storage_subquery_policies";
|
|
29502
|
+
var STORAGE_SUBQUERIES_SQL = `SELECT version FROM _naro_migrations WHERE version = ${String(STORAGE_SUBQUERIES_VERSION)} AND name = '${STORAGE_SUBQUERIES_NAME}'`;
|
|
29503
|
+
var SYSTEM_MIGRATIONS = [
|
|
29504
|
+
{
|
|
29505
|
+
version: 1,
|
|
29506
|
+
name: "system_tables",
|
|
29507
|
+
statements: [
|
|
29508
|
+
MIGRATIONS_TABLE,
|
|
29509
|
+
"CREATE TABLE IF NOT EXISTS _naro_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL DEFAULT (datetime('now')))",
|
|
29510
|
+
// Spec §11.2, as written there.
|
|
29511
|
+
"CREATE TABLE IF NOT EXISTS _naro_policies (table_name TEXT PRIMARY KEY, select_rule TEXT NOT NULL DEFAULT 'none', insert_rule TEXT NOT NULL DEFAULT 'none', update_rule TEXT NOT NULL DEFAULT 'none', delete_rule TEXT NOT NULL DEFAULT 'none', owner_column TEXT, updated_at TEXT NOT NULL DEFAULT (datetime('now')))"
|
|
29512
|
+
]
|
|
29513
|
+
},
|
|
29514
|
+
{
|
|
29515
|
+
// Spec P2-2 §3.1: an action whose rule column says 'condition' reads its
|
|
29516
|
+
// condition from these. A worker from before them reads 'condition' as
|
|
29517
|
+
// an unknown rule — none.
|
|
29518
|
+
version: 2,
|
|
29519
|
+
name: "policy_conditions",
|
|
29520
|
+
statements: [],
|
|
29521
|
+
addColumns: ["select", "insert", "update", "delete"].map((action) => ({
|
|
29522
|
+
table: "_naro_policies",
|
|
29523
|
+
column: `${action}_condition`,
|
|
29524
|
+
definition: "TEXT"
|
|
29525
|
+
}))
|
|
29526
|
+
},
|
|
29527
|
+
{
|
|
29528
|
+
// Spec P2-6 §11: the Naro worker's sign-in handoff. A provider sign-in's
|
|
29529
|
+
// flow (state → the app's redirect and the browser's PKCE challenge), the
|
|
29530
|
+
// one-time codes a redirect hands the app, the sends the per-address
|
|
29531
|
+
// email limit counts, and (adversarial review I3) the unconfirmed
|
|
29532
|
+
// accounts someone else also tried to sign up as. Times are epoch
|
|
29533
|
+
// milliseconds.
|
|
29534
|
+
version: 3,
|
|
29535
|
+
name: "auth_handoff",
|
|
29536
|
+
statements: [
|
|
29537
|
+
"CREATE TABLE IF NOT EXISTS _naro_auth_flows (state TEXT PRIMARY KEY, provider TEXT NOT NULL, code_challenge TEXT NOT NULL, redirect_to TEXT NOT NULL, expires_at INTEGER NOT NULL)",
|
|
29538
|
+
"CREATE TABLE IF NOT EXISTS _naro_auth_codes (code_hash TEXT PRIMARY KEY, code_challenge TEXT NOT NULL, session_token TEXT NOT NULL, sealed_token TEXT NOT NULL, expires_at INTEGER NOT NULL)",
|
|
29539
|
+
"CREATE TABLE IF NOT EXISTS _naro_auth_email_log (address_hash TEXT NOT NULL, sent_at INTEGER NOT NULL)",
|
|
29540
|
+
"CREATE INDEX IF NOT EXISTS _naro_auth_email_log_address ON _naro_auth_email_log (address_hash, sent_at)",
|
|
29541
|
+
"CREATE TABLE IF NOT EXISTS _naro_auth_contested (user_id TEXT PRIMARY KEY, contested_at INTEGER NOT NULL)"
|
|
29542
|
+
]
|
|
29543
|
+
},
|
|
29544
|
+
{
|
|
29545
|
+
// P2-7 §9: named policies, many per table. The old rows are converted
|
|
29546
|
+
// in the same batch and their rules set to a word old workers read as
|
|
29547
|
+
// `none`.
|
|
29548
|
+
version: 4,
|
|
29549
|
+
name: "named_policies",
|
|
29550
|
+
statements: [NAMED_POLICY_TABLE_SQL],
|
|
29551
|
+
compute: convertLegacyPolicies
|
|
29552
|
+
},
|
|
29553
|
+
{
|
|
29554
|
+
// Storage (first-release design §6): the buckets and the objects' index
|
|
29555
|
+
// with their counter triggers. Every backend gets them, Storage on or
|
|
29556
|
+
// not — what turns it on is the worker's R2 binding. A worker from
|
|
29557
|
+
// before them never reads them.
|
|
29558
|
+
version: 5,
|
|
29559
|
+
name: "storage",
|
|
29560
|
+
statements: [...STORAGE_TABLES_SQL]
|
|
29561
|
+
},
|
|
29562
|
+
{
|
|
29563
|
+
// Storage 3-1b (known-issues #107, first-release design §14): the mark
|
|
29564
|
+
// a bucket's removal sets before it empties the bucket. The claims and
|
|
29565
|
+
// the worker's bucket reads take a marked bucket as not there. A worker
|
|
29566
|
+
// from before it never reads the column.
|
|
29567
|
+
version: 6,
|
|
29568
|
+
name: "storage_bucket_deleting",
|
|
29569
|
+
statements: [],
|
|
29570
|
+
addColumns: [STORAGE_BUCKET_DELETING_COLUMN]
|
|
29571
|
+
},
|
|
29572
|
+
{
|
|
29573
|
+
// Storage 3-1j (plan correction (p)): no schema — the mark that a worker
|
|
29574
|
+
// which reads storage.objects in a policy's subquery has migrated this
|
|
29575
|
+
// database. Only the Naro worker's own `internal/migrate` applies system
|
|
29576
|
+
// migrations, so the row is there once such a worker has run, and stays:
|
|
29577
|
+
// every later worker reads them too — as long as the worker bundle does
|
|
29578
|
+
// not go back. Deploying web from before 3-1j makes the next upgrade
|
|
29579
|
+
// put an older worker back, and a run that lost its lease may upload
|
|
29580
|
+
// one late; such a policy then answers 500 POLICY_INVALID (closed, no
|
|
29581
|
+
// rows) until it is dropped or the newer worker is uploaded again
|
|
29582
|
+
// (3-1j 최종 리뷰). Migration 5 alone cannot tell —
|
|
29583
|
+
// backends migrated by 3-1a to 3-1i have it, and their worker refuses
|
|
29584
|
+
// such a policy ("A policy cannot read storage.objects in a subquery
|
|
29585
|
+
// yet") and answers every request of its table 500 POLICY_INVALID until
|
|
29586
|
+
// the next upgrade. The control plane takes one only where this row is
|
|
29587
|
+
// (`STORAGE_SUBQUERIES_SQL`, `buildPolicyBatch`).
|
|
29588
|
+
version: STORAGE_SUBQUERIES_VERSION,
|
|
29589
|
+
name: STORAGE_SUBQUERIES_NAME,
|
|
29590
|
+
statements: []
|
|
29591
|
+
}
|
|
29592
|
+
];
|
|
29593
|
+
|
|
28063
29594
|
// ../../packages/data-core/src/policy-migration.ts
|
|
28064
29595
|
var GRANT_REFUSAL = "naro has no GRANT or REVOKE: every table is behind row-level security, so write a policy instead (CREATE POLICY \u2026).";
|
|
28065
29596
|
function disableRefusal(table) {
|
|
@@ -28077,6 +29608,7 @@ function tablesChanged(head) {
|
|
|
28077
29608
|
case "create-table":
|
|
28078
29609
|
case "drop-table":
|
|
28079
29610
|
case "alter-table":
|
|
29611
|
+
case "rename-column":
|
|
28080
29612
|
return [asciiLower(head.table)];
|
|
28081
29613
|
case "rename-table":
|
|
28082
29614
|
return [asciiLower(head.table), asciiLower(head.to)];
|
|
@@ -28104,18 +29636,28 @@ function tablesInScope(policy) {
|
|
|
28104
29636
|
}
|
|
28105
29637
|
return tables;
|
|
28106
29638
|
}
|
|
29639
|
+
function readsStorageObjects(policy) {
|
|
29640
|
+
return [policy.using, policy.check].some((text2) => {
|
|
29641
|
+
if (text2 === null) return false;
|
|
29642
|
+
const parsed = parseExpression(text2);
|
|
29643
|
+
return parsed.ok && subqueriesOf(parsed.expr).some(
|
|
29644
|
+
(subquery) => isStorageObjects(subquery.table)
|
|
29645
|
+
);
|
|
29646
|
+
});
|
|
29647
|
+
}
|
|
28107
29648
|
function planPolicyMigration(statements) {
|
|
28108
29649
|
const count2 = statements.length;
|
|
28109
29650
|
const planned = [];
|
|
28110
29651
|
let hasPolicyStatements = false;
|
|
28111
29652
|
let touchesTables = false;
|
|
29653
|
+
let renamesColumns = false;
|
|
28112
29654
|
const heads = statements.map((text2) => readSqlHead(text2));
|
|
28113
29655
|
const changes = heads.map(tablesChanged);
|
|
28114
29656
|
const changedAfter = (index, tables) => changes.slice(index + 1).some(
|
|
28115
29657
|
(changed) => changed === "any" || changed.some((table) => tables.has(table))
|
|
28116
29658
|
);
|
|
28117
29659
|
for (const [index, text2] of statements.entries()) {
|
|
28118
|
-
const
|
|
29660
|
+
const refuse2 = (message) => ({
|
|
28119
29661
|
ok: false,
|
|
28120
29662
|
index,
|
|
28121
29663
|
message: `Statement ${String(index + 1)} of ${String(count2)}: ${message}`
|
|
@@ -28124,15 +29666,19 @@ function planPolicyMigration(statements) {
|
|
|
28124
29666
|
switch (head.kind) {
|
|
28125
29667
|
case "grant":
|
|
28126
29668
|
case "revoke":
|
|
28127
|
-
return
|
|
29669
|
+
return refuse2(GRANT_REFUSAL);
|
|
28128
29670
|
case "unreadable":
|
|
28129
|
-
return
|
|
29671
|
+
return refuse2(`${head.reason}.`);
|
|
28130
29672
|
case "create-table":
|
|
28131
29673
|
case "drop-table":
|
|
28132
29674
|
case "rename-table":
|
|
28133
29675
|
touchesTables = true;
|
|
28134
29676
|
planned.push({ kind: "verbatim", text: text2, head });
|
|
28135
29677
|
continue;
|
|
29678
|
+
case "rename-column":
|
|
29679
|
+
renamesColumns = true;
|
|
29680
|
+
planned.push({ kind: "verbatim", text: text2, head });
|
|
29681
|
+
continue;
|
|
28136
29682
|
case "alter-table":
|
|
28137
29683
|
case "other":
|
|
28138
29684
|
planned.push({ kind: "verbatim", text: text2, head });
|
|
@@ -28142,17 +29688,17 @@ function planPolicyMigration(statements) {
|
|
|
28142
29688
|
}
|
|
28143
29689
|
const parsed = parseMigrationPolicyStatement(text2);
|
|
28144
29690
|
if (!parsed.ok)
|
|
28145
|
-
return
|
|
29691
|
+
return refuse2(
|
|
28146
29692
|
`${parsed.reason} at character ${String(parsed.position)}.`
|
|
28147
29693
|
);
|
|
28148
29694
|
const statement2 = parsed.statement;
|
|
28149
29695
|
const table = statement2.kind === "create" ? statement2.policy.table : statement2.table;
|
|
28150
29696
|
if (!isStorageObjects(table) && isSystemTable(table))
|
|
28151
|
-
return
|
|
29697
|
+
return refuse2(`${relationMissing(table)}.`);
|
|
28152
29698
|
switch (statement2.kind) {
|
|
28153
29699
|
case "create":
|
|
28154
29700
|
if (readsBareSubqueryColumn(statement2.policy) && changedAfter(index, tablesInScope(statement2.policy)))
|
|
28155
|
-
return
|
|
29701
|
+
return refuse2(LATER_TABLE_CHANGE);
|
|
28156
29702
|
hasPolicyStatements = true;
|
|
28157
29703
|
planned.push({ kind: "create-policy", policy: statement2.policy });
|
|
28158
29704
|
break;
|
|
@@ -28180,14 +29726,19 @@ function planPolicyMigration(statements) {
|
|
|
28180
29726
|
break;
|
|
28181
29727
|
case "rls":
|
|
28182
29728
|
if (statement2.action === "disable")
|
|
28183
|
-
return
|
|
29729
|
+
return refuse2(disableRefusal(table));
|
|
28184
29730
|
planned.push({ kind: "noop" });
|
|
28185
29731
|
break;
|
|
28186
29732
|
}
|
|
28187
29733
|
}
|
|
28188
29734
|
return {
|
|
28189
29735
|
ok: true,
|
|
28190
|
-
plan: {
|
|
29736
|
+
plan: {
|
|
29737
|
+
statements: planned,
|
|
29738
|
+
hasPolicyStatements,
|
|
29739
|
+
touchesTables,
|
|
29740
|
+
renamesColumns
|
|
29741
|
+
}
|
|
28191
29742
|
};
|
|
28192
29743
|
}
|
|
28193
29744
|
function policyMigrationSql(policy) {
|
|
@@ -28202,6 +29753,7 @@ function unreadablePolicyComment(policy) {
|
|
|
28202
29753
|
var STORED_POLICIES_SQL = `SELECT table_name, name, kind, command, roles, using_expr, check_expr, owner_column, updated_at FROM ${NAMED_POLICY_TABLE} ORDER BY table_name, name`;
|
|
28203
29754
|
var SYSTEM_PREDICATE = systemTablePredicate("name");
|
|
28204
29755
|
var STORAGE_EXISTS = `EXISTS (SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ${sqlStringLiteral(STORAGE_OBJECTS_TABLE)})`;
|
|
29756
|
+
var STORAGE_SUBQUERIES_READY = `EXISTS (${STORAGE_SUBQUERIES_SQL})`;
|
|
28205
29757
|
|
|
28206
29758
|
// ../../packages/data-core/src/realtime-limits.ts
|
|
28207
29759
|
var HOBBY_REALTIME_LIMITS = Object.freeze({
|
|
@@ -28290,107 +29842,6 @@ var MULTIPART_COMPLETE_LOCK_MS = 15 * 60 * 1e3;
|
|
|
28290
29842
|
var UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
28291
29843
|
var UPLOAD_ID = new RegExp(`^(${UUID})\\.(${UUID})$`);
|
|
28292
29844
|
|
|
28293
|
-
// ../../packages/data-core/src/system-sql.ts
|
|
28294
|
-
async function convertLegacyPolicies(db, found) {
|
|
28295
|
-
const rows = rowsOf(await db.prepare(POLICY_SQL.list).all());
|
|
28296
|
-
if (rows.length === 0) return [];
|
|
28297
|
-
const read = rows.map((row) => ({
|
|
28298
|
-
policy: policyFromRow(row),
|
|
28299
|
-
asRead: legacyRowAsRead(row)
|
|
28300
|
-
}));
|
|
28301
|
-
const inForce = new Set(
|
|
28302
|
-
legacyInForce(
|
|
28303
|
-
read.flatMap(({ policy }) => policy ? [policy] : []),
|
|
28304
|
-
await readTableNames(db)
|
|
28305
|
-
)
|
|
28306
|
-
);
|
|
28307
|
-
const statements = [];
|
|
28308
|
-
for (const { policy, asRead } of read) {
|
|
28309
|
-
if (!policy || !asRead) continue;
|
|
28310
|
-
if (inForce.has(policy)) {
|
|
28311
|
-
for (const named of namedFromLegacy(policy))
|
|
28312
|
-
statements.push({
|
|
28313
|
-
sql: POLICY_SQL.insertConverted,
|
|
28314
|
-
params: [...policyValues(named), ...asRead]
|
|
28315
|
-
});
|
|
28316
|
-
found.unreadable.push(...unreadableConversions(policy));
|
|
28317
|
-
}
|
|
28318
|
-
statements.push({ sql: POLICY_SQL.markConverted, params: asRead });
|
|
28319
|
-
}
|
|
28320
|
-
return statements;
|
|
28321
|
-
}
|
|
28322
|
-
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')))";
|
|
28323
|
-
var SYSTEM_MIGRATIONS = [
|
|
28324
|
-
{
|
|
28325
|
-
version: 1,
|
|
28326
|
-
name: "system_tables",
|
|
28327
|
-
statements: [
|
|
28328
|
-
MIGRATIONS_TABLE,
|
|
28329
|
-
"CREATE TABLE IF NOT EXISTS _naro_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL DEFAULT (datetime('now')))",
|
|
28330
|
-
// Spec §11.2, as written there.
|
|
28331
|
-
"CREATE TABLE IF NOT EXISTS _naro_policies (table_name TEXT PRIMARY KEY, select_rule TEXT NOT NULL DEFAULT 'none', insert_rule TEXT NOT NULL DEFAULT 'none', update_rule TEXT NOT NULL DEFAULT 'none', delete_rule TEXT NOT NULL DEFAULT 'none', owner_column TEXT, updated_at TEXT NOT NULL DEFAULT (datetime('now')))"
|
|
28332
|
-
]
|
|
28333
|
-
},
|
|
28334
|
-
{
|
|
28335
|
-
// Spec P2-2 §3.1: an action whose rule column says 'condition' reads its
|
|
28336
|
-
// condition from these. A worker from before them reads 'condition' as
|
|
28337
|
-
// an unknown rule — none.
|
|
28338
|
-
version: 2,
|
|
28339
|
-
name: "policy_conditions",
|
|
28340
|
-
statements: [],
|
|
28341
|
-
addColumns: ["select", "insert", "update", "delete"].map((action) => ({
|
|
28342
|
-
table: "_naro_policies",
|
|
28343
|
-
column: `${action}_condition`,
|
|
28344
|
-
definition: "TEXT"
|
|
28345
|
-
}))
|
|
28346
|
-
},
|
|
28347
|
-
{
|
|
28348
|
-
// Spec P2-6 §11: the Naro worker's sign-in handoff. A provider sign-in's
|
|
28349
|
-
// flow (state → the app's redirect and the browser's PKCE challenge), the
|
|
28350
|
-
// one-time codes a redirect hands the app, the sends the per-address
|
|
28351
|
-
// email limit counts, and (adversarial review I3) the unconfirmed
|
|
28352
|
-
// accounts someone else also tried to sign up as. Times are epoch
|
|
28353
|
-
// milliseconds.
|
|
28354
|
-
version: 3,
|
|
28355
|
-
name: "auth_handoff",
|
|
28356
|
-
statements: [
|
|
28357
|
-
"CREATE TABLE IF NOT EXISTS _naro_auth_flows (state TEXT PRIMARY KEY, provider TEXT NOT NULL, code_challenge TEXT NOT NULL, redirect_to TEXT NOT NULL, expires_at INTEGER NOT NULL)",
|
|
28358
|
-
"CREATE TABLE IF NOT EXISTS _naro_auth_codes (code_hash TEXT PRIMARY KEY, code_challenge TEXT NOT NULL, session_token TEXT NOT NULL, sealed_token TEXT NOT NULL, expires_at INTEGER NOT NULL)",
|
|
28359
|
-
"CREATE TABLE IF NOT EXISTS _naro_auth_email_log (address_hash TEXT NOT NULL, sent_at INTEGER NOT NULL)",
|
|
28360
|
-
"CREATE INDEX IF NOT EXISTS _naro_auth_email_log_address ON _naro_auth_email_log (address_hash, sent_at)",
|
|
28361
|
-
"CREATE TABLE IF NOT EXISTS _naro_auth_contested (user_id TEXT PRIMARY KEY, contested_at INTEGER NOT NULL)"
|
|
28362
|
-
]
|
|
28363
|
-
},
|
|
28364
|
-
{
|
|
28365
|
-
// P2-7 §9: named policies, many per table. The old rows are converted
|
|
28366
|
-
// in the same batch and their rules set to a word old workers read as
|
|
28367
|
-
// `none`.
|
|
28368
|
-
version: 4,
|
|
28369
|
-
name: "named_policies",
|
|
28370
|
-
statements: [NAMED_POLICY_TABLE_SQL],
|
|
28371
|
-
compute: convertLegacyPolicies
|
|
28372
|
-
},
|
|
28373
|
-
{
|
|
28374
|
-
// Storage (first-release design §6): the buckets and the objects' index
|
|
28375
|
-
// with their counter triggers. Every backend gets them, Storage on or
|
|
28376
|
-
// not — what turns it on is the worker's R2 binding. A worker from
|
|
28377
|
-
// before them never reads them.
|
|
28378
|
-
version: 5,
|
|
28379
|
-
name: "storage",
|
|
28380
|
-
statements: [...STORAGE_TABLES_SQL]
|
|
28381
|
-
},
|
|
28382
|
-
{
|
|
28383
|
-
// Storage 3-1b (known-issues #107, first-release design §14): the mark
|
|
28384
|
-
// a bucket's removal sets before it empties the bucket. The claims and
|
|
28385
|
-
// the worker's bucket reads take a marked bucket as not there. A worker
|
|
28386
|
-
// from before it never reads the column.
|
|
28387
|
-
version: 6,
|
|
28388
|
-
name: "storage_bucket_deleting",
|
|
28389
|
-
statements: [],
|
|
28390
|
-
addColumns: [STORAGE_BUCKET_DELETING_COLUMN]
|
|
28391
|
-
}
|
|
28392
|
-
];
|
|
28393
|
-
|
|
28394
29845
|
// ../../packages/data-core/src/write-quota.ts
|
|
28395
29846
|
var WRITE_QUOTA_KEY = "write_quota";
|
|
28396
29847
|
var WRITE_QUOTA_SQL = {
|
|
@@ -28636,12 +30087,12 @@ import {
|
|
|
28636
30087
|
rmSync as rmSync2,
|
|
28637
30088
|
writeFileSync as writeFileSync2
|
|
28638
30089
|
} from "fs";
|
|
28639
|
-
import { join as
|
|
30090
|
+
import { join as join23 } from "path";
|
|
28640
30091
|
function linkDir(dir) {
|
|
28641
|
-
return
|
|
30092
|
+
return join23(dir, ".naro");
|
|
28642
30093
|
}
|
|
28643
30094
|
function linkFile(dir) {
|
|
28644
|
-
return
|
|
30095
|
+
return join23(linkDir(dir), "project.json");
|
|
28645
30096
|
}
|
|
28646
30097
|
function readProjectLink(dir) {
|
|
28647
30098
|
try {
|
|
@@ -28663,7 +30114,7 @@ function writeProjectLink(dir, link) {
|
|
|
28663
30114
|
writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
|
|
28664
30115
|
`);
|
|
28665
30116
|
writeFileSync2(
|
|
28666
|
-
|
|
30117
|
+
join23(linkDir(dir), "README.txt"),
|
|
28667
30118
|
"This directory links the project to Naro and should not be committed.\n"
|
|
28668
30119
|
);
|
|
28669
30120
|
}
|
|
@@ -28673,9 +30124,9 @@ function removeProjectLink(dir) {
|
|
|
28673
30124
|
return true;
|
|
28674
30125
|
}
|
|
28675
30126
|
function ensureGitignoreHasNaro(dir) {
|
|
28676
|
-
const gitignorePath =
|
|
30127
|
+
const gitignorePath = join23(dir, ".gitignore");
|
|
28677
30128
|
const hasGitignore = existsSync4(gitignorePath);
|
|
28678
|
-
if (!hasGitignore && !existsSync4(
|
|
30129
|
+
if (!hasGitignore && !existsSync4(join23(dir, ".git"))) return false;
|
|
28679
30130
|
const current = hasGitignore ? readFileSync5(gitignorePath, "utf8") : "";
|
|
28680
30131
|
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".naro" || line === ".naro/" || line === "/.naro");
|
|
28681
30132
|
if (covered) return false;
|
|
@@ -29128,15 +30579,15 @@ function policiesFromFiles(files) {
|
|
|
29128
30579
|
policies = policies.filter((policy) => !sameTable(policy.table, table));
|
|
29129
30580
|
};
|
|
29130
30581
|
for (const [file2, sql] of files.entries()) {
|
|
29131
|
-
const
|
|
29132
|
-
if (!
|
|
30582
|
+
const split2 = splitStatements(sql);
|
|
30583
|
+
if (!split2.ok)
|
|
29133
30584
|
return {
|
|
29134
30585
|
policies,
|
|
29135
30586
|
tables,
|
|
29136
|
-
refused: { file: file2, reason: `sql could not be parsed: ${
|
|
30587
|
+
refused: { file: file2, reason: `sql could not be parsed: ${split2.reason}.` }
|
|
29137
30588
|
};
|
|
29138
30589
|
const planned = planPolicyMigration(
|
|
29139
|
-
|
|
30590
|
+
split2.statements.map((statement2) => statement2.text)
|
|
29140
30591
|
);
|
|
29141
30592
|
if (!planned.ok)
|
|
29142
30593
|
return {
|
|
@@ -29204,6 +30655,31 @@ function policiesLostByReset(input) {
|
|
|
29204
30655
|
withTables: gone.filter((policy) => !remade.has(asciiLower(policy.table)))
|
|
29205
30656
|
};
|
|
29206
30657
|
}
|
|
30658
|
+
function storageRefusalOfFiles(files, backend) {
|
|
30659
|
+
for (const [file2, sql] of files.entries()) {
|
|
30660
|
+
const split2 = splitStatements(sql);
|
|
30661
|
+
if (!split2.ok) return void 0;
|
|
30662
|
+
const planned = planPolicyMigration(
|
|
30663
|
+
split2.statements.map((statement2) => statement2.text)
|
|
30664
|
+
);
|
|
30665
|
+
if (!planned.ok) return void 0;
|
|
30666
|
+
const count2 = planned.plan.statements.length;
|
|
30667
|
+
for (const [index, statement2] of planned.plan.statements.entries()) {
|
|
30668
|
+
let needs;
|
|
30669
|
+
if (statement2.kind === "create-policy")
|
|
30670
|
+
needs = readsStorageObjects(statement2.policy) ? "subqueries" : sameTable(statement2.policy.table, STORAGE_OBJECTS) ? "storage" : void 0;
|
|
30671
|
+
else if (statement2.kind === "drop-policy" && !statement2.ifExists || statement2.kind === "owner")
|
|
30672
|
+
needs = sameTable(statement2.table, STORAGE_OBJECTS) ? "storage" : void 0;
|
|
30673
|
+
const missing = needs === "subqueries" ? !backend.storage || !backend.storageSubqueries : needs === "storage" && !backend.storage;
|
|
30674
|
+
if (needs !== void 0 && missing)
|
|
30675
|
+
return { file: file2, statement: index + 1, count: count2, needs };
|
|
30676
|
+
}
|
|
30677
|
+
}
|
|
30678
|
+
return void 0;
|
|
30679
|
+
}
|
|
30680
|
+
function resetStorageOutdatedMessage(file2, at) {
|
|
30681
|
+
return `${file2} needs storage.objects (statement ${String(at.statement)} of ${String(at.count)}), which is not available on this backend yet, so the reset would stop there with the schema already dropped. Nothing was changed. Run \`naro backend enable\` to update it, then try again.`;
|
|
30682
|
+
}
|
|
29207
30683
|
function resetPoliciesLostMessage(lost) {
|
|
29208
30684
|
return `These ${String(lost.length)} policies are not in your migration files and will be gone after the reset: ${lost.map((policy) => `${policy.table}.${policy.name}`).join(", ")}. To keep them, add this to a migration first:`;
|
|
29209
30685
|
}
|
|
@@ -29282,7 +30758,7 @@ function usageStatus(report, currentMonth, requests) {
|
|
|
29282
30758
|
|
|
29283
30759
|
// src/env-consent.ts
|
|
29284
30760
|
import { existsSync as existsSync6 } from "fs";
|
|
29285
|
-
import { join as
|
|
30761
|
+
import { join as join24 } from "path";
|
|
29286
30762
|
var DOTENV_FILES = [".env.local", ".env.production", ".env"];
|
|
29287
30763
|
var PUBLIC_PREFIX = "NEXT_PUBLIC_";
|
|
29288
30764
|
function maskSecretValue(value) {
|
|
@@ -29294,7 +30770,7 @@ function maskEnvValue(key, value) {
|
|
|
29294
30770
|
return maskSecretValue(value);
|
|
29295
30771
|
}
|
|
29296
30772
|
function findDotenvFile(dir) {
|
|
29297
|
-
for (const name of DOTENV_FILES) if (existsSync6(
|
|
30773
|
+
for (const name of DOTENV_FILES) if (existsSync6(join24(dir, name))) return name;
|
|
29298
30774
|
return null;
|
|
29299
30775
|
}
|
|
29300
30776
|
function envAddGuidance(keys, environment) {
|
|
@@ -29525,9 +31001,9 @@ import {
|
|
|
29525
31001
|
cp as cp2,
|
|
29526
31002
|
lstat,
|
|
29527
31003
|
mkdir as mkdir3,
|
|
29528
|
-
readFile as
|
|
31004
|
+
readFile as readFile14,
|
|
29529
31005
|
readlink,
|
|
29530
|
-
realpath,
|
|
31006
|
+
realpath as realpath3,
|
|
29531
31007
|
rm as rm4,
|
|
29532
31008
|
stat as stat3,
|
|
29533
31009
|
symlink,
|
|
@@ -29538,10 +31014,10 @@ import {
|
|
|
29538
31014
|
basename as basename3,
|
|
29539
31015
|
dirname as dirname5,
|
|
29540
31016
|
isAbsolute as isAbsolute4,
|
|
29541
|
-
join as
|
|
31017
|
+
join as join25,
|
|
29542
31018
|
relative as relative6,
|
|
29543
31019
|
resolve as resolve10,
|
|
29544
|
-
sep as
|
|
31020
|
+
sep as sep5
|
|
29545
31021
|
} from "path";
|
|
29546
31022
|
var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.turbo|\.naro)([\\/]|$)/;
|
|
29547
31023
|
var ENV_FILE = /^\.env(\.(?!(?:example|sample|template)$)[^\\/]+)?$/i;
|
|
@@ -29579,7 +31055,7 @@ async function committedEnvFiles(sourceDir) {
|
|
|
29579
31055
|
}
|
|
29580
31056
|
async function staysInside(link, text2, root) {
|
|
29581
31057
|
try {
|
|
29582
|
-
return inside(await
|
|
31058
|
+
return inside(await realpath3(link), root);
|
|
29583
31059
|
} catch (error51) {
|
|
29584
31060
|
return error51.code === "ENOENT" && !text2.split(/[\\/]/).includes("..");
|
|
29585
31061
|
}
|
|
@@ -29602,12 +31078,12 @@ async function keptOnly(items, keep, remove) {
|
|
|
29602
31078
|
async function readLocal(path) {
|
|
29603
31079
|
const found = await stat3(path).catch(() => null);
|
|
29604
31080
|
if (!found?.isFile()) return null;
|
|
29605
|
-
return
|
|
31081
|
+
return readFile14(path).catch(() => null);
|
|
29606
31082
|
}
|
|
29607
31083
|
async function realAncestor(dir) {
|
|
29608
31084
|
for (let current = dir; ; current = dirname5(current)) {
|
|
29609
31085
|
try {
|
|
29610
|
-
return await
|
|
31086
|
+
return await realpath3(current);
|
|
29611
31087
|
} catch {
|
|
29612
31088
|
if (dirname5(current) === current)
|
|
29613
31089
|
throw new Error(`No ancestor of ${dir}`);
|
|
@@ -29618,7 +31094,7 @@ function sameText(a, b) {
|
|
|
29618
31094
|
return a.equals(b) || a.toString("utf8").replace(/\r\n/g, "\n") === b.toString("utf8").replace(/\r\n/g, "\n");
|
|
29619
31095
|
}
|
|
29620
31096
|
async function copyWorkingTree(source, checkout) {
|
|
29621
|
-
const sourceDir = await
|
|
31097
|
+
const sourceDir = await realpath3(source);
|
|
29622
31098
|
const inWorkingTree = [];
|
|
29623
31099
|
const absoluteLinks = [];
|
|
29624
31100
|
const linksOutside = [];
|
|
@@ -29637,12 +31113,12 @@ async function copyWorkingTree(source, checkout) {
|
|
|
29637
31113
|
if (entry.isFIFO() || entry.isSocket() || entry.isCharacterDevice() || entry.isBlockDevice())
|
|
29638
31114
|
return false;
|
|
29639
31115
|
if (ENV_FILE.test(basename3(src)) && !entry.isDirectory()) {
|
|
29640
|
-
inWorkingTree.push(relative6(sourceDir, src).split(
|
|
31116
|
+
inWorkingTree.push(relative6(sourceDir, src).split(sep5).join("/"));
|
|
29641
31117
|
return false;
|
|
29642
31118
|
}
|
|
29643
31119
|
if (entry.isSymbolicLink()) {
|
|
29644
31120
|
const target2 = await readlink(src);
|
|
29645
|
-
const path = relative6(sourceDir, src).split(
|
|
31121
|
+
const path = relative6(sourceDir, src).split(sep5).join("/");
|
|
29646
31122
|
if (isAbsolute4(target2)) {
|
|
29647
31123
|
absoluteLinks.push(path);
|
|
29648
31124
|
return false;
|
|
@@ -29660,17 +31136,17 @@ async function copyWorkingTree(source, checkout) {
|
|
|
29660
31136
|
return true;
|
|
29661
31137
|
}
|
|
29662
31138
|
});
|
|
29663
|
-
const root = await
|
|
31139
|
+
const root = await realpath3(checkout);
|
|
29664
31140
|
const strays = await keptOnly(
|
|
29665
31141
|
keptLinks,
|
|
29666
31142
|
async ({ path, text: text2 }) => {
|
|
29667
31143
|
try {
|
|
29668
|
-
return inside(await
|
|
31144
|
+
return inside(await realpath3(join25(checkout, path)), root);
|
|
29669
31145
|
} catch (error51) {
|
|
29670
31146
|
return error51.code === "ENOENT" && climbsOnlyFirst(text2);
|
|
29671
31147
|
}
|
|
29672
31148
|
},
|
|
29673
|
-
({ path }) => unlink(
|
|
31149
|
+
({ path }) => unlink(join25(checkout, path))
|
|
29674
31150
|
);
|
|
29675
31151
|
linksElsewhere.push(...strays.map(({ path }) => path));
|
|
29676
31152
|
const committed = await committedEnvFiles(sourceDir);
|
|
@@ -29684,7 +31160,7 @@ async function copyWorkingTree(source, checkout) {
|
|
|
29684
31160
|
outsideProject.push(path);
|
|
29685
31161
|
continue;
|
|
29686
31162
|
}
|
|
29687
|
-
const target2 =
|
|
31163
|
+
const target2 = join25(checkout, path);
|
|
29688
31164
|
if (!inside(await realAncestor(dirname5(target2)), root)) {
|
|
29689
31165
|
outsideProject.push(path);
|
|
29690
31166
|
continue;
|
|
@@ -29709,16 +31185,16 @@ async function copyWorkingTree(source, checkout) {
|
|
|
29709
31185
|
continue;
|
|
29710
31186
|
}
|
|
29711
31187
|
await writeFile7(target2, content, { flag: "wx" });
|
|
29712
|
-
const local = await readLocal(
|
|
31188
|
+
const local = await readLocal(join25(sourceDir, path));
|
|
29713
31189
|
if (!local || !sameText(local, content)) committedVersion.push(path);
|
|
29714
31190
|
}
|
|
29715
31191
|
const envStrays = await keptOnly(
|
|
29716
31192
|
envLinks,
|
|
29717
31193
|
async (path) => {
|
|
29718
|
-
const real = await
|
|
31194
|
+
const real = await realpath3(join25(checkout, path)).catch(() => null);
|
|
29719
31195
|
return real !== null && inside(real, root);
|
|
29720
31196
|
},
|
|
29721
|
-
(path) => unlink(
|
|
31197
|
+
(path) => unlink(join25(checkout, path))
|
|
29722
31198
|
);
|
|
29723
31199
|
outsideProject.push(...envStrays);
|
|
29724
31200
|
const inCommit = new Set(
|
|
@@ -29835,7 +31311,7 @@ function slugify2(value) {
|
|
|
29835
31311
|
async function projectName(sourceDir) {
|
|
29836
31312
|
try {
|
|
29837
31313
|
const pkg = JSON.parse(
|
|
29838
|
-
await
|
|
31314
|
+
await readFile15(join26(sourceDir, "package.json"), "utf8")
|
|
29839
31315
|
);
|
|
29840
31316
|
if (pkg.name) return pkg.name;
|
|
29841
31317
|
} catch {
|
|
@@ -29845,7 +31321,7 @@ async function projectName(sourceDir) {
|
|
|
29845
31321
|
function resolveBin(name) {
|
|
29846
31322
|
let dir = dirname6(fileURLToPath(import.meta.url));
|
|
29847
31323
|
for (; ; ) {
|
|
29848
|
-
const candidate =
|
|
31324
|
+
const candidate = join26(dir, "node_modules", ".bin", name);
|
|
29849
31325
|
if (existsSync7(candidate)) return candidate;
|
|
29850
31326
|
const parent = dirname6(dir);
|
|
29851
31327
|
if (parent === dir) return name;
|
|
@@ -29912,7 +31388,7 @@ function workingTreeNotices(copy) {
|
|
|
29912
31388
|
}
|
|
29913
31389
|
function localDependencies(sourceDir, say = () => void 0) {
|
|
29914
31390
|
return {
|
|
29915
|
-
createWorkspace: () => mkdtemp2(
|
|
31391
|
+
createWorkspace: () => mkdtemp2(join26(tmpdir2(), "naro-build-")),
|
|
29916
31392
|
removeWorkspace: (path) => rm5(path, { recursive: true, force: true }),
|
|
29917
31393
|
async runCommand(spec, observer) {
|
|
29918
31394
|
if (spec.command === "git" && spec.args[0] === "clone") {
|
|
@@ -29950,7 +31426,12 @@ function localDependencies(sourceDir, say = () => void 0) {
|
|
|
29950
31426
|
async function deployDirectory(sourceDir, options, log2) {
|
|
29951
31427
|
const cpUrl = controlPlaneUrl();
|
|
29952
31428
|
const production = options.production ?? false;
|
|
29953
|
-
|
|
31429
|
+
if (production && options.environment !== void 0)
|
|
31430
|
+
throw new NaroError(
|
|
31431
|
+
"VALIDATION_FAILED",
|
|
31432
|
+
"environment and production cannot be used together."
|
|
31433
|
+
);
|
|
31434
|
+
const environment = options.environment;
|
|
29954
31435
|
if (environment === "production" || environment === "preview")
|
|
29955
31436
|
throw new NaroError(
|
|
29956
31437
|
"VALIDATION_FAILED",
|
|
@@ -30178,12 +31659,14 @@ export {
|
|
|
30178
31659
|
cellText,
|
|
30179
31660
|
renderRows,
|
|
30180
31661
|
summarizeStatement,
|
|
30181
|
-
|
|
31662
|
+
parseDotenvDetailed,
|
|
30182
31663
|
serializeDotenv,
|
|
30183
31664
|
MAX_SINGLE_UPLOAD_BYTES,
|
|
31665
|
+
checkObjectName,
|
|
30184
31666
|
parseCacheControl,
|
|
30185
31667
|
STORAGE_OBJECTS_TABLE,
|
|
30186
31668
|
systemTablePredicate,
|
|
31669
|
+
dataError,
|
|
30187
31670
|
SCHEMA_SQL,
|
|
30188
31671
|
tableNames,
|
|
30189
31672
|
snapshotFromRows,
|
|
@@ -30198,6 +31681,7 @@ export {
|
|
|
30198
31681
|
indexesFromRows,
|
|
30199
31682
|
parsePolicyStatement,
|
|
30200
31683
|
splitPolicyStatements,
|
|
31684
|
+
STORAGE_SUBQUERIES_SQL,
|
|
30201
31685
|
planPolicyMigration,
|
|
30202
31686
|
unreadablePolicyComment,
|
|
30203
31687
|
parseRedirectEntry,
|
|
@@ -30220,6 +31704,8 @@ export {
|
|
|
30220
31704
|
RESET_BACKEND_OUTDATED,
|
|
30221
31705
|
buildResetPlan,
|
|
30222
31706
|
policiesLostByReset,
|
|
31707
|
+
storageRefusalOfFiles,
|
|
31708
|
+
resetStorageOutdatedMessage,
|
|
30223
31709
|
resetPoliciesLostMessage,
|
|
30224
31710
|
resetPoliciesWithTablesMessage,
|
|
30225
31711
|
resetFileRefusedMessage,
|
|
@@ -30230,7 +31716,7 @@ export {
|
|
|
30230
31716
|
promptForMissingEnv,
|
|
30231
31717
|
pullEnv,
|
|
30232
31718
|
detectGitInfo,
|
|
30233
|
-
parseDotenv2,
|
|
31719
|
+
parseDotenv2 as parseDotenv,
|
|
30234
31720
|
readProjectEnv2 as readProjectEnv,
|
|
30235
31721
|
eventsObserver,
|
|
30236
31722
|
slugify2 as slugify,
|