convex 1.27.3 → 1.27.4-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.
Files changed (171) hide show
  1. package/dist/browser.bundle.js +499 -19
  2. package/dist/browser.bundle.js.map +4 -4
  3. package/dist/cjs/browser/index.js.map +2 -2
  4. package/dist/cjs/browser/simple_client.js +115 -15
  5. package/dist/cjs/browser/simple_client.js.map +3 -3
  6. package/dist/cjs/browser/sync/client.js +2 -2
  7. package/dist/cjs/browser/sync/client.js.map +2 -2
  8. package/dist/cjs/browser/sync/local_state.js.map +2 -2
  9. package/dist/cjs/browser/sync/optimistic_updates_impl.js +8 -1
  10. package/dist/cjs/browser/sync/optimistic_updates_impl.js.map +2 -2
  11. package/dist/cjs/browser/sync/paginated_query_client.js +381 -0
  12. package/dist/cjs/browser/sync/paginated_query_client.js.map +7 -0
  13. package/dist/cjs/browser/sync/pagination.js +37 -0
  14. package/dist/cjs/browser/sync/pagination.js.map +7 -0
  15. package/dist/cjs/browser/sync/udf_path_utils.js +16 -1
  16. package/dist/cjs/browser/sync/udf_path_utils.js.map +2 -2
  17. package/dist/cjs/cli/lib/components/definition/bundle.js +51 -8
  18. package/dist/cjs/cli/lib/components/definition/bundle.js.map +2 -2
  19. package/dist/cjs/cli/lib/components/definition/directoryStructure.js +12 -12
  20. package/dist/cjs/cli/lib/components/definition/directoryStructure.js.map +2 -2
  21. package/dist/cjs/cli/lib/components.js +1 -1
  22. package/dist/cjs/cli/lib/components.js.map +2 -2
  23. package/dist/cjs/index.js +1 -1
  24. package/dist/cjs/index.js.map +1 -1
  25. package/dist/cjs/react/client.js +89 -1
  26. package/dist/cjs/react/client.js.map +2 -2
  27. package/dist/cjs/react/index.js +4 -0
  28. package/dist/cjs/react/index.js.map +2 -2
  29. package/dist/cjs/react/queries_observer.js +36 -11
  30. package/dist/cjs/react/queries_observer.js.map +2 -2
  31. package/dist/cjs/react/use_paginated_query2.js +124 -0
  32. package/dist/cjs/react/use_paginated_query2.js.map +7 -0
  33. package/dist/cjs/react/use_queries.js +9 -2
  34. package/dist/cjs/react/use_queries.js.map +2 -2
  35. package/dist/cjs/react-clerk/ConvexProviderWithClerk.js +1 -1
  36. package/dist/cjs/react-clerk/ConvexProviderWithClerk.js.map +2 -2
  37. package/dist/cjs/values/validators.js +1 -1
  38. package/dist/cjs/values/validators.js.map +2 -2
  39. package/dist/cjs-types/browser/index.d.ts +2 -1
  40. package/dist/cjs-types/browser/index.d.ts.map +1 -1
  41. package/dist/cjs-types/browser/simple_client.d.ts +20 -1
  42. package/dist/cjs-types/browser/simple_client.d.ts.map +1 -1
  43. package/dist/cjs-types/browser/sync/client.d.ts.map +1 -1
  44. package/dist/cjs-types/browser/sync/client_node_test_helpers.d.ts +5 -4
  45. package/dist/cjs-types/browser/sync/client_node_test_helpers.d.ts.map +1 -1
  46. package/dist/cjs-types/browser/sync/local_state.d.ts +12 -1
  47. package/dist/cjs-types/browser/sync/local_state.d.ts.map +1 -1
  48. package/dist/cjs-types/browser/sync/optimistic_updates_impl.d.ts.map +1 -1
  49. package/dist/cjs-types/browser/sync/paginated_query_client.d.ts +71 -0
  50. package/dist/cjs-types/browser/sync/paginated_query_client.d.ts.map +1 -0
  51. package/dist/cjs-types/browser/sync/paginated_query_client.test.d.ts +2 -0
  52. package/dist/cjs-types/browser/sync/paginated_query_client.test.d.ts.map +1 -0
  53. package/dist/cjs-types/browser/sync/pagination.d.ts +23 -0
  54. package/dist/cjs-types/browser/sync/pagination.d.ts.map +1 -0
  55. package/dist/cjs-types/browser/sync/udf_path_utils.d.ts +22 -1
  56. package/dist/cjs-types/browser/sync/udf_path_utils.d.ts.map +1 -1
  57. package/dist/cjs-types/cli/lib/components/definition/bundle.d.ts.map +1 -1
  58. package/dist/cjs-types/cli/lib/components/definition/directoryStructure.d.ts +4 -0
  59. package/dist/cjs-types/cli/lib/components/definition/directoryStructure.d.ts.map +1 -1
  60. package/dist/cjs-types/cli/lib/components.d.ts.map +1 -1
  61. package/dist/cjs-types/index.d.ts +1 -1
  62. package/dist/cjs-types/index.d.ts.map +1 -1
  63. package/dist/cjs-types/react/client.d.ts +65 -1
  64. package/dist/cjs-types/react/client.d.ts.map +1 -1
  65. package/dist/cjs-types/react/index.d.ts +2 -0
  66. package/dist/cjs-types/react/index.d.ts.map +1 -1
  67. package/dist/cjs-types/react/queries_observer.d.ts +12 -6
  68. package/dist/cjs-types/react/queries_observer.d.ts.map +1 -1
  69. package/dist/cjs-types/react/use_paginated_query2.d.ts +51 -0
  70. package/dist/cjs-types/react/use_paginated_query2.d.ts.map +1 -0
  71. package/dist/cjs-types/react/use_queries.d.ts +2 -0
  72. package/dist/cjs-types/react/use_queries.d.ts.map +1 -1
  73. package/dist/cli.bundle.cjs +75 -25
  74. package/dist/cli.bundle.cjs.map +3 -3
  75. package/dist/esm/browser/index.js.map +2 -2
  76. package/dist/esm/browser/simple_client.js +117 -15
  77. package/dist/esm/browser/simple_client.js.map +3 -3
  78. package/dist/esm/browser/sync/client.js +2 -2
  79. package/dist/esm/browser/sync/client.js.map +2 -2
  80. package/dist/esm/browser/sync/local_state.js.map +2 -2
  81. package/dist/esm/browser/sync/optimistic_updates_impl.js +8 -1
  82. package/dist/esm/browser/sync/optimistic_updates_impl.js.map +2 -2
  83. package/dist/esm/browser/sync/paginated_query_client.js +365 -0
  84. package/dist/esm/browser/sync/paginated_query_client.js.map +7 -0
  85. package/dist/esm/browser/sync/pagination.js +14 -0
  86. package/dist/esm/browser/sync/pagination.js.map +7 -0
  87. package/dist/esm/browser/sync/udf_path_utils.js +13 -0
  88. package/dist/esm/browser/sync/udf_path_utils.js.map +2 -2
  89. package/dist/esm/cli/lib/components/definition/bundle.js +51 -8
  90. package/dist/esm/cli/lib/components/definition/bundle.js.map +2 -2
  91. package/dist/esm/cli/lib/components/definition/directoryStructure.js +12 -12
  92. package/dist/esm/cli/lib/components/definition/directoryStructure.js.map +2 -2
  93. package/dist/esm/cli/lib/components.js +1 -1
  94. package/dist/esm/cli/lib/components.js.map +2 -2
  95. package/dist/esm/index.js +1 -1
  96. package/dist/esm/index.js.map +1 -1
  97. package/dist/esm/react/client.js +91 -1
  98. package/dist/esm/react/client.js.map +2 -2
  99. package/dist/esm/react/index.js +2 -0
  100. package/dist/esm/react/index.js.map +2 -2
  101. package/dist/esm/react/queries_observer.js +36 -11
  102. package/dist/esm/react/queries_observer.js.map +2 -2
  103. package/dist/esm/react/use_paginated_query2.js +101 -0
  104. package/dist/esm/react/use_paginated_query2.js.map +7 -0
  105. package/dist/esm/react/use_queries.js +9 -2
  106. package/dist/esm/react/use_queries.js.map +2 -2
  107. package/dist/esm/react-clerk/ConvexProviderWithClerk.js +1 -1
  108. package/dist/esm/react-clerk/ConvexProviderWithClerk.js.map +2 -2
  109. package/dist/esm/values/validators.js +1 -1
  110. package/dist/esm/values/validators.js.map +2 -2
  111. package/dist/esm-types/browser/index.d.ts +2 -1
  112. package/dist/esm-types/browser/index.d.ts.map +1 -1
  113. package/dist/esm-types/browser/simple_client.d.ts +20 -1
  114. package/dist/esm-types/browser/simple_client.d.ts.map +1 -1
  115. package/dist/esm-types/browser/sync/client.d.ts.map +1 -1
  116. package/dist/esm-types/browser/sync/client_node_test_helpers.d.ts +5 -4
  117. package/dist/esm-types/browser/sync/client_node_test_helpers.d.ts.map +1 -1
  118. package/dist/esm-types/browser/sync/local_state.d.ts +12 -1
  119. package/dist/esm-types/browser/sync/local_state.d.ts.map +1 -1
  120. package/dist/esm-types/browser/sync/optimistic_updates_impl.d.ts.map +1 -1
  121. package/dist/esm-types/browser/sync/paginated_query_client.d.ts +71 -0
  122. package/dist/esm-types/browser/sync/paginated_query_client.d.ts.map +1 -0
  123. package/dist/esm-types/browser/sync/paginated_query_client.test.d.ts +2 -0
  124. package/dist/esm-types/browser/sync/paginated_query_client.test.d.ts.map +1 -0
  125. package/dist/esm-types/browser/sync/pagination.d.ts +23 -0
  126. package/dist/esm-types/browser/sync/pagination.d.ts.map +1 -0
  127. package/dist/esm-types/browser/sync/udf_path_utils.d.ts +22 -1
  128. package/dist/esm-types/browser/sync/udf_path_utils.d.ts.map +1 -1
  129. package/dist/esm-types/cli/lib/components/definition/bundle.d.ts.map +1 -1
  130. package/dist/esm-types/cli/lib/components/definition/directoryStructure.d.ts +4 -0
  131. package/dist/esm-types/cli/lib/components/definition/directoryStructure.d.ts.map +1 -1
  132. package/dist/esm-types/cli/lib/components.d.ts.map +1 -1
  133. package/dist/esm-types/index.d.ts +1 -1
  134. package/dist/esm-types/index.d.ts.map +1 -1
  135. package/dist/esm-types/react/client.d.ts +65 -1
  136. package/dist/esm-types/react/client.d.ts.map +1 -1
  137. package/dist/esm-types/react/index.d.ts +2 -0
  138. package/dist/esm-types/react/index.d.ts.map +1 -1
  139. package/dist/esm-types/react/queries_observer.d.ts +12 -6
  140. package/dist/esm-types/react/queries_observer.d.ts.map +1 -1
  141. package/dist/esm-types/react/use_paginated_query2.d.ts +51 -0
  142. package/dist/esm-types/react/use_paginated_query2.d.ts.map +1 -0
  143. package/dist/esm-types/react/use_queries.d.ts +2 -0
  144. package/dist/esm-types/react/use_queries.d.ts.map +1 -1
  145. package/dist/react.bundle.js +627 -35
  146. package/dist/react.bundle.js.map +4 -4
  147. package/package.json +1 -1
  148. package/src/browser/index.ts +2 -1
  149. package/src/browser/simple_client.ts +161 -21
  150. package/src/browser/sync/client.ts +12 -3
  151. package/src/browser/sync/client_node_test_helpers.ts +30 -8
  152. package/src/browser/sync/local_state.ts +13 -1
  153. package/src/browser/sync/optimistic_updates_impl.ts +9 -2
  154. package/src/browser/sync/paginated_query_client.test.ts +389 -0
  155. package/src/browser/sync/paginated_query_client.ts +564 -0
  156. package/src/browser/sync/pagination.ts +50 -0
  157. package/src/browser/sync/udf_path_utils.ts +36 -2
  158. package/src/cli/lib/components/definition/bundle.ts +63 -8
  159. package/src/cli/lib/components/definition/directoryStructure.ts +16 -11
  160. package/src/cli/lib/components.ts +4 -1
  161. package/src/index.ts +1 -1
  162. package/src/react/client.ts +169 -4
  163. package/src/react/index.ts +2 -0
  164. package/src/react/queries_observer.test.ts +1 -1
  165. package/src/react/queries_observer.ts +75 -24
  166. package/src/react/use_paginated_query.test.tsx +979 -912
  167. package/src/react/use_paginated_query2.ts +232 -0
  168. package/src/react/use_queries.ts +14 -2
  169. package/src/react-clerk/ConvexProviderWithClerk.tsx +1 -1
  170. package/src/values/validators.ts +1 -1
  171. package/src/browser/.claude/settings.local.json +0 -11
