primitive-admin 1.1.0-alpha.53 → 1.1.0-alpha.55

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 (88) hide show
  1. package/README.md +24 -10
  2. package/assets/skill/skills/primitive-platform/SKILL.md +134 -0
  3. package/dist/bin/primitive.js +4 -0
  4. package/dist/bin/primitive.js.map +1 -1
  5. package/dist/src/commands/admins.js +22 -8
  6. package/dist/src/commands/admins.js.map +1 -1
  7. package/dist/src/commands/analytics.js +71 -1
  8. package/dist/src/commands/analytics.js.map +1 -1
  9. package/dist/src/commands/apps.js +16 -14
  10. package/dist/src/commands/apps.js.map +1 -1
  11. package/dist/src/commands/blob-buckets.js +37 -2
  12. package/dist/src/commands/blob-buckets.js.map +1 -1
  13. package/dist/src/commands/connections.d.ts +2 -0
  14. package/dist/src/commands/connections.js +95 -0
  15. package/dist/src/commands/connections.js.map +1 -0
  16. package/dist/src/commands/databases.js +280 -45
  17. package/dist/src/commands/databases.js.map +1 -1
  18. package/dist/src/commands/documents.js +615 -1
  19. package/dist/src/commands/documents.js.map +1 -1
  20. package/dist/src/commands/init.js +2 -2
  21. package/dist/src/commands/init.js.map +1 -1
  22. package/dist/src/commands/integrations.js +11 -88
  23. package/dist/src/commands/integrations.js.map +1 -1
  24. package/dist/src/commands/metadata.js +47 -30
  25. package/dist/src/commands/metadata.js.map +1 -1
  26. package/dist/src/commands/prompts.js +14 -5
  27. package/dist/src/commands/prompts.js.map +1 -1
  28. package/dist/src/commands/sessions.d.ts +2 -0
  29. package/dist/src/commands/sessions.js +63 -0
  30. package/dist/src/commands/sessions.js.map +1 -0
  31. package/dist/src/commands/settings.d.ts +1 -2
  32. package/dist/src/commands/settings.js +8 -6
  33. package/dist/src/commands/settings.js.map +1 -1
  34. package/dist/src/commands/skill.js +2 -2
  35. package/dist/src/commands/skill.js.map +1 -1
  36. package/dist/src/commands/sync-app-settings.d.ts +26 -37
  37. package/dist/src/commands/sync-app-settings.js +43 -56
  38. package/dist/src/commands/sync-app-settings.js.map +1 -1
  39. package/dist/src/commands/sync.d.ts +4 -3
  40. package/dist/src/commands/sync.js +245 -30
  41. package/dist/src/commands/sync.js.map +1 -1
  42. package/dist/src/commands/webhooks.js +71 -23
  43. package/dist/src/commands/webhooks.js.map +1 -1
  44. package/dist/src/commands/workflows.js +364 -55
  45. package/dist/src/commands/workflows.js.map +1 -1
  46. package/dist/src/lib/api-client.d.ts +294 -13
  47. package/dist/src/lib/api-client.js +345 -42
  48. package/dist/src/lib/api-client.js.map +1 -1
  49. package/dist/src/lib/app-settings-descriptor.d.ts +15 -13
  50. package/dist/src/lib/app-settings-descriptor.js +25 -23
  51. package/dist/src/lib/app-settings-descriptor.js.map +1 -1
  52. package/dist/src/lib/data-input.d.ts +23 -0
  53. package/dist/src/lib/data-input.js +50 -0
  54. package/dist/src/lib/data-input.js.map +1 -0
  55. package/dist/src/lib/generated-allowlist.js +1 -0
  56. package/dist/src/lib/generated-allowlist.js.map +1 -1
  57. package/dist/src/lib/generated-workflow-model-fields.d.ts +20 -0
  58. package/dist/src/lib/generated-workflow-model-fields.js +54 -0
  59. package/dist/src/lib/generated-workflow-model-fields.js.map +1 -0
  60. package/dist/src/lib/log-inspection.d.ts +550 -0
  61. package/dist/src/lib/log-inspection.js +609 -0
  62. package/dist/src/lib/log-inspection.js.map +1 -0
  63. package/dist/src/lib/output.d.ts +10 -0
  64. package/dist/src/lib/output.js +12 -0
  65. package/dist/src/lib/output.js.map +1 -1
  66. package/dist/src/lib/record-filter.d.ts +16 -0
  67. package/dist/src/lib/record-filter.js +53 -0
  68. package/dist/src/lib/record-filter.js.map +1 -0
  69. package/dist/src/lib/skill-installer.d.ts +4 -2
  70. package/dist/src/lib/skill-installer.js +139 -11
  71. package/dist/src/lib/skill-installer.js.map +1 -1
  72. package/dist/src/lib/watch.d.ts +121 -0
  73. package/dist/src/lib/watch.js +169 -0
  74. package/dist/src/lib/watch.js.map +1 -0
  75. package/dist/src/lib/workflow-field-descriptor.d.ts +92 -0
  76. package/dist/src/lib/workflow-field-descriptor.js +163 -0
  77. package/dist/src/lib/workflow-field-descriptor.js.map +1 -0
  78. package/dist/src/lib/workflow-fragments.d.ts +23 -0
  79. package/dist/src/lib/workflow-fragments.js +229 -8
  80. package/dist/src/lib/workflow-fragments.js.map +1 -1
  81. package/dist/src/lib/workflow-payload.d.ts +1 -1
  82. package/dist/src/lib/workflow-payload.js +17 -0
  83. package/dist/src/lib/workflow-payload.js.map +1 -1
  84. package/dist/src/lib/workflow-toml-validator.d.ts +57 -1
  85. package/dist/src/lib/workflow-toml-validator.js +104 -1
  86. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  87. package/dist/src/types/index.d.ts +21 -16
  88. package/package.json +7 -4
