primitive-admin 1.0.53 → 1.0.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 (173) hide show
  1. package/README.md +38 -10
  2. package/dist/bin/primitive.js +8 -0
  3. package/dist/bin/primitive.js.map +1 -1
  4. package/dist/src/commands/apps.js +54 -2
  5. package/dist/src/commands/apps.js.map +1 -1
  6. package/dist/src/commands/auth.js +16 -6
  7. package/dist/src/commands/auth.js.map +1 -1
  8. package/dist/src/commands/blob-buckets.js +84 -7
  9. package/dist/src/commands/blob-buckets.js.map +1 -1
  10. package/dist/src/commands/catalog.js +3 -3
  11. package/dist/src/commands/catalog.js.map +1 -1
  12. package/dist/src/commands/collections.js +12 -9
  13. package/dist/src/commands/collections.js.map +1 -1
  14. package/dist/src/commands/databases.js +220 -82
  15. package/dist/src/commands/databases.js.map +1 -1
  16. package/dist/src/commands/documents.js +77 -0
  17. package/dist/src/commands/documents.js.map +1 -1
  18. package/dist/src/commands/group-type-configs.js +1 -9
  19. package/dist/src/commands/group-type-configs.js.map +1 -1
  20. package/dist/src/commands/groups.js +12 -6
  21. package/dist/src/commands/groups.js.map +1 -1
  22. package/dist/src/commands/guides.d.ts +161 -0
  23. package/dist/src/commands/guides.js +502 -84
  24. package/dist/src/commands/guides.js.map +1 -1
  25. package/dist/src/commands/integrations.js +2 -2
  26. package/dist/src/commands/integrations.js.map +1 -1
  27. package/dist/src/commands/metadata.d.ts +2 -0
  28. package/dist/src/commands/metadata.js +344 -0
  29. package/dist/src/commands/metadata.js.map +1 -0
  30. package/dist/src/commands/prompts.js +2 -2
  31. package/dist/src/commands/prompts.js.map +1 -1
  32. package/dist/src/commands/rule-sets.d.ts +1 -0
  33. package/dist/src/commands/rule-sets.js +25 -3
  34. package/dist/src/commands/rule-sets.js.map +1 -1
  35. package/dist/src/commands/scripts.d.ts +2 -0
  36. package/dist/src/commands/scripts.js +667 -0
  37. package/dist/src/commands/scripts.js.map +1 -0
  38. package/dist/src/commands/settings.d.ts +15 -0
  39. package/dist/src/commands/settings.js +102 -0
  40. package/dist/src/commands/settings.js.map +1 -0
  41. package/dist/src/commands/sync-app-settings.d.ts +105 -0
  42. package/dist/src/commands/sync-app-settings.js +339 -0
  43. package/dist/src/commands/sync-app-settings.js.map +1 -0
  44. package/dist/src/commands/sync.d.ts +204 -9
  45. package/dist/src/commands/sync.js +1706 -371
  46. package/dist/src/commands/sync.js.map +1 -1
  47. package/dist/src/commands/vars.d.ts +8 -0
  48. package/dist/src/commands/vars.js +110 -0
  49. package/dist/src/commands/vars.js.map +1 -0
  50. package/dist/src/commands/webhooks.js +17 -3
  51. package/dist/src/commands/webhooks.js.map +1 -1
  52. package/dist/src/commands/workflows.d.ts +30 -0
  53. package/dist/src/commands/workflows.js +667 -59
  54. package/dist/src/commands/workflows.js.map +1 -1
  55. package/dist/src/lib/api-client.d.ts +122 -18
  56. package/dist/src/lib/api-client.js +131 -2
  57. package/dist/src/lib/api-client.js.map +1 -1
  58. package/dist/src/lib/app-settings-descriptor.d.ts +108 -0
  59. package/dist/src/lib/app-settings-descriptor.js +250 -0
  60. package/dist/src/lib/app-settings-descriptor.js.map +1 -0
  61. package/dist/src/lib/batch.d.ts +26 -0
  62. package/dist/src/lib/batch.js +32 -0
  63. package/dist/src/lib/batch.js.map +1 -0
  64. package/dist/src/lib/block-layout.d.ts +160 -0
  65. package/dist/src/lib/block-layout.js +451 -0
  66. package/dist/src/lib/block-layout.js.map +1 -0
  67. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +76 -0
  68. package/dist/src/lib/codegen-shared/generatedFiles.js +144 -0
  69. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -0
  70. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +68 -0
  71. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +168 -0
  72. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -0
  73. package/dist/src/lib/config-toml.d.ts +10 -0
  74. package/dist/src/lib/config-toml.js +42 -0
  75. package/dist/src/lib/config-toml.js.map +1 -0
  76. package/dist/src/lib/config.d.ts +16 -0
  77. package/dist/src/lib/config.js +41 -0
  78. package/dist/src/lib/config.js.map +1 -1
  79. package/dist/src/lib/constants.d.ts +9 -0
  80. package/dist/src/lib/constants.js +9 -0
  81. package/dist/src/lib/constants.js.map +1 -1
  82. package/dist/src/lib/csv.d.ts +1 -2
  83. package/dist/src/lib/csv.js +3 -8
  84. package/dist/src/lib/csv.js.map +1 -1
  85. package/dist/src/lib/db-codegen/dbGenerator.d.ts +21 -65
  86. package/dist/src/lib/db-codegen/dbGenerator.js +117 -321
  87. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
  88. package/dist/src/lib/db-codegen/dbNaming.d.ts +37 -0
  89. package/dist/src/lib/db-codegen/dbNaming.js +70 -0
  90. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
  91. package/dist/src/lib/db-codegen/dbTemplates.d.ts +165 -5
  92. package/dist/src/lib/db-codegen/dbTemplates.js +292 -16
  93. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
  94. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +7 -3
  95. package/dist/src/lib/db-codegen/dbTsTypes.js +23 -1
  96. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -1
  97. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
  98. package/dist/src/lib/db-codegen/dbTypeIR.js +517 -0
  99. package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
  100. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +101 -0
  101. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +200 -0
  102. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
  103. package/dist/src/lib/generated-allowlist.js +43 -0
  104. package/dist/src/lib/generated-allowlist.js.map +1 -1
  105. package/dist/src/lib/init-config.js +2 -2
  106. package/dist/src/lib/init-config.js.map +1 -1
  107. package/dist/src/lib/output.d.ts +25 -5
  108. package/dist/src/lib/output.js +32 -4
  109. package/dist/src/lib/output.js.map +1 -1
  110. package/dist/src/lib/query-operators.d.ts +43 -0
  111. package/dist/src/lib/query-operators.js +80 -0
  112. package/dist/src/lib/query-operators.js.map +1 -0
  113. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +68 -0
  114. package/dist/src/lib/swift-codegen/dbGenerator.js +380 -0
  115. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
  116. package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
  117. package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
  118. package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
  119. package/dist/src/lib/swift-codegen/generator.d.ts +84 -0
  120. package/dist/src/lib/swift-codegen/generator.js +178 -0
  121. package/dist/src/lib/swift-codegen/generator.js.map +1 -0
  122. package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +72 -0
  123. package/dist/src/lib/swift-codegen/schemaToSwift.js +644 -0
  124. package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
  125. package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
  126. package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
  127. package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
  128. package/dist/src/lib/sync-resource-types.d.ts +225 -0
  129. package/dist/src/lib/sync-resource-types.js +394 -0
  130. package/dist/src/lib/sync-resource-types.js.map +1 -0
  131. package/dist/src/lib/toml-database-config.d.ts +10 -19
  132. package/dist/src/lib/toml-database-config.js +28 -59
  133. package/dist/src/lib/toml-database-config.js.map +1 -1
  134. package/dist/src/lib/toml-metadata-config.d.ts +147 -0
  135. package/dist/src/lib/toml-metadata-config.js +463 -0
  136. package/dist/src/lib/toml-metadata-config.js.map +1 -0
  137. package/dist/src/lib/toml-native-form.d.ts +46 -0
  138. package/dist/src/lib/toml-native-form.js +78 -0
  139. package/dist/src/lib/toml-native-form.js.map +1 -0
  140. package/dist/src/lib/toml-params-validator.d.ts +34 -0
  141. package/dist/src/lib/toml-params-validator.js +118 -3
  142. package/dist/src/lib/toml-params-validator.js.map +1 -1
  143. package/dist/src/lib/workflow-apply.d.ts +86 -0
  144. package/dist/src/lib/workflow-apply.js +127 -0
  145. package/dist/src/lib/workflow-apply.js.map +1 -0
  146. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.d.ts +129 -0
  147. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js +269 -0
  148. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js.map +1 -0
  149. package/dist/src/lib/workflow-codegen/generator.d.ts +87 -0
  150. package/dist/src/lib/workflow-codegen/generator.js +290 -0
  151. package/dist/src/lib/workflow-codegen/generator.js.map +1 -0
  152. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +85 -0
  153. package/dist/src/lib/workflow-codegen/invokerIR.js +71 -0
  154. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
  155. package/dist/src/lib/workflow-codegen/naming.d.ts +33 -0
  156. package/dist/src/lib/workflow-codegen/naming.js +81 -0
  157. package/dist/src/lib/workflow-codegen/naming.js.map +1 -0
  158. package/dist/src/lib/workflow-codegen/schemaToTs.d.ts +64 -0
  159. package/dist/src/lib/workflow-codegen/schemaToTs.js +282 -0
  160. package/dist/src/lib/workflow-codegen/schemaToTs.js.map +1 -0
  161. package/dist/src/lib/workflow-fragments.js +3 -3
  162. package/dist/src/lib/workflow-fragments.js.map +1 -1
  163. package/dist/src/lib/workflow-payload.d.ts +68 -0
  164. package/dist/src/lib/workflow-payload.js +150 -0
  165. package/dist/src/lib/workflow-payload.js.map +1 -0
  166. package/dist/src/lib/workflow-toml-validator.d.ts +1 -1
  167. package/dist/src/lib/workflow-toml-validator.js +37 -1
  168. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  169. package/dist/src/types/index.d.ts +11 -0
  170. package/dist/src/validators.d.ts +4 -3
  171. package/dist/src/validators.js +6 -5
  172. package/dist/src/validators.js.map +1 -1
  173. package/package.json +7 -3