@@ -28,7 +28,7 @@ var convex = (() => {
28
28
  });
29
29
 
30
30
  // src/index.ts
31
- var version = "1.27.3";
31
+ var version = "1.27.4-alpha.0";
32
32
 
33
33
  // src/values/base64.ts
34
34
  var base64_exports = {};
@@ -613,6 +613,19 @@ var convex = (() => {
613
613
  args: convexToJson(args)
614
614
  });
615
615
  }
616
+ function serializePaginatedPathAndArgs(udfPath, args, options) {
617
+ const { initialNumItems, id } = options;
618
+ const result = JSON.stringify({
619
+ type: "paginated",
620
+ udfPath: canonicalizeUdfPath(udfPath),
621
+ args: convexToJson(args),
622
+ options: convexToJson({ initialNumItems, id })
623
+ });
624
+ return result;
625
+ }
626
+ function serializedQueryTokenIsPaginated(token) {
627
+ return JSON.parse(token).type === "paginated";
628
+ }
616
629
 
617
630
  // src/browser/sync/local_state.ts
618
631
  var LocalSyncState = class {
@@ -1295,10 +1308,17 @@ var convex = (() => {
1295
1308
  return localStore.modifiedQueries;
1296
1309
  }
1297
1310
  /**
1311
+ * "Raw" with respect to errors vs values, but query results still have
1312
+ * optimistic updates applied.
1313
+ *
1298
1314
  * @internal
1299
1315
  */
1300
1316
  rawQueryResult(queryToken) {
1301
- return this.queryResults.get(queryToken);
1317
+ const query = this.queryResults.get(queryToken);
1318
+ if (query === void 0) {
1319
+ return void 0;
1320
+ }
1321
+ return query.result;
1302
1322
  }
1303
1323
  queryResult(queryToken) {
1304
1324
  const query = this.queryResults.get(queryToken);
@@ -2744,7 +2764,7 @@ var convex = (() => {
2744
2764
  token,
2745
2765
  modification: {
2746
2766
  kind: "Updated",
2747
- result: optimisticResult.result
2767
+ result: optimisticResult
2748
2768
  }
2749
2769
  };
2750
2770
  }),
@@ -2857,7 +2877,7 @@ var convex = (() => {
2857
2877
  return this.optimisticQueryResults.queryResult(queryToken);
2858
2878
  }
2859
2879
  /**
2860
- * Whether local query result is available for a toke.
2880
+ * Whether local query result is available for a token.
2861
2881
  *
2862
2882
  * This method does not throw if the result is an error.
2863
2883
  *
@@ -3158,11 +3178,374 @@ var convex = (() => {
3158
3178
  }
3159
3179
  };
3160
3180
 
3181
+ // src/browser/sync/pagination.ts
3182
+ function asPaginationResult(value) {
3183
+ if (typeof value !== "object" || value === null || !Array.isArray(value.page) || typeof value.isDone !== "boolean" || typeof value.continueCursor !== "string") {
3184
+ throw new Error(`Not a valid paginated query result: ${value?.toString()}`);
3185
+ }
3186
+ return value;
3187
+ }
3188
+
3189
+ // src/browser/sync/paginated_query_client.ts
3190
+ var PaginatedQueryClient = class {
3191
+ constructor(client, onTransition) {
3192
+ this.client = client;
3193
+ this.onTransition = onTransition;
3194
+ this.lastTransitionTs = Long.fromNumber(0);
3195
+ this.client.addOnTransitionHandler(
3196
+ (transition) => this.onBaseTransition(transition)
3197
+ );
3198
+ }
3199
+ paginatedQuerySet = /* @__PURE__ */ new Map();
3200
+ // hold onto a real Transition so we can construct synthetic ones with that timestamp
3201
+ lastTransitionTs;
3202
+ /**
3203
+ * Subscribe to a paginated query.
3204
+ *
3205
+ * @param name - The name of the paginated query function
3206
+ * @param args - Arguments for the query (excluding paginationOpts)
3207
+ * @param options - Pagination options including initialNumItems
3208
+ * @returns Object with paginatedQueryToken and unsubscribe function
3209
+ */
3210
+ subscribe(name, args, options) {
3211
+ const canonicalizedUdfPath = canonicalizeUdfPath(name);
3212
+ const token = serializePaginatedPathAndArgs(
3213
+ canonicalizedUdfPath,
3214
+ args,
3215
+ options
3216
+ );
3217
+ const unsubscribe = () => this.removePaginatedQuerySubscriber(token);
3218
+ const existingEntry = this.paginatedQuerySet.get(token);
3219
+ if (existingEntry) {
3220
+ existingEntry.numSubscribers += 1;
3221
+ return {
3222
+ paginatedQueryToken: token,
3223
+ unsubscribe
3224
+ };
3225
+ }
3226
+ this.paginatedQuerySet.set(token, {
3227
+ token,
3228
+ canonicalizedUdfPath,
3229
+ args,
3230
+ numSubscribers: 1,
3231
+ options: { initialNumItems: options.initialNumItems },
3232
+ nextPageKey: 0,
3233
+ pageKeys: [],
3234
+ pageKeyToQuery: /* @__PURE__ */ new Map(),
3235
+ ongoingSplits: /* @__PURE__ */ new Map(),
3236
+ skip: false,
3237
+ id: options.id
3238
+ });
3239
+ this.addPageToPaginatedQuery(token, null, options.initialNumItems);
3240
+ return {
3241
+ paginatedQueryToken: token,
3242
+ unsubscribe
3243
+ };
3244
+ }
3245
+ /**
3246
+ * Get current results for a paginated query based on local state.
3247
+ *
3248
+ * Throws an error when one of the pages has errored.
3249
+ */
3250
+ localPaginatedQueryResult(name, args, options) {
3251
+ const canonicalizedUdfPath = canonicalizeUdfPath(name);
3252
+ const token = serializePaginatedPathAndArgs(
3253
+ canonicalizedUdfPath,
3254
+ args,
3255
+ options
3256
+ );
3257
+ return this.localPaginatedQueryResultByToken(token);
3258
+ }
3259
+ /**
3260
+ * @internal
3261
+ */
3262
+ localPaginatedQueryResultByToken(token) {
3263
+ const paginatedQuery = this.paginatedQuerySet.get(token);
3264
+ if (!paginatedQuery) {
3265
+ return void 0;
3266
+ }
3267
+ const activePages = this.activePageQueryTokens(paginatedQuery);
3268
+ if (activePages.length === 0) {
3269
+ return {
3270
+ results: [],
3271
+ status: "LoadingFirstPage",
3272
+ loadMore: (numItems) => {
3273
+ return this.loadMoreOfPaginatedQuery(token, numItems);
3274
+ }
3275
+ };
3276
+ }
3277
+ let allResults = [];
3278
+ let hasUndefined = false;
3279
+ let isDone = false;
3280
+ for (const pageToken of activePages) {
3281
+ const result = this.client.localQueryResultByToken(pageToken);
3282
+ if (result === void 0) {
3283
+ hasUndefined = true;
3284
+ isDone = false;
3285
+ continue;
3286
+ }
3287
+ const paginationResult = asPaginationResult(result);
3288
+ allResults = allResults.concat(paginationResult.page);
3289
+ isDone = !!paginationResult.isDone;
3290
+ }
3291
+ let status;
3292
+ if (hasUndefined) {
3293
+ status = allResults.length === 0 ? "LoadingFirstPage" : "LoadingMore";
3294
+ } else if (isDone) {
3295
+ status = "Exhausted";
3296
+ } else {
3297
+ status = "CanLoadMore";
3298
+ }
3299
+ return {
3300
+ results: allResults,
3301
+ status,
3302
+ loadMore: (numItems) => {
3303
+ return this.loadMoreOfPaginatedQuery(token, numItems);
3304
+ }
3305
+ };
3306
+ }
3307
+ onBaseTransition(transition) {
3308
+ const changedBaseTokens = transition.queries.map((q) => q.token);
3309
+ const changed = this.queriesContainingTokens(changedBaseTokens);
3310
+ let paginatedQueries = [];
3311
+ if (changed.length > 0) {
3312
+ this.processPaginatedQuerySplits(
3313
+ changed,
3314
+ (token) => this.client.localQueryResultByToken(token)
3315
+ );
3316
+ paginatedQueries = changed.map((token) => ({
3317
+ token,
3318
+ modification: {
3319
+ kind: "Updated",
3320
+ result: this.localPaginatedQueryResultByToken(token)
3321
+ }
3322
+ }));
3323
+ }
3324
+ const extendedTransition = {
3325
+ ...transition,
3326
+ paginatedQueries
3327
+ };
3328
+ this.onTransition(extendedTransition);
3329
+ }
3330
+ /**
3331
+ * Load more items for a paginated query.
3332
+ *
3333
+ * This *always* causes a transition, the status of the query
3334
+ * has probably changed from "CanLoadMore" to "LoadingMore".
3335
+ * Data might have changed too: maybe a subscription to this page
3336
+ * query already exists (unlikely but possible) or this page query
3337
+ * has an optimistic update providing some initial data.
3338
+ *
3339
+ * @internal
3340
+ */
3341
+ loadMoreOfPaginatedQuery(token, numItems) {
3342
+ this.mustGetPaginatedQuery(token);
3343
+ const lastPageToken = this.queryTokenForLastPageOfPaginatedQuery(token);
3344
+ const lastPageResult = this.client.localQueryResultByToken(lastPageToken);
3345
+ if (!lastPageResult) {
3346
+ return false;
3347
+ }
3348
+ const paginationResult = asPaginationResult(lastPageResult);
3349
+ if (paginationResult.isDone) {
3350
+ return false;
3351
+ }
3352
+ this.addPageToPaginatedQuery(
3353
+ token,
3354
+ paginationResult.continueCursor,
3355
+ numItems
3356
+ );
3357
+ const loadMoreTransition = {
3358
+ timestamp: this.lastTransitionTs,
3359
+ reflectedMutations: [],
3360
+ queries: [],
3361
+ paginatedQueries: [
3362
+ {
3363
+ token,
3364
+ modification: {
3365
+ kind: "Updated",
3366
+ result: this.localPaginatedQueryResultByToken(token)
3367
+ }
3368
+ }
3369
+ ]
3370
+ };
3371
+ this.onTransition(loadMoreTransition);
3372
+ return true;
3373
+ }
3374
+ /**
3375
+ * @internal
3376
+ */
3377
+ queriesContainingTokens(queryTokens) {
3378
+ if (queryTokens.length === 0) {
3379
+ return [];
3380
+ }
3381
+ const changed = [];
3382
+ const queryTokenSet = new Set(queryTokens);
3383
+ for (const [paginatedToken, paginatedQuery] of this.paginatedQuerySet) {
3384
+ for (const pageToken of this.allQueryTokens(paginatedQuery)) {
3385
+ if (queryTokenSet.has(pageToken)) {
3386
+ changed.push(paginatedToken);
3387
+ break;
3388
+ }
3389
+ }
3390
+ }
3391
+ return changed;
3392
+ }
3393
+ /**
3394
+ * @internal
3395
+ */
3396
+ processPaginatedQuerySplits(changed, getResult) {
3397
+ for (const paginatedQueryToken of changed) {
3398
+ const paginatedQuery = this.mustGetPaginatedQuery(paginatedQueryToken);
3399
+ const { ongoingSplits, pageKeyToQuery, pageKeys } = paginatedQuery;
3400
+ for (const [pageKey, [splitKey1, splitKey2]] of ongoingSplits) {
3401
+ const bothNewPagesLoaded = getResult(pageKeyToQuery.get(splitKey1).queryToken) !== void 0 && getResult(pageKeyToQuery.get(splitKey2).queryToken) !== void 0;
3402
+ if (bothNewPagesLoaded) {
3403
+ this.completePaginatedQuerySplit(
3404
+ paginatedQuery,
3405
+ pageKey,
3406
+ splitKey1,
3407
+ splitKey2
3408
+ );
3409
+ }
3410
+ }
3411
+ for (const pageKey of pageKeys) {
3412
+ if (ongoingSplits.has(pageKey)) {
3413
+ continue;
3414
+ }
3415
+ const pageToken = pageKeyToQuery.get(pageKey).queryToken;
3416
+ const pageResult = getResult(pageToken);
3417
+ if (!pageResult) {
3418
+ continue;
3419
+ }
3420
+ const result = asPaginationResult(pageResult);
3421
+ const shouldSplit = result.splitCursor && (result.pageStatus === "SplitRecommended" || result.pageStatus === "SplitRequired" || // This client-driven page splitting condition will change in the future.
3422
+ result.page.length > paginatedQuery.options.initialNumItems * 2);
3423
+ if (shouldSplit) {
3424
+ this.splitPaginatedQueryPage(
3425
+ paginatedQuery,
3426
+ pageKey,
3427
+ result.splitCursor,
3428
+ // we just checked
3429
+ result.continueCursor
3430
+ );
3431
+ }
3432
+ }
3433
+ }
3434
+ }
3435
+ splitPaginatedQueryPage(paginatedQuery, pageKey, splitCursor, continueCursor) {
3436
+ const splitKey1 = paginatedQuery.nextPageKey++;
3437
+ const splitKey2 = paginatedQuery.nextPageKey++;
3438
+ const paginationOpts = {
3439
+ cursor: continueCursor,
3440
+ numItems: paginatedQuery.options.initialNumItems,
3441
+ id: paginatedQuery.id
3442
+ };
3443
+ const firstSubscription = this.client.subscribe(
3444
+ paginatedQuery.canonicalizedUdfPath,
3445
+ {
3446
+ ...paginatedQuery.args,
3447
+ paginationOpts: {
3448
+ ...paginationOpts,
3449
+ cursor: null,
3450
+ // Start from beginning for first split
3451
+ endCursor: splitCursor
3452
+ }
3453
+ }
3454
+ );
3455
+ paginatedQuery.pageKeyToQuery.set(splitKey1, firstSubscription);
3456
+ const secondSubscription = this.client.subscribe(
3457
+ paginatedQuery.canonicalizedUdfPath,
3458
+ {
3459
+ ...paginatedQuery.args,
3460
+ paginationOpts: {
3461
+ ...paginationOpts,
3462
+ cursor: splitCursor,
3463
+ endCursor: continueCursor
3464
+ }
3465
+ }
3466
+ );
3467
+ paginatedQuery.pageKeyToQuery.set(splitKey2, secondSubscription);
3468
+ paginatedQuery.ongoingSplits.set(pageKey, [splitKey1, splitKey2]);
3469
+ }
3470
+ /**
3471
+ * @internal
3472
+ */
3473
+ addPageToPaginatedQuery(token, continueCursor, numItems) {
3474
+ const paginatedQuery = this.mustGetPaginatedQuery(token);
3475
+ const pageKey = paginatedQuery.nextPageKey++;
3476
+ const paginationOpts = {
3477
+ cursor: continueCursor,
3478
+ numItems,
3479
+ id: paginatedQuery.id
3480
+ };
3481
+ const pageArgs = {
3482
+ ...paginatedQuery.args,
3483
+ paginationOpts
3484
+ };
3485
+ const subscription = this.client.subscribe(
3486
+ paginatedQuery.canonicalizedUdfPath,
3487
+ pageArgs
3488
+ );
3489
+ paginatedQuery.pageKeys.push(pageKey);
3490
+ paginatedQuery.pageKeyToQuery.set(pageKey, subscription);
3491
+ return subscription;
3492
+ }
3493
+ removePaginatedQuerySubscriber(token) {
3494
+ const paginatedQuery = this.paginatedQuerySet.get(token);
3495
+ if (!paginatedQuery) {
3496
+ return;
3497
+ }
3498
+ paginatedQuery.numSubscribers -= 1;
3499
+ if (paginatedQuery.numSubscribers > 0) {
3500
+ return;
3501
+ }
3502
+ for (const subscription of paginatedQuery.pageKeyToQuery.values()) {
3503
+ subscription.unsubscribe();
3504
+ }
3505
+ this.paginatedQuerySet.delete(token);
3506
+ }
3507
+ completePaginatedQuerySplit(paginatedQuery, pageKey, splitKey1, splitKey2) {
3508
+ const originalQuery = paginatedQuery.pageKeyToQuery.get(pageKey);
3509
+ paginatedQuery.pageKeyToQuery.delete(pageKey);
3510
+ const pageIndex = paginatedQuery.pageKeys.indexOf(pageKey);
3511
+ paginatedQuery.pageKeys.splice(pageIndex, 1, splitKey1, splitKey2);
3512
+ paginatedQuery.ongoingSplits.delete(pageKey);
3513
+ originalQuery.unsubscribe();
3514
+ }
3515
+ /** The query tokens for all active pages, in result order */
3516
+ activePageQueryTokens(paginatedQuery) {
3517
+ return paginatedQuery.pageKeys.map(
3518
+ (pageKey) => paginatedQuery.pageKeyToQuery.get(pageKey).queryToken
3519
+ );
3520
+ }
3521
+ allQueryTokens(paginatedQuery) {
3522
+ return Array.from(paginatedQuery.pageKeyToQuery.values()).map(
3523
+ (sub) => sub.queryToken
3524
+ );
3525
+ }
3526
+ queryTokenForLastPageOfPaginatedQuery(token) {
3527
+ const paginatedQuery = this.mustGetPaginatedQuery(token);
3528
+ const lastPageKey = paginatedQuery.pageKeys[paginatedQuery.pageKeys.length - 1];
3529
+ if (lastPageKey === void 0) {
3530
+ throw new Error(`No pages for paginated query ${token}`);
3531
+ }
3532
+ return paginatedQuery.pageKeyToQuery.get(lastPageKey).queryToken;
3533
+ }
3534
+ mustGetPaginatedQuery(token) {
3535
+ const paginatedQuery = this.paginatedQuerySet.get(token);
3536
+ if (!paginatedQuery) {
3537
+ throw new Error("paginated query no longer exists for token " + token);
3538
+ }
3539
+ return paginatedQuery;
3540
+ }
3541
+ };
3542
+
3161
3543
  // src/browser/simple_client.ts
3162
3544
  var defaultWebSocketConstructor;
3163
3545
  var ConvexClient = class {
3164
3546
  listeners;
3165
3547
  _client;
3548
+ _paginatedClient;
3166
3549
  // A synthetic server event to run callbacks the first time
3167
3550
  callNewListenersWithCurrentValuesTimer;
3168
3551
  _closed;
@@ -3177,6 +3560,10 @@ var convex = (() => {
3177
3560
  if (this._client) return this._client;
3178
3561
  throw new Error("ConvexClient is disabled");
3179
3562
  }
3563
+ get paginatedClient() {
3564
+ if (this._paginatedClient) return this._paginatedClient;
3565
+ throw new Error("ConvexClient is disabled");
3566
+ }
3180
3567
  get disabled() {
3181
3568
  return this._disabled;
3182
3569
  }
@@ -3201,9 +3588,15 @@ var convex = (() => {
3201
3588
  if (!this.disabled) {
3202
3589
  this._client = new BaseConvexClient(
3203
3590
  address,
3204
- (updatedQueries) => this._transition(updatedQueries),
3591
+ () => {
3592
+ },
3593
+ // NOP, let the paginated query client do it all
3205
3594
  baseOptions
3206
3595
  );
3596
+ this._paginatedClient = new PaginatedQueryClient(
3597
+ this._client,
3598
+ (transition) => this._transition(transition)
3599
+ );
3207
3600
  }
3208
3601
  this.listeners = /* @__PURE__ */ new Set();
3209
3602
  }
@@ -3241,15 +3634,7 @@ var convex = (() => {
3241
3634
  */
3242
3635
  onUpdate(query, args, callback, onError) {
3243
3636
  if (this.disabled) {
3244
- const disabledUnsubscribe = () => {
3245
- };
3246
- const unsubscribeProps2 = {
3247
- unsubscribe: disabledUnsubscribe,
3248
- getCurrentValue: () => void 0,
3249
- getQueryLogs: () => void 0
3250
- };
3251
- Object.assign(disabledUnsubscribe, unsubscribeProps2);
3252
- return disabledUnsubscribe;
3637
+ return this.createDisabledUnsubscribe();
3253
3638
  }
3254
3639
  const { queryToken, unsubscribe } = this.client.subscribe(
3255
3640
  getFunctionName(query),
@@ -3262,7 +3647,8 @@ var convex = (() => {
3262
3647
  unsubscribe,
3263
3648
  hasEverRun: false,
3264
3649
  query,
3265
- args
3650
+ args,
3651
+ paginationOptions: void 0
3266
3652
  };
3267
3653
  this.listeners.add(queryInfo);
3268
3654
  if (this.queryResultReady(queryToken) && this.callNewListenersWithCurrentValuesTimer === void 0) {
@@ -3286,19 +3672,100 @@ var convex = (() => {
3286
3672
  Object.assign(ret, unsubscribeProps);
3287
3673
  return ret;
3288
3674
  }
3675
+ /**
3676
+ * Call a callback whenever a new result for a paginated query is received.
3677
+ *
3678
+ * @param query - A {@link server.FunctionReference} for the public query to run.
3679
+ * @param args - The arguments to run the query with.
3680
+ * @param options - Options for the paginated query including initialNumItems and id.
3681
+ * @param callback - Function to call when the query result updates.
3682
+ * @param onError - Function to call when the query result updates with an error.
3683
+ *
3684
+ * @return an {@link Unsubscribe} function to stop calling the callback.
3685
+ */
3686
+ onPaginatedUpdate(query, args, options, callback, onError) {
3687
+ if (this.disabled) {
3688
+ return this.createDisabledUnsubscribe();
3689
+ }
3690
+ const paginationOptions = {
3691
+ initialNumItems: options.initialNumItems,
3692
+ id: -1
3693
+ };
3694
+ const { paginatedQueryToken, unsubscribe } = this.paginatedClient.subscribe(
3695
+ getFunctionName(query),
3696
+ args,
3697
+ // Simple client doesn't use IDs, there's no expectation that these queries remain separate.
3698
+ paginationOptions
3699
+ );
3700
+ const queryInfo = {
3701
+ queryToken: paginatedQueryToken,
3702
+ callback,
3703
+ onError,
3704
+ unsubscribe,
3705
+ hasEverRun: false,
3706
+ query,
3707
+ args,
3708
+ paginationOptions
3709
+ };
3710
+ this.listeners.add(queryInfo);
3711
+ if (!!this.paginatedClient.localPaginatedQueryResultByToken(
3712
+ paginatedQueryToken
3713
+ ) && this.callNewListenersWithCurrentValuesTimer === void 0) {
3714
+ this.callNewListenersWithCurrentValuesTimer = setTimeout(
3715
+ () => this.callNewListenersWithCurrentValues(),
3716
+ 0
3717
+ );
3718
+ }
3719
+ const unsubscribeProps = {
3720
+ unsubscribe: () => {
3721
+ if (this.closed) {
3722
+ return;
3723
+ }
3724
+ this.listeners.delete(queryInfo);
3725
+ unsubscribe();
3726
+ },
3727
+ getCurrentValue: () => {
3728
+ const result = this.paginatedClient.localPaginatedQueryResult(
3729
+ getFunctionName(query),
3730
+ args,
3731
+ paginationOptions
3732
+ );
3733
+ return result;
3734
+ },
3735
+ getQueryLogs: () => []
3736
+ // Paginated queries don't aggregate their logs
3737
+ };
3738
+ const ret = unsubscribeProps.unsubscribe;
3739
+ Object.assign(ret, unsubscribeProps);
3740
+ return ret;
3741
+ }
3289
3742
  // Run all callbacks that have never been run before if they have a query
3290
3743
  // result available now.
3291
3744
  callNewListenersWithCurrentValues() {
3292
3745
  this.callNewListenersWithCurrentValuesTimer = void 0;
3293
- this._transition([], true);
3746
+ this._transition({ queries: [], paginatedQueries: [] }, true);
3294
3747
  }
3295
3748
  queryResultReady(queryToken) {
3296
3749
  return this.client.hasLocalQueryResultByToken(queryToken);
3297
3750
  }
3751
+ createDisabledUnsubscribe() {
3752
+ const disabledUnsubscribe = () => {
3753
+ };
3754
+ const unsubscribeProps = {
3755
+ unsubscribe: disabledUnsubscribe,
3756
+ getCurrentValue: () => void 0,
3757
+ getQueryLogs: () => void 0
3758
+ };
3759
+ Object.assign(disabledUnsubscribe, unsubscribeProps);
3760
+ return disabledUnsubscribe;
3761
+ }
3298
3762
  async close() {
3299
3763
  if (this.disabled) return;
3300
3764
  this.listeners.clear();
3301
3765
  this._closed = true;
3766
+ if (this._paginatedClient) {
3767
+ this._paginatedClient = void 0;
3768
+ }
3302
3769
  return this.client.close();
3303
3770
  }
3304
3771
  /**
@@ -3330,14 +3797,27 @@ var convex = (() => {
3330
3797
  /**
3331
3798
  * @internal
3332
3799
  */
3333
- _transition(updatedQueries, callNewListeners = false) {
3800
+ _transition({
3801
+ queries,
3802
+ paginatedQueries
3803
+ }, callNewListeners = false) {
3804
+ const updatedQueries = [
3805
+ ...queries.map((q) => q.token),
3806
+ ...paginatedQueries.map((q) => q.token)
3807
+ ];
3334
3808
  for (const queryInfo of this.listeners) {
3335
3809
  const { callback, queryToken, onError, hasEverRun } = queryInfo;
3336
- if (updatedQueries.includes(queryToken) || callNewListeners && !hasEverRun && this.client.hasLocalQueryResultByToken(queryToken)) {
3810
+ const isPaginatedQuery = serializedQueryTokenIsPaginated(queryToken);
3811
+ const hasResultReady = isPaginatedQuery ? !!this.paginatedClient.localPaginatedQueryResultByToken(queryToken) : this.client.hasLocalQueryResultByToken(queryToken);
3812
+ if (updatedQueries.includes(queryToken) || callNewListeners && !hasEverRun && hasResultReady) {
3337
3813
  queryInfo.hasEverRun = true;
3338
3814
  let newValue;
3339
3815
  try {
3340
- newValue = this.client.localQueryResultByToken(queryToken);
3816
+ if (isPaginatedQuery) {
3817
+ newValue = this.paginatedClient.localPaginatedQueryResultByToken(queryToken);
3818
+ } else {
3819
+ newValue = this.client.localQueryResultByToken(queryToken);
3820
+ }
3341
3821
  } catch (error) {
3342
3822
  if (!(error instanceof Error)) throw error;
3343
3823
  if (onError) {