@@ -1,6 +1,5 @@
1
1
  import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, readdirSync } from "fs";
2
2
  import * as path from "path";
3
- import { parseConfigToml } from "../lib/config-toml.js";
4
3
  import { ApiClient } from "../lib/api-client.js";
5
4
  import { getCurrentAppId } from "../lib/config.js";
6
5
  import { resolveCodegenSourceDir } from "../lib/codegen-shared/resolveCodegenSourceDir.js";
@@ -10,6 +9,9 @@ import { generateDbTypes, } from "../lib/db-codegen/dbGenerator.js";
10
9
  import { generateDbSwiftTypes } from "../lib/swift-codegen/dbGenerator.js";
11
10
  import { success, error, info, warn, keyValue, result as printResult, formatTable, formatId, formatDate, json, } from "../lib/output.js";
12
11
  import { confirmPrompt } from "../lib/confirm-prompt.js";
12
+ import { parseFilterOptions } from "../lib/record-filter.js";
13
+ import { parseDataOption } from "../lib/data-input.js";
14
+ import { ulid } from "ulid";
13
15
  function resolveAppId(appId, options) {
14
16
  const resolved = appId || options.app || getCurrentAppId();
15
17
  if (!resolved) {
@@ -18,6 +20,43 @@ function resolveAppId(appId, options) {
18
20
  }
19
21
  return resolved;
20
22
  }
23
+ /**
24
+ * Render the flat `{ success, id, appliedFields }` response that
25
+ * `records/save` and `records/patch` return — these endpoints answer with a
26
+ * write receipt, not the stored record, so the human view shows the resolved id
27
+ * plus any server-applied fields (autoStamps / trigger-computed values).
28
+ */
29
+ function renderRecordWriteResult(result, fallbackId, message) {
30
+ success(message);
31
+ keyValue("id", String(result?.id ?? fallbackId));
32
+ const applied = result?.appliedFields;
33
+ if (applied && typeof applied === "object" && Object.keys(applied).length > 0) {
34
+ for (const [key, value] of Object.entries(applied)) {
35
+ keyValue(`applied.${key}`, value === null || value === undefined ? "—" : String(value));
36
+ }
37
+ }
38
+ }
39
+ /**
40
+ * Refuse a write whose `--data` carries an `id` that is not the record being
41
+ * written. The server strips `id` from the record body on both the save and the
42
+ * patch path, so such an `id` would be dropped silently and the write would
43
+ * land on the id from the argument — somewhere the caller did not intend.
44
+ *
45
+ * `recordId` is the id the caller passed as an argument (absent on a `save`
46
+ * that mints one); `writtenId` is the id actually being written. Exits non-zero
47
+ * when they disagree with `--data` (#1965 review).
48
+ */
49
+ function assertDataIdMatches(data, recordId, writtenId, commandPath) {
50
+ if (!data || typeof data !== "object")
51
+ return;
52
+ const dataId = data.id;
53
+ if (dataId === undefined || dataId === writtenId)
54
+ return;
55
+ error(recordId
56
+ ? `The "id" in --data ("${dataId}") does not match the record id "${recordId}". Remove it, or pass the id you mean as the argument.`
57
+ : `Pass the record id as the argument, not inside --data: ${commandPath} ${dataId} --data ...`);
58
+ process.exit(1);
59
+ }
21
60
  /**
22
61
  * Validate that the named operation is an import-compatible, save-like
23
62
  * mutation before importing (issue #145, Codex finding (a)).
@@ -68,11 +107,17 @@ export function registerDatabasesCommands(program) {
68
107
  .addHelpText("after", `
69
108
  Examples:
70
109
  $ primitive databases list
110
+ $ primitive databases list --owner <user-id>
71
111
  $ primitive databases create "My Database"
72
112
  $ primitive databases get <database-id>
73
113
  $ primitive databases records models <database-id>
74
114
  $ primitive databases records describe <database-id> <model-name>
75
115
  $ primitive databases records query <database-id> <model-name> --filter '{"name":"Alice"}'
116
+ $ primitive databases records get <database-id> <model-name> <record-id>
117
+ $ primitive databases records count <database-id> <model-name> --filter '{"status":"open"}'
118
+ $ primitive databases records aggregate <database-id> <model-name> --op avg --field price
119
+ $ primitive databases records save <database-id> <model-name> [record-id] --data '{"name":"Alice"}'
120
+ $ primitive databases records patch <database-id> <model-name> <record-id> --data '{"name":"Alice"}'
76
121
  $ primitive databases operations list <database-id>
77
122
  $ primitive databases operations execute <database-id> <operation-name> --params '{"key":"value"}'
78
123
  $ primitive databases indexes list <database-id>
@@ -94,12 +139,15 @@ the 'database-types' group:
94
139
  .description("List databases in an app")
95
140
  .argument("[app-id]", "App ID (uses current app if not specified)")
96
141
  .option("--app <app-id>", "App ID")
142
+ .option("--owner <user-id>", "Only list databases created by this user")
97
143
  .option("--json", "Output as JSON")
98
144
  .action(async (appId, options) => {
99
145
  const resolvedAppId = resolveAppId(appId, options);
100
146
  const client = new ApiClient();
101
147
  try {
102
- const result = await client.listDatabases(resolvedAppId);
148
+ const result = await client.listDatabases(resolvedAppId, {
149
+ owner: options.owner,
150
+ });
103
151
  const list = Array.isArray(result) ? result : result?.databases ?? [];
104
152
  if (options.json) {
105
153
  json(list);
@@ -114,6 +162,18 @@ the 'database-types' group:
114
162
  { header: "TITLE", key: "title" },
115
163
  { header: "TYPE", key: "databaseType", format: (v) => v || "—" },
116
164
  { header: "PERMISSION", key: "permission" },
165
+ // `permission` reads "owner" on every row for an app admin, so it
166
+ // cannot confirm --owner narrowed anything. Show who created each
167
+ // database when the filter is in play.
168
+ ...(options.owner
169
+ ? [
170
+ {
171
+ header: "CREATED BY",
172
+ key: "createdBy",
173
+ format: (v) => (v ? formatId(v) : "—"),
174
+ },
175
+ ]
176
+ : []),
117
177
  { header: "CREATED", key: "createdAt", format: formatDate },
118
178
  ]));
119
179
  }
@@ -465,7 +525,7 @@ the 'database-types' group:
465
525
  process.exit(1);
466
526
  }
467
527
  });
468
- // Query records (direct introspection via admin-data route)
528
+ // Query records (direct introspection via the records route)
469
529
  records
470
530
  .command("query")
471
531
  .description("Query records in a database model (admin only)")
@@ -480,36 +540,7 @@ the 'database-types' group:
480
540
  .action(async (databaseId, modelName, options) => {
481
541
  const resolvedAppId = resolveAppId(undefined, options);
482
542
  const client = new ApiClient();
483
- if (options.filter && options.filterFile) {
484
- error("Cannot use both --filter and --filter-file.");
485
- process.exit(1);
486
- }
487
- let filter;
488
- if (options.filterFile) {
489
- try {
490
- const content = readFileSync(options.filterFile, "utf-8");
491
- if (options.filterFile.endsWith(".toml")) {
492
- const parsed = parseConfigToml(content);
493
- filter = parsed.filter || parsed;
494
- }
495
- else {
496
- filter = JSON.parse(content);
497
- }
498
- }
499
- catch (err) {
500
- error(`Failed to read filter file: ${err.message}`);
501
- process.exit(1);
502
- }
503
- }
504
- else if (options.filter) {
505
- try {
506
- filter = JSON.parse(options.filter);
507
- }
508
- catch {
509
- error("Invalid --filter JSON.");
510
- process.exit(1);
511
- }
512
- }
543
+ const filter = parseFilterOptions(options);
513
544
  try {
514
545
  const result = await client.queryDatabaseRecords(resolvedAppId, databaseId, modelName, { filter, limit: options.limit, cursor: options.cursor });
515
546
  if (options.json) {
@@ -560,6 +591,218 @@ the 'database-types' group:
560
591
  process.exit(1);
561
592
  }
562
593
  });
594
+ // Get a single record by id
595
+ records
596
+ .command("get")
597
+ .description("Get a single record by id (admin only)")
598
+ .argument("<database-id>", "Database ID")
599
+ .argument("<model-name>", "Model name")
600
+ .argument("<record-id>", "Record ID")
601
+ .option("--app <app-id>", "App ID")
602
+ .option("--json", "Output as JSON")
603
+ .action(async (databaseId, modelName, recordId, options) => {
604
+ const resolvedAppId = resolveAppId(undefined, options);
605
+ const client = new ApiClient();
606
+ try {
607
+ // A single-record fetch is a query filtered by primary-key id (there is
608
+ // no dedicated get endpoint — reuse queryDatabaseRecords).
609
+ const result = await client.queryDatabaseRecords(resolvedAppId, databaseId, modelName, { filter: { id: recordId }, limit: 1 });
610
+ const record = (result?.data || [])[0];
611
+ if (options.json) {
612
+ json(record ?? null);
613
+ return;
614
+ }
615
+ if (!record) {
616
+ info(`No record found with id ${recordId} in ${modelName}.`);
617
+ return;
618
+ }
619
+ for (const [key, value] of Object.entries(record)) {
620
+ const rendered = value === null || value === undefined
621
+ ? "—"
622
+ : typeof value === "object"
623
+ ? JSON.stringify(value)
624
+ : String(value);
625
+ keyValue(key, rendered);
626
+ }
627
+ }
628
+ catch (err) {
629
+ error(err.message);
630
+ process.exit(1);
631
+ }
632
+ });
633
+ // Count records
634
+ records
635
+ .command("count")
636
+ .description("Count records in a database model (admin only)")
637
+ .argument("<database-id>", "Database ID")
638
+ .argument("<model-name>", "Model name")
639
+ .option("--app <app-id>", "App ID")
640
+ .option("--filter <json>", "Filter as JSON (e.g. '{\"name\":\"Alice\"}')")
641
+ .option("--filter-file <path>", "Read filter from a JSON or TOML file")
642
+ .option("--json", "Output as JSON")
643
+ .action(async (databaseId, modelName, options) => {
644
+ const resolvedAppId = resolveAppId(undefined, options);
645
+ const filter = parseFilterOptions(options);
646
+ const client = new ApiClient();
647
+ try {
648
+ const result = await client.countDatabaseRecords(resolvedAppId, databaseId, modelName, filter);
649
+ if (options.json) {
650
+ json(result);
651
+ return;
652
+ }
653
+ keyValue("count", String(result?.count ?? 0));
654
+ }
655
+ catch (err) {
656
+ error(err.message);
657
+ process.exit(1);
658
+ }
659
+ });
660
+ // Aggregate records
661
+ const AGGREGATE_OPS = ["count", "sum", "avg", "min", "max"];
662
+ records
663
+ .command("aggregate")
664
+ .description("Aggregate records in a database model (count/sum/avg/min/max, optional group-by; admin only)")
665
+ .argument("<database-id>", "Database ID")
666
+ .argument("<model-name>", "Model name")
667
+ .option("--app <app-id>", "App ID")
668
+ .requiredOption("--op <operation>", `Aggregate operation: ${AGGREGATE_OPS.join("|")}`)
669
+ .option("--field <field>", "Field to aggregate (required for sum/avg/min/max)")
670
+ .option("--group-by <field>", "Group results by this field (repeatable)", (value, previous = []) => previous.concat(value), [])
671
+ .option("--filter <json>", "Filter as JSON")
672
+ .option("--filter-file <path>", "Read filter from a JSON or TOML file")
673
+ .option("--json", "Output as JSON")
674
+ .action(async (databaseId, modelName, options) => {
675
+ const resolvedAppId = resolveAppId(undefined, options);
676
+ if (!AGGREGATE_OPS.includes(options.op)) {
677
+ error(`Invalid --op "${options.op}". Expected one of: ${AGGREGATE_OPS.join(", ")}.`);
678
+ process.exit(1);
679
+ }
680
+ const operation = { type: options.op };
681
+ if (options.op === "count") {
682
+ if (options.field) {
683
+ error("--field is not valid for --op count.");
684
+ process.exit(1);
685
+ }
686
+ }
687
+ else {
688
+ if (!options.field) {
689
+ error(`--op ${options.op} requires --field.`);
690
+ process.exit(1);
691
+ }
692
+ operation.field = options.field;
693
+ }
694
+ const filter = parseFilterOptions(options);
695
+ const groupBy = options.groupBy || [];
696
+ const client = new ApiClient();
697
+ try {
698
+ const result = await client.aggregateDatabaseRecords(resolvedAppId, databaseId, modelName, { operations: [operation], groupBy, filter });
699
+ if (options.json) {
700
+ json(result);
701
+ return;
702
+ }
703
+ const aggregated = result?.result;
704
+ // Label the value column after the operation, including the field it
705
+ // ran over: `SUM` alone does not say what was summed.
706
+ const valueLabel = options.op === "count"
707
+ ? "COUNT"
708
+ : `${options.op.toUpperCase()}(${options.field})`;
709
+ if (groupBy.length === 0) {
710
+ // Ungrouped: one scalar per operation, keyed by the endpoint's
711
+ // operation name (`count`, `sum_<field>`, …). Render it as a
712
+ // key/value line — there is no group column to table.
713
+ const value = (aggregated ?? {})[options.op === "count" ? "count" : `${options.op}_${options.field}`];
714
+ keyValue(valueLabel.toLowerCase(), value === null || value === undefined ? "—" : String(value));
715
+ return;
716
+ }
717
+ if (aggregated &&
718
+ typeof aggregated === "object" &&
719
+ !Array.isArray(aggregated) &&
720
+ Object.values(aggregated).every((v) => typeof v !== "object" || v === null)) {
721
+ // Flat { group -> value } shape (single operation, single group-by): render a table.
722
+ const rows = Object.entries(aggregated).map(([group, value]) => ({
723
+ group,
724
+ value: value === null || value === undefined ? "—" : String(value),
725
+ }));
726
+ console.log(formatTable(rows, [
727
+ { header: "GROUP", key: "group" },
728
+ { header: valueLabel, key: "value" },
729
+ ]));
730
+ }
731
+ else {
732
+ // Grouped/nested or multi-operation shape: print the structured result.
733
+ console.log(JSON.stringify(aggregated ?? {}, null, 2));
734
+ }
735
+ }
736
+ catch (err) {
737
+ error(err.message);
738
+ process.exit(1);
739
+ }
740
+ });
741
+ // Upsert a record (create, or merge into an existing one)
742
+ records
743
+ .command("save")
744
+ .description("Create a record, or merge fields into an existing one (admin only)")
745
+ .argument("<database-id>", "Database ID")
746
+ .argument("<model-name>", "Model name")
747
+ .argument("[record-id]", "Record ID (generated when omitted)")
748
+ .option("--app <app-id>", "App ID")
749
+ .option("--data <json>", "Record fields as inline JSON")
750
+ .option("--data-file <path>", "Read the record fields from a JSON file")
751
+ .option("--json", "Output as JSON")
752
+ .action(async (databaseId, modelName, recordId, options) => {
753
+ const resolvedAppId = resolveAppId(undefined, options);
754
+ const data = parseDataOption(options, "record");
755
+ const client = new ApiClient();
756
+ // The DatabaseDO requires an id on the plain save path (it only generates
757
+ // one for `upsertOn` writes). Mint the id client-side for a create so
758
+ // `save` works without an id, the same way CSV import does
759
+ // (`lib/csv.ts`).
760
+ const id = recordId || ulid();
761
+ assertDataIdMatches(data, recordId, id, "primitive databases records save <database-id> <model-name>");
762
+ try {
763
+ const result = await client.saveDatabaseRecord(resolvedAppId, databaseId, modelName, id, data);
764
+ if (options.json) {
765
+ json(result);
766
+ return;
767
+ }
768
+ renderRecordWriteResult(result, id, `Record saved to ${modelName}.`);
769
+ }
770
+ catch (err) {
771
+ error(err.message);
772
+ process.exit(1);
773
+ }
774
+ });
775
+ // Patch a record (partial merge)
776
+ records
777
+ .command("patch")
778
+ .description("Merge fields into an existing record (admin only)")
779
+ .argument("<database-id>", "Database ID")
780
+ .argument("<model-name>", "Model name")
781
+ .argument("<record-id>", "Record ID")
782
+ .option("--app <app-id>", "App ID")
783
+ .option("--data <json>", "Fields to merge as inline JSON")
784
+ .option("--data-file <path>", "Read the fields to merge from a JSON file")
785
+ .option("--json", "Output as JSON")
786
+ .action(async (databaseId, modelName, recordId, options) => {
787
+ const resolvedAppId = resolveAppId(undefined, options);
788
+ const data = parseDataOption(options, "fields to merge");
789
+ const client = new ApiClient();
790
+ // Same guard as `save` — on this path `recordId` is required, so the
791
+ // "pass it as the argument" branch is unreachable here.
792
+ assertDataIdMatches(data, recordId, recordId, "primitive databases records patch <database-id> <model-name>");
793
+ try {
794
+ const result = await client.patchDatabaseRecord(resolvedAppId, databaseId, modelName, recordId, data);
795
+ if (options.json) {
796
+ json(result);
797
+ return;
798
+ }
799
+ renderRecordWriteResult(result, recordId, `Record ${recordId} patched in ${modelName}.`);
800
+ }
801
+ catch (err) {
802
+ error(err.message);
803
+ process.exit(1);
804
+ }
805
+ });
563
806
  // Delete a single record
564
807
  records
565
808
  .command("delete")
@@ -608,21 +851,13 @@ the 'database-types' group:
608
851
  .argument("<model-name>", "Model name")
609
852
  .option("--app <app-id>", "App ID")
610
853
  .option("--filter <json>", "Filter as JSON to select which records to delete")
854
+ .option("--filter-file <path>", "Read filter from a JSON or TOML file")
611
855
  .option("-y, --yes", "Skip confirmation prompt")
612
856
  .option("--json", "Output as JSON")
613
857
  .action(async (databaseId, modelName, options) => {
614
858
  const resolvedAppId = resolveAppId(undefined, options);
615
859
  const client = new ApiClient();
616
- let filter;
617
- if (options.filter) {
618
- try {
619
- filter = JSON.parse(options.filter);
620
- }
621
- catch {
622
- error("Invalid --filter JSON.");
623
- process.exit(1);
624
- }
625
- }
860
+ const filter = parseFilterOptions(options);
626
861
  try {
627
862
  // First, query to find all matching records
628
863
  let allRecords = [];
@@ -1094,7 +1329,7 @@ the 'database-types' group:
1094
1329
  }
1095
1330
  // Validate --batch-size up front so a bad value fails before any write.
1096
1331
  // Unlike `import-csv` (registered-batch path, hard 100k server cap),
1097
- // `admin-data/batch` has no fixed server item cap — bound it with a
1332
+ // `records/batch` has no fixed server item cap — bound it with a
1098
1333
  // conservative client-side ceiling to keep each request within
1099
1334
  // Cloudflare body-size and Durable Object memory limits.
1100
1335
  const parsedBatchSize = parseBatchSize(options.batchSize, {
@@ -1685,7 +1920,7 @@ async function exportSingleDatabase(client, appId, databaseId, outputDir, jsonOu
1685
1920
  }
1686
1921
  /**
1687
1922
  * Conservative client-side ceiling for `databases import --batch-size`.
1688
- * `admin-data/batch` has no fixed server-side item cap (unlike the
1923
+ * `records/batch` has no fixed server-side item cap (unlike the
1689
1924
  * registered-batch path used by `import-csv`), so this bounds each request to
1690
1925
  * keep it within Cloudflare body-size and Durable Object memory limits.
1691
1926
  */