primitive-admin 1.1.0-alpha.84 → 1.1.0-alpha.85

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 (95) hide show
  1. package/README.md +43 -1
  2. package/dist/src/commands/admins.js +29 -32
  3. package/dist/src/commands/admins.js.map +1 -1
  4. package/dist/src/commands/analytics.js +2 -1
  5. package/dist/src/commands/analytics.js.map +1 -1
  6. package/dist/src/commands/apps.js +12 -5
  7. package/dist/src/commands/apps.js.map +1 -1
  8. package/dist/src/commands/blob-buckets.js +2 -1
  9. package/dist/src/commands/blob-buckets.js.map +1 -1
  10. package/dist/src/commands/catalog.js +26 -10
  11. package/dist/src/commands/catalog.js.map +1 -1
  12. package/dist/src/commands/collection-type-configs.js +2 -1
  13. package/dist/src/commands/collection-type-configs.js.map +1 -1
  14. package/dist/src/commands/collections.js +27 -18
  15. package/dist/src/commands/collections.js.map +1 -1
  16. package/dist/src/commands/connections.js +8 -9
  17. package/dist/src/commands/connections.js.map +1 -1
  18. package/dist/src/commands/cron-triggers.js +2 -1
  19. package/dist/src/commands/cron-triggers.js.map +1 -1
  20. package/dist/src/commands/database-type-configs.js +3 -2
  21. package/dist/src/commands/database-type-configs.js.map +1 -1
  22. package/dist/src/commands/databases.js +16 -8
  23. package/dist/src/commands/databases.js.map +1 -1
  24. package/dist/src/commands/documents.js +23 -11
  25. package/dist/src/commands/documents.js.map +1 -1
  26. package/dist/src/commands/email-templates.js +2 -1
  27. package/dist/src/commands/email-templates.js.map +1 -1
  28. package/dist/src/commands/env.js +11 -1
  29. package/dist/src/commands/env.js.map +1 -1
  30. package/dist/src/commands/feature-flags.js +2 -1
  31. package/dist/src/commands/feature-flags.js.map +1 -1
  32. package/dist/src/commands/functions.js +2 -1
  33. package/dist/src/commands/functions.js.map +1 -1
  34. package/dist/src/commands/group-type-configs.js +2 -1
  35. package/dist/src/commands/group-type-configs.js.map +1 -1
  36. package/dist/src/commands/groups.js +29 -8
  37. package/dist/src/commands/groups.js.map +1 -1
  38. package/dist/src/commands/guides.js +17 -11
  39. package/dist/src/commands/guides.js.map +1 -1
  40. package/dist/src/commands/integrations.js +26 -9
  41. package/dist/src/commands/integrations.js.map +1 -1
  42. package/dist/src/commands/locks.js +2 -1
  43. package/dist/src/commands/locks.js.map +1 -1
  44. package/dist/src/commands/metadata-category-configs.js +2 -1
  45. package/dist/src/commands/metadata-category-configs.js.map +1 -1
  46. package/dist/src/commands/metadata.js +8 -1
  47. package/dist/src/commands/metadata.js.map +1 -1
  48. package/dist/src/commands/prompts.js +28 -11
  49. package/dist/src/commands/prompts.js.map +1 -1
  50. package/dist/src/commands/rule-sets.js +2 -1
  51. package/dist/src/commands/rule-sets.js.map +1 -1
  52. package/dist/src/commands/scripts.js +15 -6
  53. package/dist/src/commands/scripts.js.map +1 -1
  54. package/dist/src/commands/secrets.js +2 -1
  55. package/dist/src/commands/secrets.js.map +1 -1
  56. package/dist/src/commands/sessions.js +10 -9
  57. package/dist/src/commands/sessions.js.map +1 -1
  58. package/dist/src/commands/sync.d.ts +64 -0
  59. package/dist/src/commands/sync.js +347 -17
  60. package/dist/src/commands/sync.js.map +1 -1
  61. package/dist/src/commands/tokens.js +2 -1
  62. package/dist/src/commands/tokens.js.map +1 -1
  63. package/dist/src/commands/users.js +16 -9
  64. package/dist/src/commands/users.js.map +1 -1
  65. package/dist/src/commands/vars.js +2 -1
  66. package/dist/src/commands/vars.js.map +1 -1
  67. package/dist/src/commands/waitlist.js +2 -1
  68. package/dist/src/commands/waitlist.js.map +1 -1
  69. package/dist/src/commands/webhooks.js +17 -10
  70. package/dist/src/commands/webhooks.js.map +1 -1
  71. package/dist/src/commands/workflows.js +36 -18
  72. package/dist/src/commands/workflows.js.map +1 -1
  73. package/dist/src/lib/api-client.d.ts +83 -1
  74. package/dist/src/lib/api-client.js +139 -55
  75. package/dist/src/lib/api-client.js.map +1 -1
  76. package/dist/src/lib/config-object-descriptor.js +29 -7
  77. package/dist/src/lib/config-object-descriptor.js.map +1 -1
  78. package/dist/src/lib/config-payload.js +27 -2
  79. package/dist/src/lib/config-payload.js.map +1 -1
  80. package/dist/src/lib/config-surface.d.ts +2 -1
  81. package/dist/src/lib/config-surface.js +50 -3
  82. package/dist/src/lib/config-surface.js.map +1 -1
  83. package/dist/src/lib/generated-config-surfaces.d.ts +182 -1
  84. package/dist/src/lib/generated-config-surfaces.js +441 -2
  85. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  86. package/dist/src/lib/list-options.d.ts +68 -0
  87. package/dist/src/lib/list-options.js +89 -0
  88. package/dist/src/lib/list-options.js.map +1 -0
  89. package/dist/src/lib/log-inspection.d.ts +16 -0
  90. package/dist/src/lib/log-inspection.js +23 -0
  91. package/dist/src/lib/log-inspection.js.map +1 -1
  92. package/dist/src/lib/paginate.d.ts +15 -0
  93. package/dist/src/lib/paginate.js +17 -0
  94. package/dist/src/lib/paginate.js.map +1 -1
  95. package/package.json +2 -2
