kitcn 0.23.0 → 0.24.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/dist/aggregate/index.d.ts +1 -1
- package/dist/auth/nextjs/index.d.ts +1 -1
- package/dist/cli.mjs +96 -20
- package/dist/crpc/index.d.ts +3 -3
- package/dist/crpc/index.js +2 -2
- package/dist/{http-types-BoSDAh4Y.d.ts → http-types-DXOgaerG.d.ts} +31 -1
- package/dist/orm/index.d.ts +1 -1
- package/dist/{procedure-name-C55TynK3.d.ts → procedure-name-C20pFZnk.d.ts} +1 -1
- package/dist/{query-options-C_eBSIXG.js → query-options-CzRV4G4N.js} +33 -1
- package/dist/ratelimit/index.d.ts +45 -9
- package/dist/ratelimit/index.js +253 -99
- package/dist/react/index.d.ts +9 -6
- package/dist/react/index.js +36 -17
- package/dist/rsc/index.d.ts +4 -7
- package/dist/rsc/index.js +4 -8
- package/dist/server/index.d.ts +2 -2
- package/dist/solid/index.d.ts +9 -6
- package/dist/solid/index.js +36 -17
- package/dist/{types-C0Xl7P8K.d.ts → types-BHR7ZKKD.d.ts} +1 -1
- package/dist/{where-clause-compiler-B_H3oio5.d.ts → where-clause-compiler-DW6jy2er.d.ts} +59 -59
- package/package.json +1 -1
- package/skills/kitcn/references/features/http.md +26 -1
- package/skills/kitcn/references/features/ratelimit.md +11 -0
- package/skills/kitcn/references/features/react.md +1 -1
- package/skills/kitcn/references/setup/server.md +35 -5
package/dist/solid/index.d.ts
CHANGED
|
@@ -580,6 +580,12 @@ interface CRPCHttpRouter<TRecord extends HttpRouterRecord> {
|
|
|
580
580
|
}
|
|
581
581
|
//#endregion
|
|
582
582
|
//#region src/crpc/http-types.d.ts
|
|
583
|
+
/** Exact cache key for one route + args, as stored in the QueryClient */
|
|
584
|
+
type HttpQueryKey = readonly ['httpQuery', string, unknown];
|
|
585
|
+
/** Route-wide prefix key that matches every args variant of one route */
|
|
586
|
+
type HttpQueryPrefixKey = readonly ['httpQuery', string];
|
|
587
|
+
/** Mutation key for HTTP endpoints */
|
|
588
|
+
type HttpMutationKey = readonly ['httpMutation', string];
|
|
583
589
|
/** Error codes that can be returned from HTTP endpoints */
|
|
584
590
|
type HttpErrorCode = 'BAD_REQUEST' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'NOT_FOUND' | 'METHOD_NOT_SUPPORTED' | 'CONFLICT' | 'UNPROCESSABLE_CONTENT' | 'TOO_MANY_REQUESTS' | 'INTERNAL_SERVER_ERROR' | 'UNKNOWN';
|
|
585
591
|
/** HTTP client error */
|
|
@@ -782,9 +788,6 @@ type InferHttpClientArgs<T> = T extends HttpProcedure<infer TInput, infer _TOutp
|
|
|
782
788
|
}> : HttpInputArgs;
|
|
783
789
|
/** Infer output type from HttpProcedure */
|
|
784
790
|
type InferHttpOutput<T> = T extends HttpProcedure<infer _TInput, infer TOutput, infer _TParams, infer _TQuery> ? TOutput extends UnsetMarker ? unknown : TOutput extends z.ZodTypeAny ? z.infer<TOutput> : unknown : unknown;
|
|
785
|
-
/** Query key with args (3-element) or prefix key without args (2-element) for invalidation */
|
|
786
|
-
type HttpQueryKey = readonly ['httpQuery', string, unknown] | readonly ['httpQuery', string];
|
|
787
|
-
type HttpMutationKey = readonly ['httpMutation', string];
|
|
788
791
|
type ReservedQueryOptions$1 = 'queryKey' | 'queryFn';
|
|
789
792
|
type ReservedMutationOptions$1 = 'mutationFn';
|
|
790
793
|
/** Query options for GET HTTP endpoints - compatible with both createQuery and createSuspenseQuery */
|
|
@@ -803,8 +806,8 @@ type HttpMutationOptions<T extends HttpProcedure> = DistributiveOmit<HttpMutatio
|
|
|
803
806
|
* - mutationOptions: For one-time actions like exports (createMutation)
|
|
804
807
|
*/
|
|
805
808
|
type DecorateHttpQuery<T extends HttpProcedure> = {
|
|
806
|
-
queryOptions: keyof InferHttpInput<T> extends never ? (args?: InferHttpClientArgs<T>, opts?: HttpQueryOptions<T>) => HttpQueryOptsReturn<T> : object extends InferHttpInput<T> ? (args?: InferHttpClientArgs<T>, opts?: HttpQueryOptions<T>) => HttpQueryOptsReturn<T> : (args: InferHttpClientArgs<T>, opts?: HttpQueryOptions<T>) => HttpQueryOptsReturn<T>; /** Get
|
|
807
|
-
queryKey: (args?: InferHttpClientArgs<T>) => HttpQueryKey; /** Get query filter
|
|
809
|
+
queryOptions: keyof InferHttpInput<T> extends never ? (args?: InferHttpClientArgs<T>, opts?: HttpQueryOptions<T>) => HttpQueryOptsReturn<T> : object extends InferHttpInput<T> ? (args?: InferHttpClientArgs<T>, opts?: HttpQueryOptions<T>) => HttpQueryOptsReturn<T> : (args: InferHttpClientArgs<T>, opts?: HttpQueryOptions<T>) => HttpQueryOptsReturn<T>; /** Get the exact cache key these args are stored under (getQueryData/setQueryData) */
|
|
810
|
+
queryKey: (args?: InferHttpClientArgs<T>) => HttpQueryKey; /** Get query filter matching every args variant (e.g., invalidateQueries) */
|
|
808
811
|
queryFilter: (args?: InferHttpClientArgs<T>, filters?: DistributiveOmit<QueryFilters, 'queryKey'>) => QueryFilters; /** Mutation options for GET endpoints (exports, downloads - no caching) */
|
|
809
812
|
mutationOptions: (opts?: HttpMutationOptions<T>) => HttpMutationOptsReturn<T>; /** Get mutation key for QueryClient methods */
|
|
810
813
|
mutationKey: () => HttpMutationKey;
|
|
@@ -1609,4 +1612,4 @@ declare function useUploadMutationOptions<TGenerateUrlMutation extends FunctionR
|
|
|
1609
1612
|
*/
|
|
1610
1613
|
declare function createVanillaCRPCProxy<TApi extends Record<string, unknown>>(api: TApi, meta: CallerMeta, convexClient: ConvexClient, transformer?: DataTransformerOptions): VanillaCRPCClient<TApi>;
|
|
1611
1614
|
//#endregion
|
|
1612
|
-
export { AuthMutationError, AuthProvider, AuthStore, AuthStoreState, AuthType, Authenticated, CRPCClient, CRPCHttpOptions, ConvexActionOptions, ConvexAuthBridge, ConvexAuthProvider, ConvexAuthProviderProps, ConvexInfiniteQueryOptions, ConvexInfiniteQueryOptionsWithRef, ConvexProvider, ConvexProviderWithAuth, ConvexQueryClient, ConvexQueryClientOptions, ConvexQueryClientSingletonOptions, ConvexQueryOptions, CreateCRPCContextOptions, DecorateAction, DecorateInfiniteQuery, DecorateMutation, DecorateQuery, FetchAccessTokenContext, FetchAccessTokenFn, HttpCRPCClient, HttpCRPCClientFromRouter, type HttpClientOptions, type HttpFormValue, type HttpInputArgs, HttpMutationKey, HttpProxyOptions, HttpQueryKey, HttpRouteInfo, HttpRouteMap, InfiniteQueryOptsParam, MaybeAuthenticated, MaybeUnauthenticated, MetaContext, PaginationState, PaginationStatus, SolidAuthClient, SolidAuthProviderClient, Unauthenticated, Unsubscribe, UseInfiniteQueryResult, VanillaCRPCClient, VanillaHttpCRPCClient, VanillaHttpCRPCClientFromRouter, VanillaQuery, createAuthMutations, createCRPCContext, createCRPCOptionsProxy, createHttpProxy, createVanillaCRPCProxy, decodeJwtExp, getAuthType, getConvexQueryClientSingleton, getQueryClientSingleton, isAuthMutationError, useAuth, useAuthGuard, useAuthSkip, useAuthStore, useAuthValue, useConvex, useConvexActionOptions, useConvexActionQueryOptions, useConvexAuth, useConvexAuthBridge, useConvexInfiniteQueryOptions, useConvexMutationOptions, useConvexQueryClient, useConvexQueryOptions, useFetchAccessToken, useFnMeta, useInfiniteQuery, useIsAuth, useMaybeAuth, useMeta, useSafeConvexAuth, useUploadMutationOptions };
|
|
1615
|
+
export { AuthMutationError, AuthProvider, AuthStore, AuthStoreState, AuthType, Authenticated, CRPCClient, CRPCHttpOptions, ConvexActionOptions, ConvexAuthBridge, ConvexAuthProvider, ConvexAuthProviderProps, ConvexInfiniteQueryOptions, ConvexInfiniteQueryOptionsWithRef, ConvexProvider, ConvexProviderWithAuth, ConvexQueryClient, ConvexQueryClientOptions, ConvexQueryClientSingletonOptions, ConvexQueryOptions, CreateCRPCContextOptions, DecorateAction, DecorateInfiniteQuery, DecorateMutation, DecorateQuery, FetchAccessTokenContext, FetchAccessTokenFn, HttpCRPCClient, HttpCRPCClientFromRouter, type HttpClientOptions, type HttpFormValue, type HttpInputArgs, type HttpMutationKey, HttpProxyOptions, type HttpQueryKey, type HttpQueryPrefixKey, HttpRouteInfo, HttpRouteMap, InfiniteQueryOptsParam, MaybeAuthenticated, MaybeUnauthenticated, MetaContext, PaginationState, PaginationStatus, SolidAuthClient, SolidAuthProviderClient, Unauthenticated, Unsubscribe, UseInfiniteQueryResult, VanillaCRPCClient, VanillaHttpCRPCClient, VanillaHttpCRPCClientFromRouter, VanillaQuery, createAuthMutations, createCRPCContext, createCRPCOptionsProxy, createHttpProxy, createVanillaCRPCProxy, decodeJwtExp, getAuthType, getConvexQueryClientSingleton, getQueryClientSingleton, isAuthMutationError, useAuth, useAuthGuard, useAuthSkip, useAuthStore, useAuthValue, useConvex, useConvexActionOptions, useConvexActionQueryOptions, useConvexAuth, useConvexAuthBridge, useConvexInfiniteQueryOptions, useConvexMutationOptions, useConvexQueryClient, useConvexQueryOptions, useFetchAccessToken, useFnMeta, useInfiniteQuery, useIsAuth, useMaybeAuth, useMeta, useSafeConvexAuth, useUploadMutationOptions };
|
package/dist/solid/index.js
CHANGED
|
@@ -427,6 +427,38 @@ function getFunctionMeta(path, source) {
|
|
|
427
427
|
|
|
428
428
|
//#endregion
|
|
429
429
|
//#region src/crpc/http-types.ts
|
|
430
|
+
/**
|
|
431
|
+
* Build the exact cache key for an HTTP route.
|
|
432
|
+
*
|
|
433
|
+
* Missing args normalize to `{}` so the RSC prefetch, the browser observer, and
|
|
434
|
+
* a hand-written `getQueryData` call all hash to the same key. Every producer
|
|
435
|
+
* of an `httpQuery` cache key goes through here.
|
|
436
|
+
*/
|
|
437
|
+
function buildHttpQueryKey(routeKey, args) {
|
|
438
|
+
return [
|
|
439
|
+
"httpQuery",
|
|
440
|
+
routeKey,
|
|
441
|
+
args ?? {}
|
|
442
|
+
];
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Build the route-wide prefix key.
|
|
446
|
+
*
|
|
447
|
+
* Filters match on prefix, so this matches every args variant of the route.
|
|
448
|
+
* Not a cache key: nothing is ever stored under it.
|
|
449
|
+
*/
|
|
450
|
+
function buildHttpQueryPrefixKey(routeKey) {
|
|
451
|
+
return ["httpQuery", routeKey];
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Freshness window for `crpc.http.*` routes, in milliseconds.
|
|
455
|
+
*
|
|
456
|
+
* HTTP routes are a pull model with no push channel, so hydrated data needs a
|
|
457
|
+
* real freshness window or the browser refetches it on mount. The RSC
|
|
458
|
+
* QueryClient reads the same constant, so the server dedupe window and the
|
|
459
|
+
* client freshness window cannot drift apart.
|
|
460
|
+
*/
|
|
461
|
+
const HTTP_DEFAULT_STALE_TIME = 3e4;
|
|
430
462
|
/** HTTP client error */
|
|
431
463
|
var HttpClientError = class extends Error {
|
|
432
464
|
name = "HttpClientError";
|
|
@@ -825,12 +857,9 @@ function createRecursiveHttpProxy(opts, path = []) {
|
|
|
825
857
|
if (!route) throw new Error(`Unknown HTTP procedure: ${routeKey}`);
|
|
826
858
|
if (route.method !== "GET") throw new Error(`queryOptions is only available for GET endpoints, got ${route.method} for ${routeKey}`);
|
|
827
859
|
return (args, queryOpts) => ({
|
|
860
|
+
staleTime: HTTP_DEFAULT_STALE_TIME,
|
|
828
861
|
...queryOpts,
|
|
829
|
-
queryKey:
|
|
830
|
-
"httpQuery",
|
|
831
|
-
routeKey,
|
|
832
|
-
args
|
|
833
|
-
],
|
|
862
|
+
queryKey: buildHttpQueryKey(routeKey, args),
|
|
834
863
|
queryFn: async () => {
|
|
835
864
|
try {
|
|
836
865
|
return await executeHttpRequest({
|
|
@@ -849,22 +878,12 @@ function createRecursiveHttpProxy(opts, path = []) {
|
|
|
849
878
|
}
|
|
850
879
|
});
|
|
851
880
|
}
|
|
852
|
-
if (prop === "queryKey") return (args) =>
|
|
853
|
-
return args !== void 0 && !(typeof args === "object" && args !== null && Object.keys(args).length === 0) ? [
|
|
854
|
-
"httpQuery",
|
|
855
|
-
routeKey,
|
|
856
|
-
args
|
|
857
|
-
] : ["httpQuery", routeKey];
|
|
858
|
-
};
|
|
881
|
+
if (prop === "queryKey") return (args) => buildHttpQueryKey(routeKey, args);
|
|
859
882
|
if (prop === "queryFilter") return (args, filters) => {
|
|
860
883
|
const hasArgs = args !== void 0 && !(typeof args === "object" && args !== null && Object.keys(args).length === 0);
|
|
861
884
|
return {
|
|
862
885
|
...filters,
|
|
863
|
-
queryKey: hasArgs ?
|
|
864
|
-
"httpQuery",
|
|
865
|
-
routeKey,
|
|
866
|
-
args
|
|
867
|
-
] : ["httpQuery", routeKey]
|
|
886
|
+
queryKey: hasArgs ? buildHttpQueryKey(routeKey, args) : buildHttpQueryPrefixKey(routeKey)
|
|
868
887
|
};
|
|
869
888
|
};
|
|
870
889
|
if (prop === "mutationOptions") {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { P as CombinedDataTransformer, r as HttpClientError } from "./http-types-DXOgaerG.js";
|
|
2
2
|
import { FunctionArgs, FunctionReference, FunctionReturnType } from "convex/server";
|
|
3
3
|
|
|
4
4
|
//#region src/crpc/http-client.d.ts
|
|
@@ -3850,7 +3850,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
3850
3850
|
readonly aggregate_bucket: ConvexTableWithColumns<{
|
|
3851
3851
|
name: "aggregate_bucket";
|
|
3852
3852
|
columns: {
|
|
3853
|
-
|
|
3853
|
+
tableKey: ConvexTextBuilderInitial<""> & {
|
|
3854
3854
|
_: {
|
|
3855
3855
|
notNull: true;
|
|
3856
3856
|
};
|
|
@@ -3860,10 +3860,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
3860
3860
|
};
|
|
3861
3861
|
} & {
|
|
3862
3862
|
_: {
|
|
3863
|
-
fieldName: "
|
|
3863
|
+
fieldName: "tableKey";
|
|
3864
3864
|
};
|
|
3865
3865
|
};
|
|
3866
|
-
|
|
3866
|
+
indexName: ConvexTextBuilderInitial<""> & {
|
|
3867
3867
|
_: {
|
|
3868
3868
|
notNull: true;
|
|
3869
3869
|
};
|
|
@@ -3873,10 +3873,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
3873
3873
|
};
|
|
3874
3874
|
} & {
|
|
3875
3875
|
_: {
|
|
3876
|
-
fieldName: "
|
|
3876
|
+
fieldName: "indexName";
|
|
3877
3877
|
};
|
|
3878
3878
|
};
|
|
3879
|
-
|
|
3879
|
+
updatedAt: ConvexNumberBuilderInitial<""> & {
|
|
3880
3880
|
_: {
|
|
3881
3881
|
notNull: true;
|
|
3882
3882
|
};
|
|
@@ -3886,10 +3886,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
3886
3886
|
};
|
|
3887
3887
|
} & {
|
|
3888
3888
|
_: {
|
|
3889
|
-
fieldName: "
|
|
3889
|
+
fieldName: "updatedAt";
|
|
3890
3890
|
};
|
|
3891
3891
|
};
|
|
3892
|
-
|
|
3892
|
+
keyHash: ConvexTextBuilderInitial<""> & {
|
|
3893
3893
|
_: {
|
|
3894
3894
|
notNull: true;
|
|
3895
3895
|
};
|
|
@@ -3899,10 +3899,14 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
3899
3899
|
};
|
|
3900
3900
|
} & {
|
|
3901
3901
|
_: {
|
|
3902
|
-
fieldName: "
|
|
3902
|
+
fieldName: "keyHash";
|
|
3903
3903
|
};
|
|
3904
3904
|
};
|
|
3905
|
-
|
|
3905
|
+
keyParts: ConvexCustomBuilderInitial<"", convex_values0.VArray<any[], convex_values0.VId<any, any> | convex_values0.VString<any, any> | convex_values0.VFloat64<any, any> | convex_values0.VInt64<any, any> | convex_values0.VBoolean<any, any> | convex_values0.VNull<any, any> | convex_values0.VAny<any, any, string> | convex_values0.VLiteral<any, any> | convex_values0.VBytes<any, any> | convex_values0.VObject<any, Record<string, convex_values0.Validator<any, convex_values0.OptionalProperty, any>>, any, any> | convex_values0.VArray<any, convex_values0.Validator<any, "required", any>, any> | convex_values0.VRecord<any, convex_values0.Validator<string, "required", any>, convex_values0.Validator<any, "required", any>, any, any> | convex_values0.VUnion<any, convex_values0.Validator<any, "required", any>[], any, any>, "required">> & {
|
|
3906
|
+
_: {
|
|
3907
|
+
$type: convex_values0.Value[];
|
|
3908
|
+
};
|
|
3909
|
+
} & {
|
|
3906
3910
|
_: {
|
|
3907
3911
|
notNull: true;
|
|
3908
3912
|
};
|
|
@@ -3912,14 +3916,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
3912
3916
|
};
|
|
3913
3917
|
} & {
|
|
3914
3918
|
_: {
|
|
3915
|
-
fieldName: "
|
|
3919
|
+
fieldName: "keyParts";
|
|
3916
3920
|
};
|
|
3917
3921
|
};
|
|
3918
|
-
|
|
3919
|
-
_: {
|
|
3920
|
-
$type: convex_values0.Value[];
|
|
3921
|
-
};
|
|
3922
|
-
} & {
|
|
3922
|
+
count: ConvexNumberBuilderInitial<""> & {
|
|
3923
3923
|
_: {
|
|
3924
3924
|
notNull: true;
|
|
3925
3925
|
};
|
|
@@ -3929,7 +3929,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
3929
3929
|
};
|
|
3930
3930
|
} & {
|
|
3931
3931
|
_: {
|
|
3932
|
-
fieldName: "
|
|
3932
|
+
fieldName: "count";
|
|
3933
3933
|
};
|
|
3934
3934
|
};
|
|
3935
3935
|
sumValues: (NotNull<$Type<ConvexCustomBuilderInitial<"", convex_values0.VRecord<Record<string, any>, convex_values0.VString<string, "required">, convex_values0.VId<any, any> | convex_values0.VString<any, any> | convex_values0.VFloat64<any, any> | convex_values0.VInt64<any, any> | convex_values0.VBoolean<any, any> | convex_values0.VNull<any, any> | convex_values0.VAny<any, any, string> | convex_values0.VLiteral<any, any> | convex_values0.VBytes<any, any> | convex_values0.VObject<any, Record<string, convex_values0.Validator<any, convex_values0.OptionalProperty, any>>, any, any> | convex_values0.VArray<any, convex_values0.Validator<any, "required", any>, any> | convex_values0.VRecord<any, convex_values0.Validator<string, "required", any>, convex_values0.Validator<any, "required", any>, any, any> | convex_values0.VUnion<any, convex_values0.Validator<any, "required", any>[], any, any>, "required", string>>, Record<string, number>>> | NotNull<$Type<ConvexCustomBuilderInitial<"", convex_values0.VId<any, any> | convex_values0.VString<any, any> | convex_values0.VFloat64<any, any> | convex_values0.VInt64<any, any> | convex_values0.VBoolean<any, any> | convex_values0.VNull<any, any> | convex_values0.VAny<any, any, string> | convex_values0.VLiteral<any, any> | convex_values0.VBytes<any, any> | convex_values0.VObject<any, Record<string, convex_values0.Validator<any, convex_values0.OptionalProperty, any>>, any, any> | convex_values0.VArray<any, convex_values0.Validator<any, "required", any>, any> | convex_values0.VRecord<any, convex_values0.Validator<string, "required", any>, convex_values0.Validator<any, "required", any>, any, any> | convex_values0.VUnion<any, convex_values0.Validator<any, "required", any>[], any, any>>, Record<string, number>>>) & {
|
|
@@ -3972,7 +3972,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
3972
3972
|
fieldName: "kind";
|
|
3973
3973
|
};
|
|
3974
3974
|
};
|
|
3975
|
-
|
|
3975
|
+
tableKey: ConvexTextBuilderInitial<""> & {
|
|
3976
3976
|
_: {
|
|
3977
3977
|
notNull: true;
|
|
3978
3978
|
};
|
|
@@ -3982,10 +3982,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
3982
3982
|
};
|
|
3983
3983
|
} & {
|
|
3984
3984
|
_: {
|
|
3985
|
-
fieldName: "
|
|
3985
|
+
fieldName: "tableKey";
|
|
3986
3986
|
};
|
|
3987
3987
|
};
|
|
3988
|
-
|
|
3988
|
+
indexName: ConvexTextBuilderInitial<""> & {
|
|
3989
3989
|
_: {
|
|
3990
3990
|
notNull: true;
|
|
3991
3991
|
};
|
|
@@ -3995,10 +3995,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
3995
3995
|
};
|
|
3996
3996
|
} & {
|
|
3997
3997
|
_: {
|
|
3998
|
-
fieldName: "
|
|
3998
|
+
fieldName: "indexName";
|
|
3999
3999
|
};
|
|
4000
4000
|
};
|
|
4001
|
-
|
|
4001
|
+
updatedAt: ConvexNumberBuilderInitial<""> & {
|
|
4002
4002
|
_: {
|
|
4003
4003
|
notNull: true;
|
|
4004
4004
|
};
|
|
@@ -4008,7 +4008,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4008
4008
|
};
|
|
4009
4009
|
} & {
|
|
4010
4010
|
_: {
|
|
4011
|
-
fieldName: "
|
|
4011
|
+
fieldName: "updatedAt";
|
|
4012
4012
|
};
|
|
4013
4013
|
};
|
|
4014
4014
|
keyHash: ConvexTextBuilderInitial<""> & {
|
|
@@ -4142,7 +4142,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4142
4142
|
fieldName: "value";
|
|
4143
4143
|
};
|
|
4144
4144
|
};
|
|
4145
|
-
|
|
4145
|
+
tableKey: ConvexTextBuilderInitial<""> & {
|
|
4146
4146
|
_: {
|
|
4147
4147
|
notNull: true;
|
|
4148
4148
|
};
|
|
@@ -4152,10 +4152,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4152
4152
|
};
|
|
4153
4153
|
} & {
|
|
4154
4154
|
_: {
|
|
4155
|
-
fieldName: "
|
|
4155
|
+
fieldName: "tableKey";
|
|
4156
4156
|
};
|
|
4157
4157
|
};
|
|
4158
|
-
|
|
4158
|
+
indexName: ConvexTextBuilderInitial<""> & {
|
|
4159
4159
|
_: {
|
|
4160
4160
|
notNull: true;
|
|
4161
4161
|
};
|
|
@@ -4165,10 +4165,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4165
4165
|
};
|
|
4166
4166
|
} & {
|
|
4167
4167
|
_: {
|
|
4168
|
-
fieldName: "
|
|
4168
|
+
fieldName: "indexName";
|
|
4169
4169
|
};
|
|
4170
4170
|
};
|
|
4171
|
-
|
|
4171
|
+
updatedAt: ConvexNumberBuilderInitial<""> & {
|
|
4172
4172
|
_: {
|
|
4173
4173
|
notNull: true;
|
|
4174
4174
|
};
|
|
@@ -4178,10 +4178,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4178
4178
|
};
|
|
4179
4179
|
} & {
|
|
4180
4180
|
_: {
|
|
4181
|
-
fieldName: "
|
|
4181
|
+
fieldName: "updatedAt";
|
|
4182
4182
|
};
|
|
4183
4183
|
};
|
|
4184
|
-
|
|
4184
|
+
keyHash: ConvexTextBuilderInitial<""> & {
|
|
4185
4185
|
_: {
|
|
4186
4186
|
notNull: true;
|
|
4187
4187
|
};
|
|
@@ -4191,10 +4191,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4191
4191
|
};
|
|
4192
4192
|
} & {
|
|
4193
4193
|
_: {
|
|
4194
|
-
fieldName: "
|
|
4194
|
+
fieldName: "keyHash";
|
|
4195
4195
|
};
|
|
4196
4196
|
};
|
|
4197
|
-
|
|
4197
|
+
count: ConvexNumberBuilderInitial<""> & {
|
|
4198
4198
|
_: {
|
|
4199
4199
|
notNull: true;
|
|
4200
4200
|
};
|
|
@@ -4204,7 +4204,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4204
4204
|
};
|
|
4205
4205
|
} & {
|
|
4206
4206
|
_: {
|
|
4207
|
-
fieldName: "
|
|
4207
|
+
fieldName: "count";
|
|
4208
4208
|
};
|
|
4209
4209
|
};
|
|
4210
4210
|
fieldName: ConvexTextBuilderInitial<""> & {
|
|
@@ -4412,19 +4412,6 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4412
4412
|
fieldName: "cursor";
|
|
4413
4413
|
};
|
|
4414
4414
|
};
|
|
4415
|
-
updatedAt: ConvexNumberBuilderInitial<""> & {
|
|
4416
|
-
_: {
|
|
4417
|
-
notNull: true;
|
|
4418
|
-
};
|
|
4419
|
-
} & {
|
|
4420
|
-
_: {
|
|
4421
|
-
tableName: "aggregate_state";
|
|
4422
|
-
};
|
|
4423
|
-
} & {
|
|
4424
|
-
_: {
|
|
4425
|
-
fieldName: "updatedAt";
|
|
4426
|
-
};
|
|
4427
|
-
};
|
|
4428
4415
|
tableKey: ConvexTextBuilderInitial<""> & {
|
|
4429
4416
|
_: {
|
|
4430
4417
|
notNull: true;
|
|
@@ -4503,6 +4490,19 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4503
4490
|
fieldName: "startedAt";
|
|
4504
4491
|
};
|
|
4505
4492
|
};
|
|
4493
|
+
updatedAt: ConvexNumberBuilderInitial<""> & {
|
|
4494
|
+
_: {
|
|
4495
|
+
notNull: true;
|
|
4496
|
+
};
|
|
4497
|
+
} & {
|
|
4498
|
+
_: {
|
|
4499
|
+
tableName: "aggregate_state";
|
|
4500
|
+
};
|
|
4501
|
+
} & {
|
|
4502
|
+
_: {
|
|
4503
|
+
fieldName: "updatedAt";
|
|
4504
|
+
};
|
|
4505
|
+
};
|
|
4506
4506
|
completedAt: ConvexNumberBuilderInitial<""> & {
|
|
4507
4507
|
_: {
|
|
4508
4508
|
tableName: "aggregate_state";
|
|
@@ -4563,7 +4563,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4563
4563
|
fieldName: "direction";
|
|
4564
4564
|
};
|
|
4565
4565
|
};
|
|
4566
|
-
|
|
4566
|
+
processed: ConvexNumberBuilderInitial<""> & {
|
|
4567
4567
|
_: {
|
|
4568
4568
|
notNull: true;
|
|
4569
4569
|
};
|
|
@@ -4573,29 +4573,29 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4573
4573
|
};
|
|
4574
4574
|
} & {
|
|
4575
4575
|
_: {
|
|
4576
|
-
fieldName: "
|
|
4576
|
+
fieldName: "processed";
|
|
4577
4577
|
};
|
|
4578
4578
|
};
|
|
4579
|
-
|
|
4580
|
-
_: {
|
|
4581
|
-
notNull: true;
|
|
4582
|
-
};
|
|
4583
|
-
} & {
|
|
4579
|
+
startedAt: ConvexNumberBuilderInitial<""> & {
|
|
4584
4580
|
_: {
|
|
4585
4581
|
tableName: "migration_state";
|
|
4586
4582
|
};
|
|
4587
4583
|
} & {
|
|
4588
4584
|
_: {
|
|
4589
|
-
fieldName: "
|
|
4585
|
+
fieldName: "startedAt";
|
|
4590
4586
|
};
|
|
4591
4587
|
};
|
|
4592
|
-
|
|
4588
|
+
updatedAt: ConvexNumberBuilderInitial<""> & {
|
|
4589
|
+
_: {
|
|
4590
|
+
notNull: true;
|
|
4591
|
+
};
|
|
4592
|
+
} & {
|
|
4593
4593
|
_: {
|
|
4594
4594
|
tableName: "migration_state";
|
|
4595
4595
|
};
|
|
4596
4596
|
} & {
|
|
4597
4597
|
_: {
|
|
4598
|
-
fieldName: "
|
|
4598
|
+
fieldName: "updatedAt";
|
|
4599
4599
|
};
|
|
4600
4600
|
};
|
|
4601
4601
|
completedAt: ConvexNumberBuilderInitial<""> & {
|
|
@@ -4712,7 +4712,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4712
4712
|
fieldName: "direction";
|
|
4713
4713
|
};
|
|
4714
4714
|
};
|
|
4715
|
-
|
|
4715
|
+
startedAt: ConvexNumberBuilderInitial<""> & {
|
|
4716
4716
|
_: {
|
|
4717
4717
|
notNull: true;
|
|
4718
4718
|
};
|
|
@@ -4722,10 +4722,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4722
4722
|
};
|
|
4723
4723
|
} & {
|
|
4724
4724
|
_: {
|
|
4725
|
-
fieldName: "
|
|
4725
|
+
fieldName: "startedAt";
|
|
4726
4726
|
};
|
|
4727
4727
|
};
|
|
4728
|
-
|
|
4728
|
+
updatedAt: ConvexNumberBuilderInitial<""> & {
|
|
4729
4729
|
_: {
|
|
4730
4730
|
notNull: true;
|
|
4731
4731
|
};
|
|
@@ -4735,7 +4735,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
|
|
|
4735
4735
|
};
|
|
4736
4736
|
} & {
|
|
4737
4737
|
_: {
|
|
4738
|
-
fieldName: "
|
|
4738
|
+
fieldName: "updatedAt";
|
|
4739
4739
|
};
|
|
4740
4740
|
};
|
|
4741
4741
|
completedAt: ConvexNumberBuilderInitial<""> & {
|
package/package.json
CHANGED
|
@@ -495,8 +495,18 @@ crpc.http.todos.list.queryOptions({
|
|
|
495
495
|
searchParams: { limit: "10" },
|
|
496
496
|
headers: { "X-Custom": "value" },
|
|
497
497
|
});
|
|
498
|
+
|
|
499
|
+
// Override the default 30s staleTime
|
|
500
|
+
crpc.http.todos.list.queryOptions(
|
|
501
|
+
{ searchParams: { limit: "10" } },
|
|
502
|
+
{ staleTime: 0 }
|
|
503
|
+
);
|
|
498
504
|
```
|
|
499
505
|
|
|
506
|
+
`queryOptions` defaults to a 30 second `staleTime` so RSC-prefetched entries hydrate
|
|
507
|
+
without an immediate refetch. `refetchOnMount` keeps TanStack's default, so a route
|
|
508
|
+
invalidated while unmounted still refetches.
|
|
509
|
+
|
|
500
510
|
### One-Time Fetch
|
|
501
511
|
|
|
502
512
|
```ts
|
|
@@ -558,6 +568,20 @@ const updateTodo = useMutation(
|
|
|
558
568
|
);
|
|
559
569
|
```
|
|
560
570
|
|
|
571
|
+
### Cache Keys
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
const queryKey = crpc.http.todos.list.queryKey();
|
|
575
|
+
// => ['httpQuery', 'todos.list', {}]
|
|
576
|
+
queryClient.getQueryData(queryKey);
|
|
577
|
+
|
|
578
|
+
crpc.http.todos.get.queryKey({ params: { id: '123' } });
|
|
579
|
+
// => ['httpQuery', 'todos.get', { params: { id: '123' } }]
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
`queryKey()` is the exact key `queryOptions()` and RSC prefetching store under, so
|
|
583
|
+
`getQueryData`/`setQueryData` hit. Use `queryFilter()` to match every args variant.
|
|
584
|
+
|
|
561
585
|
See [Client Methods](#client-methods) in API Reference.
|
|
562
586
|
|
|
563
587
|
## RSC Prefetching
|
|
@@ -645,4 +669,5 @@ Client args can include `params`, `searchParams`, `form`, custom `fetch`,
|
|
|
645
669
|
### Client Methods
|
|
646
670
|
|
|
647
671
|
Generated clients expose `queryOptions`, `staticQueryOptions`,
|
|
648
|
-
`mutationOptions`, `queryKey
|
|
672
|
+
`mutationOptions`, `queryKey` (exact cache key), and `queryFilter` (matches every
|
|
673
|
+
args variant when called without args).
|
|
@@ -81,6 +81,17 @@ For denied reserved fixed-window and token-bucket requests, `reset` is when the
|
|
|
81
81
|
|
|
82
82
|
A request larger than every shard's capacity plus finite reservation headroom can never succeed. It returns `reason: 'requestTooLarge'` and `reset: 0` without reading shard state. When only smaller shards cannot serve a request, they are excluded from retry calculations. Fresh, full, and partial snapshot projections preserve permanent denial as `retryAfter: Infinity`, and the React hook reports `ok: false` without scheduling a retry timer. Reduce `count`, reduce `shards`, or raise the configured capacity.
|
|
83
83
|
|
|
84
|
+
## Protection / deny list — delta from parity
|
|
85
|
+
|
|
86
|
+
With `enableProtection: true`, failures are counted per identifier, ip, userAgent, and country.
|
|
87
|
+
|
|
88
|
+
- A value's block is cached for up to 24 h once it reaches `denyListThreshold` **within a rolling 10 minute window**. Failures paced wider than that window decay, so a shared NAT/carrier IP is not blocked by unrelated failures accumulated over days.
|
|
89
|
+
- Protection state is a bounded in-memory LRU. Failure histories are evicted before blocks, active blocks refresh on use, and the coldest block can be evicted above 4,096 simultaneous blocked values per prefix. Evicted values fall through to the database-backed limiter.
|
|
90
|
+
- A success clears the **identifier** counter only. ip/userAgent counters are attacker-supplied; clearing them on success would let a caller loop `threshold - 1` failures plus one success indefinitely, or reset a victim's counter by forging their user-agent.
|
|
91
|
+
- State is module-scope memory: at most 4096 tracked values per prefix with least-recently-hit eviction, and values over 128 characters stored truncated. It is per-isolate, so it does not survive a cold start and is not shared across function entries.
|
|
92
|
+
- Increments are not rolled back when Convex retries a mutation after a write conflict, so a value can count slightly more failures than it was served.
|
|
93
|
+
- `pickDeniedValue` runs before any database read, so a blocked value is rejected without touching `ratelimitState`.
|
|
94
|
+
|
|
84
95
|
## Convex constraints
|
|
85
96
|
|
|
86
97
|
- `blockUntilReady()` needs `setTimeout`, so it only runs in actions or non-Convex runtimes. It throws with that guidance inside queries and mutations. `limit()` and `check()` never touch timers.
|
|
@@ -28,7 +28,7 @@ export const { CRPCProvider, useCRPC, useCRPCClient } = createCRPCContext({
|
|
|
28
28
|
|
|
29
29
|
### QueryClient
|
|
30
30
|
|
|
31
|
-
cRPC auto-sets `staleTime: Infinity`, `refetch*: false` per query (Convex pushes via WebSocket — never stale).
|
|
31
|
+
cRPC auto-sets `staleTime: Infinity`, `refetch*: false` per query (Convex pushes via WebSocket — never stale). `crpc.http.*` routes are pull-model instead: `staleTime: 30_000` with `refetchOnMount` left at TanStack's default, so RSC-prefetched data hydrates but an invalidated route still refetches.
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
34
|
// src/lib/convex/query-client.ts
|
|
@@ -356,7 +356,7 @@ Rate limiting is opt-in: scaffold the full starter once.
|
|
|
356
356
|
bunx kitcn add ratelimit
|
|
357
357
|
```
|
|
358
358
|
|
|
359
|
-
This creates `convex/lib/plugins/ratelimit/schema.ts`, `convex/lib/plugins/ratelimit/plugin.ts`, and registers `ratelimitExtension()` in `convex/functions/schema.ts`.
|
|
359
|
+
This creates `convex/lib/plugins/ratelimit/schema.ts`, `convex/lib/plugins/ratelimit/plugin.ts`, `convex/functions/plugins/ratelimit.ts`, and registers `ratelimitExtension()` in `convex/functions/schema.ts`.
|
|
360
360
|
|
|
361
361
|
```ts
|
|
362
362
|
import { defineSchema } from "kitcn/orm";
|
|
@@ -365,12 +365,18 @@ import { ratelimitExtension } from "../lib/plugins/ratelimit/schema";
|
|
|
365
365
|
export default defineSchema(tables).extend(ratelimitExtension());
|
|
366
366
|
```
|
|
367
367
|
|
|
368
|
-
Create `convex/lib/plugins/ratelimit/plugin.ts` and call `ratelimit.middleware()` from mutation builders. Use the default bucket for normal writes
|
|
368
|
+
Create `convex/lib/plugins/ratelimit/plugin.ts` and call `ratelimit.middleware()` from mutation builders. Use the default bucket for normal writes; the starter's `interactive` bucket demonstrates a named `.meta({ ratelimit: ... })` override. Rename or remove it to match the application. Runtime accounting (`shards`, `check()`, snapshots, read accuracy) lives in `features/ratelimit.md`.
|
|
369
369
|
|
|
370
370
|
Use `RatelimitPlugin` from `kitcn/ratelimit`:
|
|
371
371
|
|
|
372
372
|
```ts
|
|
373
|
-
import {
|
|
373
|
+
import {
|
|
374
|
+
type LimitRequest,
|
|
375
|
+
MINUTE,
|
|
376
|
+
Ratelimit,
|
|
377
|
+
RatelimitPlugin,
|
|
378
|
+
SECOND,
|
|
379
|
+
} from "kitcn/ratelimit";
|
|
374
380
|
import type { MutationCtx } from "../../../functions/generated/server";
|
|
375
381
|
|
|
376
382
|
const fixed = (rate: number) => Ratelimit.fixedWindow(rate, MINUTE);
|
|
@@ -381,6 +387,11 @@ export const ratelimitBuckets = {
|
|
|
381
387
|
free: fixed(60),
|
|
382
388
|
premium: fixed(200),
|
|
383
389
|
},
|
|
390
|
+
interactive: {
|
|
391
|
+
public: Ratelimit.fixedWindow(3, 30 * SECOND),
|
|
392
|
+
free: Ratelimit.fixedWindow(3, 30 * SECOND),
|
|
393
|
+
premium: Ratelimit.fixedWindow(3, 30 * SECOND),
|
|
394
|
+
},
|
|
384
395
|
} as const;
|
|
385
396
|
|
|
386
397
|
type RatelimitTier = keyof (typeof ratelimitBuckets)["default"];
|
|
@@ -415,14 +426,27 @@ async function getRequestSignals(ctx: RatelimitCtx) {
|
|
|
415
426
|
};
|
|
416
427
|
}
|
|
417
428
|
|
|
429
|
+
function getRequestIdentifier(
|
|
430
|
+
user: RatelimitUser | null,
|
|
431
|
+
signals: LimitRequest | undefined
|
|
432
|
+
) {
|
|
433
|
+
if (user) return user.id;
|
|
434
|
+
return signals?.ip ? `ip:${signals.ip}` : "ip:unknown";
|
|
435
|
+
}
|
|
436
|
+
|
|
418
437
|
export const ratelimit = RatelimitPlugin.configure({
|
|
419
438
|
buckets: ratelimitBuckets,
|
|
420
439
|
getBucket: ({ meta }: { meta: RatelimitMeta }) => meta.ratelimit ?? "default",
|
|
421
440
|
getUser: ({ ctx }: { ctx: RatelimitCtx }) => ctx.user ?? null,
|
|
422
|
-
getIdentifier: ({ user }: { user: RatelimitUser | null }) =>
|
|
423
|
-
user?.id ?? "anonymous",
|
|
424
441
|
getTier: getUserTier,
|
|
425
442
|
getSignals: ({ ctx }: { ctx: RatelimitCtx }) => getRequestSignals(ctx),
|
|
443
|
+
getIdentifier: ({
|
|
444
|
+
user,
|
|
445
|
+
signals,
|
|
446
|
+
}: {
|
|
447
|
+
user: RatelimitUser | null;
|
|
448
|
+
signals: LimitRequest | undefined;
|
|
449
|
+
}) => getRequestIdentifier(user, signals),
|
|
426
450
|
prefix: ({ bucket, tier }) => `ratelimit:${bucket}:${tier}`,
|
|
427
451
|
failureMode: "closed",
|
|
428
452
|
enableProtection: true,
|
|
@@ -432,6 +456,12 @@ export const ratelimit = RatelimitPlugin.configure({
|
|
|
432
456
|
|
|
433
457
|
Use `ctx.meta.getRequestMetadata()` on Convex 1.38.0+ for IP/user-agent signals in mutation rate limits. Convex also exposes this metadata in actions; route action-side enforcement through a mutation when database-backed ratelimit state is required.
|
|
434
458
|
|
|
459
|
+
The identifier is the partition key: every request resolving to the same string shares one budget and one `ratelimitState` document. Never key unauthenticated traffic to a constant — `fixedWindow(30, MINUTE)` under a constant identifier is 30 req/min for the whole deployment, and 30 consecutive denials arm a 24 h deny-list block against that constant. `getSignals` runs once per request, before `getIdentifier`, so keying on `signals.ip` costs no extra syscall.
|
|
460
|
+
|
|
461
|
+
Plan for the two consequences: shared NAT/CGNAT addresses share a budget, and calls without request metadata all land on `ip:unknown`. One `ratelimitState` row exists per identifier per `bucket:tier`; run the scaffolded `plugins/ratelimit:cleanup` private mutation on demand, with `olderThanMs` longer than every configured rate-limit window, and repeat while it returns `hasMore: true`.
|
|
462
|
+
|
|
463
|
+
`RatelimitPlugin.configure` also forwards `failureMode`, `timeout`, `enableProtection`, `denyListThreshold`, `denyList`, `dynamicLimits`, and `ephemeralCache` to the limiter.
|
|
464
|
+
|
|
435
465
|
### 9.5 Scheduling gate
|
|
436
466
|
|
|
437
467
|
Create `convex/functions/crons.ts` with `cronJobs()` and use `caller.schedule.now/after/at` in mutations/actions for delayed procedure jobs (`ctx.scheduler.*` only for raw `internal.*` functions).
|