@@ -1,10 +1,13 @@
1
1
  import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, readdirSync } from "fs";
2
2
  import * as path from "path";
3
- import * as TOML from "@iarna/toml";
3
+ import { parseConfigToml } from "../lib/config-toml.js";
4
4
  import { ApiClient } from "../lib/api-client.js";
5
5
  import { getCurrentAppId } from "../lib/config.js";
6
- import { parseCsv, applyColumnMap, buildRows, chunk } from "../lib/csv.js";
6
+ import { resolveCodegenSourceDir } from "../lib/codegen-shared/resolveCodegenSourceDir.js";
7
+ import { parseCsv, applyColumnMap, buildRows } from "../lib/csv.js";
8
+ import { chunk, parseBatchSize } from "../lib/batch.js";
7
9
  import { generateDbTypes, } from "../lib/db-codegen/dbGenerator.js";
10
+ import { generateDbSwiftTypes } from "../lib/swift-codegen/dbGenerator.js";
8
11
  import { success, error, info, warn, keyValue, result as printResult, formatTable, formatId, formatDate, json, } from "../lib/output.js";
9
12
  import { confirmPrompt } from "../lib/confirm-prompt.js";
10
13
  function resolveAppId(appId, options) {
@@ -128,6 +131,7 @@ the 'database-types' group:
128
131
  .requiredOption("--type <database-type>", "Database type (required)")
129
132
  .option("--cel-context <json>", "Initial CEL context as JSON (referenced from access rules as database.celContext.*)")
130
133
  .option("--metadata <json>", "Initial CEL context as JSON (legacy alias for --cel-context)")
134
+ .option("--initial-metadata <json>", "Create-time resource metadata as JSON, keyed by category name (issue #1420; distinct from --cel-context)")
131
135
  .option("--json", "Output as JSON")
132
136
  .action(async (title, options) => {
133
137
  const resolvedAppId = resolveAppId(undefined, options);
@@ -147,6 +151,17 @@ the 'database-types' group:
147
151
  process.exit(1);
148
152
  }
149
153
  }
154
+ // `--initial-metadata` is a separate concept: create-time per-category
155
+ // ResourceMetadata rows (issue #1420), not the database CEL context.
156
+ if (options.initialMetadata) {
157
+ try {
158
+ params.initialMetadata = JSON.parse(options.initialMetadata);
159
+ }
160
+ catch {
161
+ error("Invalid --initial-metadata JSON.");
162
+ process.exit(1);
163
+ }
164
+ }
150
165
  const result = await client.createDatabase(resolvedAppId, params);
151
166
  if (options.json) {
152
167
  json(result);
@@ -474,7 +489,7 @@ the 'database-types' group:
474
489
  try {
475
490
  const content = readFileSync(options.filterFile, "utf-8");
476
491
  if (options.filterFile.endsWith(".toml")) {
477
- const parsed = TOML.parse(content);
492
+ const parsed = parseConfigToml(content);
478
493
  filter = parsed.filter || parsed;
479
494
  }
480
495
  else {
@@ -1059,6 +1074,8 @@ the 'database-types' group:
1059
1074
  .option("--app <app-id>", "App ID")
1060
1075
  .option("--overwrite", "Replace existing database if ID collides")
1061
1076
  .option("--dry-run", "Show what would be imported without making changes")
1077
+ .option("--batch-size <n>", "Records per batch write request (default: 5000)", "5000")
1078
+ .option("--stop-on-error", "Abort the whole run on the first failing chunk (default: continue and report)")
1062
1079
  .option("--json", "Output as JSON")
1063
1080
  .action(async (first, second, options) => {
1064
1081
  let resolvedAppId;
@@ -1075,6 +1092,21 @@ the 'database-types' group:
1075
1092
  error("Import path is required.");
1076
1093
  process.exit(1);
1077
1094
  }
1095
+ // Validate --batch-size up front so a bad value fails before any write.
1096
+ // Unlike `import-csv` (registered-batch path, hard 100k server cap),
1097
+ // `admin-data/batch` has no fixed server item cap — bound it with a
1098
+ // conservative client-side ceiling to keep each request within
1099
+ // Cloudflare body-size and Durable Object memory limits.
1100
+ const parsedBatchSize = parseBatchSize(options.batchSize, {
1101
+ max: IMPORT_BATCH_SIZE_CEILING,
1102
+ overMaxMessage: `--batch-size cannot exceed ${IMPORT_BATCH_SIZE_CEILING} (a conservative client-side limit for batch write requests).`,
1103
+ });
1104
+ if ("error" in parsedBatchSize) {
1105
+ error(parsedBatchSize.error);
1106
+ process.exit(1);
1107
+ return;
1108
+ }
1109
+ const batchSize = parsedBatchSize.value;
1078
1110
  const client = new ApiClient();
1079
1111
  try {
1080
1112
  // Determine if this is a manifest-based export or a single database
@@ -1109,12 +1141,32 @@ the 'database-types' group:
1109
1141
  info("No databases to import.");
1110
1142
  return;
1111
1143
  }
1112
- const summary = { created: 0, updated: 0, skipped: 0, recordsImported: 0, indexesRegistered: 0 };
1144
+ const summary = {
1145
+ created: 0,
1146
+ updated: 0,
1147
+ skipped: 0,
1148
+ recordsImported: 0,
1149
+ recordsFailed: 0,
1150
+ batches: 0,
1151
+ aborted: false,
1152
+ indexesRegistered: 0,
1153
+ batchErrors: [],
1154
+ };
1113
1155
  for (const dbDir of dbDirs) {
1114
- await importSingleDatabase(client, resolvedAppId, dbDir, options.overwrite || false, options.dryRun || false, summary);
1156
+ await importSingleDatabase(client, resolvedAppId, dbDir, {
1157
+ overwrite: options.overwrite || false,
1158
+ dryRun: options.dryRun || false,
1159
+ batchSize,
1160
+ stopOnError: options.stopOnError || false,
1161
+ json: options.json || false,
1162
+ summary,
1163
+ });
1164
+ // Fork 3: --stop-on-error stops the whole run across a multi-DB dir.
1165
+ if (summary.aborted)
1166
+ break;
1115
1167
  }
1116
1168
  if (options.json) {
1117
- json(summary);
1169
+ json({ ...summary, dryRun: options.dryRun || false });
1118
1170
  }
1119
1171
  else {
1120
1172
  success("Database import complete:");
@@ -1124,8 +1176,24 @@ the 'database-types' group:
1124
1176
  if (summary.skipped > 0)
1125
1177
  keyValue(" Databases skipped", String(summary.skipped));
1126
1178
  keyValue(" Records imported", String(summary.recordsImported));
1179
+ if (summary.recordsFailed > 0)
1180
+ keyValue(" Records failed", String(summary.recordsFailed));
1181
+ keyValue(" Batches", String(summary.batches));
1127
1182
  if (summary.indexesRegistered > 0)
1128
1183
  keyValue(" Indexes registered", String(summary.indexesRegistered));
1184
+ if (summary.aborted)
1185
+ warn("Aborted after a failing chunk (--stop-on-error).");
1186
+ if (summary.batchErrors.length > 0) {
1187
+ error(`${summary.batchErrors.length} chunk(s) errored:`);
1188
+ for (const e of summary.batchErrors) {
1189
+ error(` ${e.database} batch ${e.batch + 1}: ${e.error}`);
1190
+ }
1191
+ }
1192
+ }
1193
+ // Non-zero exit if any chunk errored (default continue-and-report or
1194
+ // --stop-on-error both surface failures this way).
1195
+ if (summary.batchErrors.length > 0) {
1196
+ process.exit(1);
1129
1197
  }
1130
1198
  }
1131
1199
  catch (err) {
@@ -1178,18 +1246,18 @@ the 'database-types' group:
1178
1246
  return;
1179
1247
  }
1180
1248
  }
1181
- const batchSize = parseInt(options.batchSize, 10);
1182
- if (!Number.isInteger(batchSize) || batchSize < 1) {
1183
- error("--batch-size must be a positive integer.");
1184
- process.exit(1);
1185
- return;
1186
- }
1187
- // The server rejects batch requests over 100000 items; keep chunks under it.
1188
- if (batchSize > 100000) {
1189
- error("--batch-size cannot exceed 100000 (the server's per-request cap).");
1249
+ // The registered-batch (`executeBatch`) path rejects requests over
1250
+ // 100000 items server-side; keep chunks under it.
1251
+ const parsedBatchSize = parseBatchSize(options.batchSize, {
1252
+ max: 100000,
1253
+ overMaxMessage: "--batch-size cannot exceed 100000 (the server's per-request cap).",
1254
+ });
1255
+ if ("error" in parsedBatchSize) {
1256
+ error(parsedBatchSize.error);
1190
1257
  process.exit(1);
1191
1258
  return;
1192
1259
  }
1260
+ const batchSize = parsedBatchSize.value;
1193
1261
  if (!existsSync(file)) {
1194
1262
  error(`CSV file not found: ${file}`);
1195
1263
  process.exit(1);
@@ -1356,7 +1424,7 @@ the 'database-types' group:
1356
1424
  }
1357
1425
  // 3. For each candidate, splice the scaffolded TOML at a stable
1358
1426
  // anchor (before the first `[[operations]]` block, or at EOF). Per
1359
- // the plan-of-record, textual insertion is deliberate — `@iarna/toml`
1427
+ // the plan-of-record, textual insertion is deliberate — smol-toml
1360
1428
  // parse/stringify drops comments, so we splice as a raw string.
1361
1429
  for (const targetPath of candidateFiles) {
1362
1430
  const original = readFileSync(targetPath, "utf-8");
@@ -1401,67 +1469,47 @@ the 'database-types' group:
1401
1469
  // ============================================
1402
1470
  databases
1403
1471
  .command("codegen")
1404
- .description("Generate TypeScript record + operation types from the local database-types/*.toml schema. Emits a <type>.generated.ts per database type (record interfaces, per-op input-params interfaces, and per-op result aliases).")
1472
+ .description("Generate record + operation types from the local database-types/*.toml schema. TypeScript (default): a <type>.generated.ts per database type (record interfaces, per-op input-params interfaces, per-op result aliases, and a typed <type>Ops factory). Swift (--lang swift): a <type>.generated.swift per database type (record structs, per-op params structs, per-op result typealiases, and a typed <Type>Ops factory).")
1405
1473
  .argument("[database-type]", "Generate for a single database type (defaults to every database-types/*.toml found)")
1406
1474
  .option("--app <app-id>", "App ID")
1407
1475
  .option("--sync-dir <path>", "Override path to the sync directory (defaults to ./.primitive/sync/<env>/<appId>/)")
1408
- .option("-o, --output <dir>", "Output directory for *.generated.ts files (defaults to <sync-dir>/database-types/generated/)")
1476
+ .option("-o, --output <dir>", "Output directory for the generated files (defaults to <sync-dir>/database-types/generated/)")
1477
+ .option("--lang <lang>", "Target language for the generated types: 'ts' (default) or 'swift'.", "ts")
1409
1478
  .option("--check", "Exit non-zero if generated output is out of date (CI guard); does not write.")
1410
1479
  .option("--json", "Output the result summary as JSON")
1411
1480
  .action(async (databaseType, options) => {
1412
1481
  try {
1413
- // 1. Locate the database-types directory. Mirrors `schema generate`'s
1414
- // sync-dir resolution: honor --sync-dir, else search any
1415
- // env/appId subtree under ./.primitive/sync that has a
1416
- // database-types/ directory.
1417
- const dbTypesDirs = [];
1418
- if (options.syncDir) {
1419
- dbTypesDirs.push(path.join(options.syncDir, "database-types"));
1420
- }
1421
- else {
1422
- const root = path.join(process.cwd(), ".primitive", "sync");
1423
- if (existsSync(root)) {
1424
- for (const env of readdirSync(root)) {
1425
- const envDir = path.join(root, env);
1426
- try {
1427
- for (const app of readdirSync(envDir)) {
1428
- const candidate = path.join(envDir, app, "database-types");
1429
- if (existsSync(candidate)) {
1430
- dbTypesDirs.push(candidate);
1431
- }
1432
- }
1433
- }
1434
- catch {
1435
- // skip non-dirs
1436
- }
1437
- }
1438
- }
1439
- }
1440
- if (dbTypesDirs.length === 0) {
1441
- error("No database-types/ directory found. Pass --sync-dir or run `primitive sync pull` first.");
1482
+ // 0. Validate --lang up front so an unknown value fails clearly rather
1483
+ // than silently defaulting to the TS emitter.
1484
+ const lang = String(options.lang ?? "ts").toLowerCase();
1485
+ if (lang !== "ts" && lang !== "swift") {
1486
+ error(`Unknown --lang "${options.lang}". Use "ts" or "swift".`);
1442
1487
  process.exit(1);
1443
1488
  }
1489
+ // 1. Resolve the single source database-types/ directory via the
1490
+ // shared active-environment resolver (issue #1510). Honors
1491
+ // --sync-dir / --app overrides and resolves the active env
1492
+ // (--env → PRIMITIVE_ENV → defaultEnvironment → single-env) instead
1493
+ // of silently merging types across every synced env.
1494
+ const dbTypesSourceDir = resolveCodegenSourceDir({
1495
+ subdir: "database-types",
1496
+ options: { app: options.app, syncDir: options.syncDir },
1497
+ });
1444
1498
  // 2. Collect the source .toml files (one per database type), filtered
1445
1499
  // to a single type when an argument is given.
1446
1500
  const inputs = [];
1447
- const seenTypes = new Set();
1448
- for (const dir of dbTypesDirs) {
1449
- for (const fileName of readdirSync(dir)) {
1450
- if (!fileName.endsWith(".toml"))
1451
- continue;
1452
- const typeName = fileName.slice(0, -".toml".length);
1453
- if (databaseType && typeName !== databaseType)
1454
- continue;
1455
- if (seenTypes.has(typeName))
1456
- continue;
1457
- seenTypes.add(typeName);
1458
- const tomlPath = path.join(dir, fileName);
1459
- inputs.push({
1460
- databaseType: typeName,
1461
- tomlPath,
1462
- tomlContent: readFileSync(tomlPath, "utf-8"),
1463
- });
1464
- }
1501
+ for (const fileName of readdirSync(dbTypesSourceDir)) {
1502
+ if (!fileName.endsWith(".toml"))
1503
+ continue;
1504
+ const typeName = fileName.slice(0, -".toml".length);
1505
+ if (databaseType && typeName !== databaseType)
1506
+ continue;
1507
+ const tomlPath = path.join(dbTypesSourceDir, fileName);
1508
+ inputs.push({
1509
+ databaseType: typeName,
1510
+ tomlPath,
1511
+ tomlContent: readFileSync(tomlPath, "utf-8"),
1512
+ });
1465
1513
  }
1466
1514
  if (inputs.length === 0) {
1467
1515
  error(databaseType
@@ -1474,16 +1522,26 @@ the 'database-types' group:
1474
1522
  const outputDir = options.output
1475
1523
  ? path.resolve(options.output)
1476
1524
  : path.join(path.dirname(inputs[0].tomlPath), "generated");
1477
- // 4. Run codegen (or --check). When a single database-type filter
1525
+ // 4. Run codegen (or --check) with the selected emitter. Both the TS
1526
+ // and Swift generators return the same WriteOrCheckResult shape, so
1527
+ // the reporting below is emitter-agnostic; only the emitter (and the
1528
+ // regenerate hint) differs. When a single database-type filter
1478
1529
  // argument is given, the input set is partial — flag it so stale
1479
1530
  // cleanup is scoped to that type and sibling types' generated files
1480
1531
  // are left intact.
1481
- const codegenResult = await generateDbTypes({
1482
- inputs,
1483
- outputDir,
1484
- check: !!options.check,
1485
- singleType: !!databaseType,
1486
- });
1532
+ const codegenResult = lang === "swift"
1533
+ ? await generateDbSwiftTypes({
1534
+ inputs,
1535
+ outputDir,
1536
+ check: !!options.check,
1537
+ singleType: !!databaseType,
1538
+ })
1539
+ : await generateDbTypes({
1540
+ inputs,
1541
+ outputDir,
1542
+ check: !!options.check,
1543
+ singleType: !!databaseType,
1544
+ });
1487
1545
  if (options.check) {
1488
1546
  if (codegenResult.mismatches.length > 0) {
1489
1547
  if (options.json) {
@@ -1494,7 +1552,9 @@ the 'database-types' group:
1494
1552
  for (const m of codegenResult.mismatches) {
1495
1553
  error(` ${m.reason}: ${path.relative(process.cwd(), m.filePath)}`);
1496
1554
  }
1497
- info("Run `primitive databases codegen` to regenerate.");
1555
+ info(lang === "swift"
1556
+ ? "Run `primitive databases codegen --lang swift` to regenerate."
1557
+ : "Run `primitive databases codegen` to regenerate.");
1498
1558
  }
1499
1559
  process.exit(1);
1500
1560
  }
@@ -1570,8 +1630,12 @@ async function exportSingleDatabase(client, appId, databaseId, outputDir, jsonOu
1570
1630
  metadata: dbMeta.metadata || null,
1571
1631
  };
1572
1632
  writeFileSync(path.join(dbDir, "metadata.json"), JSON.stringify(metadata, null, 2));
1573
- // 3. Export records: list models, then query each
1574
- const models = await client.listDatabaseModels(appId, databaseId);
1633
+ // 3. Export records: list models, then query each.
1634
+ // listDatabaseModels() resolves to `{ models: string[] }`, not a bare array,
1635
+ // so unwrap it before iterating (mirrors the `databases records models`
1636
+ // consumer above). Without this, `for (const modelName of models)` throws
1637
+ // "models is not iterable" and records.jsonl is left empty (#1396).
1638
+ const models = (await client.listDatabaseModels(appId, databaseId)).models || [];
1575
1639
  let totalRecords = 0;
1576
1640
  const recordsFile = path.join(dbDir, "records.jsonl");
1577
1641
  // Clear file
@@ -1619,7 +1683,15 @@ async function exportSingleDatabase(client, appId, databaseId, outputDir, jsonOu
1619
1683
  keyValue(" Indexes", String(indexes.length));
1620
1684
  }
1621
1685
  }
1622
- async function importSingleDatabase(client, appId, dbDir, overwrite, dryRun, summary) {
1686
+ /**
1687
+ * Conservative client-side ceiling for `databases import --batch-size`.
1688
+ * `admin-data/batch` has no fixed server-side item cap (unlike the
1689
+ * registered-batch path used by `import-csv`), so this bounds each request to
1690
+ * keep it within Cloudflare body-size and Durable Object memory limits.
1691
+ */
1692
+ const IMPORT_BATCH_SIZE_CEILING = 25000;
1693
+ async function importSingleDatabase(client, appId, dbDir, options) {
1694
+ const { overwrite, dryRun, batchSize, stopOnError, json: jsonOutput, summary } = options;
1623
1695
  const metadataPath = path.join(dbDir, "metadata.json");
1624
1696
  if (!existsSync(metadataPath)) {
1625
1697
  warn(`Skipping ${dbDir}: no metadata.json found`);
@@ -1629,8 +1701,14 @@ async function importSingleDatabase(client, appId, dbDir, overwrite, dryRun, sum
1629
1701
  const metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));
1630
1702
  const databaseId = metadata.databaseId;
1631
1703
  if (dryRun) {
1632
- info(`[dry-run] Would import database ${databaseId} (${metadata.title || "untitled"})`);
1704
+ // Report record/batch counts without writing anything (no DB creation,
1705
+ // no index registration, no batch write).
1706
+ const recordCount = countRecordLines(dbDir);
1707
+ const batchCount = chunk(new Array(recordCount), batchSize).length;
1633
1708
  summary.created++;
1709
+ summary.recordsImported += recordCount;
1710
+ summary.batches += batchCount;
1711
+ info(`[dry-run] Would import database ${databaseId} (${metadata.title || "untitled"}) — ${recordCount} record(s), ${batchCount} batch(es)`);
1634
1712
  return;
1635
1713
  }
1636
1714
  // Check if database type exists — warn if not
@@ -1707,19 +1785,79 @@ async function importSingleDatabase(client, appId, dbDir, overwrite, dryRun, sum
1707
1785
  }
1708
1786
  }
1709
1787
  }
1710
- // Import records
1788
+ // Import records via chunked batch writes (was one save per record).
1711
1789
  const recordsPath = path.join(dbDir, "records.jsonl");
1712
1790
  if (existsSync(recordsPath)) {
1713
1791
  const lines = readFileSync(recordsPath, "utf-8").split("\n").filter(Boolean);
1714
- for (const line of lines) {
1792
+ // Map each records.jsonl line to a batch save op. Preserve the explicit
1793
+ // `_type`/`_id`; strip the redundant id/type keys out of `_data` (the
1794
+ // per-record loop did the same) so they don't duplicate into stored data.
1795
+ const ops = lines.map((line) => {
1715
1796
  const record = JSON.parse(line);
1716
1797
  const { _type, _id, _data } = record;
1717
- // Remove 'id' and 'type' from _data to avoid duplication
1718
1798
  const { id: _ignoredId, type: _ignoredType, ...data } = _data;
1719
- await client.saveDatabaseRecord(appId, targetDatabaseId, _type, _id, data);
1720
- summary.recordsImported++;
1799
+ return { op: "save", modelName: _type, id: _id, data };
1800
+ });
1801
+ const chunks = chunk(ops, batchSize);
1802
+ summary.batches += chunks.length;
1803
+ for (let i = 0; i < chunks.length; i++) {
1804
+ const piece = chunks[i];
1805
+ let chunkFailed = false;
1806
+ try {
1807
+ const res = await client.batchDatabaseRecords(appId, targetDatabaseId, piece);
1808
+ const results = res?.results ?? [];
1809
+ let imported = 0;
1810
+ let failed = 0;
1811
+ const firstErrors = [];
1812
+ for (let j = 0; j < piece.length; j++) {
1813
+ const r = results[j];
1814
+ if (r && r.success) {
1815
+ imported++;
1816
+ }
1817
+ else {
1818
+ failed++;
1819
+ if (r?.error && firstErrors.length < 3)
1820
+ firstErrors.push(r.error);
1821
+ }
1822
+ }
1823
+ summary.recordsImported += imported;
1824
+ summary.recordsFailed += failed;
1825
+ if (failed > 0) {
1826
+ chunkFailed = true;
1827
+ summary.batchErrors.push({
1828
+ database: databaseId,
1829
+ batch: i,
1830
+ error: `${failed} record(s) failed${firstErrors.length ? `: ${firstErrors.join("; ")}` : ""}`,
1831
+ });
1832
+ }
1833
+ if (!jsonOutput) {
1834
+ info(` ${databaseId} batch ${i + 1}/${chunks.length}: imported ${imported}, failed ${failed}`);
1835
+ }
1836
+ }
1837
+ catch (err) {
1838
+ // Chunk-level failure (e.g. a 400 for a malformed op): the whole chunk
1839
+ // is rejected, so every item in it counts as failed.
1840
+ const msg = err?.message || String(err);
1841
+ chunkFailed = true;
1842
+ summary.recordsFailed += piece.length;
1843
+ summary.batchErrors.push({ database: databaseId, batch: i, error: msg });
1844
+ if (!jsonOutput) {
1845
+ warn(` ${databaseId} batch ${i + 1}/${chunks.length} failed: ${msg}`);
1846
+ }
1847
+ }
1848
+ if (chunkFailed && stopOnError) {
1849
+ summary.aborted = true;
1850
+ return;
1851
+ }
1721
1852
  }
1722
1853
  }
1723
1854
  info(`Imported database ${databaseId} (${metadata.title || "untitled"})`);
1724
1855
  }
1856
+ /** Count non-empty lines in a database export's records.jsonl (0 if absent). */
1857
+ function countRecordLines(dbDir) {
1858
+ const recordsPath = path.join(dbDir, "records.jsonl");
1859
+ if (!existsSync(recordsPath))
1860
+ return 0;
1861
+ return readFileSync(recordsPath, "utf-8").split("\n").filter(Boolean).length;
1862
+ }
1725
1863
  //# sourceMappingURL=databases.js.map