@@ -518,6 +518,34 @@ export declare class ApiClient {
518
518
  put<T = any>(path: string, body?: any): Promise<T>;
519
519
  patch<T = any>(path: string, body?: any): Promise<T>;
520
520
  delete<T = any>(path: string): Promise<T>;
521
+ /**
522
+ * One page of the apps the caller can reach.
523
+ *
524
+ * The route paginates cursor-style (default pageSize=25, cap=100). This
525
+ * method used to walk the whole chain so `primitive apps list` could not
526
+ * truncate at 25 (#436); since #3646 the page boundary belongs to the
527
+ * caller instead — `apps list` declares `--limit`/`--cursor` and prints the
528
+ * `hasMore`/`nextCursor` the server reported, and the aggregate readers that
529
+ * really do mean "every app" call `listAllApps()`.
530
+ */
531
+ listAppsPage(params?: {
532
+ limit?: number;
533
+ cursor?: string;
534
+ }): Promise<{
535
+ items: Array<{
536
+ id: string;
537
+ name: string;
538
+ role?: string;
539
+ }>;
540
+ hasMore?: boolean;
541
+ nextCursor?: string | null;
542
+ }>;
543
+ /**
544
+ * Every app the caller can reach, cursor chain walked (#436).
545
+ *
546
+ * For readers that genuinely need the whole set — an app picker, a sweep —
547
+ * rather than the one page `apps list` prints.
548
+ */
521
549
  listApps(): Promise<{
522
550
  apps: Array<{
523
551
  id: string;
@@ -568,6 +596,7 @@ export declare class ApiClient {
568
596
  cursor?: string;
569
597
  }): Promise<{
570
598
  items: any[];
599
+ hasMore?: boolean;
571
600
  nextCursor?: string | null;
572
601
  }>;
573
602
  removeUser(appId: string, userId: string): Promise<void>;
@@ -642,6 +671,7 @@ export declare class ApiClient {
642
671
  limit?: number;
643
672
  cursor?: string;
644
673
  }): Promise<{
674
+ items: any[];
645
675
  grants: any[];
646
676
  nextCursor?: string | null;
647
677
  hasMore?: boolean;
@@ -653,6 +683,7 @@ export declare class ApiClient {
653
683
  limit?: number;
654
684
  }): Promise<{
655
685
  items: any[];
686
+ hasMore?: boolean;
656
687
  nextCursor?: string | null;
657
688
  }>;
658
689
  /** Integration detail (#2631: `accessRule` is on the detail, not the list summary). */
@@ -716,6 +747,7 @@ export declare class ApiClient {
716
747
  limit?: number;
717
748
  }): Promise<{
718
749
  items: any[];
750
+ hasMore?: boolean;
719
751
  nextCursor?: string | null;
720
752
  }>;
