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
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { Command, Option } from "@commander-js/extra-typings";
|
|
2
|
+
import { Context } from "../bundler/context.js";
|
|
3
|
+
import { logFinishedStep, logMessage, logOutput } from "../bundler/log.js";
|
|
4
|
+
import { DeploymentSelectionOptions } from "./lib/api.js";
|
|
5
|
+
import { actionDescription } from "./lib/command.js";
|
|
6
|
+
import { ensureHasConvexDependency } from "./lib/utils/utils.js";
|
|
7
|
+
import { withRunningBackend } from "./lib/localDeployment/run.js";
|
|
8
|
+
import { selectEnvDeployment } from "./env.js";
|
|
9
|
+
import {
|
|
10
|
+
compareMetricNames,
|
|
11
|
+
createUsageLimit,
|
|
12
|
+
deleteUsageLimit,
|
|
13
|
+
findUsageLimitByKey,
|
|
14
|
+
getCurrentUsage,
|
|
15
|
+
listUsageLimits,
|
|
16
|
+
listUsageLimitsWithStatus,
|
|
17
|
+
metricLabel,
|
|
18
|
+
updateUsageLimit,
|
|
19
|
+
USAGE_LIMIT_METRICS,
|
|
20
|
+
USAGE_LIMIT_TYPES,
|
|
21
|
+
USAGE_LIMIT_WINDOWS,
|
|
22
|
+
UsageLimitStatus,
|
|
23
|
+
} from "./lib/usageLimits.js";
|
|
24
|
+
|
|
25
|
+
async function parseLimit(ctx: Context, value: string): Promise<number> {
|
|
26
|
+
const limit = Number(value);
|
|
27
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
28
|
+
return ctx.crash({
|
|
29
|
+
exitCode: 1,
|
|
30
|
+
errorType: "fatal",
|
|
31
|
+
printedMessage: `error: --limit must be a positive integer, got "${value}".`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return limit;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatTable(
|
|
38
|
+
header: string[],
|
|
39
|
+
rows: string[][],
|
|
40
|
+
rightAlign: number[] = [],
|
|
41
|
+
): string {
|
|
42
|
+
const right = new Set(rightAlign);
|
|
43
|
+
const widths = header.map((cell, i) =>
|
|
44
|
+
Math.max(cell.length, ...rows.map((row) => (row[i] ?? "").length)),
|
|
45
|
+
);
|
|
46
|
+
const pad = (cell: string, i: number) =>
|
|
47
|
+
right.has(i) ? cell.padStart(widths[i]) : cell.padEnd(widths[i]);
|
|
48
|
+
const rule = (left: string, mid: string, r: string) =>
|
|
49
|
+
left + widths.map((w) => "\u2500".repeat(w + 2)).join(mid) + r;
|
|
50
|
+
const line = (cells: string[]) =>
|
|
51
|
+
"\u2502 " +
|
|
52
|
+
cells.map((cell, i) => pad(cell ?? "", i)).join(" \u2502 ") +
|
|
53
|
+
" \u2502";
|
|
54
|
+
return [
|
|
55
|
+
rule("\u250c", "\u252c", "\u2510"),
|
|
56
|
+
line(header),
|
|
57
|
+
rule("\u251c", "\u253c", "\u2524"),
|
|
58
|
+
...rows.map(line),
|
|
59
|
+
rule("\u2514", "\u2534", "\u2518"),
|
|
60
|
+
].join("\n");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const NUMBER_FORMAT_COMPACT = new Intl.NumberFormat("en-US", {
|
|
64
|
+
notation: "compact",
|
|
65
|
+
compactDisplay: "short",
|
|
66
|
+
maximumFractionDigits: 3,
|
|
67
|
+
});
|
|
68
|
+
function formatNumberCompact(value: number): string {
|
|
69
|
+
const formatted = NUMBER_FORMAT_COMPACT.format(value);
|
|
70
|
+
return formatted === "-0" ? "0" : formatted;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Exact, thousands-separated amount for prose (success messages), where a
|
|
74
|
+
// precise value reads clearer than the compact table form.
|
|
75
|
+
const LIMIT_FORMAT = new Intl.NumberFormat("en-US");
|
|
76
|
+
function formatLimitAmount(value: number): string {
|
|
77
|
+
return LIMIT_FORMAT.format(value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const NUMBER_FORMAT = new Intl.NumberFormat("en-US");
|
|
81
|
+
function formatNumber(value: number): string {
|
|
82
|
+
const formatted = NUMBER_FORMAT.format(value);
|
|
83
|
+
return formatted === "-0" ? "0" : formatted;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function unitFor(amount: number, unit: string): string {
|
|
87
|
+
return amount === 1 && unit === "calls" ? "call" : unit;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function formatAmount(value: number, unit: string | null): string {
|
|
91
|
+
return unit === null
|
|
92
|
+
? formatNumberCompact(value)
|
|
93
|
+
: `${formatNumberCompact(value)} ${unitFor(value, unit)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function usageLimitRow(limit: UsageLimitStatus): string[] {
|
|
97
|
+
const currentUsage =
|
|
98
|
+
limit.currentUsage === null
|
|
99
|
+
? "\u2014"
|
|
100
|
+
: `${formatAmount(limit.currentUsage, limit.unit)} (${formatNumber(
|
|
101
|
+
Math.round((limit.currentUsage / limit.limit) * 100),
|
|
102
|
+
)}%)`;
|
|
103
|
+
return [
|
|
104
|
+
metricLabel(limit.metric),
|
|
105
|
+
limit.window,
|
|
106
|
+
limit.limitType,
|
|
107
|
+
formatAmount(limit.limit, limit.unit),
|
|
108
|
+
currentUsage,
|
|
109
|
+
limit.enabled ? "yes" : "no",
|
|
110
|
+
limit.triggered ? "yes" : "no",
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// The (metric, window, type) triple uniquely identifies a usage limit. `set`
|
|
115
|
+
// requires all three plus a limit; `update`/`remove` reuse them to locate an
|
|
116
|
+
// existing limit without needing its opaque id.
|
|
117
|
+
const metricOption = new Option(
|
|
118
|
+
"--metric <metric>",
|
|
119
|
+
"The metric to limit.",
|
|
120
|
+
).choices(USAGE_LIMIT_METRICS);
|
|
121
|
+
const windowOption = new Option(
|
|
122
|
+
"--window <window>",
|
|
123
|
+
"The window the limit is measured over.",
|
|
124
|
+
).choices(USAGE_LIMIT_WINDOWS);
|
|
125
|
+
const typeOption = new Option(
|
|
126
|
+
"--type <type>",
|
|
127
|
+
"`warning` only notifies; `disable` pauses the deployment when exceeded.",
|
|
128
|
+
).choices(USAGE_LIMIT_TYPES);
|
|
129
|
+
|
|
130
|
+
function seedStatusMessage(
|
|
131
|
+
seedStatus: "pending" | "partial" | "failed",
|
|
132
|
+
): string {
|
|
133
|
+
return seedStatus === "failed"
|
|
134
|
+
? "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."
|
|
135
|
+
: "Historical usage is still being loaded, so the usage shown below may understate this deployment's actual usage. Check back shortly for accurate totals.";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const listCmd = new Command("list")
|
|
139
|
+
.summary("List configured usage limits")
|
|
140
|
+
.description(
|
|
141
|
+
[
|
|
142
|
+
"List the usage limits configured on your deployment.",
|
|
143
|
+
"",
|
|
144
|
+
"• List all usage limits: `npx convex deployment usage-limits list`",
|
|
145
|
+
"• Print as JSON: `npx convex deployment usage-limits list --json`",
|
|
146
|
+
].join("\n"),
|
|
147
|
+
)
|
|
148
|
+
.option("--json", "Output the usage limits as JSON.")
|
|
149
|
+
.configureHelp({ showGlobalOptions: true })
|
|
150
|
+
.allowExcessArguments(false)
|
|
151
|
+
.action(async (cmdOptions, cmd) => {
|
|
152
|
+
const options = cmd.optsWithGlobals() as DeploymentSelectionOptions & {
|
|
153
|
+
json?: boolean;
|
|
154
|
+
};
|
|
155
|
+
const { ctx, deployment } = await selectEnvDeployment(options);
|
|
156
|
+
await ensureHasConvexDependency(ctx, "deployment usage-limits list");
|
|
157
|
+
await withRunningBackend({
|
|
158
|
+
ctx,
|
|
159
|
+
deployment,
|
|
160
|
+
action: async () => {
|
|
161
|
+
const { limits, seedStatus } = await listUsageLimitsWithStatus(
|
|
162
|
+
ctx,
|
|
163
|
+
deployment,
|
|
164
|
+
);
|
|
165
|
+
if (cmdOptions.json) {
|
|
166
|
+
logOutput(JSON.stringify(limits, null, 2));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (limits.length === 0) {
|
|
170
|
+
logMessage(
|
|
171
|
+
`No usage limits configured${deployment.deploymentNotice}.`,
|
|
172
|
+
);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// "Triggered" is derived from reported usage, which a non-`complete`
|
|
176
|
+
// backfill may understate.
|
|
177
|
+
if (seedStatus !== "complete") {
|
|
178
|
+
logMessage(seedStatusMessage(seedStatus));
|
|
179
|
+
}
|
|
180
|
+
logOutput(
|
|
181
|
+
formatTable(
|
|
182
|
+
[
|
|
183
|
+
"Metric",
|
|
184
|
+
"Window",
|
|
185
|
+
"Type",
|
|
186
|
+
"Limit",
|
|
187
|
+
"Current Usage",
|
|
188
|
+
"Active",
|
|
189
|
+
"Triggered",
|
|
190
|
+
],
|
|
191
|
+
limits.map(usageLimitRow),
|
|
192
|
+
[3, 4],
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const setCmd = new Command("set")
|
|
200
|
+
.summary("Create or update a usage limit")
|
|
201
|
+
.description(
|
|
202
|
+
[
|
|
203
|
+
"Create a usage limit, or update the existing one for the same",
|
|
204
|
+
"(metric, window, type). At most one limit exists per combination.",
|
|
205
|
+
"",
|
|
206
|
+
"• Set the amount (creates or replaces it):",
|
|
207
|
+
" `npx convex deployment usage-limits set --metric functionCalls --window day --type disable --limit 1000000`",
|
|
208
|
+
"• Deactivate without deleting: add `--inactive` (use `--active` to re-enable).",
|
|
209
|
+
"• Toggle active state without changing the amount: omit `--limit`.",
|
|
210
|
+
].join("\n"),
|
|
211
|
+
)
|
|
212
|
+
.addOption(metricOption.makeOptionMandatory())
|
|
213
|
+
.addOption(windowOption.makeOptionMandatory())
|
|
214
|
+
.addOption(typeOption.makeOptionMandatory())
|
|
215
|
+
.option(
|
|
216
|
+
"--limit <limit>",
|
|
217
|
+
"The limit amount, in the metric's native units. Required when creating; kept as-is when omitted while updating.",
|
|
218
|
+
)
|
|
219
|
+
.option("--active", "Enforce the limit (the default for a new limit).")
|
|
220
|
+
.option("--inactive", "Create or leave the limit unenforced.")
|
|
221
|
+
.configureHelp({ showGlobalOptions: true })
|
|
222
|
+
.allowExcessArguments(false)
|
|
223
|
+
.action(async (cmdOptions, cmd) => {
|
|
224
|
+
const options = cmd.optsWithGlobals() as DeploymentSelectionOptions;
|
|
225
|
+
const { ctx, deployment } = await selectEnvDeployment(options);
|
|
226
|
+
await ensureHasConvexDependency(ctx, "deployment usage-limits set");
|
|
227
|
+
if (cmdOptions.active && cmdOptions.inactive) {
|
|
228
|
+
return ctx.crash({
|
|
229
|
+
exitCode: 1,
|
|
230
|
+
errorType: "fatal",
|
|
231
|
+
printedMessage: "error: Pass at most one of --active and --inactive.",
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
const newLimit =
|
|
235
|
+
cmdOptions.limit === undefined
|
|
236
|
+
? undefined
|
|
237
|
+
: await parseLimit(ctx, cmdOptions.limit);
|
|
238
|
+
await withRunningBackend({
|
|
239
|
+
ctx,
|
|
240
|
+
deployment,
|
|
241
|
+
action: async () => {
|
|
242
|
+
const existing = (await listUsageLimits(ctx, deployment)).find(
|
|
243
|
+
(l) =>
|
|
244
|
+
l.metric === cmdOptions.metric &&
|
|
245
|
+
l.window === cmdOptions.window &&
|
|
246
|
+
l.limitType === cmdOptions.type,
|
|
247
|
+
);
|
|
248
|
+
const label = `${cmdOptions.type} usage limit on ${metricLabel(cmdOptions.metric)} per ${cmdOptions.window}`;
|
|
249
|
+
|
|
250
|
+
// No existing limit: create one. A limit amount is required.
|
|
251
|
+
if (existing === undefined) {
|
|
252
|
+
if (newLimit === undefined) {
|
|
253
|
+
return ctx.crash({
|
|
254
|
+
exitCode: 1,
|
|
255
|
+
errorType: "fatal",
|
|
256
|
+
printedMessage:
|
|
257
|
+
"error: --limit is required when creating a usage limit.",
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
const enabled = !cmdOptions.inactive;
|
|
261
|
+
const created = await createUsageLimit(ctx, deployment, {
|
|
262
|
+
metric: cmdOptions.metric,
|
|
263
|
+
window: cmdOptions.window,
|
|
264
|
+
limitType: cmdOptions.type,
|
|
265
|
+
limit: newLimit,
|
|
266
|
+
enabled,
|
|
267
|
+
});
|
|
268
|
+
logFinishedStep(
|
|
269
|
+
`Created ${label}: ${formatLimitAmount(created.limit)}, ${created.enabled ? "active" : "inactive"}${deployment.deploymentNotice}.`,
|
|
270
|
+
);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Existing limit: update it, surfacing exactly which fields changed.
|
|
275
|
+
const enabled = cmdOptions.active
|
|
276
|
+
? true
|
|
277
|
+
: cmdOptions.inactive
|
|
278
|
+
? false
|
|
279
|
+
: existing.enabled;
|
|
280
|
+
const limit = newLimit ?? existing.limit;
|
|
281
|
+
const changes: string[] = [];
|
|
282
|
+
if (limit !== existing.limit) {
|
|
283
|
+
changes.push(
|
|
284
|
+
`limit ${formatLimitAmount(existing.limit)} \u2192 ${formatLimitAmount(limit)}`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
if (enabled !== existing.enabled) {
|
|
288
|
+
changes.push(
|
|
289
|
+
`${existing.enabled ? "active" : "inactive"} \u2192 ${enabled ? "active" : "inactive"}`,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
if (changes.length === 0) {
|
|
293
|
+
logFinishedStep(
|
|
294
|
+
`No changes to ${label} (${formatLimitAmount(existing.limit)}, ${existing.enabled ? "active" : "inactive"})${deployment.deploymentNotice}.`,
|
|
295
|
+
);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
await updateUsageLimit(ctx, deployment, existing.id, {
|
|
299
|
+
metric: existing.metric,
|
|
300
|
+
window: existing.window,
|
|
301
|
+
limitType: existing.limitType,
|
|
302
|
+
limit,
|
|
303
|
+
enabled,
|
|
304
|
+
});
|
|
305
|
+
logFinishedStep(
|
|
306
|
+
`Updated ${label}: ${changes.join(", ")}${deployment.deploymentNotice}.`,
|
|
307
|
+
);
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const removeCmd = new Command("remove")
|
|
313
|
+
.alias("rm")
|
|
314
|
+
.alias("delete")
|
|
315
|
+
.summary("Delete a usage limit")
|
|
316
|
+
.description(
|
|
317
|
+
[
|
|
318
|
+
"Delete a usage limit, identified by its (metric, window, type).",
|
|
319
|
+
"",
|
|
320
|
+
"• `npx convex deployment usage-limits remove --metric functionCalls --window day --type warning`",
|
|
321
|
+
].join("\n"),
|
|
322
|
+
)
|
|
323
|
+
.addOption(metricOption.makeOptionMandatory())
|
|
324
|
+
.addOption(windowOption.makeOptionMandatory())
|
|
325
|
+
.addOption(typeOption.makeOptionMandatory())
|
|
326
|
+
.configureHelp({ showGlobalOptions: true })
|
|
327
|
+
.allowExcessArguments(false)
|
|
328
|
+
.action(async (cmdOptions, cmd) => {
|
|
329
|
+
const options = cmd.optsWithGlobals() as DeploymentSelectionOptions;
|
|
330
|
+
const { ctx, deployment } = await selectEnvDeployment(options);
|
|
331
|
+
await ensureHasConvexDependency(ctx, "deployment usage-limits remove");
|
|
332
|
+
await withRunningBackend({
|
|
333
|
+
ctx,
|
|
334
|
+
deployment,
|
|
335
|
+
action: async () => {
|
|
336
|
+
const existing = await findUsageLimitByKey(ctx, deployment, {
|
|
337
|
+
metric: cmdOptions.metric,
|
|
338
|
+
window: cmdOptions.window,
|
|
339
|
+
limitType: cmdOptions.type,
|
|
340
|
+
});
|
|
341
|
+
await deleteUsageLimit(ctx, deployment, existing.id);
|
|
342
|
+
logFinishedStep(
|
|
343
|
+
`Deleted ${existing.limitType} usage limit on ${metricLabel(existing.metric)} per ${existing.window}${deployment.deploymentNotice}.`,
|
|
344
|
+
);
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
export const usage = new Command("usage")
|
|
350
|
+
.summary("Show current usage for each metric")
|
|
351
|
+
.description(
|
|
352
|
+
[
|
|
353
|
+
"Show usage so far in the current day and calendar month for every metric.",
|
|
354
|
+
"",
|
|
355
|
+
"• Show current usage: `npx convex deployment usage`",
|
|
356
|
+
"• Print as JSON: `npx convex deployment usage --json`",
|
|
357
|
+
].join("\n"),
|
|
358
|
+
)
|
|
359
|
+
.option("--json", "Output the usage as JSON.")
|
|
360
|
+
.configureHelp({ showGlobalOptions: true })
|
|
361
|
+
.allowExcessArguments(false)
|
|
362
|
+
.addDeploymentSelectionOptions(actionDescription("Show current usage for"))
|
|
363
|
+
.action(async (cmdOptions, cmd) => {
|
|
364
|
+
const options = cmd.optsWithGlobals() as DeploymentSelectionOptions & {
|
|
365
|
+
json?: boolean;
|
|
366
|
+
};
|
|
367
|
+
const { ctx, deployment } = await selectEnvDeployment(options);
|
|
368
|
+
await ensureHasConvexDependency(ctx, "deployment usage");
|
|
369
|
+
await withRunningBackend({
|
|
370
|
+
ctx,
|
|
371
|
+
deployment,
|
|
372
|
+
action: async () => {
|
|
373
|
+
const usage = await getCurrentUsage(ctx, deployment);
|
|
374
|
+
if (cmdOptions.json) {
|
|
375
|
+
logOutput(JSON.stringify(usage, null, 2));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
// A non-`complete` backfill means the numbers can understate reality.
|
|
379
|
+
if (usage.seedStatus !== "complete") {
|
|
380
|
+
logMessage(seedStatusMessage(usage.seedStatus));
|
|
381
|
+
}
|
|
382
|
+
logOutput(
|
|
383
|
+
formatTable(
|
|
384
|
+
["Metric", "Day", "Month"],
|
|
385
|
+
Object.entries(usage.metrics)
|
|
386
|
+
.sort(([a], [b]) => compareMetricNames(a, b))
|
|
387
|
+
.map(([metric, m]) => [
|
|
388
|
+
metricLabel(metric),
|
|
389
|
+
formatAmount(m.usage.current_day, m.unit),
|
|
390
|
+
formatAmount(m.usage.current_month, m.unit),
|
|
391
|
+
]),
|
|
392
|
+
),
|
|
393
|
+
);
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
export const usageLimits = new Command("usage-limits")
|
|
399
|
+
.summary("List and configure deployment usage limits")
|
|
400
|
+
.description(
|
|
401
|
+
[
|
|
402
|
+
"List and configure usage limits on your deployment.",
|
|
403
|
+
"",
|
|
404
|
+
"A usage limit either warns or pauses your deployment when a metric",
|
|
405
|
+
"(function calls, database bandwidth, …) crosses a threshold within a",
|
|
406
|
+
"daily or monthly window. Each limit is identified by its",
|
|
407
|
+
"(metric, window, type).",
|
|
408
|
+
"",
|
|
409
|
+
"• List usage limits: `npx convex deployment usage-limits list`",
|
|
410
|
+
"• Create or update one: `npx convex deployment usage-limits set --metric functionCalls --window day --type disable --limit 1000000`",
|
|
411
|
+
"• Delete one: `npx convex deployment usage-limits remove --metric functionCalls --window day --type disable`",
|
|
412
|
+
].join("\n"),
|
|
413
|
+
)
|
|
414
|
+
.addCommand(listCmd)
|
|
415
|
+
.addCommand(setCmd)
|
|
416
|
+
.addCommand(removeCmd)
|
|
417
|
+
.helpCommand(false)
|
|
418
|
+
.addDeploymentSelectionOptions(
|
|
419
|
+
actionDescription("List and configure usage limits on"),
|
|
420
|
+
);
|
package/src/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "1.
|
|
1
|
+
export const version = "1.43.0-alpha.0";
|
|
@@ -10,6 +10,10 @@ import {
|
|
|
10
10
|
ConvexReactClient,
|
|
11
11
|
useConvexAuth,
|
|
12
12
|
} from "./index.js";
|
|
13
|
+
import {
|
|
14
|
+
BaseConvexClient,
|
|
15
|
+
BaseConvexClientInterface,
|
|
16
|
+
} from "../browser/index.js";
|
|
13
17
|
|
|
14
18
|
vi.useFakeTimers();
|
|
15
19
|
|
|
@@ -18,6 +22,16 @@ const flushPromises = async () => {
|
|
|
18
22
|
await act(() => new Promise((timers as any).setImmediate));
|
|
19
23
|
};
|
|
20
24
|
|
|
25
|
+
// Test helpers reach into `BaseConvexClient`'s private internals; the tests
|
|
26
|
+
// only ever construct `ConvexReactClient` without a `baseClient` override, so
|
|
27
|
+
// `sync` is always a real `BaseConvexClient` under the hood.
|
|
28
|
+
const syncAsBaseConvexClient = (sync: BaseConvexClientInterface) => {
|
|
29
|
+
if (!(sync instanceof BaseConvexClient)) {
|
|
30
|
+
throw new Error("Expected `sync` to be a `BaseConvexClient`");
|
|
31
|
+
}
|
|
32
|
+
return sync;
|
|
33
|
+
};
|
|
34
|
+
|
|
21
35
|
test("setAuth legacy signature typechecks and doesn't throw", async () => {
|
|
22
36
|
const convex = new ConvexReactClient("https://127.0.0.1:3001");
|
|
23
37
|
// We're moving towards removing the Promise, but for backwards compatibility
|
|
@@ -201,8 +215,10 @@ function mockServerConfirmsAuth(
|
|
|
201
215
|
clientClockSkew?: number,
|
|
202
216
|
) {
|
|
203
217
|
act(() => {
|
|
204
|
-
const querySetVersion = client.sync[
|
|
205
|
-
|
|
218
|
+
const querySetVersion = syncAsBaseConvexClient(client.sync)[
|
|
219
|
+
"remoteQuerySet"
|
|
220
|
+
]["version"];
|
|
221
|
+
syncAsBaseConvexClient(client.sync)["authenticationManager"].onTransition({
|
|
206
222
|
type: "Transition",
|
|
207
223
|
startVersion: {
|
|
208
224
|
...querySetVersion,
|
|
@@ -311,7 +327,7 @@ test("isRefreshing reflects reauthentication in React tree", async () => {
|
|
|
311
327
|
|
|
312
328
|
// Server rejects a token mid-session, triggering reauthentication.
|
|
313
329
|
act(() => {
|
|
314
|
-
convex.sync["authenticationManager"].onAuthError({
|
|
330
|
+
syncAsBaseConvexClient(convex.sync)["authenticationManager"].onAuthError({
|
|
315
331
|
type: "AuthError",
|
|
316
332
|
error: "token expired",
|
|
317
333
|
baseVersion: 2,
|
|
@@ -362,7 +378,7 @@ test("isRefreshing becomes false when reauth fetch fails", async () => {
|
|
|
362
378
|
|
|
363
379
|
// Server rejects the token; tryToReauthenticate runs but the fetcher
|
|
364
380
|
// returns null on its retry.
|
|
365
|
-
client.sync["authenticationManager"].onAuthError({
|
|
381
|
+
syncAsBaseConvexClient(client.sync)["authenticationManager"].onAuthError({
|
|
366
382
|
type: "AuthError",
|
|
367
383
|
error: "token expired",
|
|
368
384
|
baseVersion: 1,
|
|
@@ -435,7 +451,7 @@ test("isFromOutdatedConfig during reauth resets isRefreshing via resetAuthState"
|
|
|
435
451
|
onRefreshChange.mockClear();
|
|
436
452
|
|
|
437
453
|
// Server rejects the token, kicking off tryToReauthenticate.
|
|
438
|
-
client.sync["authenticationManager"].onAuthError({
|
|
454
|
+
syncAsBaseConvexClient(client.sync)["authenticationManager"].onAuthError({
|
|
439
455
|
type: "AuthError",
|
|
440
456
|
error: "token expired",
|
|
441
457
|
baseVersion: 1,
|
|
@@ -482,7 +498,7 @@ test("isRefreshing is true during reauthentication after auth error", async () =
|
|
|
482
498
|
|
|
483
499
|
// Simulate auth error from server
|
|
484
500
|
act(() => {
|
|
485
|
-
client.sync["authenticationManager"].onAuthError({
|
|
501
|
+
syncAsBaseConvexClient(client.sync)["authenticationManager"].onAuthError({
|
|
486
502
|
type: "AuthError",
|
|
487
503
|
error: "token expired",
|
|
488
504
|
baseVersion: 1,
|
|
@@ -527,7 +543,7 @@ test("stop() during reauth notifies isRefreshing false", async () => {
|
|
|
527
543
|
onRefreshChange.mockClear();
|
|
528
544
|
|
|
529
545
|
// Server rejects the token so isRefreshing becomes true.
|
|
530
|
-
client.sync["authenticationManager"].onAuthError({
|
|
546
|
+
syncAsBaseConvexClient(client.sync)["authenticationManager"].onAuthError({
|
|
531
547
|
type: "AuthError",
|
|
532
548
|
error: "token expired",
|
|
533
549
|
baseVersion: 1,
|
|
@@ -564,7 +580,7 @@ test("initialAuthTokenReuse: sends only one Authenticate", async () => {
|
|
|
564
580
|
);
|
|
565
581
|
|
|
566
582
|
const sendMessageSpy = vi.spyOn(
|
|
567
|
-
client.sync["webSocketManager"],
|
|
583
|
+
syncAsBaseConvexClient(client.sync)["webSocketManager"],
|
|
568
584
|
"sendMessage",
|
|
569
585
|
);
|
|
570
586
|
|