convex 1.42.2 → 1.43.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -1
- package/bin/main-dev +6 -1
- package/dist/browser.bundle.js +49 -10
- package/dist/browser.bundle.js.map +2 -2
- package/dist/cjs/browser/index.js.map +2 -2
- package/dist/cjs/browser/simple_client-node.js +35 -35
- package/dist/cjs/browser/simple_client-node.js.map +2 -2
- package/dist/cjs/browser/sync/client.js.map +1 -1
- package/dist/cjs/browser/sync/paginated_query_client.js +20 -9
- package/dist/cjs/browser/sync/paginated_query_client.js.map +2 -2
- package/dist/cjs/cli/codegen_templates/component_api.js +2 -2
- package/dist/cjs/cli/codegen_templates/component_api.js.map +2 -2
- package/dist/cjs/cli/codegen_templates/validator_helpers.js +2 -0
- package/dist/cjs/cli/codegen_templates/validator_helpers.js.map +2 -2
- package/dist/cjs/cli/deployment.js +2 -1
- package/dist/cjs/cli/deployment.js.map +2 -2
- package/dist/cjs/cli/deploymentCreate.js.map +2 -2
- package/dist/cjs/cli/generatedApi.js.map +1 -1
- package/dist/cjs/cli/lib/deployApi/validator.js +1 -0
- package/dist/cjs/cli/lib/deployApi/validator.js.map +2 -2
- package/dist/cjs/cli/lib/deploymentSelection.js.map +2 -2
- package/dist/cjs/cli/lib/generateDocs.js +1 -1
- package/dist/cjs/cli/lib/generateDocs.js.map +2 -2
- package/dist/cjs/cli/lib/generatedFunctionLogsApi.js.map +1 -1
- package/dist/cjs/cli/lib/localDeployment/projectMismatch.js.map +2 -2
- package/dist/cjs/cli/lib/login.js +23 -23
- package/dist/cjs/cli/lib/login.js.map +2 -2
- package/dist/cjs/cli/lib/typecheck.js +28 -25
- package/dist/cjs/cli/lib/typecheck.js.map +2 -2
- package/dist/cjs/cli/lib/usageLimits.js +164 -0
- package/dist/cjs/cli/lib/usageLimits.js.map +7 -0
- package/dist/cjs/cli/usageLimits.js +326 -0
- package/dist/cjs/cli/usageLimits.js.map +7 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/client.js.map +2 -2
- package/dist/cjs/server/components/index.js.map +2 -2
- package/dist/cjs/server/cron.js +33 -29
- package/dist/cjs/server/cron.js.map +2 -2
- package/dist/cjs/server/database.js.map +1 -1
- package/dist/cjs/server/impl/database_impl.js +3 -0
- package/dist/cjs/server/impl/database_impl.js.map +2 -2
- package/dist/cjs/server/log.js +2 -1
- package/dist/cjs/server/log.js.map +2 -2
- package/dist/cjs/server/logVars.js +12 -1
- package/dist/cjs/server/logVars.js.map +2 -2
- package/dist/cjs/server/meta.js.map +1 -1
- package/dist/cjs/values/compare.js +5 -1
- package/dist/cjs/values/compare.js.map +2 -2
- package/dist/cjs/values/index.js +1 -0
- package/dist/cjs/values/index.js.map +2 -2
- package/dist/cjs/values/size.js +4 -0
- package/dist/cjs/values/size.js.map +2 -2
- package/dist/cjs/values/validator.js +11 -0
- package/dist/cjs/values/validator.js.map +2 -2
- package/dist/cjs/values/validators.js +20 -0
- package/dist/cjs/values/validators.js.map +2 -2
- package/dist/cjs/values/value.js +31 -0
- package/dist/cjs/values/value.js.map +2 -2
- package/dist/cjs-types/browser/index.d.ts.map +1 -1
- package/dist/cjs-types/browser/sync/paginated_query_client.d.ts +1 -38
- package/dist/cjs-types/browser/sync/paginated_query_client.d.ts.map +1 -1
- package/dist/cjs-types/cli/codegen_templates/component_api.d.ts.map +1 -1
- package/dist/cjs-types/cli/codegen_templates/validator_helpers.d.ts.map +1 -1
- package/dist/cjs-types/cli/codegen_templates/validator_helpers.test.d.ts +2 -0
- package/dist/cjs-types/cli/codegen_templates/validator_helpers.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/deployment.d.ts.map +1 -1
- package/dist/cjs-types/cli/deploymentCreate.d.ts +2 -2
- package/dist/cjs-types/cli/generatedApi.d.ts +43 -43
- package/dist/cjs-types/cli/generatedApi.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/deployApi/validator.d.ts +6 -0
- package/dist/cjs-types/cli/lib/deployApi/validator.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/deploymentSelection.d.ts +2 -2
- package/dist/cjs-types/cli/lib/deploymentSelection.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/generatedFunctionLogsApi.d.ts +45 -45
- package/dist/cjs-types/cli/lib/generatedFunctionLogsApi.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/localDeployment/projectMismatch.d.ts +2 -2
- package/dist/cjs-types/cli/lib/localDeployment/projectMismatch.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/login.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/mcp/tools/insights.d.ts +24 -24
- package/dist/cjs-types/cli/lib/typecheck.d.ts +2 -0
- package/dist/cjs-types/cli/lib/typecheck.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/typecheck.test.d.ts +2 -0
- package/dist/cjs-types/cli/lib/typecheck.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/usageLimits.d.ts +46 -0
- package/dist/cjs-types/cli/lib/usageLimits.d.ts.map +1 -0
- package/dist/cjs-types/cli/usageLimits.d.ts +22 -0
- package/dist/cjs-types/cli/usageLimits.d.ts.map +1 -0
- package/dist/cjs-types/index.d.ts +1 -1
- package/dist/cjs-types/index.d.ts.map +1 -1
- package/dist/cjs-types/react/client.d.ts.map +1 -1
- package/dist/cjs-types/server/components/index.d.ts +26 -8
- package/dist/cjs-types/server/components/index.d.ts.map +1 -1
- package/dist/cjs-types/server/cron.d.ts +20 -11
- package/dist/cjs-types/server/cron.d.ts.map +1 -1
- package/dist/cjs-types/server/cron.test.d.ts +2 -0
- package/dist/cjs-types/server/cron.test.d.ts.map +1 -0
- package/dist/cjs-types/server/database.d.ts.map +1 -1
- package/dist/cjs-types/server/impl/database_impl.d.ts.map +1 -1
- package/dist/cjs-types/server/log.d.ts.map +1 -1
- package/dist/cjs-types/server/logVars.d.ts +14 -1
- package/dist/cjs-types/server/logVars.d.ts.map +1 -1
- package/dist/cjs-types/server/meta.d.ts +2 -2
- package/dist/cjs-types/server/meta.d.ts.map +1 -1
- package/dist/cjs-types/values/compare.d.ts.map +1 -1
- package/dist/cjs-types/values/index.d.ts +2 -2
- package/dist/cjs-types/values/index.d.ts.map +1 -1
- package/dist/cjs-types/values/size.d.ts +1 -1
- package/dist/cjs-types/values/size.d.ts.map +1 -1
- package/dist/cjs-types/values/validator.d.ts +10 -1
- package/dist/cjs-types/values/validator.d.ts.map +1 -1
- package/dist/cjs-types/values/validators.d.ts +14 -3
- package/dist/cjs-types/values/validators.d.ts.map +1 -1
- package/dist/cjs-types/values/value.d.ts +18 -1
- package/dist/cjs-types/values/value.d.ts.map +1 -1
- package/dist/cli.bundle.cjs +2327 -1866
- package/dist/cli.bundle.cjs.map +4 -4
- package/dist/esm/browser/index.js.map +2 -2
- package/dist/esm/browser/simple_client-node.js +35 -35
- package/dist/esm/browser/simple_client-node.js.map +1 -1
- package/dist/esm/browser/sync/client.js.map +1 -1
- package/dist/esm/browser/sync/paginated_query_client.js +20 -9
- package/dist/esm/browser/sync/paginated_query_client.js.map +2 -2
- package/dist/esm/cli/codegen_templates/component_api.js +8 -3
- package/dist/esm/cli/codegen_templates/component_api.js.map +2 -2
- package/dist/esm/cli/codegen_templates/validator_helpers.js +2 -0
- package/dist/esm/cli/codegen_templates/validator_helpers.js.map +2 -2
- package/dist/esm/cli/deployment.js +2 -1
- package/dist/esm/cli/deployment.js.map +2 -2
- package/dist/esm/cli/deploymentCreate.js.map +2 -2
- package/dist/esm/cli/lib/deployApi/validator.js +1 -0
- package/dist/esm/cli/lib/deployApi/validator.js.map +2 -2
- package/dist/esm/cli/lib/deploymentSelection.js.map +2 -2
- package/dist/esm/cli/lib/generateDocs.js +1 -1
- package/dist/esm/cli/lib/generateDocs.js.map +2 -2
- package/dist/esm/cli/lib/localDeployment/projectMismatch.js.map +2 -2
- package/dist/esm/cli/lib/login.js +23 -23
- package/dist/esm/cli/lib/login.js.map +2 -2
- package/dist/esm/cli/lib/typecheck.js +27 -25
- package/dist/esm/cli/lib/typecheck.js.map +2 -2
- package/dist/esm/cli/lib/usageLimits.js +129 -0
- package/dist/esm/cli/lib/usageLimits.js.map +7 -0
- package/dist/esm/cli/usageLimits.js +316 -0
- package/dist/esm/cli/usageLimits.js.map +7 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/client.js.map +2 -2
- package/dist/esm/server/components/index.js.map +2 -2
- package/dist/esm/server/cron.js +33 -29
- package/dist/esm/server/cron.js.map +2 -2
- package/dist/esm/server/impl/database_impl.js +4 -1
- package/dist/esm/server/impl/database_impl.js.map +2 -2
- package/dist/esm/server/log.js +3 -2
- package/dist/esm/server/log.js.map +2 -2
- package/dist/esm/server/logVars.js +11 -1
- package/dist/esm/server/logVars.js.map +2 -2
- package/dist/esm/values/compare.js +5 -1
- package/dist/esm/values/compare.js.map +2 -2
- package/dist/esm/values/index.js +1 -1
- package/dist/esm/values/index.js.map +2 -2
- package/dist/esm/values/size.js +4 -0
- package/dist/esm/values/size.js.map +2 -2
- package/dist/esm/values/validator.js +12 -0
- package/dist/esm/values/validator.js.map +2 -2
- package/dist/esm/values/validators.js +19 -0
- package/dist/esm/values/validators.js.map +2 -2
- package/dist/esm/values/value.js +29 -1
- package/dist/esm/values/value.js.map +2 -2
- package/dist/esm-types/browser/index.d.ts.map +1 -1
- package/dist/esm-types/browser/sync/paginated_query_client.d.ts +1 -38
- package/dist/esm-types/browser/sync/paginated_query_client.d.ts.map +1 -1
- package/dist/esm-types/cli/codegen_templates/component_api.d.ts.map +1 -1
- package/dist/esm-types/cli/codegen_templates/validator_helpers.d.ts.map +1 -1
- package/dist/esm-types/cli/codegen_templates/validator_helpers.test.d.ts +2 -0
- package/dist/esm-types/cli/codegen_templates/validator_helpers.test.d.ts.map +1 -0
- package/dist/esm-types/cli/deployment.d.ts.map +1 -1
- package/dist/esm-types/cli/deploymentCreate.d.ts +2 -2
- package/dist/esm-types/cli/generatedApi.d.ts +43 -43
- package/dist/esm-types/cli/generatedApi.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/deployApi/validator.d.ts +6 -0
- package/dist/esm-types/cli/lib/deployApi/validator.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/deploymentSelection.d.ts +2 -2
- package/dist/esm-types/cli/lib/deploymentSelection.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/generatedFunctionLogsApi.d.ts +45 -45
- package/dist/esm-types/cli/lib/generatedFunctionLogsApi.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/localDeployment/projectMismatch.d.ts +2 -2
- package/dist/esm-types/cli/lib/localDeployment/projectMismatch.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/login.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/mcp/tools/insights.d.ts +24 -24
- package/dist/esm-types/cli/lib/typecheck.d.ts +2 -0
- package/dist/esm-types/cli/lib/typecheck.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/typecheck.test.d.ts +2 -0
- package/dist/esm-types/cli/lib/typecheck.test.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/usageLimits.d.ts +46 -0
- package/dist/esm-types/cli/lib/usageLimits.d.ts.map +1 -0
- package/dist/esm-types/cli/usageLimits.d.ts +22 -0
- package/dist/esm-types/cli/usageLimits.d.ts.map +1 -0
- package/dist/esm-types/index.d.ts +1 -1
- package/dist/esm-types/index.d.ts.map +1 -1
- package/dist/esm-types/react/client.d.ts.map +1 -1
- package/dist/esm-types/server/components/index.d.ts +26 -8
- package/dist/esm-types/server/components/index.d.ts.map +1 -1
- package/dist/esm-types/server/cron.d.ts +20 -11
- package/dist/esm-types/server/cron.d.ts.map +1 -1
- package/dist/esm-types/server/cron.test.d.ts +2 -0
- package/dist/esm-types/server/cron.test.d.ts.map +1 -0
- package/dist/esm-types/server/database.d.ts.map +1 -1
- package/dist/esm-types/server/impl/database_impl.d.ts.map +1 -1
- package/dist/esm-types/server/log.d.ts.map +1 -1
- package/dist/esm-types/server/logVars.d.ts +14 -1
- package/dist/esm-types/server/logVars.d.ts.map +1 -1
- package/dist/esm-types/server/meta.d.ts +2 -2
- package/dist/esm-types/server/meta.d.ts.map +1 -1
- package/dist/esm-types/values/compare.d.ts.map +1 -1
- package/dist/esm-types/values/index.d.ts +2 -2
- package/dist/esm-types/values/index.d.ts.map +1 -1
- package/dist/esm-types/values/size.d.ts +1 -1
- package/dist/esm-types/values/size.d.ts.map +1 -1
- package/dist/esm-types/values/validator.d.ts +10 -1
- package/dist/esm-types/values/validator.d.ts.map +1 -1
- package/dist/esm-types/values/validators.d.ts +14 -3
- package/dist/esm-types/values/validators.d.ts.map +1 -1
- package/dist/esm-types/values/value.d.ts +18 -1
- package/dist/esm-types/values/value.d.ts.map +1 -1
- package/dist/react.bundle.js +53 -11
- package/dist/react.bundle.js.map +2 -2
- package/package.json +8 -8
- package/schemas/convex.schema.json +2 -1
- package/src/browser/index.ts +2 -0
- package/src/browser/sync/client.test.ts +8 -1
- package/src/browser/sync/client.ts +63 -0
- package/src/browser/sync/paginated_query_client.test.ts +209 -1
- package/src/browser/sync/paginated_query_client.ts +29 -11
- package/src/cli/codegen_templates/component_api.ts +8 -3
- package/src/cli/codegen_templates/validator_helpers.test.ts +31 -0
- package/src/cli/codegen_templates/validator_helpers.ts +6 -0
- package/src/cli/deployment.ts +4 -1
- package/src/cli/deploymentCreate.test.ts +2 -2
- package/src/cli/deploymentCreate.ts +2 -2
- package/src/cli/generatedApi.ts +43 -43
- package/src/cli/lib/deployApi/validator.ts +1 -0
- package/src/cli/lib/deploymentSelection.ts +2 -2
- package/src/cli/lib/generateDocs.test.ts +1 -1
- package/src/cli/lib/generateDocs.ts +3 -3
- package/src/cli/lib/generatedFunctionLogsApi.ts +45 -45
- package/src/cli/lib/localDeployment/projectMismatch.ts +2 -2
- package/src/cli/lib/login.ts +33 -30
- package/src/cli/lib/typecheck.test.ts +71 -0
- package/src/cli/lib/typecheck.ts +33 -27
- package/src/cli/lib/usageLimits.ts +242 -0
- package/src/cli/usageLimits.ts +420 -0
- package/src/index.ts +1 -1
- package/src/react/ConvexAuthState.test.tsx +24 -8
- package/src/react/auth_websocket.test.tsx +39 -29
- package/src/react/client.ts +3 -2
- package/src/react/client_provided_base_client.test.tsx +154 -36
- package/src/server/audit_logging.test.ts +2 -0
- package/src/server/components/index.test.ts +33 -1
- package/src/server/components/index.ts +41 -14
- package/src/server/cron.test.ts +58 -0
- package/src/server/cron.ts +82 -21
- package/src/server/database.ts +17 -0
- package/src/server/impl/database_impl.ts +4 -1
- package/src/server/log.ts +4 -1
- package/src/server/logVars.ts +17 -1
- package/src/server/meta.ts +2 -2
- package/src/server/schema.test.ts +6 -1
- package/src/values/compare.ts +5 -2
- package/src/values/index.ts +2 -1
- package/src/values/size.test.ts +5 -0
- package/src/values/size.ts +5 -1
- package/src/values/validator.test.ts +16 -1
- package/src/values/validator.ts +13 -0
- package/src/values/validators.ts +29 -1
- package/src/values/value.test.ts +35 -0
- package/src/values/value.ts +56 -1
|
@@ -82,31 +82,7 @@ export async function typeCheckFunctions(ctx, typescriptCompiler, functionsDir,
|
|
|
82
82
|
);
|
|
83
83
|
}
|
|
84
84
|
async function runTsc(ctx, typescriptCompiler, tscArgs, handleResult) {
|
|
85
|
-
|
|
86
|
-
switch (typescriptCompiler) {
|
|
87
|
-
case "tsgo": {
|
|
88
|
-
const tsgoPaths = ["tsgo", "tsgo.js"].map(
|
|
89
|
-
(filename) => path.join(
|
|
90
|
-
"node_modules",
|
|
91
|
-
"@typescript",
|
|
92
|
-
"native-preview",
|
|
93
|
-
"bin",
|
|
94
|
-
filename
|
|
95
|
-
)
|
|
96
|
-
);
|
|
97
|
-
compilerPath = tsgoPaths.find((path2) => ctx.fs.exists(path2));
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
case "tsc": {
|
|
101
|
-
const tscPath = path.join("node_modules", "typescript", "bin", "tsc");
|
|
102
|
-
if (ctx.fs.exists(tscPath)) {
|
|
103
|
-
compilerPath = tscPath;
|
|
104
|
-
}
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
default:
|
|
108
|
-
typescriptCompiler;
|
|
109
|
-
}
|
|
85
|
+
const compilerPath = findTypeScriptCompilerPath(ctx.fs, typescriptCompiler);
|
|
110
86
|
if (!compilerPath) {
|
|
111
87
|
return handleResult("cantTypeCheck", () => {
|
|
112
88
|
logError(
|
|
@@ -131,6 +107,32 @@ async function runTsc(ctx, typescriptCompiler, tscArgs, handleResult) {
|
|
|
131
107
|
);
|
|
132
108
|
}
|
|
133
109
|
}
|
|
110
|
+
export function findTypeScriptCompilerPath(fs, typescriptCompiler) {
|
|
111
|
+
switch (typescriptCompiler) {
|
|
112
|
+
case "tsgo": {
|
|
113
|
+
const tsgoPaths = ["tsgo", "tsgo.js"].map(
|
|
114
|
+
(filename) => path.join(
|
|
115
|
+
"node_modules",
|
|
116
|
+
"@typescript",
|
|
117
|
+
"native-preview",
|
|
118
|
+
"bin",
|
|
119
|
+
filename
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
return tsgoPaths.find((path2) => fs.exists(path2));
|
|
123
|
+
}
|
|
124
|
+
case "tsc": {
|
|
125
|
+
const tscPaths = [
|
|
126
|
+
// TypeScript 7's recommended alias when the TypeScript 6 API is also installed.
|
|
127
|
+
path.join("node_modules", "@typescript", "native", "bin", "tsc"),
|
|
128
|
+
path.join("node_modules", "typescript", "bin", "tsc")
|
|
129
|
+
];
|
|
130
|
+
return tscPaths.find((path2) => fs.exists(path2));
|
|
131
|
+
}
|
|
132
|
+
default:
|
|
133
|
+
typescriptCompiler;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
134
136
|
async function runTscInner(ctx, tscPath, tscArgs, handleResult) {
|
|
135
137
|
const result = await spawnAsync(ctx, process.execPath, [
|
|
136
138
|
tscPath,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/cli/lib/typecheck.ts"],
|
|
4
|
-
"sourcesContent": ["import { chalkStderr } from \"chalk\";\nimport path from \"path\";\nimport { performance } from \"perf_hooks\";\nimport { Context } from \"../../bundler/context.js\";\nimport {\n changeSpinner,\n logError,\n logFailure,\n logMessage,\n showSpinner,\n} from \"../../bundler/log.js\";\nimport * as Sentry from \"@sentry/node\";\nimport * as semver from \"semver\";\nimport { formatDuration, spawnAsync } from \"./utils/utils.js\";\nimport { readProjectConfig } from \"./config.js\";\nimport { WatchContext } from \"./watch.js\";\n\nexport type TypecheckResult = \"cantTypeCheck\" | \"success\" | \"typecheckFailed\";\n\nexport type TypeCheckMode = \"enable\" | \"try\" | \"disable\";\n\nexport type TypescriptCompiler = \"tsc\" | \"tsgo\";\n\nconst SLOW_TYPECHECK_THRESHOLD_MS = 10_000;\nconst SLOW_TYPECHECK_DOCS_URL =\n \"https://docs.convex.dev/cli/troubleshooting/typecheck-performance\";\n\n/**\n * Resolves the TypeScript compiler to use based on CLI flag, config file, and default.\n * Precedence: CLI flag \u2192 config file \u2192 default \"tsc\"\n */\nexport async function resolveTypescriptCompiler(\n ctx: Context,\n cliOption?: TypescriptCompiler,\n): Promise<TypescriptCompiler> {\n const { projectConfig } = await readProjectConfig(ctx);\n return cliOption ?? projectConfig?.typescriptCompiler ?? \"tsc\";\n}\n\ntype TypecheckResultHandler = (\n result: TypecheckResult,\n logSpecificError?: () => void,\n // If given, we run it to print out errors.\n // We expect it to throw or resolve to \"success\"\n // if a concurrent change invalidated the error result.\n runOnError?: () => Promise<\"success\">,\n) => Promise<void>;\n\n/**\n * Conditionally run a typecheck function and interpret the result.\n *\n * If typeCheckMode === \"disable\", never run the typecheck function.\n * If typeCheckMode === \"enable\", run the typecheck and crash if typechecking\n * fails or we can't find tsc.\n * If typeCheckMode === \"try\", try and run the typecheck. crash if typechecking\n * fails but don't worry if tsc is missing and we can't run it.\n */\nexport async function typeCheckFunctionsInMode(\n ctx: Context,\n typeCheckMode: TypeCheckMode,\n functionsDir: string,\n): Promise<void> {\n if (typeCheckMode === \"disable\") {\n return;\n }\n const typescriptCompiler = await resolveTypescriptCompiler(ctx);\n const typecheckStart = performance.now();\n const slowTypecheckTimeout = setTimeout(() => {\n changeSpinner(\n `Still running TypeScript... see ${SLOW_TYPECHECK_DOCS_URL} to troubleshoot.`,\n );\n }, SLOW_TYPECHECK_THRESHOLD_MS);\n try {\n await typeCheckFunctions(\n ctx,\n typescriptCompiler,\n functionsDir,\n async (result, logSpecificError, runOnError) => {\n if (\n (result === \"cantTypeCheck\" && typeCheckMode === \"enable\") ||\n result === \"typecheckFailed\"\n ) {\n logSpecificError?.();\n logError(\n chalkStderr.gray(\n \"To ignore failing typecheck, use `--typecheck=disable`.\",\n ),\n );\n try {\n const result = await runOnError?.();\n // Concurrent change invalidated the error, don't fail\n if (result === \"success\") {\n return;\n }\n } catch {\n // As expected, `runOnError` threw\n }\n await ctx.crash({\n exitCode: 1,\n errorType: \"invalid filesystem data\",\n printedMessage: null,\n });\n }\n },\n );\n } finally {\n clearTimeout(slowTypecheckTimeout);\n }\n maybeLogSlowTypecheckSuggestion(ctx, performance.now() - typecheckStart);\n}\n\n// Runs TypeScript compiler to typecheck Convex query and mutation functions.\nexport async function typeCheckFunctions(\n ctx: Context,\n typescriptCompiler: TypescriptCompiler,\n functionsDir: string,\n handleResult: TypecheckResultHandler,\n): Promise<void> {\n const tsconfig = path.join(functionsDir, \"tsconfig.json\");\n if (!ctx.fs.exists(tsconfig)) {\n return handleResult(\"cantTypeCheck\", () => {\n logError(\n \"Found no convex/tsconfig.json to use to typecheck Convex functions, so skipping typecheck.\",\n );\n logError(\"Run `npx convex codegen --init` to create one.\");\n });\n }\n await runTsc(\n ctx,\n typescriptCompiler,\n [\"--project\", functionsDir],\n handleResult,\n );\n}\n\nasync function runTsc(\n ctx: Context,\n typescriptCompiler: TypescriptCompiler,\n tscArgs: string[],\n handleResult: TypecheckResultHandler,\n): Promise<void> {\n // Check if the TypeScript compiler is even installed\n let compilerPath: string | undefined;\n switch (typescriptCompiler) {\n case \"tsgo\": {\n // @typescript/native-preview@7.0.0-dev.20260626.1 switched to using `bin/tsgo` instead of `bin/tsgo.js`.\n const tsgoPaths = [\"tsgo\", \"tsgo.js\"].map((filename) =>\n path.join(\n \"node_modules\",\n \"@typescript\",\n \"native-preview\",\n \"bin\",\n filename,\n ),\n );\n compilerPath = tsgoPaths.find((path) => ctx.fs.exists(path));\n break;\n }\n case \"tsc\": {\n const tscPath = path.join(\"node_modules\", \"typescript\", \"bin\", \"tsc\");\n if (ctx.fs.exists(tscPath)) {\n compilerPath = tscPath;\n }\n break;\n }\n default:\n typescriptCompiler satisfies never;\n }\n if (!compilerPath) {\n return handleResult(\"cantTypeCheck\", () => {\n logError(\n chalkStderr.gray(\n `No \\`${typescriptCompiler}\\` binary found, so skipping typecheck.`,\n ),\n );\n });\n }\n\n // Check the TypeScript version matches the recommendation from Convex\n const versionResult = await spawnAsync(ctx, process.execPath, [\n compilerPath,\n \"--version\",\n ]);\n\n const version = versionResult.stdout.match(/Version (.*)/)?.[1] ?? null;\n const hasOlderTypeScriptVersion = version && semver.lt(version, \"4.8.4\");\n\n await runTscInner(ctx, compilerPath, tscArgs, handleResult);\n\n // Print this warning after any logs from running `tsc`\n if (hasOlderTypeScriptVersion) {\n logError(\n chalkStderr.yellow(\n \"Convex works best with TypeScript version 4.8.4 or newer -- npm i --save-dev typescript@latest to update.\",\n ),\n );\n }\n}\n\nasync function runTscInner(\n ctx: Context,\n tscPath: string,\n tscArgs: string[],\n handleResult: TypecheckResultHandler,\n) {\n // Run `tsc` once and have it print out the files it touched. This output won't\n // be very useful if there's an error, but we'll run it again to get a nice\n // user-facing error in this exceptional case.\n // The `--listFiles` command prints out files touched on success or error.\n const result = await spawnAsync(ctx, process.execPath, [\n tscPath,\n ...tscArgs,\n \"--listFiles\",\n ]);\n if (result.status === null) {\n return handleResult(\"typecheckFailed\", () => {\n logFailure(`TypeScript typecheck timed out.`);\n if (result.error) {\n logError(chalkStderr.red(`${result.error.toString()}`));\n }\n });\n }\n // Okay, we may have failed `tsc` but at least it returned. Try to parse its\n // output to discover which files it touched.\n const filesTouched = result.stdout\n .split(\"\\n\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n let anyPathsFound = false;\n for (const fileTouched of filesTouched) {\n const absPath = path.resolve(fileTouched);\n let st;\n try {\n st = ctx.fs.stat(absPath);\n anyPathsFound = true;\n } catch {\n // Just move on if we have a bogus path from `tsc`. We'll log below if\n // we fail to stat *any* of the paths emitted by `tsc`.\n // TODO: Switch to using their JS API so we can get machine readable output.\n continue;\n }\n ctx.fs.registerPath(absPath, st);\n }\n if (filesTouched.length > 0 && !anyPathsFound) {\n const err = new Error(\n `Failed to stat any files emitted by tsc (received ${filesTouched.length})`,\n );\n Sentry.captureException(err);\n }\n\n if (!result.error && result.status === 0) {\n return handleResult(\"success\");\n }\n\n // This is the \"No inputs were found\", which is fine and we shouldn't\n // report it to the user.\n if (result.stdout.startsWith(\"error TS18003\")) {\n return handleResult(\"success\");\n }\n\n // At this point we know that `tsc` failed. Rerun it without `--listFiles`\n // and with stderr redirected to have it print out a nice error.\n return handleResult(\n \"typecheckFailed\",\n () => {\n logFailure(\"TypeScript typecheck via `tsc` failed.\");\n },\n async () => {\n showSpinner(\"Collecting TypeScript errors\");\n await spawnAsync(\n ctx,\n process.execPath,\n [tscPath, ...tscArgs, \"--pretty\", \"true\"],\n {\n stdio: \"inherit\",\n },\n );\n // If this passes, we had a concurrent file change that'll overlap with\n // our observations in the first run. Invalidate our context's filesystem\n // but allow the rest of the system to observe the success.\n ctx.fs.invalidate();\n return \"success\";\n },\n );\n}\n\nfunction maybeLogSlowTypecheckSuggestion(ctx: Context, durationMs: number) {\n if (!(ctx instanceof WatchContext)) {\n return;\n }\n if (!ctx.isFirstPush) {\n return;\n }\n if (durationMs <= SLOW_TYPECHECK_THRESHOLD_MS) {\n return;\n }\n const formattedDuration = formatDuration(durationMs);\n logMessage(\n chalkStderr.gray(\n `Typechecking took ${formattedDuration}. For faster iteration, see ${SLOW_TYPECHECK_DOCS_URL} to troubleshoot typecheck performance.`,\n ),\n );\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,mBAAmB;AAC5B,OAAO,UAAU;AACjB,SAAS,mBAAmB;AAE5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,YAAY,YAAY;AACxB,YAAY,YAAY;AACxB,SAAS,gBAAgB,kBAAkB;AAC3C,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { chalkStderr } from \"chalk\";\nimport path from \"path\";\nimport { performance } from \"perf_hooks\";\nimport { Context } from \"../../bundler/context.js\";\nimport {\n changeSpinner,\n logError,\n logFailure,\n logMessage,\n showSpinner,\n} from \"../../bundler/log.js\";\nimport * as Sentry from \"@sentry/node\";\nimport * as semver from \"semver\";\nimport { formatDuration, spawnAsync } from \"./utils/utils.js\";\nimport { readProjectConfig } from \"./config.js\";\nimport { WatchContext } from \"./watch.js\";\nimport { Filesystem } from \"../../bundler/fs.js\";\n\nexport type TypecheckResult = \"cantTypeCheck\" | \"success\" | \"typecheckFailed\";\n\nexport type TypeCheckMode = \"enable\" | \"try\" | \"disable\";\n\nexport type TypescriptCompiler = \"tsc\" | \"tsgo\";\n\nconst SLOW_TYPECHECK_THRESHOLD_MS = 10_000;\nconst SLOW_TYPECHECK_DOCS_URL =\n \"https://docs.convex.dev/cli/troubleshooting/typecheck-performance\";\n\n/**\n * Resolves the TypeScript compiler to use based on CLI flag, config file, and default.\n * Precedence: CLI flag \u2192 config file \u2192 default \"tsc\"\n */\nexport async function resolveTypescriptCompiler(\n ctx: Context,\n cliOption?: TypescriptCompiler,\n): Promise<TypescriptCompiler> {\n const { projectConfig } = await readProjectConfig(ctx);\n return cliOption ?? projectConfig?.typescriptCompiler ?? \"tsc\";\n}\n\ntype TypecheckResultHandler = (\n result: TypecheckResult,\n logSpecificError?: () => void,\n // If given, we run it to print out errors.\n // We expect it to throw or resolve to \"success\"\n // if a concurrent change invalidated the error result.\n runOnError?: () => Promise<\"success\">,\n) => Promise<void>;\n\n/**\n * Conditionally run a typecheck function and interpret the result.\n *\n * If typeCheckMode === \"disable\", never run the typecheck function.\n * If typeCheckMode === \"enable\", run the typecheck and crash if typechecking\n * fails or we can't find tsc.\n * If typeCheckMode === \"try\", try and run the typecheck. crash if typechecking\n * fails but don't worry if tsc is missing and we can't run it.\n */\nexport async function typeCheckFunctionsInMode(\n ctx: Context,\n typeCheckMode: TypeCheckMode,\n functionsDir: string,\n): Promise<void> {\n if (typeCheckMode === \"disable\") {\n return;\n }\n const typescriptCompiler = await resolveTypescriptCompiler(ctx);\n const typecheckStart = performance.now();\n const slowTypecheckTimeout = setTimeout(() => {\n changeSpinner(\n `Still running TypeScript... see ${SLOW_TYPECHECK_DOCS_URL} to troubleshoot.`,\n );\n }, SLOW_TYPECHECK_THRESHOLD_MS);\n try {\n await typeCheckFunctions(\n ctx,\n typescriptCompiler,\n functionsDir,\n async (result, logSpecificError, runOnError) => {\n if (\n (result === \"cantTypeCheck\" && typeCheckMode === \"enable\") ||\n result === \"typecheckFailed\"\n ) {\n logSpecificError?.();\n logError(\n chalkStderr.gray(\n \"To ignore failing typecheck, use `--typecheck=disable`.\",\n ),\n );\n try {\n const result = await runOnError?.();\n // Concurrent change invalidated the error, don't fail\n if (result === \"success\") {\n return;\n }\n } catch {\n // As expected, `runOnError` threw\n }\n await ctx.crash({\n exitCode: 1,\n errorType: \"invalid filesystem data\",\n printedMessage: null,\n });\n }\n },\n );\n } finally {\n clearTimeout(slowTypecheckTimeout);\n }\n maybeLogSlowTypecheckSuggestion(ctx, performance.now() - typecheckStart);\n}\n\n// Runs TypeScript compiler to typecheck Convex query and mutation functions.\nexport async function typeCheckFunctions(\n ctx: Context,\n typescriptCompiler: TypescriptCompiler,\n functionsDir: string,\n handleResult: TypecheckResultHandler,\n): Promise<void> {\n const tsconfig = path.join(functionsDir, \"tsconfig.json\");\n if (!ctx.fs.exists(tsconfig)) {\n return handleResult(\"cantTypeCheck\", () => {\n logError(\n \"Found no convex/tsconfig.json to use to typecheck Convex functions, so skipping typecheck.\",\n );\n logError(\"Run `npx convex codegen --init` to create one.\");\n });\n }\n await runTsc(\n ctx,\n typescriptCompiler,\n [\"--project\", functionsDir],\n handleResult,\n );\n}\n\nasync function runTsc(\n ctx: Context,\n typescriptCompiler: TypescriptCompiler,\n tscArgs: string[],\n handleResult: TypecheckResultHandler,\n): Promise<void> {\n const compilerPath = findTypeScriptCompilerPath(ctx.fs, typescriptCompiler);\n if (!compilerPath) {\n return handleResult(\"cantTypeCheck\", () => {\n logError(\n chalkStderr.gray(\n `No \\`${typescriptCompiler}\\` binary found, so skipping typecheck.`,\n ),\n );\n });\n }\n\n // Check the TypeScript version matches the recommendation from Convex\n const versionResult = await spawnAsync(ctx, process.execPath, [\n compilerPath,\n \"--version\",\n ]);\n\n const version = versionResult.stdout.match(/Version (.*)/)?.[1] ?? null;\n const hasOlderTypeScriptVersion = version && semver.lt(version, \"4.8.4\");\n\n await runTscInner(ctx, compilerPath, tscArgs, handleResult);\n\n // Print this warning after any logs from running `tsc`\n if (hasOlderTypeScriptVersion) {\n logError(\n chalkStderr.yellow(\n \"Convex works best with TypeScript version 4.8.4 or newer -- npm i --save-dev typescript@latest to update.\",\n ),\n );\n }\n}\n\nexport function findTypeScriptCompilerPath(\n fs: Pick<Filesystem, \"exists\">,\n typescriptCompiler: TypescriptCompiler,\n): string | undefined {\n switch (typescriptCompiler) {\n case \"tsgo\": {\n // @typescript/native-preview@7.0.0-dev.20260626.1 switched to using `bin/tsgo` instead of `bin/tsgo.js`.\n const tsgoPaths = [\"tsgo\", \"tsgo.js\"].map((filename) =>\n path.join(\n \"node_modules\",\n \"@typescript\",\n \"native-preview\",\n \"bin\",\n filename,\n ),\n );\n return tsgoPaths.find((path) => fs.exists(path));\n }\n case \"tsc\": {\n const tscPaths = [\n // TypeScript 7's recommended alias when the TypeScript 6 API is also installed.\n path.join(\"node_modules\", \"@typescript\", \"native\", \"bin\", \"tsc\"),\n path.join(\"node_modules\", \"typescript\", \"bin\", \"tsc\"),\n ];\n return tscPaths.find((path) => fs.exists(path));\n }\n default:\n typescriptCompiler satisfies never;\n }\n}\n\nasync function runTscInner(\n ctx: Context,\n tscPath: string,\n tscArgs: string[],\n handleResult: TypecheckResultHandler,\n) {\n // Run `tsc` once and have it print out the files it touched. This output won't\n // be very useful if there's an error, but we'll run it again to get a nice\n // user-facing error in this exceptional case.\n // The `--listFiles` command prints out files touched on success or error.\n const result = await spawnAsync(ctx, process.execPath, [\n tscPath,\n ...tscArgs,\n \"--listFiles\",\n ]);\n if (result.status === null) {\n return handleResult(\"typecheckFailed\", () => {\n logFailure(`TypeScript typecheck timed out.`);\n if (result.error) {\n logError(chalkStderr.red(`${result.error.toString()}`));\n }\n });\n }\n // Okay, we may have failed `tsc` but at least it returned. Try to parse its\n // output to discover which files it touched.\n const filesTouched = result.stdout\n .split(\"\\n\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n let anyPathsFound = false;\n for (const fileTouched of filesTouched) {\n const absPath = path.resolve(fileTouched);\n let st;\n try {\n st = ctx.fs.stat(absPath);\n anyPathsFound = true;\n } catch {\n // Just move on if we have a bogus path from `tsc`. We'll log below if\n // we fail to stat *any* of the paths emitted by `tsc`.\n // TODO: Switch to using their JS API so we can get machine readable output.\n continue;\n }\n ctx.fs.registerPath(absPath, st);\n }\n if (filesTouched.length > 0 && !anyPathsFound) {\n const err = new Error(\n `Failed to stat any files emitted by tsc (received ${filesTouched.length})`,\n );\n Sentry.captureException(err);\n }\n\n if (!result.error && result.status === 0) {\n return handleResult(\"success\");\n }\n\n // This is the \"No inputs were found\", which is fine and we shouldn't\n // report it to the user.\n if (result.stdout.startsWith(\"error TS18003\")) {\n return handleResult(\"success\");\n }\n\n // At this point we know that `tsc` failed. Rerun it without `--listFiles`\n // and with stderr redirected to have it print out a nice error.\n return handleResult(\n \"typecheckFailed\",\n () => {\n logFailure(\"TypeScript typecheck via `tsc` failed.\");\n },\n async () => {\n showSpinner(\"Collecting TypeScript errors\");\n await spawnAsync(\n ctx,\n process.execPath,\n [tscPath, ...tscArgs, \"--pretty\", \"true\"],\n {\n stdio: \"inherit\",\n },\n );\n // If this passes, we had a concurrent file change that'll overlap with\n // our observations in the first run. Invalidate our context's filesystem\n // but allow the rest of the system to observe the success.\n ctx.fs.invalidate();\n return \"success\";\n },\n );\n}\n\nfunction maybeLogSlowTypecheckSuggestion(ctx: Context, durationMs: number) {\n if (!(ctx instanceof WatchContext)) {\n return;\n }\n if (!ctx.isFirstPush) {\n return;\n }\n if (durationMs <= SLOW_TYPECHECK_THRESHOLD_MS) {\n return;\n }\n const formattedDuration = formatDuration(durationMs);\n logMessage(\n chalkStderr.gray(\n `Typechecking took ${formattedDuration}. For faster iteration, see ${SLOW_TYPECHECK_DOCS_URL} to troubleshoot typecheck performance.`,\n ),\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["result", "path"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
import { deploymentFetch, logAndHandleFetchError } from "./utils/utils.js";
|
|
3
|
+
export const USAGE_LIMIT_METRICS = [
|
|
4
|
+
"functionCalls",
|
|
5
|
+
"queryMutationComputeGbHours",
|
|
6
|
+
"actionComputeConvexGbHours",
|
|
7
|
+
"actionComputeNodeJsGbHours",
|
|
8
|
+
"actionComputeCpuGbHours",
|
|
9
|
+
"databaseIoGb",
|
|
10
|
+
"searchQueryGb",
|
|
11
|
+
"dataEgressGb"
|
|
12
|
+
];
|
|
13
|
+
const _metricsExhaustive = true;
|
|
14
|
+
void _metricsExhaustive;
|
|
15
|
+
export const USAGE_LIMIT_WINDOWS = ["day", "month"];
|
|
16
|
+
export const USAGE_LIMIT_TYPES = ["warning", "disable"];
|
|
17
|
+
export const METRIC_LABELS = {
|
|
18
|
+
functionCalls: "Function calls",
|
|
19
|
+
queryMutationComputeGbHours: "Query/Mutation compute",
|
|
20
|
+
actionComputeConvexGbHours: "Action compute",
|
|
21
|
+
actionComputeNodeJsGbHours: "Action compute (Node.js)",
|
|
22
|
+
actionComputeCpuGbHours: "Action compute (CPU)",
|
|
23
|
+
databaseIoGb: "Database I/O",
|
|
24
|
+
searchQueryGb: "Search queries",
|
|
25
|
+
dataEgressGb: "Data egress"
|
|
26
|
+
};
|
|
27
|
+
export function metricLabel(metric) {
|
|
28
|
+
return METRIC_LABELS[metric] ?? metric;
|
|
29
|
+
}
|
|
30
|
+
async function usageLimitFetch(ctx, deployment, path, init) {
|
|
31
|
+
const fetch = deploymentFetch(ctx, deployment);
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(`/api/v1${path}`, {
|
|
34
|
+
method: init?.method ?? "GET",
|
|
35
|
+
...init?.body !== void 0 ? { body: JSON.stringify(init.body) } : {}
|
|
36
|
+
});
|
|
37
|
+
const text = await response.text();
|
|
38
|
+
return text.length > 0 ? JSON.parse(text) : void 0;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return await logAndHandleFetchError(ctx, e);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function listUsageLimits(ctx, deployment) {
|
|
44
|
+
const result = await usageLimitFetch(
|
|
45
|
+
ctx,
|
|
46
|
+
deployment,
|
|
47
|
+
"/list_usage_limits"
|
|
48
|
+
);
|
|
49
|
+
return result.usageLimits;
|
|
50
|
+
}
|
|
51
|
+
export async function createUsageLimit(ctx, deployment, config) {
|
|
52
|
+
const result = await usageLimitFetch(
|
|
53
|
+
ctx,
|
|
54
|
+
deployment,
|
|
55
|
+
"/create_usage_limit",
|
|
56
|
+
{
|
|
57
|
+
method: "POST",
|
|
58
|
+
body: config
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
return result.usageLimit;
|
|
62
|
+
}
|
|
63
|
+
export async function updateUsageLimit(ctx, deployment, id, config) {
|
|
64
|
+
const result = await usageLimitFetch(
|
|
65
|
+
ctx,
|
|
66
|
+
deployment,
|
|
67
|
+
`/update_usage_limit/${id}`,
|
|
68
|
+
{ method: "POST", body: config }
|
|
69
|
+
);
|
|
70
|
+
return result.usageLimit;
|
|
71
|
+
}
|
|
72
|
+
export async function deleteUsageLimit(ctx, deployment, id) {
|
|
73
|
+
await usageLimitFetch(ctx, deployment, `/delete_usage_limit/${id}`, {
|
|
74
|
+
method: "POST"
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export async function getCurrentUsage(ctx, deployment) {
|
|
78
|
+
return await usageLimitFetch(
|
|
79
|
+
ctx,
|
|
80
|
+
deployment,
|
|
81
|
+
"/get_current_usage"
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
function metricRank(metric) {
|
|
85
|
+
const i = USAGE_LIMIT_METRICS.indexOf(metric);
|
|
86
|
+
return i === -1 ? USAGE_LIMIT_METRICS.length : i;
|
|
87
|
+
}
|
|
88
|
+
const windowRank = (w) => w === "month" ? 0 : 1;
|
|
89
|
+
const typeRank = (t) => t === "warning" ? 0 : 1;
|
|
90
|
+
export function compareUsageLimits(a, b) {
|
|
91
|
+
return metricRank(a.metric) - metricRank(b.metric) || windowRank(a.window) - windowRank(b.window) || typeRank(a.limitType) - typeRank(b.limitType);
|
|
92
|
+
}
|
|
93
|
+
export function compareMetricNames(a, b) {
|
|
94
|
+
return metricRank(a) - metricRank(b);
|
|
95
|
+
}
|
|
96
|
+
function usageInWindow(usage, metric, window) {
|
|
97
|
+
const m = usage.metrics[metric];
|
|
98
|
+
if (m === void 0) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
return window === "day" ? m.usage.current_day : m.usage.current_month;
|
|
102
|
+
}
|
|
103
|
+
export async function listUsageLimitsWithStatus(ctx, deployment) {
|
|
104
|
+
const [limits, usage] = await Promise.all([
|
|
105
|
+
listUsageLimits(ctx, deployment),
|
|
106
|
+
getCurrentUsage(ctx, deployment)
|
|
107
|
+
]);
|
|
108
|
+
const withStatus = limits.map((limit) => {
|
|
109
|
+
const currentUsage = usageInWindow(usage, limit.metric, limit.window);
|
|
110
|
+
const unit = usage.metrics[limit.metric]?.unit ?? null;
|
|
111
|
+
const triggered = limit.enabled && currentUsage !== null && currentUsage >= limit.limit;
|
|
112
|
+
return { ...limit, currentUsage, unit, triggered };
|
|
113
|
+
}).sort(compareUsageLimits);
|
|
114
|
+
return { limits: withStatus, seedStatus: usage.seedStatus };
|
|
115
|
+
}
|
|
116
|
+
export async function findUsageLimitByKey(ctx, deployment, key) {
|
|
117
|
+
const match = (await listUsageLimits(ctx, deployment)).find(
|
|
118
|
+
(limit) => limit.metric === key.metric && limit.window === key.window && limit.limitType === key.limitType
|
|
119
|
+
);
|
|
120
|
+
if (match === void 0) {
|
|
121
|
+
return ctx.crash({
|
|
122
|
+
exitCode: 1,
|
|
123
|
+
errorType: "fatal",
|
|
124
|
+
printedMessage: `error: No ${key.limitType} usage limit on ${key.metric} per ${key.window}${deployment.deploymentNotice}.`
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return match;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=usageLimits.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/cli/lib/usageLimits.ts"],
|
|
4
|
+
"sourcesContent": ["import type {\n GetCurrentUsageResponse,\n UsageLimitConfigRequest,\n UsageLimitConfigResponse,\n UsageLimitMetric,\n} from \"@convex-dev/platform/deploymentApi\";\nimport { Context } from \"../../bundler/context.js\";\nimport { deploymentFetch, logAndHandleFetchError } from \"./utils/utils.js\";\n\nexport type UsageLimitDeployment = {\n deploymentUrl: string;\n adminKey: string;\n};\n\nexport const USAGE_LIMIT_METRICS = [\n \"functionCalls\",\n \"queryMutationComputeGbHours\",\n \"actionComputeConvexGbHours\",\n \"actionComputeNodeJsGbHours\",\n \"actionComputeCpuGbHours\",\n \"databaseIoGb\",\n \"searchQueryGb\",\n \"dataEgressGb\",\n] as const satisfies readonly UsageLimitMetric[];\n\n// Fails to compile if the backend's `UsageLimitMetric` gains a metric not listed\n// above, so the CLI's `--metric` choices and ordering stay complete.\ntype MissingMetric = Exclude<\n UsageLimitMetric,\n (typeof USAGE_LIMIT_METRICS)[number]\n>;\nconst _metricsExhaustive: [MissingMetric] extends [never] ? true : never = true;\nvoid _metricsExhaustive;\n\nexport const USAGE_LIMIT_WINDOWS = [\"day\", \"month\"] as const;\nexport type UsageLimitWindow = (typeof USAGE_LIMIT_WINDOWS)[number];\n\nexport const USAGE_LIMIT_TYPES = [\"warning\", \"disable\"] as const;\nexport type UsageLimitType = (typeof USAGE_LIMIT_TYPES)[number];\n\nexport const METRIC_LABELS: Record<UsageLimitMetric, string> = {\n functionCalls: \"Function calls\",\n queryMutationComputeGbHours: \"Query/Mutation compute\",\n actionComputeConvexGbHours: \"Action compute\",\n actionComputeNodeJsGbHours: \"Action compute (Node.js)\",\n actionComputeCpuGbHours: \"Action compute (CPU)\",\n databaseIoGb: \"Database I/O\",\n searchQueryGb: \"Search queries\",\n dataEgressGb: \"Data egress\",\n};\n\nexport function metricLabel(metric: string): string {\n // Look up permissively: a newer backend may report a metric this CLI's label\n // map doesn't know, in which case fall back to the raw id.\n return (METRIC_LABELS as Record<string, string>)[metric] ?? metric;\n}\n\nasync function usageLimitFetch(\n ctx: Context,\n deployment: UsageLimitDeployment,\n path: string,\n init?: { method?: string; body?: unknown },\n): Promise<unknown> {\n const fetch = deploymentFetch(ctx, deployment);\n try {\n const response = await fetch(`/api/v1${path}`, {\n method: init?.method ?? \"GET\",\n ...(init?.body !== undefined ? { body: JSON.stringify(init.body) } : {}),\n });\n const text = await response.text();\n return text.length > 0 ? JSON.parse(text) : undefined;\n } catch (e) {\n return await logAndHandleFetchError(ctx, e);\n }\n}\n\nexport async function listUsageLimits(\n ctx: Context,\n deployment: UsageLimitDeployment,\n): Promise<UsageLimitConfigResponse[]> {\n const result = (await usageLimitFetch(\n ctx,\n deployment,\n \"/list_usage_limits\",\n )) as {\n usageLimits: UsageLimitConfigResponse[];\n };\n return result.usageLimits;\n}\n\nexport async function createUsageLimit(\n ctx: Context,\n deployment: UsageLimitDeployment,\n config: UsageLimitConfigRequest,\n): Promise<UsageLimitConfigResponse> {\n const result = (await usageLimitFetch(\n ctx,\n deployment,\n \"/create_usage_limit\",\n {\n method: \"POST\",\n body: config,\n },\n )) as { usageLimit: UsageLimitConfigResponse };\n return result.usageLimit;\n}\n\nexport async function updateUsageLimit(\n ctx: Context,\n deployment: UsageLimitDeployment,\n id: string,\n config: UsageLimitConfigRequest,\n): Promise<UsageLimitConfigResponse> {\n const result = (await usageLimitFetch(\n ctx,\n deployment,\n `/update_usage_limit/${id}`,\n { method: \"POST\", body: config },\n )) as { usageLimit: UsageLimitConfigResponse };\n return result.usageLimit;\n}\n\nexport async function deleteUsageLimit(\n ctx: Context,\n deployment: UsageLimitDeployment,\n id: string,\n): Promise<void> {\n await usageLimitFetch(ctx, deployment, `/delete_usage_limit/${id}`, {\n method: \"POST\",\n });\n}\n\nexport async function getCurrentUsage(\n ctx: Context,\n deployment: UsageLimitDeployment,\n): Promise<GetCurrentUsageResponse> {\n return (await usageLimitFetch(\n ctx,\n deployment,\n \"/get_current_usage\",\n )) as GetCurrentUsageResponse;\n}\n\nfunction metricRank(metric: string): number {\n const i = (USAGE_LIMIT_METRICS as readonly string[]).indexOf(metric);\n // Unknown metrics (e.g. a newer backend adds one) sort last, stably.\n return i === -1 ? USAGE_LIMIT_METRICS.length : i;\n}\n\n// Windows coarsest-first and warning-before-disable,.\nconst windowRank = (w: string) => (w === \"month\" ? 0 : 1);\nconst typeRank = (t: string) => (t === \"warning\" ? 0 : 1);\n\nexport function compareUsageLimits(\n a: { metric: string; window: string; limitType: string },\n b: { metric: string; window: string; limitType: string },\n): number {\n return (\n metricRank(a.metric) - metricRank(b.metric) ||\n windowRank(a.window) - windowRank(b.window) ||\n typeRank(a.limitType) - typeRank(b.limitType)\n );\n}\n\nexport function compareMetricNames(a: string, b: string): number {\n return metricRank(a) - metricRank(b);\n}\n\n// A limit annotated with the current usage in its window and whether it's\n// currently triggered.\nexport type UsageLimitStatus = UsageLimitConfigResponse & {\n currentUsage: number | null;\n unit: string | null;\n triggered: boolean;\n};\n\nfunction usageInWindow(\n usage: GetCurrentUsageResponse,\n metric: string,\n window: string,\n): number | null {\n const m = usage.metrics[metric];\n if (m === undefined) {\n return null;\n }\n return window === \"day\" ? m.usage.current_day : m.usage.current_month;\n}\n\n// List usage limits annotated with current usage and triggered state, sorted in\n// dashboard order. Fetches the limits and current usage together.\nexport async function listUsageLimitsWithStatus(\n ctx: Context,\n deployment: UsageLimitDeployment,\n): Promise<{\n limits: UsageLimitStatus[];\n seedStatus: GetCurrentUsageResponse[\"seedStatus\"];\n}> {\n const [limits, usage] = await Promise.all([\n listUsageLimits(ctx, deployment),\n getCurrentUsage(ctx, deployment),\n ]);\n const withStatus = limits\n .map((limit): UsageLimitStatus => {\n const currentUsage = usageInWindow(usage, limit.metric, limit.window);\n const unit = usage.metrics[limit.metric]?.unit ?? null;\n const triggered =\n limit.enabled && currentUsage !== null && currentUsage >= limit.limit;\n return { ...limit, currentUsage, unit, triggered };\n })\n .sort(compareUsageLimits);\n return { limits: withStatus, seedStatus: usage.seedStatus };\n}\n\n// A usage limit is uniquely identified by (metric, window, limitType) \u2014 the\n// same key the backend enforces uniqueness on. The HTTP API addresses limits by\n// document id, so callers who know the natural key list and resolve it here.\nexport type UsageLimitKey = {\n metric: UsageLimitMetric;\n window: string;\n limitType: string;\n};\n\nexport async function findUsageLimitByKey(\n ctx: Context,\n deployment: UsageLimitDeployment & { deploymentNotice: string },\n key: UsageLimitKey,\n): Promise<UsageLimitConfigResponse> {\n const match = (await listUsageLimits(ctx, deployment)).find(\n (limit) =>\n limit.metric === key.metric &&\n limit.window === key.window &&\n limit.limitType === key.limitType,\n );\n if (match === undefined) {\n return ctx.crash({\n exitCode: 1,\n errorType: \"fatal\",\n printedMessage: `error: No ${key.limitType} usage limit on ${key.metric} per ${key.window}${deployment.deploymentNotice}.`,\n });\n }\n return match;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
import { Command, Option } from "@commander-js/extra-typings";
|
|
3
|
+
import { logFinishedStep, logMessage, logOutput } from "../bundler/log.js";
|
|
4
|
+
import { actionDescription } from "./lib/command.js";
|
|
5
|
+
import { ensureHasConvexDependency } from "./lib/utils/utils.js";
|
|
6
|
+
import { withRunningBackend } from "./lib/localDeployment/run.js";
|
|
7
|
+
import { selectEnvDeployment } from "./env.js";
|
|
8
|
+
import {
|
|
9
|
+
compareMetricNames,
|
|
10
|
+
createUsageLimit,
|
|
11
|
+
deleteUsageLimit,
|
|
12
|
+
findUsageLimitByKey,
|
|
13
|
+
getCurrentUsage,
|
|
14
|
+
listUsageLimits,
|
|
15
|
+
listUsageLimitsWithStatus,
|
|
16
|
+
metricLabel,
|
|
17
|
+
updateUsageLimit,
|
|
18
|
+
USAGE_LIMIT_METRICS,
|
|
19
|
+
USAGE_LIMIT_TYPES,
|
|
20
|
+
USAGE_LIMIT_WINDOWS
|
|
21
|
+
} from "./lib/usageLimits.js";
|
|
22
|
+
async function parseLimit(ctx, value) {
|
|
23
|
+
const limit = Number(value);
|
|
24
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
25
|
+
return ctx.crash({
|
|
26
|
+
exitCode: 1,
|
|
27
|
+
errorType: "fatal",
|
|
28
|
+
printedMessage: `error: --limit must be a positive integer, got "${value}".`
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return limit;
|
|
32
|
+
}
|
|
33
|
+
function formatTable(header, rows, rightAlign = []) {
|
|
34
|
+
const right = new Set(rightAlign);
|
|
35
|
+
const widths = header.map(
|
|
36
|
+
(cell, i) => Math.max(cell.length, ...rows.map((row) => (row[i] ?? "").length))
|
|
37
|
+
);
|
|
38
|
+
const pad = (cell, i) => right.has(i) ? cell.padStart(widths[i]) : cell.padEnd(widths[i]);
|
|
39
|
+
const rule = (left, mid, r) => left + widths.map((w) => "\u2500".repeat(w + 2)).join(mid) + r;
|
|
40
|
+
const line = (cells) => "\u2502 " + cells.map((cell, i) => pad(cell ?? "", i)).join(" \u2502 ") + " \u2502";
|
|
41
|
+
return [
|
|
42
|
+
rule("\u250C", "\u252C", "\u2510"),
|
|
43
|
+
line(header),
|
|
44
|
+
rule("\u251C", "\u253C", "\u2524"),
|
|
45
|
+
...rows.map(line),
|
|
46
|
+
rule("\u2514", "\u2534", "\u2518")
|
|
47
|
+
].join("\n");
|
|
48
|
+
}
|
|
49
|
+
const NUMBER_FORMAT_COMPACT = new Intl.NumberFormat("en-US", {
|
|
50
|
+
notation: "compact",
|
|
51
|
+
compactDisplay: "short",
|
|
52
|
+
maximumFractionDigits: 3
|
|
53
|
+
});
|
|
54
|
+
function formatNumberCompact(value) {
|
|
55
|
+
const formatted = NUMBER_FORMAT_COMPACT.format(value);
|
|
56
|
+
return formatted === "-0" ? "0" : formatted;
|
|
57
|
+
}
|
|
58
|
+
const LIMIT_FORMAT = new Intl.NumberFormat("en-US");
|
|
59
|
+
function formatLimitAmount(value) {
|
|
60
|
+
return LIMIT_FORMAT.format(value);
|
|
61
|
+
}
|
|
62
|
+
const NUMBER_FORMAT = new Intl.NumberFormat("en-US");
|
|
63
|
+
function formatNumber(value) {
|
|
64
|
+
const formatted = NUMBER_FORMAT.format(value);
|
|
65
|
+
return formatted === "-0" ? "0" : formatted;
|
|
66
|
+
}
|
|
67
|
+
function unitFor(amount, unit) {
|
|
68
|
+
return amount === 1 && unit === "calls" ? "call" : unit;
|
|
69
|
+
}
|
|
70
|
+
function formatAmount(value, unit) {
|
|
71
|
+
return unit === null ? formatNumberCompact(value) : `${formatNumberCompact(value)} ${unitFor(value, unit)}`;
|
|
72
|
+
}
|
|
73
|
+
function usageLimitRow(limit) {
|
|
74
|
+
const currentUsage = limit.currentUsage === null ? "\u2014" : `${formatAmount(limit.currentUsage, limit.unit)} (${formatNumber(
|
|
75
|
+
Math.round(limit.currentUsage / limit.limit * 100)
|
|
76
|
+
)}%)`;
|
|
77
|
+
return [
|
|
78
|
+
metricLabel(limit.metric),
|
|
79
|
+
limit.window,
|
|
80
|
+
limit.limitType,
|
|
81
|
+
formatAmount(limit.limit, limit.unit),
|
|
82
|
+
currentUsage,
|
|
83
|
+
limit.enabled ? "yes" : "no",
|
|
84
|
+
limit.triggered ? "yes" : "no"
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
const metricOption = new Option(
|
|
88
|
+
"--metric <metric>",
|
|
89
|
+
"The metric to limit."
|
|
90
|
+
).choices(USAGE_LIMIT_METRICS);
|
|
91
|
+
const windowOption = new Option(
|
|
92
|
+
"--window <window>",
|
|
93
|
+
"The window the limit is measured over."
|
|
94
|
+
).choices(USAGE_LIMIT_WINDOWS);
|
|
95
|
+
const typeOption = new Option(
|
|
96
|
+
"--type <type>",
|
|
97
|
+
"`warning` only notifies; `disable` pauses the deployment when exceeded."
|
|
98
|
+
).choices(USAGE_LIMIT_TYPES);
|
|
99
|
+
function seedStatusMessage(seedStatus) {
|
|
100
|
+
return seedStatus === "failed" ? "We couldn't load this deployment's historical usage, so the usage shown below may understate its actual usage. Limits are still enforced going forward." : "Historical usage is still being loaded, so the usage shown below may understate this deployment's actual usage. Check back shortly for accurate totals.";
|
|
101
|
+
}
|
|
102
|
+
const listCmd = new Command("list").summary("List configured usage limits").description(
|
|
103
|
+
[
|
|
104
|
+
"List the usage limits configured on your deployment.",
|
|
105
|
+
"",
|
|
106
|
+
"\u2022 List all usage limits: `npx convex deployment usage-limits list`",
|
|
107
|
+
"\u2022 Print as JSON: `npx convex deployment usage-limits list --json`"
|
|
108
|
+
].join("\n")
|
|
109
|
+
).option("--json", "Output the usage limits as JSON.").configureHelp({ showGlobalOptions: true }).allowExcessArguments(false).action(async (cmdOptions, cmd) => {
|
|
110
|
+
const options = cmd.optsWithGlobals();
|
|
111
|
+
const { ctx, deployment } = await selectEnvDeployment(options);
|
|
112
|
+
await ensureHasConvexDependency(ctx, "deployment usage-limits list");
|
|
113
|
+
await withRunningBackend({
|
|
114
|
+
ctx,
|
|
115
|
+
deployment,
|
|
116
|
+
action: async () => {
|
|
117
|
+
const { limits, seedStatus } = await listUsageLimitsWithStatus(
|
|
118
|
+
ctx,
|
|
119
|
+
deployment
|
|
120
|
+
);
|
|
121
|
+
if (cmdOptions.json) {
|
|
122
|
+
logOutput(JSON.stringify(limits, null, 2));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (limits.length === 0) {
|
|
126
|
+
logMessage(
|
|
127
|
+
`No usage limits configured${deployment.deploymentNotice}.`
|
|
128
|
+
);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (seedStatus !== "complete") {
|
|
132
|
+
logMessage(seedStatusMessage(seedStatus));
|
|
133
|
+
}
|
|
134
|
+
logOutput(
|
|
135
|
+
formatTable(
|
|
136
|
+
[
|
|
137
|
+
"Metric",
|
|
138
|
+
"Window",
|
|
139
|
+
"Type",
|
|
140
|
+
"Limit",
|
|
141
|
+
"Current Usage",
|
|
142
|
+
"Active",
|
|
143
|
+
"Triggered"
|
|
144
|
+
],
|
|
145
|
+
limits.map(usageLimitRow),
|
|
146
|
+
[3, 4]
|
|
147
|
+
)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
const setCmd = new Command("set").summary("Create or update a usage limit").description(
|
|
153
|
+
[
|
|
154
|
+
"Create a usage limit, or update the existing one for the same",
|
|
155
|
+
"(metric, window, type). At most one limit exists per combination.",
|
|
156
|
+
"",
|
|
157
|
+
"\u2022 Set the amount (creates or replaces it):",
|
|
158
|
+
" `npx convex deployment usage-limits set --metric functionCalls --window day --type disable --limit 1000000`",
|
|
159
|
+
"\u2022 Deactivate without deleting: add `--inactive` (use `--active` to re-enable).",
|
|
160
|
+
"\u2022 Toggle active state without changing the amount: omit `--limit`."
|
|
161
|
+
].join("\n")
|
|
162
|
+
).addOption(metricOption.makeOptionMandatory()).addOption(windowOption.makeOptionMandatory()).addOption(typeOption.makeOptionMandatory()).option(
|
|
163
|
+
"--limit <limit>",
|
|
164
|
+
"The limit amount, in the metric's native units. Required when creating; kept as-is when omitted while updating."
|
|
165
|
+
).option("--active", "Enforce the limit (the default for a new limit).").option("--inactive", "Create or leave the limit unenforced.").configureHelp({ showGlobalOptions: true }).allowExcessArguments(false).action(async (cmdOptions, cmd) => {
|
|
166
|
+
const options = cmd.optsWithGlobals();
|
|
167
|
+
const { ctx, deployment } = await selectEnvDeployment(options);
|
|
168
|
+
await ensureHasConvexDependency(ctx, "deployment usage-limits set");
|
|
169
|
+
if (cmdOptions.active && cmdOptions.inactive) {
|
|
170
|
+
return ctx.crash({
|
|
171
|
+
exitCode: 1,
|
|
172
|
+
errorType: "fatal",
|
|
173
|
+
printedMessage: "error: Pass at most one of --active and --inactive."
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const newLimit = cmdOptions.limit === void 0 ? void 0 : await parseLimit(ctx, cmdOptions.limit);
|
|
177
|
+
await withRunningBackend({
|
|
178
|
+
ctx,
|
|
179
|
+
deployment,
|
|
180
|
+
action: async () => {
|
|
181
|
+
const existing = (await listUsageLimits(ctx, deployment)).find(
|
|
182
|
+
(l) => l.metric === cmdOptions.metric && l.window === cmdOptions.window && l.limitType === cmdOptions.type
|
|
183
|
+
);
|
|
184
|
+
const label = `${cmdOptions.type} usage limit on ${metricLabel(cmdOptions.metric)} per ${cmdOptions.window}`;
|
|
185
|
+
if (existing === void 0) {
|
|
186
|
+
if (newLimit === void 0) {
|
|
187
|
+
return ctx.crash({
|
|
188
|
+
exitCode: 1,
|
|
189
|
+
errorType: "fatal",
|
|
190
|
+
printedMessage: "error: --limit is required when creating a usage limit."
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const enabled2 = !cmdOptions.inactive;
|
|
194
|
+
const created = await createUsageLimit(ctx, deployment, {
|
|
195
|
+
metric: cmdOptions.metric,
|
|
196
|
+
window: cmdOptions.window,
|
|
197
|
+
limitType: cmdOptions.type,
|
|
198
|
+
limit: newLimit,
|
|
199
|
+
enabled: enabled2
|
|
200
|
+
});
|
|
201
|
+
logFinishedStep(
|
|
202
|
+
`Created ${label}: ${formatLimitAmount(created.limit)}, ${created.enabled ? "active" : "inactive"}${deployment.deploymentNotice}.`
|
|
203
|
+
);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const enabled = cmdOptions.active ? true : cmdOptions.inactive ? false : existing.enabled;
|
|
207
|
+
const limit = newLimit ?? existing.limit;
|
|
208
|
+
const changes = [];
|
|
209
|
+
if (limit !== existing.limit) {
|
|
210
|
+
changes.push(
|
|
211
|
+
`limit ${formatLimitAmount(existing.limit)} \u2192 ${formatLimitAmount(limit)}`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
if (enabled !== existing.enabled) {
|
|
215
|
+
changes.push(
|
|
216
|
+
`${existing.enabled ? "active" : "inactive"} \u2192 ${enabled ? "active" : "inactive"}`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
if (changes.length === 0) {
|
|
220
|
+
logFinishedStep(
|
|
221
|
+
`No changes to ${label} (${formatLimitAmount(existing.limit)}, ${existing.enabled ? "active" : "inactive"})${deployment.deploymentNotice}.`
|
|
222
|
+
);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
await updateUsageLimit(ctx, deployment, existing.id, {
|
|
226
|
+
metric: existing.metric,
|
|
227
|
+
window: existing.window,
|
|
228
|
+
limitType: existing.limitType,
|
|
229
|
+
limit,
|
|
230
|
+
enabled
|
|
231
|
+
});
|
|
232
|
+
logFinishedStep(
|
|
233
|
+
`Updated ${label}: ${changes.join(", ")}${deployment.deploymentNotice}.`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
const removeCmd = new Command("remove").alias("rm").alias("delete").summary("Delete a usage limit").description(
|
|
239
|
+
[
|
|
240
|
+
"Delete a usage limit, identified by its (metric, window, type).",
|
|
241
|
+
"",
|
|
242
|
+
"\u2022 `npx convex deployment usage-limits remove --metric functionCalls --window day --type warning`"
|
|
243
|
+
].join("\n")
|
|
244
|
+
).addOption(metricOption.makeOptionMandatory()).addOption(windowOption.makeOptionMandatory()).addOption(typeOption.makeOptionMandatory()).configureHelp({ showGlobalOptions: true }).allowExcessArguments(false).action(async (cmdOptions, cmd) => {
|
|
245
|
+
const options = cmd.optsWithGlobals();
|
|
246
|
+
const { ctx, deployment } = await selectEnvDeployment(options);
|
|
247
|
+
await ensureHasConvexDependency(ctx, "deployment usage-limits remove");
|
|
248
|
+
await withRunningBackend({
|
|
249
|
+
ctx,
|
|
250
|
+
deployment,
|
|
251
|
+
action: async () => {
|
|
252
|
+
const existing = await findUsageLimitByKey(ctx, deployment, {
|
|
253
|
+
metric: cmdOptions.metric,
|
|
254
|
+
window: cmdOptions.window,
|
|
255
|
+
limitType: cmdOptions.type
|
|
256
|
+
});
|
|
257
|
+
await deleteUsageLimit(ctx, deployment, existing.id);
|
|
258
|
+
logFinishedStep(
|
|
259
|
+
`Deleted ${existing.limitType} usage limit on ${metricLabel(existing.metric)} per ${existing.window}${deployment.deploymentNotice}.`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
export const usage = new Command("usage").summary("Show current usage for each metric").description(
|
|
265
|
+
[
|
|
266
|
+
"Show usage so far in the current day and calendar month for every metric.",
|
|
267
|
+
"",
|
|
268
|
+
"\u2022 Show current usage: `npx convex deployment usage`",
|
|
269
|
+
"\u2022 Print as JSON: `npx convex deployment usage --json`"
|
|
270
|
+
].join("\n")
|
|
271
|
+
).option("--json", "Output the usage as JSON.").configureHelp({ showGlobalOptions: true }).allowExcessArguments(false).addDeploymentSelectionOptions(actionDescription("Show current usage for")).action(async (cmdOptions, cmd) => {
|
|
272
|
+
const options = cmd.optsWithGlobals();
|
|
273
|
+
const { ctx, deployment } = await selectEnvDeployment(options);
|
|
274
|
+
await ensureHasConvexDependency(ctx, "deployment usage");
|
|
275
|
+
await withRunningBackend({
|
|
276
|
+
ctx,
|
|
277
|
+
deployment,
|
|
278
|
+
action: async () => {
|
|
279
|
+
const usage2 = await getCurrentUsage(ctx, deployment);
|
|
280
|
+
if (cmdOptions.json) {
|
|
281
|
+
logOutput(JSON.stringify(usage2, null, 2));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (usage2.seedStatus !== "complete") {
|
|
285
|
+
logMessage(seedStatusMessage(usage2.seedStatus));
|
|
286
|
+
}
|
|
287
|
+
logOutput(
|
|
288
|
+
formatTable(
|
|
289
|
+
["Metric", "Day", "Month"],
|
|
290
|
+
Object.entries(usage2.metrics).sort(([a], [b]) => compareMetricNames(a, b)).map(([metric, m]) => [
|
|
291
|
+
metricLabel(metric),
|
|
292
|
+
formatAmount(m.usage.current_day, m.unit),
|
|
293
|
+
formatAmount(m.usage.current_month, m.unit)
|
|
294
|
+
])
|
|
295
|
+
)
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
export const usageLimits = new Command("usage-limits").summary("List and configure deployment usage limits").description(
|
|
301
|
+
[
|
|
302
|
+
"List and configure usage limits on your deployment.",
|
|
303
|
+
"",
|
|
304
|
+
"A usage limit either warns or pauses your deployment when a metric",
|
|
305
|
+
"(function calls, database bandwidth, \u2026) crosses a threshold within a",
|
|
306
|
+
"daily or monthly window. Each limit is identified by its",
|
|
307
|
+
"(metric, window, type).",
|
|
308
|
+
"",
|
|
309
|
+
"\u2022 List usage limits: `npx convex deployment usage-limits list`",
|
|
310
|
+
"\u2022 Create or update one: `npx convex deployment usage-limits set --metric functionCalls --window day --type disable --limit 1000000`",
|
|
311
|
+
"\u2022 Delete one: `npx convex deployment usage-limits remove --metric functionCalls --window day --type disable`"
|
|
312
|
+
].join("\n")
|
|
313
|
+
).addCommand(listCmd).addCommand(setCmd).addCommand(removeCmd).helpCommand(false).addDeploymentSelectionOptions(
|
|
314
|
+
actionDescription("List and configure usage limits on")
|
|
315
|
+
);
|
|
316
|
+
//# sourceMappingURL=usageLimits.js.map
|