721
753
  getWebhook(appId: string, webhookId: string): Promise<any>;
@@ -939,6 +971,7 @@ export declare class ApiClient {
939
971
  cursor?: string;
940
972
  }): Promise<{
941
973
  items: unknown[];
974
+ hasMore?: boolean;
942
975
  nextCursor?: string | null;
943
976
  }>;
944
977
  listSessions(appId: string, userId: string, params?: {
@@ -946,6 +979,7 @@ export declare class ApiClient {
946
979
  cursor?: string;
947
980
  }): Promise<{
948
981
  items: unknown[];
982
+ hasMore?: boolean;
949
983
  nextCursor?: string | null;
950
984
  }>;
951
985
  testWebhook(appId: string, webhookId: string, payload?: any): Promise<any>;
@@ -1045,6 +1079,7 @@ export declare class ApiClient {
1045
1079
  cursor?: string;
1046
1080
  }): Promise<{
1047
1081
  items: any[];
1082
+ hasMore?: boolean;
1048
1083
  nextCursor?: string | null;
1049
1084
  }>;
1050
1085
  getPrompt(appId: string, promptId: string): Promise<any>;
@@ -1144,6 +1179,7 @@ export declare class ApiClient {
1144
1179
  cursor?: string;
1145
1180
  }): Promise<{
1146
1181
  items: any[];
1182
+ hasMore?: boolean;
1147
1183
  nextCursor?: string;
1148
1184
  }>;
1149
1185
  getTestCase(appId: string, blockType: "prompt" | "integration" | "workflow" | "script", blockId: string, testCaseId: string): Promise<any>;
@@ -1222,6 +1258,7 @@ export declare class ApiClient {
1222
1258
  cursor?: string;
1223
1259
  }): Promise<{
1224
1260
  items: any[];
1261
+ hasMore?: boolean;
1225
1262
  nextCursor?: string | null;
1226
1263
  }>;
1227
1264
  getWorkflow(appId: string, workflowId: string): Promise<any>;
