kitcn 0.16.0 → 0.17.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.
Files changed (38) hide show
  1. package/dist/aggregate/index.d.ts +1 -1
  2. package/dist/auth/client/index.js +1 -1
  3. package/dist/auth/index.js +19 -21
  4. package/dist/auth/nextjs/index.d.ts +1 -1
  5. package/dist/auth/nextjs/index.js +4 -4
  6. package/dist/{auth-store-ssZDPa37.js → auth-store-BnGZxmnY.js} +4 -1
  7. package/dist/{backend-core-DqPydYyx.mjs → backend-core-BsKP1LVg.mjs} +204 -164
  8. package/dist/{builder-DBgto1yn.js → builder-f4F_NRvK.js} +245 -153
  9. package/dist/{caller-factory-NEfgD5E0.js → caller-factory-DHywSoGZ.js} +7 -5
  10. package/dist/cli.mjs +14 -7
  11. package/dist/crpc/index.js +1 -127
  12. package/dist/{middleware-Bg-PdtrI.js → middleware-Cgrv2jIu.js} +1 -1
  13. package/dist/orm/index.d.ts +1 -1
  14. package/dist/orm/index.js +486 -121
  15. package/dist/plugins/index.js +1 -1
  16. package/dist/{procedure-caller-9m6NBxQu.js → procedure-caller-Rj6z3ai7.js} +1 -1
  17. package/dist/{procedure-name-Cy1AxayA.d.ts → procedure-name-Bo5KMcqc.d.ts} +15 -3
  18. package/dist/{query-context-ydn9kb6P.js → query-context-C90vNlc9.js} +131 -30
  19. package/dist/query-options-C_eBSIXG.js +247 -0
  20. package/dist/ratelimit/index.d.ts +26 -6
  21. package/dist/ratelimit/index.js +427 -100
  22. package/dist/ratelimit/react/index.d.ts +14 -0
  23. package/dist/ratelimit/react/index.js +149 -16
  24. package/dist/react/index.d.ts +3 -1
  25. package/dist/react/index.js +48 -15
  26. package/dist/rsc/index.js +22 -33
  27. package/dist/server/index.d.ts +1 -1
  28. package/dist/server/index.js +3 -3
  29. package/dist/solid/index.js +19 -5
  30. package/dist/watcher.mjs +2 -2
  31. package/dist/{where-clause-compiler-WF9UcrAB.d.ts → where-clause-compiler-BRhLW1dp.d.ts} +51 -0
  32. package/package.json +1 -1
  33. package/skills/kitcn/SKILL.md +1 -0
  34. package/skills/kitcn/references/features/create-plugins.md +1 -1
  35. package/skills/kitcn/references/features/orm.md +11 -1
  36. package/skills/kitcn/references/features/ratelimit.md +105 -0
  37. package/skills/kitcn/references/setup/server.md +1 -1
  38. package/dist/query-options-C96zLANM.js +0 -121
@@ -1,3 +1,3 @@
1
- import { n as resolvePluginOptions, t as definePlugin } from "../middleware-Bg-PdtrI.js";
1
+ import { n as resolvePluginOptions, t as definePlugin } from "../middleware-Cgrv2jIu.js";
2
2
 
3
3
  export { definePlugin, resolvePluginOptions };
@@ -1,5 +1,5 @@
1
1
  import { i as decodeWire, o as encodeWire } from "./transformer-C6pGVHqx.js";
2
- import { _ as CRPCError } from "./builder-DBgto1yn.js";
2
+ import { _ as CRPCError } from "./builder-f4F_NRvK.js";
3
3
  import { z } from "zod";
4
4
 
5
5
  //#region src/server/env.ts
@@ -734,7 +734,12 @@ declare function createMiddlewareFactory<TDefaultContext, TMeta = object>(): <TC
734
734
  /** Internal definition storing procedure state */
735
735
  type ProcedureBuilderDef<TMeta = object> = {
736
736
  middlewares: AnyMiddleware[];
737
- inputSchemas: Record<string, any>[];
737
+ /**
738
+ * Full `.input()` schemas, not their shapes: object-level checks
739
+ * (`.refine()`, `.superRefine()`, strict/catchall) only survive on the
740
+ * schema itself.
741
+ */
742
+ inputSchemas: z.ZodObject<any>[];
738
743
  outputSchema?: z.ZodTypeAny;
739
744
  meta?: TMeta;
740
745
  procedureName?: string;
@@ -764,7 +769,7 @@ declare class ProcedureBuilder<TBaseCtx, _TContext, TContextOverrides extends Un
764
769
  protected _meta(value: TMeta): ProcedureBuilderDef<TMeta>;
765
770
  /** Set server-only procedure name for middleware/logging */
766
771
  protected _name(value: string): ProcedureBuilderDef<TMeta>;
767
- /** Merge all input schemas into one */
772
+ /** Merge every `.input()` shape - used to build the Convex arg validator */
768
773
  protected _getMergedInput(): Record<string, any> | undefined;
769
774
  protected _createFunction(handler: any, baseFunction: any, customFn: typeof zCustomQuery | typeof zCustomMutation | typeof zCustomAction, fnType: 'query' | 'mutation' | 'action'): Record<string, unknown>;
770
775
  }
@@ -1076,7 +1081,14 @@ type TokenResult = {
1076
1081
  type GetTokenFn = (siteUrl: string, headers: Headers, opts?: unknown) => Promise<TokenResult>;
1077
1082
  /** Auth options for server-side calls. */
1078
1083
  type AuthOptions = {
1079
- /** Function to extract auth token from request headers. */getToken: GetTokenFn; /** Custom function to detect UNAUTHORIZED errors. Default checks code property. */
1084
+ /** Function to extract auth token from request headers. */getToken: GetTokenFn;
1085
+ /**
1086
+ * Custom function to detect UNAUTHORIZED errors.
1087
+ * Set this to resolve those errors to `null` instead of throwing.
1088
+ *
1089
+ * Refreshing an expired cached token and retrying does not require this:
1090
+ * that always falls back to `defaultIsUnauthorized`.
1091
+ */
1080
1092
  isUnauthorized?: (error: unknown) => boolean;
1081
1093
  };
1082
1094
  type CreateCallerFactoryOptions<TApi> = {
@@ -23,6 +23,71 @@ function isFieldReference(value) {
23
23
  return value && typeof value === "object" && value.__brand === "FieldReference";
24
24
  }
25
25
  /**
26
+ * SQL `LIKE` semantics: `%` matches any run of characters, `_` matches exactly
27
+ * one, everything else is literal. Wildcards work anywhere in the pattern, not
28
+ * only at the ends.
29
+ *
30
+ * Matched with a single-backtrack scan rather than a compiled regex. A pattern
31
+ * is caller data — anything interpolated into `` `%${query}%` `` reaches here —
32
+ * and translating `%` to a regex quantifier makes `'%%%%%z'` backtrack
33
+ * exponentially, which would burn the whole Convex function CPU budget on one
34
+ * row. This scan remembers only the most recent `%`, so the worst case is
35
+ * O(value * pattern) with no catastrophic case at all.
36
+ */
37
+ function matchLikePattern(value, pattern, caseInsensitive) {
38
+ const target = Array.from(caseInsensitive ? value.toLowerCase() : value);
39
+ const source = Array.from(caseInsensitive ? pattern.toLowerCase() : pattern);
40
+ let valueIndex = 0;
41
+ let patternIndex = 0;
42
+ let wildcardPatternIndex = -1;
43
+ let wildcardValueIndex = 0;
44
+ while (valueIndex < target.length) {
45
+ const patternChar = patternIndex < source.length ? source[patternIndex] : void 0;
46
+ if (patternChar === "_" || patternChar === target[valueIndex]) {
47
+ valueIndex += 1;
48
+ patternIndex += 1;
49
+ } else if (patternChar === "%") {
50
+ wildcardPatternIndex = patternIndex;
51
+ wildcardValueIndex = valueIndex;
52
+ patternIndex += 1;
53
+ } else if (wildcardPatternIndex === -1) return false;
54
+ else {
55
+ wildcardValueIndex += 1;
56
+ valueIndex = wildcardValueIndex;
57
+ patternIndex = wildcardPatternIndex + 1;
58
+ }
59
+ }
60
+ while (patternIndex < source.length && source[patternIndex] === "%") patternIndex += 1;
61
+ return patternIndex === source.length;
62
+ }
63
+ /**
64
+ * Structural equality matching Convex's own `q.eq`. Arrays, objects and bytes
65
+ * are compared by content: reference identity would make every `eq`/`in` filter
66
+ * on a `custom(v.array(...))` / `custom(v.object(...))` column match nothing.
67
+ */
68
+ function filterValuesEqual(a, b) {
69
+ if (a === b) return true;
70
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
71
+ if (Array.isArray(a) || Array.isArray(b)) {
72
+ if (!(Array.isArray(a) && Array.isArray(b)) || a.length !== b.length) return false;
73
+ return a.every((item, index) => filterValuesEqual(item, b[index]));
74
+ }
75
+ if (a instanceof ArrayBuffer || b instanceof ArrayBuffer) {
76
+ if (!(a instanceof ArrayBuffer && b instanceof ArrayBuffer) || a.byteLength !== b.byteLength) return false;
77
+ const left = new Uint8Array(a);
78
+ const right = new Uint8Array(b);
79
+ return left.every((byte, index) => byte === right[index]);
80
+ }
81
+ const aKeys = Object.keys(a);
82
+ const bKeys = Object.keys(b);
83
+ if (aKeys.length !== bKeys.length) return false;
84
+ return aKeys.every((key) => Object.hasOwn(b, key) && filterValuesEqual(a[key], b[key]));
85
+ }
86
+ /** `inArray`/`notInArray` membership using {@link filterValuesEqual}. */
87
+ function filterValueInList(value, list) {
88
+ return list.some((candidate) => filterValuesEqual(candidate, value));
89
+ }
90
+ /**
26
91
  * Create a column wrapper
27
92
  * Used internally by query builder's _createColumnProxies
28
93
  */
@@ -1121,11 +1186,13 @@ var FlatMapStreamIterator = class {
1121
1186
  #currentOuterItem = null;
1122
1187
  #mapper;
1123
1188
  #mappedIndexFields;
1124
- constructor(outerStream, mapper, mappedIndexFields) {
1189
+ #emptyMappedKeyAllowed;
1190
+ constructor(outerStream, mapper, mappedIndexFields, emptyMappedKeyAllowed) {
1125
1191
  this.#outerIterator = outerStream.iterWithKeys()[Symbol.asyncIterator]();
1126
1192
  this.#outerStream = outerStream;
1127
1193
  this.#mapper = mapper;
1128
1194
  this.#mappedIndexFields = mappedIndexFields;
1195
+ this.#emptyMappedKeyAllowed = emptyMappedKeyAllowed;
1129
1196
  }
1130
1197
  singletonSkipInnerStream() {
1131
1198
  const indexKey = this.#mappedIndexFields.map(() => null);
@@ -1136,7 +1203,7 @@ var FlatMapStreamIterator = class {
1136
1203
  let innerStream;
1137
1204
  if (t === null) innerStream = this.singletonSkipInnerStream();
1138
1205
  else {
1139
- innerStream = await this.#mapper(t);
1206
+ innerStream = await this.#mapper(t, indexKey);
1140
1207
  if (!equalIndexFields(innerStream.getIndexFields(), this.#mappedIndexFields)) throw new Error(`FlatMapStream: inner stream has different index fields than expected: ${JSON.stringify(innerStream.getIndexFields())} vs ${JSON.stringify(this.#mappedIndexFields)}`);
1141
1208
  if (innerStream.getOrder() !== this.#outerStream.getOrder()) throw new Error(`FlatMapStream: inner stream has different order than outer stream: ${innerStream.getOrder()} vs ${this.#outerStream.getOrder()}`);
1142
1209
  }
@@ -1159,7 +1226,7 @@ var FlatMapStreamIterator = class {
1159
1226
  }
1160
1227
  const result = await this.#currentOuterItem.innerIterator.next();
1161
1228
  if (result.done) {
1162
- if (this.#currentOuterItem.count > 0) this.#currentOuterItem = null;
1229
+ if (this.#currentOuterItem.count > 0 || !this.#emptyMappedKeyAllowed(this.#currentOuterItem.indexKey)) this.#currentOuterItem = null;
1163
1230
  else this.#currentOuterItem.innerIterator = this.singletonSkipInnerStream().iterWithKeys()[Symbol.asyncIterator]();
1164
1231
  return await this.next();
1165
1232
  }
@@ -1175,18 +1242,21 @@ var FlatMapStream = class FlatMapStream extends QueryStream {
1175
1242
  #stream;
1176
1243
  #mapper;
1177
1244
  #mappedIndexFields;
1178
- constructor(stream, mapper, mappedIndexFields) {
1245
+ #emptyMappedKeyAllowed;
1246
+ constructor(stream, mapper, mappedIndexFields, emptyMappedKeyAllowed = () => true) {
1179
1247
  super();
1180
1248
  this.#stream = stream;
1181
1249
  this.#mapper = mapper;
1182
1250
  this.#mappedIndexFields = mappedIndexFields;
1251
+ this.#emptyMappedKeyAllowed = emptyMappedKeyAllowed;
1183
1252
  }
1184
1253
  iterWithKeys() {
1185
1254
  const outerStream = this.#stream;
1186
1255
  const mapper = this.#mapper;
1187
1256
  const mappedIndexFields = this.#mappedIndexFields;
1257
+ const emptyMappedKeyAllowed = this.#emptyMappedKeyAllowed;
1188
1258
  return { [Symbol.asyncIterator]() {
1189
- return new FlatMapStreamIterator(outerStream, mapper, mappedIndexFields);
1259
+ return new FlatMapStreamIterator(outerStream, mapper, mappedIndexFields, emptyMappedKeyAllowed);
1190
1260
  } };
1191
1261
  }
1192
1262
  getOrder() {
@@ -1210,15 +1280,34 @@ var FlatMapStream = class FlatMapStream extends QueryStream {
1210
1280
  upperBound: outerUpperBound,
1211
1281
  upperBoundInclusive: innerUpperBound.length === 0 ? indexBounds.upperBoundInclusive : true
1212
1282
  };
1213
- const innerIndexBounds = {
1214
- lowerBound: innerLowerBound,
1215
- lowerBoundInclusive: innerLowerBound.length === 0 ? true : indexBounds.lowerBoundInclusive,
1216
- upperBound: innerUpperBound,
1217
- upperBoundInclusive: innerUpperBound.length === 0 ? true : indexBounds.upperBoundInclusive
1283
+ const unrestricted = {
1284
+ lowerBound: [],
1285
+ lowerBoundInclusive: true,
1286
+ upperBound: [],
1287
+ upperBoundInclusive: true
1218
1288
  };
1219
- return new FlatMapStream(this.#stream.narrow(outerIndexBounds), async (t) => {
1220
- return (await this.#mapper(t)).narrow(innerIndexBounds);
1221
- }, this.#mappedIndexFields);
1289
+ const isBoundaryParent = (parentKey, bound) => bound.length === outerLength && compareKeys({
1290
+ value: parentKey,
1291
+ kind: "exact"
1292
+ }, {
1293
+ value: bound,
1294
+ kind: "exact"
1295
+ }) === 0;
1296
+ const innerBoundsForParent = (parentKey) => {
1297
+ const atLowerBound = innerLowerBound.length > 0 && isBoundaryParent(parentKey, outerLowerBound);
1298
+ const atUpperBound = innerUpperBound.length > 0 && isBoundaryParent(parentKey, outerUpperBound);
1299
+ if (!(atLowerBound || atUpperBound)) return unrestricted;
1300
+ return {
1301
+ lowerBound: atLowerBound ? innerLowerBound : unrestricted.lowerBound,
1302
+ lowerBoundInclusive: atLowerBound ? indexBounds.lowerBoundInclusive : true,
1303
+ upperBound: atUpperBound ? innerUpperBound : unrestricted.upperBound,
1304
+ upperBoundInclusive: atUpperBound ? indexBounds.upperBoundInclusive : true
1305
+ };
1306
+ };
1307
+ const emptyMappedIndexKey = this.#mappedIndexFields.map(() => null);
1308
+ return new FlatMapStream(this.#stream.narrow(outerIndexBounds), async (t, parentKey) => {
1309
+ return (await this.#mapper(t, parentKey)).narrow(innerBoundsForParent(parentKey));
1310
+ }, this.#mappedIndexFields, (parentKey) => this.#emptyMappedKeyAllowed(parentKey) && indexKeyWithinBounds(emptyMappedIndexKey, innerBoundsForParent(parentKey)));
1222
1311
  }
1223
1312
  };
1224
1313
  var SingletonStream = class SingletonStream extends QueryStream {
@@ -1264,25 +1353,37 @@ var SingletonStream = class SingletonStream extends QueryStream {
1264
1353
  return this.#equalityIndexFilter;
1265
1354
  }
1266
1355
  narrow(indexBounds) {
1267
- const compareLowerBound = compareKeys({
1268
- value: indexBounds.lowerBound,
1269
- kind: indexBounds.lowerBoundInclusive ? "exact" : "successor"
1270
- }, {
1271
- value: this.#indexKey,
1272
- kind: "exact"
1273
- });
1274
- const compareUpperBound = compareKeys({
1275
- value: this.#indexKey,
1276
- kind: "exact"
1277
- }, {
1278
- value: indexBounds.upperBound,
1279
- kind: indexBounds.upperBoundInclusive ? "exact" : "predecessor"
1280
- });
1281
- if (compareLowerBound <= 0 && compareUpperBound <= 0) return new SingletonStream(this.#value, this.#order, this.#indexFields, this.#indexKey, this.#equalityIndexFilter);
1356
+ if (indexKeyWithinBounds(this.#indexKey, indexBounds)) return new SingletonStream(this.#value, this.#order, this.#indexFields, this.#indexKey, this.#equalityIndexFilter);
1282
1357
  return new EmptyStream(this.#order, this.#indexFields);
1283
1358
  }
1284
1359
  };
1285
1360
  /**
1361
+ * True when `lowerBound <= indexKey <= upperBound`, honouring each bound's
1362
+ * inclusivity.
1363
+ *
1364
+ * Bounds are compared as `predecessor`/`successor` rather than `exact`, the
1365
+ * same convention `StreamQuery.narrow` uses. That is what lets a bound be a
1366
+ * prefix of the key — including the empty prefix that means unbounded — and it
1367
+ * agrees with `exact` whenever the two are the same length.
1368
+ */
1369
+ function indexKeyWithinBounds(indexKey, indexBounds) {
1370
+ const compareLowerBound = compareKeys({
1371
+ value: indexBounds.lowerBound,
1372
+ kind: indexBounds.lowerBoundInclusive ? "predecessor" : "successor"
1373
+ }, {
1374
+ value: indexKey,
1375
+ kind: "exact"
1376
+ });
1377
+ const compareUpperBound = compareKeys({
1378
+ value: indexKey,
1379
+ kind: "exact"
1380
+ }, {
1381
+ value: indexBounds.upperBound,
1382
+ kind: indexBounds.upperBoundInclusive ? "successor" : "predecessor"
1383
+ });
1384
+ return compareLowerBound <= 0 && compareUpperBound <= 0;
1385
+ }
1386
+ /**
1286
1387
  * This is a completely empty stream that yields no values, and in particular
1287
1388
  * does not count towards maxScan.
1288
1389
  * Compare to SingletonStream(null, ...), which yields no values but does count
@@ -1320,7 +1421,7 @@ var EmptyStream = class extends QueryStream {
1320
1421
  }
1321
1422
  };
1322
1423
  function normalizeIndexFields(indexFields) {
1323
- if (!indexFields.includes("_creationTime") && (indexFields.length !== 1 || indexFields[0] !== "_id")) indexFields.push("_creationTime");
1424
+ if (!indexFields.includes("_creationTime") && !indexFields.includes("_id")) indexFields.push("_creationTime");
1324
1425
  if (!indexFields.includes("_id")) indexFields.push("_id");
1325
1426
  }
1326
1427
  function* getOrderingIndexFields(stream) {
@@ -1515,4 +1616,4 @@ async function getByIdWithOrmQueryFallback(ctx, tableName, id) {
1515
1616
  }
1516
1617
 
1517
1618
  //#endregion
1518
- export { ne as A, inArray as C, like as D, isNull as E, notLike as F, or as I, startsWith as L, notBetween as M, notIlike as N, lt as O, notInArray as P, ilike as S, isNotNull as T, endsWith as _, mergedStream as a, gt as b, isUnsetToken as c, arrayContained as d, arrayContains as f, contains as g, column as h, getIndexFields as i, not as j, lte as k, unsetToken as l, between as m, EmptyStream as n, stream as o, arrayOverlaps as p, QueryStream as r, streamIndexRange as s, getByIdWithOrmQueryFallback as t, and as u, eq as v, isFieldReference as w, gte as x, fieldRef as y };
1619
+ export { like as A, or as B, gt as C, isFieldReference as D, inArray as E, not as F, notBetween as I, notIlike as L, lte as M, matchLikePattern as N, isNotNull as O, ne as P, notInArray as R, filterValuesEqual as S, ilike as T, startsWith as V, contains as _, indexKeyWithinBounds as a, fieldRef as b, streamIndexRange as c, and as d, arrayContained as f, column as g, between as h, getIndexFields as i, lt as j, isNull as k, isUnsetToken as l, arrayOverlaps as m, EmptyStream as n, mergedStream as o, arrayContains as p, QueryStream as r, stream as s, getByIdWithOrmQueryFallback as t, unsetToken as u, endsWith as v, gte as w, filterValueInList as x, eq as y, notLike as z };
@@ -0,0 +1,247 @@
1
+ import { getFunctionName } from "convex/server";
2
+
3
+ //#region src/crpc/http-types.ts
4
+ /** HTTP client error */
5
+ var HttpClientError = class extends Error {
6
+ name = "HttpClientError";
7
+ code;
8
+ status;
9
+ procedureName;
10
+ constructor(opts) {
11
+ super(opts.message ?? `${opts.code}: ${opts.procedureName}`);
12
+ this.code = opts.code;
13
+ this.status = opts.status;
14
+ this.procedureName = opts.procedureName;
15
+ }
16
+ };
17
+ /** Type guard for HttpClientError */
18
+ const isHttpClientError = (error) => error instanceof HttpClientError;
19
+
20
+ //#endregion
21
+ //#region src/crpc/http-client.ts
22
+ /**
23
+ * HTTP Client Helpers
24
+ *
25
+ * Framework-agnostic utilities for executing HTTP requests
26
+ * against Convex HTTP endpoints.
27
+ */
28
+ /** Reserved keys that are not part of JSON body */
29
+ const RESERVED_KEYS = new Set([
30
+ "params",
31
+ "searchParams",
32
+ "form",
33
+ "fetch",
34
+ "init",
35
+ "headers"
36
+ ]);
37
+ /**
38
+ * Replace URL path parameters with actual values.
39
+ * e.g., '/users/:id' with { id: '123' } -> '/users/123'
40
+ */
41
+ function replaceUrlParam(url, params) {
42
+ return url.replace(/:(\w+)/g, (_, key) => {
43
+ const value = params[key];
44
+ return value !== void 0 ? encodeURIComponent(value) : `:${key}`;
45
+ });
46
+ }
47
+ /**
48
+ * Build URLSearchParams from query object.
49
+ * Handles array values as multiple params with same key (like Hono).
50
+ */
51
+ function buildSearchParams(query) {
52
+ const params = new URLSearchParams();
53
+ for (const [key, value] of Object.entries(query)) if (Array.isArray(value)) for (const v of value) params.append(key, v);
54
+ else if (value !== void 0 && value !== null) params.append(key, value);
55
+ return params;
56
+ }
57
+ /**
58
+ * Hono-style HTTP request executor.
59
+ * Processes args in the same way as Hono's ClientRequestImpl.fetch().
60
+ */
61
+ async function executeHttpRequest(opts) {
62
+ const { method, path } = opts.route;
63
+ const args = opts.args ?? {};
64
+ let rBody;
65
+ let cType;
66
+ if (args.form) {
67
+ const form = new FormData();
68
+ for (const [k, v] of Object.entries(args.form)) if (Array.isArray(v)) for (const v2 of v) form.append(k, v2);
69
+ else form.append(k, v);
70
+ rBody = form;
71
+ } else {
72
+ const jsonBody = {};
73
+ for (const [key, value] of Object.entries(args)) if (!RESERVED_KEYS.has(key) && value !== void 0) jsonBody[key] = value;
74
+ if (Object.keys(jsonBody).length > 0) {
75
+ rBody = JSON.stringify(opts.transformer.input.serialize(jsonBody));
76
+ cType = "application/json";
77
+ }
78
+ }
79
+ const argsClientOpts = {};
80
+ if (args.fetch) argsClientOpts.fetch = args.fetch;
81
+ if (args.init) argsClientOpts.init = args.init;
82
+ if (args.headers) argsClientOpts.headers = args.headers;
83
+ const mergedClientOpts = {
84
+ ...opts.clientOpts,
85
+ ...argsClientOpts
86
+ };
87
+ const resolvedBaseHeaders = typeof opts.baseHeaders === "function" ? await opts.baseHeaders() : opts.baseHeaders;
88
+ const headerValues = { ...typeof mergedClientOpts.headers === "function" ? await mergedClientOpts.headers() : mergedClientOpts.headers };
89
+ if (cType) headerValues["Content-Type"] = cType;
90
+ const finalHeaders = {};
91
+ if (resolvedBaseHeaders) {
92
+ for (const [key, value] of Object.entries(resolvedBaseHeaders)) if (value !== void 0) finalHeaders[key] = value;
93
+ }
94
+ Object.assign(finalHeaders, headerValues);
95
+ let url = opts.convexSiteUrl + path;
96
+ if (args.params) url = opts.convexSiteUrl + replaceUrlParam(path, args.params);
97
+ if (args.searchParams) {
98
+ const queryString = buildSearchParams(args.searchParams).toString();
99
+ if (queryString) url = `${url}?${queryString}`;
100
+ }
101
+ const methodUpperCase = method.toUpperCase();
102
+ const setBody = !(methodUpperCase === "GET" || methodUpperCase === "HEAD");
103
+ const response = await (mergedClientOpts.fetch ?? opts.baseFetch ?? globalThis.fetch)(url, {
104
+ body: setBody ? rBody : void 0,
105
+ method: methodUpperCase,
106
+ headers: finalHeaders,
107
+ ...mergedClientOpts.init
108
+ });
109
+ if (!response.ok) {
110
+ const errorData = await response.json().catch(() => ({ error: {
111
+ code: "UNKNOWN",
112
+ message: response.statusText
113
+ } }));
114
+ const errorCode = errorData?.error?.code || "UNKNOWN";
115
+ const errorMessage = errorData?.error?.message || response.statusText;
116
+ throw new HttpClientError({
117
+ code: errorCode,
118
+ status: response.status,
119
+ procedureName: opts.procedureName,
120
+ message: errorMessage
121
+ });
122
+ }
123
+ if (response.headers.get("content-length") === "0" || response.status === 204) return;
124
+ if ((response.headers.get("content-type") || "").includes("application/json")) return opts.transformer.output.deserialize(await response.json());
125
+ return response.text();
126
+ }
127
+
128
+ //#endregion
129
+ //#region src/crpc/query-options.ts
130
+ /**
131
+ * Query options factory for Convex query function subscriptions.
132
+ * Requires `convexQueryClient.queryFn()` set as the default `queryFn` globally.
133
+ */
134
+ function convexQuery(funcRef, args, meta, opts) {
135
+ const finalArgs = args ?? {};
136
+ const isSkip = finalArgs === "skip";
137
+ const funcName = getFunctionName(funcRef);
138
+ const [namespace, fnName] = funcName.split(":");
139
+ const authType = meta?.[namespace]?.[fnName]?.auth;
140
+ const skipUnauth = opts?.skipUnauth;
141
+ return {
142
+ queryKey: [
143
+ "convexQuery",
144
+ funcName,
145
+ isSkip ? "skip" : finalArgs
146
+ ],
147
+ staleTime: Number.POSITIVE_INFINITY,
148
+ refetchInterval: false,
149
+ refetchOnMount: false,
150
+ refetchOnReconnect: false,
151
+ refetchOnWindowFocus: false,
152
+ ...isSkip ? { enabled: false } : {},
153
+ meta: {
154
+ authType,
155
+ skipUnauth,
156
+ subscribe: true
157
+ }
158
+ };
159
+ }
160
+ /**
161
+ * Query options factory for Convex action functions.
162
+ * Actions are NOT reactive - they follow normal TanStack Query semantics.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * useQuery(convexAction(api.ai.generate, { prompt }))
167
+ * ```
168
+ *
169
+ * @example With additional options (use spread):
170
+ * ```ts
171
+ * useQuery({
172
+ * ...convexAction(api.files.process, { fileId }),
173
+ * staleTime: 60_000
174
+ * });
175
+ * ```
176
+ */
177
+ function convexAction(funcRef, args, meta, opts) {
178
+ const finalArgs = args ?? {};
179
+ const isSkip = finalArgs === "skip";
180
+ const funcName = getFunctionName(funcRef);
181
+ const [namespace, fnName] = funcName.split(":");
182
+ const authType = meta?.[namespace]?.[fnName]?.auth;
183
+ const skipUnauth = opts?.skipUnauth;
184
+ return {
185
+ queryKey: [
186
+ "convexAction",
187
+ funcName,
188
+ isSkip ? {} : finalArgs
189
+ ],
190
+ staleTime: Number.POSITIVE_INFINITY,
191
+ refetchInterval: false,
192
+ refetchOnMount: false,
193
+ refetchOnReconnect: false,
194
+ refetchOnWindowFocus: false,
195
+ ...isSkip ? { enabled: false } : {},
196
+ meta: {
197
+ authType,
198
+ skipUnauth,
199
+ subscribe: false
200
+ }
201
+ };
202
+ }
203
+ /**
204
+ * Infinite query options factory for paginated Convex queries.
205
+ * Server-safe (non-hook) - can be used in RSC.
206
+ *
207
+ * Uses flat { cursor, limit } input like tRPC.
208
+ */
209
+ function convexInfiniteQueryOptions(funcRef, args, opts = {}, meta) {
210
+ const { limit, skipUnauth, enabled, ...queryOptions } = opts;
211
+ const finalArgs = args === "skip" ? {} : args;
212
+ const isSkip = args === "skip";
213
+ const funcName = getFunctionName(funcRef);
214
+ const [namespace, fnName] = funcName.split(":");
215
+ const authType = (meta?.[namespace]?.[fnName])?.auth;
216
+ const firstPageArgs = {
217
+ ...finalArgs,
218
+ cursor: null,
219
+ limit
220
+ };
221
+ const finalEnabled = isSkip ? false : enabled;
222
+ return {
223
+ queryKey: [
224
+ "convexQuery",
225
+ funcName,
226
+ firstPageArgs
227
+ ],
228
+ staleTime: Number.POSITIVE_INFINITY,
229
+ refetchInterval: false,
230
+ refetchOnMount: false,
231
+ refetchOnReconnect: false,
232
+ refetchOnWindowFocus: false,
233
+ ...queryOptions,
234
+ ...finalEnabled === void 0 ? {} : { enabled: finalEnabled },
235
+ meta: {
236
+ authType,
237
+ skipUnauth,
238
+ subscribe: true,
239
+ queryName: funcName,
240
+ args: finalArgs,
241
+ limit
242
+ }
243
+ };
244
+ }
245
+
246
+ //#endregion
247
+ export { buildSearchParams as a, HttpClientError as c, RESERVED_KEYS as i, isHttpClientError as l, convexInfiniteQueryOptions as n, executeHttpRequest as o, convexQuery as r, replaceUrlParam as s, convexAction as t };
@@ -9,7 +9,7 @@ type Duration = number | DurationString;
9
9
  declare function toMs(duration: Duration): number;
10
10
  //#endregion
11
11
  //#region src/ratelimit/types.d.ts
12
- type RatelimitReason = 'timeout' | 'cacheBlock' | 'denyList';
12
+ type RatelimitReason = 'timeout' | 'cacheBlock' | 'denyList' | 'requestTooLarge';
13
13
  type RatelimitResponse = {
14
14
  success: boolean;
15
15
  ok: boolean;
@@ -28,17 +28,25 @@ type RemainingResponse = {
28
28
  type DynamicLimitResponse = {
29
29
  dynamicLimit: number | null;
30
30
  };
31
- type RatelimitState = {
31
+ type RatelimitStoredState = {
32
32
  value: number;
33
33
  ts: number;
34
34
  auxValue?: number;
35
35
  auxTs?: number;
36
36
  };
37
+ type RatelimitShardState = {
38
+ shard: number;
39
+ state: RatelimitStoredState;
40
+ };
41
+ type RatelimitState = RatelimitStoredState & {
42
+ shards?: RatelimitShardState[];
43
+ };
37
44
  type RatelimitSnapshot = {
38
45
  value: number;
39
46
  ts: number;
40
47
  shard: number;
41
48
  config: ResolvedAlgorithm;
49
+ state: RatelimitState;
42
50
  };
43
51
  type BaseAlgorithmOptions = {
44
52
  shards?: number;
@@ -151,12 +159,22 @@ declare function applyDynamicLimit(algorithm: ResolvedAlgorithm, dynamicLimit: n
151
159
  //#region src/ratelimit/core/calculate-rate-limit.d.ts
152
160
  type EvaluationResult = {
153
161
  state: RatelimitState;
154
- retryAfter?: number;
155
- remaining: number;
162
+ retryAfter?: number; /** Tokens left after this request, floored to `0`. */
163
+ remaining: number; /** Exact tokens left after this request. Negative when the request overdraws. */
164
+ remainingRaw: number;
156
165
  reset: number;
157
166
  limit: number;
158
167
  };
159
168
  declare function calculateRatelimit(state: RatelimitState | null, algorithm: ResolvedAlgorithm, now: number, count: number): EvaluationResult;
169
+ /**
170
+ * Convert a {@link RatelimitSnapshot} back into the `RatelimitState` shape that
171
+ * {@link calculateRatelimit} consumes.
172
+ *
173
+ * Snapshot `value` is always "tokens left". Fixed window and token bucket store
174
+ * that directly, but sliding window state stores the used count, so it has to be
175
+ * inverted before it can be replayed.
176
+ */
177
+ declare function snapshotToState(snapshot: RatelimitSnapshot): RatelimitState;
160
178
  //#endregion
161
179
  //#region src/ratelimit/plugin.d.ts
162
180
  type MaybePromise<T> = T | Promise<T>;
@@ -224,6 +242,7 @@ declare class Ratelimit {
224
242
  private readonly blockCache?;
225
243
  private readonly blockCacheSource?;
226
244
  private readonly checkCache;
245
+ private cacheGeneration;
227
246
  constructor(config: RatelimitConfig);
228
247
  limit(identifier: string, request?: LimitRequest): Promise<RatelimitResponse>;
229
248
  check(identifier: string, request?: CheckRequest): Promise<RatelimitResponse>;
@@ -246,9 +265,10 @@ declare class Ratelimit {
246
265
  };
247
266
  private withDb;
248
267
  private evaluate;
268
+ private blockKey;
269
+ private readShards;
249
270
  private evaluateCandidates;
250
271
  private resolveAlgorithm;
251
- private rawLimit;
252
272
  private runWithTimeout;
253
273
  private timeoutResponse;
254
274
  }
@@ -265,4 +285,4 @@ declare const HOUR: number;
265
285
  declare const DAY: number;
266
286
  declare const WEEK: number;
267
287
  //#endregion
268
- export { type CheckRequest, type ConvexQueryBuilder, type ConvexRatelimitDbReader, type ConvexRatelimitDbWriter, DAY, type Duration, type DurationString, type DurationUnit, type DynamicLimitResponse, type FixedWindowAlgorithm, HOUR, type HookAPIOptions, type HookCheckValue, type LimitRequest, MINUTE, RATE_LIMIT_DYNAMIC_TABLE, RATE_LIMIT_HIT_TABLE, RATE_LIMIT_STATE_TABLE, Ratelimit, type RatelimitConfig, RatelimitPlugin, type RatelimitPluginOptions, type RatelimitReason, type RatelimitResponse, type RatelimitRow, type RatelimitSnapshot, type RatelimitState, type RemainingResponse, type ResolvedAlgorithm, SECOND, type SlidingWindowAlgorithm, type TokenBucketAlgorithm, WEEK, applyDynamicLimit, calculateRatelimit, fixedWindow, slidingWindow, toMs, tokenBucket };
288
+ export { type CheckRequest, type ConvexQueryBuilder, type ConvexRatelimitDbReader, type ConvexRatelimitDbWriter, DAY, type Duration, type DurationString, type DurationUnit, type DynamicLimitResponse, type EvaluationResult, type FixedWindowAlgorithm, HOUR, type HookAPIOptions, type HookCheckValue, type LimitRequest, MINUTE, RATE_LIMIT_DYNAMIC_TABLE, RATE_LIMIT_HIT_TABLE, RATE_LIMIT_STATE_TABLE, Ratelimit, type RatelimitConfig, RatelimitPlugin, type RatelimitPluginOptions, type RatelimitReason, type RatelimitResponse, type RatelimitRow, type RatelimitShardState, type RatelimitSnapshot, type RatelimitState, type RatelimitStoredState, type RemainingResponse, type ResolvedAlgorithm, SECOND, type SlidingWindowAlgorithm, type TokenBucketAlgorithm, WEEK, applyDynamicLimit, calculateRatelimit, fixedWindow, slidingWindow, snapshotToState, toMs, tokenBucket };