@@ -1566,9 +1603,22 @@ export declare class ApiClient {
1566
1603
  * `options.owner` narrows the result to the databases that user created; the
1567
1604
  * server resolves it through the `databasesByCreator` index (#1965).
1568
1605
  */
1606
+ /**
1607
+ * One page of an app's databases (#3646).
1608
+ *
1609
+ * `databases list` used to walk the chain here. The route pages, so the page
1610
+ * boundary belongs to the caller: the verb declares `--limit`/`--cursor` and
1611
+ * prints what came back.
1612
+ */
1569
1613
  listDatabases(appId: string, options?: {
1570
1614
  owner?: string;
1571
- }): Promise<any[]>;
1615
+ limit?: number;
1616
+ cursor?: string;
1617
+ }): Promise<{
1618
+ items: any[];
1619
+ hasMore?: boolean;
1620
+ nextCursor?: string | null;
1621
+ }>;
1572
1622
  createDatabase(appId: string, data: {
1573
1623
  title: string;
1574
1624
  databaseType?: string;
@@ -1823,8 +1873,18 @@ export declare class ApiClient {
1823
1873
  description?: string;
1824
1874
  }): Promise<any>;
1825
1875
  deleteGroup(appId: string, groupType: string, groupId: string): Promise<any>;
1876
+ /**
1877
+ * One page of a group's members.
1878
+ *
1879
+ * The route paginates cursor-style (no default limit, cap 200) and reports
1880
+ * the boundary, so `groups members list` carries `--limit`/`--cursor` and
1881
+ * forwards them (#3646) rather than printing a first page that claims to be
1882
+ * the whole membership.
1883
+ */
1826
1884
  listGroupMembers(appId: string, groupType: string, groupId: string, options?: {
1827
1885
  include?: "profiles";
1886
+ limit?: number;
1887
+ cursor?: string;
1828
1888
  }): Promise<{
1829
1889
  items: any[];
1830
1890
  nextCursor?: string;
@@ -1850,6 +1910,7 @@ export declare class ApiClient {
1850
1910
  cursor?: string;
1851
1911
  }): Promise<{
1852
1912
  items: any[];
1913
+ hasMore?: boolean;
1853
1914
  nextCursor?: string | null;
1854
1915
  }>;
1855
1916
  listAllCollections(appId: string, params?: {
@@ -1857,6 +1918,7 @@ export declare class ApiClient {
1857
1918
  cursor?: string;
1858
1919
  }): Promise<{
1859
1920
  items: any[];
1921
+ hasMore?: boolean;
1860
1922
  nextCursor?: string | null;
1861
1923
  }>;
1862
1924
  createCollection(appId: string, data: {
@@ -1881,6 +1943,7 @@ export declare class ApiClient {
1881
1943
  cursor?: string;
1882
1944
  }): Promise<{
1883
1945
  items: any[];
1946
+ hasMore?: boolean;
1884
1947
  nextCursor?: string | null;
1885
1948
  }>;
1886
1949
  addCollectionDocument(appId: string, collectionId: string, data: {
@@ -2287,6 +2350,25 @@ export declare class ApiClient {
2287
2350
  * `{ documents }` array and no cursor: that reads as a single page.
2288
2351
  */
2289
2352
  listAdminDocuments(appId: string, userId: string): Promise<any[]>;
2353
+ /**
2354
+ * One page of the admin inventory of a user's documents (#3646).
2355
+ *
2356
+ * `documents list --user-id` prints exactly this — the page the caller's
2357
+ * `--limit`/`--cursor` asked for, with the server's own page boundary
2358
+ * beside it. `documents export-all`, which really does mean every document,
2359
+ * keeps the walking `listAdminDocuments()` above.
2360
+ *
2361
+ * Tolerant of a pre-contract server, which answers with a bare
2362
+ * `{ documents }` array and no cursor: that reads as a single page.
2363
+ */
2364
+ listAdminDocumentsPage(appId: string, userId: string, options?: {
2365
+ limit?: number;
2366
+ cursor?: string;
2367
+ }): Promise<{
2368
+ items: any[];
2369
+ hasMore?: boolean;
2370
+ nextCursor?: string | null;
2371
+ }>;
2290
2372
  /**
2291
2373
  * The document `AppUser.rootDocId` points at, or null when the user has none
2292
2374
  * yet (#3135). A user who is not a member of the app is a 404, which
@@ -391,24 +391,43 @@ export class ApiClient {
391
391
  // ============================================
392
392
  // APPS
393
393
  // ============================================
394
+ /**
395
+ * One page of the apps the caller can reach.
396
+ *
397
+ * The route paginates cursor-style (default pageSize=25, cap=100). This
398
+ * method used to walk the whole chain so `primitive apps list` could not
399
+ * truncate at 25 (#436); since #3646 the page boundary belongs to the
400
+ * caller instead — `apps list` declares `--limit`/`--cursor` and prints the
401
+ * `hasMore`/`nextCursor` the server reported, and the aggregate readers that
402
+ * really do mean "every app" call `listAllApps()`.
403
+ */
404
+ async listAppsPage(params) {
405
+ const qs = new URLSearchParams();
406
+ if (params?.limit !== undefined)
407
+ qs.set("limit", String(params.limit));
408
+ if (params?.cursor)
409
+ qs.set("cursor", params.cursor);
410
+ const q = qs.toString();
411
+ const resp = await this.get(`/admin/api/admins/me/apps${q ? `?${q}` : ""}`);
412
+ // The server now emits `items` with `apps` as a deprecation-window alias
413
+ // (#1316). Read `items` first so listings keep working once the alias is
414
+ // removed; fall back to the legacy `apps` key meanwhile.
415
+ return {
416
+ items: resp?.items ?? (Array.isArray(resp?.apps) ? resp.apps : []),
417
+ hasMore: resp?.hasMore,
418
+ nextCursor: resp?.nextCursor ?? resp?.cursor ?? null,
419
+ };
420
+ }
421
+ /**
422
+ * Every app the caller can reach, cursor chain walked (#436).
423
+ *
424
+ * For readers that genuinely need the whole set — an app picker, a sweep —
425
+ * rather than the one page `apps list` prints.
426
+ */
394
427
  async listApps() {
395
- // The server paginates cursor-style (default pageSize=25, cap=100). Historically
396
- // this method issued a single un-paginated GET and silently truncated to the
397
- // first page, which broke `primitive apps list` for admins with more than
398
- // 25 apps (see #436). Iterate every page.
399
428
  const apps = await paginateAll(async (cursor) => {
400
- const qs = new URLSearchParams({ limit: "100" });
401
- if (cursor)
402
- qs.set("cursor", cursor);
403
- const resp = await this.get(`/admin/api/admins/me/apps?${qs.toString()}`);
404
- // The server now emits `items` with `apps` as a deprecation-window
405
- // alias (#1316). Read `items` first so listings keep working once the
406
- // alias is removed; fall back to the legacy `apps` key meanwhile.
407
- return {
408
- items: resp.items ??
409
- (Array.isArray(resp.apps) ? resp.apps : []),
410
- nextCursor: resp.nextCursor ?? resp.cursor,
411
- };
429
+ const page = await this.listAppsPage({ limit: 100, cursor });
430
+ return { items: page.items, nextCursor: page.nextCursor };
412
431
  });
413
432
  return { apps };
414
433
  }
@@ -494,6 +513,9 @@ export class ApiClient {
494
513
  const result = await this.get(path);
495
514
  return {
496
515
  items: result?.items ?? (Array.isArray(result) ? result : []),
516
+ // `hasMore` travels with the page so the caller prints the server's own
517
+ // page boundary rather than inferring one from the cursor (#3646).
518
+ hasMore: result?.hasMore,
497
519
  nextCursor: result?.nextCursor ?? null,
498
520
  };
499
521
  }
@@ -604,8 +626,12 @@ export class ApiClient {
604
626
  // ============================================
605
627
  async listDeferredGrants(appId, params) {
606
628
  const result = await this.get(`/app/${appId}/api/deferred-grants`, params);
629
+ // `items` is the key every list envelope is built from (#3646); `grants`
630
+ // stays as the name this route has always used.
631
+ const items = result?.grants ?? result?.items ?? [];
607
632
  return {
608
- grants: result?.grants ?? result?.items ?? [],
633
+ items,
634
+ grants: items,
609
635
  nextCursor: result?.nextCursor ?? null,
610
636
  hasMore: result?.hasMore,
611
637
  };
@@ -620,6 +646,7 @@ export class ApiClient {
620
646
  const result = await this.get(`/admin/api/apps/${appId}/integrations`, params);
621
647
  return {
622
648
  items: result?.items ?? [],
649
+ hasMore: result?.hasMore,
623
650
  nextCursor: result?.nextCursor ?? null,
624
651
  };
625
652
  }
@@ -730,6 +757,7 @@ export class ApiClient {
730
757
  const result = await this.get(`/admin/api/apps/${appId}/webhooks`, params);
731
758
  return {
732
759
  items: result?.items ?? [],
760
+ hasMore: result?.hasMore,
733
761
  nextCursor: result?.nextCursor ?? null,
734
762
  };
735
763
  }
@@ -1011,6 +1039,7 @@ export class ApiClient {
1011
1039
  });
1012
1040
  return {
1013
1041
  items: result?.items ?? [],
1042
+ hasMore: result?.hasMore,
1014
1043
  nextCursor: result?.nextCursor ?? null,
1015
1044
  };
1016
1045
  }
@@ -1021,6 +1050,7 @@ export class ApiClient {
1021
1050
  });
1022
1051
  return {
1023
1052
  items: result?.items ?? [],
1053
+ hasMore: result?.hasMore,
1024
1054
  nextCursor: result?.nextCursor ?? null,
1025
1055
  };
1026
1056
  }
@@ -1164,6 +1194,7 @@ export class ApiClient {
1164
1194
  const result = await this.get(`/admin/api/apps/${appId}/prompts`, params);
1165
1195
  return {
1166
1196
  items: result?.items ?? [],
1197
+ hasMore: result?.hasMore,
1167
1198
  nextCursor: result?.nextCursor ?? null,
1168
1199
  };
1169
1200
  }
@@ -1311,6 +1342,7 @@ export class ApiClient {
1311
1342
  const result = await this.get(`/admin/api/apps/${appId}/blocks/${blockType}/${blockId}/test-cases${suffix}`);
1312
1343
  return {
1313
1344
  items: result?.items ?? result ?? [],
1345
+ hasMore: result?.hasMore,
1314
1346
  nextCursor: result?.nextCursor ?? undefined,
1315
1347
  };
1316
1348
  }
@@ -1462,6 +1494,7 @@ export class ApiClient {
1462
1494
  const result = await this.get(`/admin/api/apps/${appId}/workflows`, params);
1463
1495
  return {
1464
1496
  items: result?.items ?? [],
1497
+ hasMore: result?.hasMore,
1465
1498
  nextCursor: result?.nextCursor ?? null,
1466
1499
  };
1467
1500
  }
@@ -1653,8 +1686,13 @@ export class ApiClient {
1653
1686
  const result = await this.get("/admin/api/admins", params);
1654
1687
  // #1316: prefer `nextCursor`; fall back to the deprecated `cursor` alias.
1655
1688
  const nextCursor = result?.nextCursor ?? result?.cursor ?? null;
1689
+ const items = result?.admins ?? result?.items ?? [];
1656
1690
  return {
1657
- admins: result?.admins ?? result?.items ?? [],
1691
+ // `items` is the key every list envelope is built from (#3646); `admins`
1692
+ // stays as the name this route has always used.
1693
+ items,
1694
+ admins: items,
1695
+ hasMore: result?.hasMore,
1658
1696
  nextCursor,
1659
1697
  cursor: nextCursor,
1660
1698
  };
@@ -1689,6 +1727,7 @@ export class ApiClient {
1689
1727
  const result = await this.get("/admin/api/catalog/prompts", params);
1690
1728
  return {
1691
1729
  items: result?.items ?? [],
1730
+ hasMore: result?.hasMore,
1692
1731
  nextCursor: result?.nextCursor ?? null,
1693
1732
  };
1694
1733
  }
@@ -1708,6 +1747,7 @@ export class ApiClient {
1708
1747
  const result = await this.get("/admin/api/catalog/integrations", params);
1709
1748
  return {
1710
1749
  items: result?.items ?? [],
1750
+ hasMore: result?.hasMore,
1711
1751
  nextCursor: result?.nextCursor ?? null,
1712
1752
  };
1713
1753
  }
@@ -1799,30 +1839,42 @@ export class ApiClient {
1799
1839
  * `options.owner` narrows the result to the databases that user created; the
1800
1840
  * server resolves it through the `databasesByCreator` index (#1965).
1801
1841
  */
1842
+ /**
1843
+ * One page of an app's databases (#3646).
1844
+ *
1845
+ * `databases list` used to walk the chain here. The route pages, so the page
1846
+ * boundary belongs to the caller: the verb declares `--limit`/`--cursor` and
1847
+ * prints what came back.
1848
+ */
1802
1849
  async listDatabases(appId, options) {
1803
- return paginateAll(async (cursor) => {
1804
- const params = new URLSearchParams();
1805
- if (options?.owner)
1806
- params.set("owner", options.owner);
1807
- if (cursor)
1808
- params.set("cursor", cursor);
1809
- const qs = params.toString() ? `?${params.toString()}` : "";
1810
- const resp = await this.get(`/app/${appId}/api/databases${qs}`);
1811
- // Tolerant read: a server older than #1958 answers with a bare array.
1812
- // Without this a published CLI pointed at a not-yet-upgraded server
1813
- // would print nothing at all. Such a server also predates the `owner`
1814
- // filter (#1965) and ignores the unknown query parameter, so apply the
1815
- // filter here — the legacy rows carry `createdBy`, so this reproduces
1816
- // the server's own post-join filter (#2245).
1817
- if (Array.isArray(resp)) {
1818
- return {
1819
- items: options?.owner
1820
- ? resp.filter((db) => db?.createdBy === options.owner)
1821
- : resp,
1822
- };
1823
- }
1824
- return { items: resp?.items ?? [], nextCursor: resp?.nextCursor };
1825
- });
1850
+ const params = new URLSearchParams();
1851
+ if (options?.owner)
1852
+ params.set("owner", options.owner);
1853
+ if (options?.limit !== undefined)
1854
+ params.set("limit", String(options.limit));
1855
+ if (options?.cursor)
1856
+ params.set("cursor", options.cursor);
1857
+ const qs = params.toString() ? `?${params.toString()}` : "";
1858
+ const resp = await this.get(`/app/${appId}/api/databases${qs}`);
1859
+ // Tolerant read: a server older than #1958 answers with a bare array.
1860
+ // Without this a published CLI pointed at a not-yet-upgraded server would
1861
+ // print nothing at all. Such a server also predates the `owner` filter
1862
+ // (#1965) and ignores the unknown query parameter, so apply the filter
1863
+ // here — the legacy rows carry `createdBy`, so this reproduces the
1864
+ // server's own post-join filter (#2245). It has no pages either.
1865
+ if (Array.isArray(resp)) {
1866
+ return {
1867
+ items: options?.owner
1868
+ ? resp.filter((db) => db?.createdBy === options.owner)
1869
+ : resp,
1870
+ hasMore: false,
1871
+ };
1872
+ }
1873
+ return {
1874
+ items: resp?.items ?? [],
1875
+ hasMore: resp?.hasMore,
1876
+ nextCursor: resp?.nextCursor ?? null,
1877
+ };
1826
1878
  }
1827
1879
  async createDatabase(appId, data) {
1828
1880
  return this.post(`/app/${appId}/api/databases`, data);
@@ -2206,9 +2258,20 @@ export class ApiClient {
2206
2258
  // ============================================
2207
2259
  // GROUP MEMBERS
2208
2260
  // ============================================
2261
+ /**
2262
+ * One page of a group's members.
2263
+ *
2264
+ * The route paginates cursor-style (no default limit, cap 200) and reports
2265
+ * the boundary, so `groups members list` carries `--limit`/`--cursor` and
2266
+ * forwards them (#3646) rather than printing a first page that claims to be
2267
+ * the whole membership.
2268
+ */
2209
2269
  async listGroupMembers(appId, groupType, groupId, options) {
2210
- const qs = options?.include ? `?include=${options.include}` : "";
2211
- const result = await this.get(`/app/${appId}/api/groups/${groupType}/${groupId}/members${qs}`);
2270
+ const result = await this.get(`/app/${appId}/api/groups/${groupType}/${groupId}/members`, {
2271
+ ...(options?.include ? { include: options.include } : {}),
2272
+ ...(options?.limit !== undefined ? { limit: options.limit } : {}),
2273
+ ...(options?.cursor ? { cursor: options.cursor } : {}),
2274
+ });
2212
2275
  // #1316: prefer `nextCursor`; fall back to the deprecated `cursor` alias
2213
2276
  // so the CLI keeps paginating against pre-#1316 servers.
2214
2277
  const nextCursor = result?.nextCursor ?? result?.cursor;
@@ -2262,6 +2325,7 @@ export class ApiClient {
2262
2325
  const result = await this.get(`/app/${appId}/api/collections${q ? `?${q}` : ""}`);
2263
2326
  return {
2264
2327
  items: result?.items ?? [],
2328
+ hasMore: result?.hasMore,
2265
2329
  nextCursor: result?.nextCursor ?? result?.cursor ?? null,
2266
2330
  };
2267
2331
  }
@@ -2275,6 +2339,7 @@ export class ApiClient {
2275
2339
  const result = await this.get(`/app/${appId}/api/admin/collections${q ? `?${q}` : ""}`);
2276
2340
  return {
2277
2341
  items: result?.items ?? [],
2342
+ hasMore: result?.hasMore,
2278
2343
  nextCursor: result?.nextCursor ?? result?.cursor ?? null,
2279
2344
  };
2280
2345
  }
@@ -2303,6 +2368,7 @@ export class ApiClient {
2303
2368
  const result = await this.get(`/app/${appId}/api/collections/${collectionId}/documents${q ? `?${q}` : ""}`);
2304
2369
  return {
2305
2370
  items: result?.items ?? [],
2371
+ hasMore: result?.hasMore,
2306
2372
  nextCursor: result?.nextCursor ?? result?.cursor ?? null,
2307
2373
  };
2308
2374
  }
@@ -2870,21 +2936,39 @@ export class ApiClient {
2870
2936
  */
2871
2937
  async listAdminDocuments(appId, userId) {
2872
2938
  return paginateAll(async (cursor) => {
2873
- const params = new URLSearchParams({
2874
- userId,
2875
- limit: String(ADMIN_INVENTORY_PAGE_SIZE),
2939
+ const page = await this.listAdminDocumentsPage(appId, userId, {
2940
+ limit: ADMIN_INVENTORY_PAGE_SIZE,
2941
+ cursor,
2876
2942
  });
2877
- if (cursor)
2878
- params.set("cursor", cursor);
2879
- const result = await this.get(`/admin/api/apps/${appId}/documents?${params.toString()}`);
2880
- return {
2881
- items: result?.items ?? result?.documents ?? [],
2882
- // #1316: prefer `nextCursor`; the deprecated `cursor` alias keeps a
2883
- // shipped CLI paginating against a server that still dual-emits.
2884
- nextCursor: result?.nextCursor ?? result?.cursor,
2885
- };
2943
+ return { items: page.items, nextCursor: page.nextCursor };
2886
2944
  });
2887
2945
  }
2946
+ /**
2947
+ * One page of the admin inventory of a user's documents (#3646).
2948
+ *
2949
+ * `documents list --user-id` prints exactly this — the page the caller's
2950
+ * `--limit`/`--cursor` asked for, with the server's own page boundary
2951
+ * beside it. `documents export-all`, which really does mean every document,
2952
+ * keeps the walking `listAdminDocuments()` above.
2953
+ *
2954
+ * Tolerant of a pre-contract server, which answers with a bare
2955
+ * `{ documents }` array and no cursor: that reads as a single page.
2956
+ */
2957
+ async listAdminDocumentsPage(appId, userId, options = {}) {
2958
+ const params = new URLSearchParams({ userId });
2959
+ if (options.limit !== undefined)
2960
+ params.set("limit", String(options.limit));
2961
+ if (options.cursor)
2962
+ params.set("cursor", options.cursor);
2963
+ const result = await this.get(`/admin/api/apps/${appId}/documents?${params.toString()}`);
2964
+ return {
2965
+ items: result?.items ?? result?.documents ?? [],
2966
+ hasMore: result?.hasMore,
2967
+ // #1316: prefer `nextCursor`; the deprecated `cursor` alias keeps a
2968
+ // shipped CLI paginating against a server that still dual-emits.
2969
+ nextCursor: result?.nextCursor ?? result?.cursor ?? null,
2970
+ };
2971
+ }
2888
2972
  /**
2889
2973
  * The document `AppUser.rootDocId` points at, or null when the user has none
2890
2974
  * yet (#3135). A user who is not a member of the app is a 404, which