primitive-admin 1.2.0-alpha.4 → 1.2.0-alpha.5
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.
- package/assets/skill/skills/primitive-platform/SKILL.md +17 -0
- package/dist/src/commands/auth.js +7 -4
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/connections.js +11 -12
- package/dist/src/commands/connections.js.map +1 -1
- package/dist/src/commands/database-type-configs.js +2 -76
- package/dist/src/commands/database-type-configs.js.map +1 -1
- package/dist/src/commands/database-types.js +313 -13
- package/dist/src/commands/database-types.js.map +1 -1
- package/dist/src/commands/databases.d.ts +9 -0
- package/dist/src/commands/databases.js +67 -367
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.d.ts +32 -2
- package/dist/src/commands/documents.js +281 -685
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/functions.js +2 -2
- package/dist/src/commands/functions.js.map +1 -1
- package/dist/src/commands/llm.js +6 -9
- package/dist/src/commands/llm.js.map +1 -1
- package/dist/src/commands/settings.d.ts +9 -12
- package/dist/src/commands/settings.js +79 -31
- package/dist/src/commands/settings.js.map +1 -1
- package/dist/src/commands/sync.d.ts +31 -81
- package/dist/src/commands/sync.js +228 -1113
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +30 -199
- package/dist/src/lib/api-client.js +31 -297
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/codegen-shared/generatedFiles.d.ts +6 -7
- package/dist/src/lib/codegen-shared/generatedFiles.js +2 -2
- package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
- package/dist/src/lib/codegen-shared/prettierStable.d.ts +1 -1
- package/dist/src/lib/codegen-shared/prettierStable.js +1 -1
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +2 -2
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +2 -2
- package/dist/src/lib/config-object-descriptor.js +1 -5
- package/dist/src/lib/config-object-descriptor.js.map +1 -1
- package/dist/src/lib/config-surface.d.ts +2 -1
- package/dist/src/lib/config-surface.js +4 -1
- package/dist/src/lib/config-surface.js.map +1 -1
- package/dist/src/lib/connections-list.d.ts +10 -0
- package/dist/src/lib/connections-list.js +18 -0
- package/dist/src/lib/connections-list.js.map +1 -0
- package/dist/src/lib/csv.d.ts +8 -9
- package/dist/src/lib/csv.js +3 -4
- package/dist/src/lib/csv.js.map +1 -1
- package/dist/src/lib/db-codegen/dbNaming.d.ts +2 -1
- package/dist/src/lib/db-codegen/dbNaming.js +2 -1
- package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTsTypes.d.ts +2 -2
- package/dist/src/lib/db-codegen/dbTsTypes.js +2 -2
- package/dist/src/lib/db-codegen/dbTypeIR.d.ts +59 -1
- package/dist/src/lib/db-codegen/dbTypeIR.js +12 -1
- package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -1
- package/dist/src/lib/document-export-record-log.d.ts +57 -0
- package/dist/src/lib/document-export-record-log.js +215 -0
- package/dist/src/lib/document-export-record-log.js.map +1 -0
- package/dist/src/lib/document-ingest-artifact.d.ts +13 -2
- package/dist/src/lib/document-ingest-artifact.js +7 -3
- package/dist/src/lib/document-ingest-artifact.js.map +1 -1
- package/dist/src/lib/document-ingest-rows.d.ts +16 -0
- package/dist/src/lib/document-ingest-rows.js +17 -0
- package/dist/src/lib/document-ingest-rows.js.map +1 -1
- package/dist/src/lib/document-ingest.d.ts +22 -0
- package/dist/src/lib/document-ingest.js +33 -0
- package/dist/src/lib/document-ingest.js.map +1 -1
- package/dist/src/lib/function-triggers.d.ts +3 -15
- package/dist/src/lib/function-triggers.js +10 -72
- package/dist/src/lib/function-triggers.js.map +1 -1
- package/dist/src/lib/generated-allowlist.js +0 -20
- package/dist/src/lib/generated-allowlist.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +40 -2
- package/dist/src/lib/generated-config-surfaces.js +160 -94
- package/dist/src/lib/generated-config-surfaces.js.map +1 -1
- package/dist/src/lib/generated-sdk-types.d.ts +1 -1
- package/dist/src/lib/generated-sdk-types.js +1 -1
- package/dist/src/lib/generated-sdk-types.js.map +1 -1
- package/dist/src/lib/login-scope-lines.d.ts +15 -0
- package/dist/src/lib/login-scope-lines.js +22 -0
- package/dist/src/lib/login-scope-lines.js.map +1 -0
- package/dist/src/lib/paginate.d.ts +0 -5
- package/dist/src/lib/paginate.js +0 -5
- package/dist/src/lib/paginate.js.map +1 -1
- package/dist/src/lib/refresh-admin-credentials.d.ts +7 -0
- package/dist/src/lib/refresh-admin-credentials.js +16 -4
- package/dist/src/lib/refresh-admin-credentials.js.map +1 -1
- package/dist/src/lib/root-import-decision.d.ts +15 -14
- package/dist/src/lib/root-import-decision.js +44 -51
- package/dist/src/lib/root-import-decision.js.map +1 -1
- package/dist/src/lib/snapshot-build-rows.d.ts +21 -0
- package/dist/src/lib/snapshot-build-rows.js +22 -0
- package/dist/src/lib/snapshot-build-rows.js.map +1 -1
- package/dist/src/lib/snapshot-build.js +8 -3
- package/dist/src/lib/snapshot-build.js.map +1 -1
- package/dist/src/lib/swift-codegen/banners.d.ts +34 -7
- package/dist/src/lib/swift-codegen/banners.js +37 -7
- package/dist/src/lib/swift-codegen/banners.js.map +1 -1
- package/dist/src/lib/swift-codegen/functionGenerator.d.ts +3 -3
- package/dist/src/lib/swift-codegen/functionGenerator.js +22 -17
- package/dist/src/lib/swift-codegen/functionGenerator.js.map +1 -1
- package/dist/src/lib/swift-codegen/generator.js +27 -27
- package/dist/src/lib/swift-codegen/generator.js.map +1 -1
- package/dist/src/lib/toml-database-config.d.ts +14 -75
- package/dist/src/lib/toml-database-config.js +14 -305
- package/dist/src/lib/toml-database-config.js.map +1 -1
- package/dist/src/lib/workflow-toml-validator.js +15 -1
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/package.json +3 -5
- package/dist/src/lib/generated-workflow-model-fields.d.ts +0 -20
- package/dist/src/lib/generated-workflow-model-fields.js +0 -53
- package/dist/src/lib/generated-workflow-model-fields.js.map +0 -1
- package/dist/src/lib/workflow-field-descriptor.d.ts +0 -91
- package/dist/src/lib/workflow-field-descriptor.js +0 -153
- package/dist/src/lib/workflow-field-descriptor.js.map +0 -1
|
@@ -2,12 +2,9 @@ import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, rea
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { ApiClient } from "../lib/api-client.js";
|
|
4
4
|
import { getCurrentAppId, resolveAppId } from "../lib/config.js";
|
|
5
|
-
import { resolveCodegenSourceDir } from "../lib/codegen-shared/resolveCodegenSourceDir.js";
|
|
6
5
|
import { resolveSyncDir } from "../lib/sync-paths.js";
|
|
7
6
|
import { parseCsv, applyColumnMap, buildRows } from "../lib/csv.js";
|
|
8
7
|
import { chunk, parseBatchSize } from "../lib/batch.js";
|
|
9
|
-
import { generateDbTypes, } from "../lib/db-codegen/dbGenerator.js";
|
|
10
|
-
import { generateDbSwiftTypes } from "../lib/swift-codegen/dbGenerator.js";
|
|
11
8
|
import { success, error, info, warn, keyValue, result as printResult, formatTable, formatId, formatDate, json, } from "../lib/output.js";
|
|
12
9
|
import { renderAggregateResult } from "../lib/aggregate-output.js";
|
|
13
10
|
import { confirmPrompt } from "../lib/confirm-prompt.js";
|
|
@@ -53,49 +50,6 @@ function assertDataIdMatches(data, recordId, writtenId, commandPath) {
|
|
|
53
50
|
: `Pass the record id as the argument, not inside --data: ${commandPath} ${dataId} --data ...`);
|
|
54
51
|
process.exit(1);
|
|
55
52
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Validate that the named operation is an import-compatible, save-like
|
|
58
|
-
* mutation before importing (issue #145, Codex finding (a)).
|
|
59
|
-
*
|
|
60
|
-
* The importer always builds `params: { modelName, id, data }`, and the batch
|
|
61
|
-
* endpoint rejects undeclared params per item. So we require the op to be a
|
|
62
|
-
* `mutation` whose declared `params` cover `modelName`, `id`, and `data`. This
|
|
63
|
-
* surfaces a clear up-front error instead of letting every row fail
|
|
64
|
-
* server-side. Exits non-zero on any mismatch.
|
|
65
|
-
*/
|
|
66
|
-
async function validateImportOperation(client, appId, databaseId, operationName) {
|
|
67
|
-
let ops;
|
|
68
|
-
try {
|
|
69
|
-
ops = await client.listDatabaseOperations(appId, databaseId);
|
|
70
|
-
}
|
|
71
|
-
catch (err) {
|
|
72
|
-
error(`Could not list operations for this database: ${err?.message || err}`);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const op = (ops || []).find((o) => o?.name === operationName);
|
|
77
|
-
if (!op) {
|
|
78
|
-
error(`Operation "${operationName}" not found — create a mutation op (e.g. ` +
|
|
79
|
-
`\`seed_save\`) on this database type first.`);
|
|
80
|
-
process.exit(1);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if (op.type !== "mutation") {
|
|
84
|
-
error(`Operation "${operationName}" is a "${op.type}" op, not an import-compatible ` +
|
|
85
|
-
`mutation. CSV import needs a save-like mutation taking {modelName, id, data}.`);
|
|
86
|
-
process.exit(1);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const params = op.params || {};
|
|
90
|
-
const missing = ["modelName", "id", "data"].filter((p) => !(p in params));
|
|
91
|
-
if (missing.length > 0) {
|
|
92
|
-
error(`Operation "${operationName}" is not import-compatible: its params are missing ` +
|
|
93
|
-
`${missing.join(", ")}. CSV import builds {modelName, id, data} per item, so the ` +
|
|
94
|
-
`op must declare those params (like \`seed_save\`).`);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
53
|
/**
|
|
100
54
|
* The full `databases records bulk` precondition value space, audited in one
|
|
101
55
|
* pass (issue #2437, Codex cycle 3). A precondition is field-equality only, so
|
|
@@ -1158,144 +1112,6 @@ database-type-config' and the 'database-type-configs' group for inspection:
|
|
|
1158
1112
|
process.exit(1);
|
|
1159
1113
|
}
|
|
1160
1114
|
});
|
|
1161
|
-
// ---- Operations subcommand group ----
|
|
1162
|
-
const operations = databases
|
|
1163
|
-
.command("operations")
|
|
1164
|
-
.description("List and execute registered operations on a database");
|
|
1165
|
-
// List operations
|
|
1166
|
-
operations
|
|
1167
|
-
.command("list")
|
|
1168
|
-
.description("List registered operations available on a database")
|
|
1169
|
-
.argument("<database-id>", "Database ID")
|
|
1170
|
-
.option("--app <app-id>", "App ID")
|
|
1171
|
-
.option("--json", "Output as JSON")
|
|
1172
|
-
.action(async (databaseId, options) => {
|
|
1173
|
-
const resolvedAppId = resolveAppId(undefined, options);
|
|
1174
|
-
const client = new ApiClient();
|
|
1175
|
-
try {
|
|
1176
|
-
const result = await client.listDatabaseOperations(resolvedAppId, databaseId);
|
|
1177
|
-
const list = Array.isArray(result) ? result : [];
|
|
1178
|
-
if (options.json) {
|
|
1179
|
-
json(wholeListEnvelope(list));
|
|
1180
|
-
return;
|
|
1181
|
-
}
|
|
1182
|
-
if (list.length === 0) {
|
|
1183
|
-
info("No operations found.");
|
|
1184
|
-
return;
|
|
1185
|
-
}
|
|
1186
|
-
console.log(formatTable(list, [
|
|
1187
|
-
{ header: "NAME", key: "name" },
|
|
1188
|
-
{ header: "TYPE", key: "type" },
|
|
1189
|
-
{ header: "MODEL", key: "modelName" },
|
|
1190
|
-
{ header: "ACCESS", key: "access" },
|
|
1191
|
-
]));
|
|
1192
|
-
}
|
|
1193
|
-
catch (err) {
|
|
1194
|
-
error(err.message);
|
|
1195
|
-
process.exit(1);
|
|
1196
|
-
}
|
|
1197
|
-
});
|
|
1198
|
-
// Execute operation
|
|
1199
|
-
operations
|
|
1200
|
-
.command("execute")
|
|
1201
|
-
.description("Execute a registered operation on a database")
|
|
1202
|
-
.argument("<database-id>", "Database ID")
|
|
1203
|
-
.argument("<operation-name>", "Operation name")
|
|
1204
|
-
.option("--params <json>", "Operation parameters as JSON")
|
|
1205
|
-
.option("--limit <n>", "Max records to return (for queries)")
|
|
1206
|
-
.option("--cursor <cursor>", "Pagination cursor")
|
|
1207
|
-
.option("--app <app-id>", "App ID")
|
|
1208
|
-
.option("--json", "Output as JSON")
|
|
1209
|
-
.option("--timing", "Show server-side timing breakdown")
|
|
1210
|
-
.action(async (databaseId, operationName, options) => {
|
|
1211
|
-
const resolvedAppId = resolveAppId(undefined, options);
|
|
1212
|
-
const client = new ApiClient();
|
|
1213
|
-
let params;
|
|
1214
|
-
if (options.params) {
|
|
1215
|
-
try {
|
|
1216
|
-
params = JSON.parse(options.params);
|
|
1217
|
-
}
|
|
1218
|
-
catch {
|
|
1219
|
-
error("Invalid --params JSON.");
|
|
1220
|
-
process.exit(1);
|
|
1221
|
-
return;
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
const body = {};
|
|
1225
|
-
if (params)
|
|
1226
|
-
body.params = params;
|
|
1227
|
-
if (options.limit)
|
|
1228
|
-
body.limit = parseInt(options.limit, 10);
|
|
1229
|
-
if (options.cursor)
|
|
1230
|
-
body.cursor = options.cursor;
|
|
1231
|
-
try {
|
|
1232
|
-
const result = await client.executeDatabaseOperation(resolvedAppId, databaseId, operationName, body, { timing: !!options.timing });
|
|
1233
|
-
if (options.json) {
|
|
1234
|
-
json(result);
|
|
1235
|
-
return;
|
|
1236
|
-
}
|
|
1237
|
-
// Smart display based on result shape. `executeDatabaseOperation()`
|
|
1238
|
-
// has already normalized a query page to `{ items, hasMore,
|
|
1239
|
-
// nextCursor? }` (#2440), so `items` is exactly "this result was a
|
|
1240
|
-
// list" — the two branches cannot disagree about it.
|
|
1241
|
-
if (Array.isArray(result.items)) {
|
|
1242
|
-
if (result.items.length === 0) {
|
|
1243
|
-
info("No records found.");
|
|
1244
|
-
}
|
|
1245
|
-
else {
|
|
1246
|
-
const cols = Object.keys(result.items[0]).filter((k) => k !== "type");
|
|
1247
|
-
console.log(formatTable(result.items, cols.map((c) => ({
|
|
1248
|
-
header: c.toUpperCase(),
|
|
1249
|
-
key: c,
|
|
1250
|
-
format: (v) => {
|
|
1251
|
-
if (v === null || v === undefined)
|
|
1252
|
-
return "—";
|
|
1253
|
-
if (typeof v === "object") {
|
|
1254
|
-
const s = JSON.stringify(v);
|
|
1255
|
-
return s.length > 50 ? s.slice(0, 47) + "..." : s;
|
|
1256
|
-
}
|
|
1257
|
-
const s = String(v);
|
|
1258
|
-
return s.length > 50 ? s.slice(0, 47) + "..." : s;
|
|
1259
|
-
},
|
|
1260
|
-
}))));
|
|
1261
|
-
}
|
|
1262
|
-
// Gate on the cursor itself, the way `records query` does: `hasMore`
|
|
1263
|
-
// without a cursor used to print the literal `undefined` (#2440).
|
|
1264
|
-
if (result.nextCursor) {
|
|
1265
|
-
info(`More records available. Use --cursor ${result.nextCursor} to continue.`);
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
else if (result.count !== undefined) {
|
|
1269
|
-
console.log(`Count: ${result.count}`);
|
|
1270
|
-
}
|
|
1271
|
-
else if (result.result !== undefined) {
|
|
1272
|
-
// Aggregate result
|
|
1273
|
-
json(result);
|
|
1274
|
-
}
|
|
1275
|
-
else if (result.results && Array.isArray(result.results)) {
|
|
1276
|
-
// Batch/mutation results
|
|
1277
|
-
success(`Operation executed (${result.results.length} result${result.results.length === 1 ? "" : "s"}).`);
|
|
1278
|
-
}
|
|
1279
|
-
else if (result.success !== undefined) {
|
|
1280
|
-
success("Operation executed.");
|
|
1281
|
-
}
|
|
1282
|
-
else {
|
|
1283
|
-
json(result);
|
|
1284
|
-
}
|
|
1285
|
-
// Display timing if present
|
|
1286
|
-
if (result._timing) {
|
|
1287
|
-
console.log("");
|
|
1288
|
-
console.log("Timing:");
|
|
1289
|
-
for (const [key, value] of Object.entries(result._timing)) {
|
|
1290
|
-
console.log(` ${key}: ${value}ms`);
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
catch (err) {
|
|
1295
|
-
error(err.message);
|
|
1296
|
-
process.exit(1);
|
|
1297
|
-
}
|
|
1298
|
-
});
|
|
1299
1115
|
// ---- Indexes subcommand group ----
|
|
1300
1116
|
const indexes = databases
|
|
1301
1117
|
.command("indexes")
|
|
@@ -1616,11 +1432,10 @@ database-type-config' and the 'database-type-configs' group for inspection:
|
|
|
1616
1432
|
// ============================================
|
|
1617
1433
|
databases
|
|
1618
1434
|
.command("import-csv")
|
|
1619
|
-
.description("Import rows from a CSV file into a database
|
|
1435
|
+
.description("Import rows from a CSV file into a database: one save per row through records/batch, best-effort (a row the database refuses is counted as failed and the rest are written)")
|
|
1620
1436
|
.argument("<database-id>", "Database ID")
|
|
1621
1437
|
.argument("<file>", "Path to the CSV file")
|
|
1622
|
-
.requiredOption("--model <name>", "Model
|
|
1623
|
-
.option("--operation <op>", "Registered mutation operation to use (must be a {modelName,id,data} save-like op)", "seed_save")
|
|
1438
|
+
.requiredOption("--model <name>", "Model every row is saved into")
|
|
1624
1439
|
.option("--column-map <json>", 'Map CSV headers to field names, e.g. \'{"CSV Header":"fieldName"}\'')
|
|
1625
1440
|
.option("--types <json>", 'Coerce fields by type, e.g. \'{"price":"number","active":"boolean"}\'')
|
|
1626
1441
|
.option("--id-column <col>", "Use this CSV column for record ids (else a ULID is generated)")
|
|
@@ -1656,11 +1471,11 @@ database-type-config' and the 'database-type-configs' group for inspection:
|
|
|
1656
1471
|
return;
|
|
1657
1472
|
}
|
|
1658
1473
|
}
|
|
1659
|
-
//
|
|
1660
|
-
//
|
|
1474
|
+
// `records/batch` has no fixed server item cap — bound it with the same
|
|
1475
|
+
// conservative client-side ceiling `databases import` uses.
|
|
1661
1476
|
const parsedBatchSize = parseBatchSize(options.batchSize, {
|
|
1662
|
-
max:
|
|
1663
|
-
overMaxMessage:
|
|
1477
|
+
max: IMPORT_BATCH_SIZE_CEILING,
|
|
1478
|
+
overMaxMessage: `--batch-size cannot exceed ${IMPORT_BATCH_SIZE_CEILING} (a conservative client-side limit for batch write requests).`,
|
|
1664
1479
|
});
|
|
1665
1480
|
if ("error" in parsedBatchSize) {
|
|
1666
1481
|
error(parsedBatchSize.error);
|
|
@@ -1679,21 +1494,14 @@ database-type-config' and the 'database-type-configs' group for inspection:
|
|
|
1679
1494
|
let rows = parseCsv(raw, options.delimiter);
|
|
1680
1495
|
// 2. Apply column map.
|
|
1681
1496
|
rows = applyColumnMap(rows, columnMap);
|
|
1682
|
-
// 3. Coerce types, assign ids, shape into batch
|
|
1497
|
+
// 3. Coerce types, assign ids, shape into records/batch saves.
|
|
1683
1498
|
const { batch, rowCount } = buildRows(rows, {
|
|
1684
1499
|
modelName: options.model,
|
|
1685
1500
|
types,
|
|
1686
1501
|
idColumn: options.idColumn,
|
|
1687
1502
|
});
|
|
1688
1503
|
const chunks = chunk(batch, batchSize);
|
|
1689
|
-
// 4.
|
|
1690
|
-
// BEFORE writing (op-not-found / incompatible → clear up-front error
|
|
1691
|
-
// rather than per-row server-side failure). Skip the round-trip only
|
|
1692
|
-
// when there is genuinely nothing to import in a dry run.
|
|
1693
|
-
if (!options.dryRun || rowCount > 0) {
|
|
1694
|
-
await validateImportOperation(client, resolvedAppId, databaseId, options.operation);
|
|
1695
|
-
}
|
|
1696
|
-
// 5. Empty / headers-only CSV: no request, report 0/0.
|
|
1504
|
+
// 4. Empty / headers-only CSV: no request, report 0/0.
|
|
1697
1505
|
if (rowCount === 0) {
|
|
1698
1506
|
if (options.json) {
|
|
1699
1507
|
json({ imported: 0, failed: 0, rows: 0, batches: 0, errors: [], dryRun: !!options.dryRun });
|
|
@@ -1704,7 +1512,7 @@ database-type-config' and the 'database-type-configs' group for inspection:
|
|
|
1704
1512
|
}
|
|
1705
1513
|
return;
|
|
1706
1514
|
}
|
|
1707
|
-
//
|
|
1515
|
+
// 5. Dry run: report counts, no writes.
|
|
1708
1516
|
if (options.dryRun) {
|
|
1709
1517
|
if (options.json) {
|
|
1710
1518
|
json({ dryRun: true, rows: rowCount, batches: chunks.length, batchSize });
|
|
@@ -1717,32 +1525,60 @@ database-type-config' and the 'database-type-configs' group for inspection:
|
|
|
1717
1525
|
}
|
|
1718
1526
|
return;
|
|
1719
1527
|
}
|
|
1720
|
-
//
|
|
1528
|
+
// 6. Import chunk by chunk through records/batch, non-atomic (the
|
|
1529
|
+
// route's default): each row's outcome is its own, so a refused row
|
|
1530
|
+
// is counted failed and its siblings commit.
|
|
1721
1531
|
let imported = 0;
|
|
1722
1532
|
let failed = 0;
|
|
1723
1533
|
const errors = [];
|
|
1724
1534
|
let aborted = false;
|
|
1725
1535
|
for (let i = 0; i < chunks.length; i++) {
|
|
1726
1536
|
const piece = chunks[i];
|
|
1537
|
+
let chunkFailed = false;
|
|
1727
1538
|
try {
|
|
1728
|
-
const res = await client.
|
|
1729
|
-
|
|
1730
|
-
|
|
1539
|
+
const res = await client.batchDatabaseRecords(resolvedAppId, databaseId, piece);
|
|
1540
|
+
const results = res?.results ?? [];
|
|
1541
|
+
let chunkImported = 0;
|
|
1542
|
+
let chunkRefused = 0;
|
|
1543
|
+
const firstErrors = [];
|
|
1544
|
+
for (let j = 0; j < piece.length; j++) {
|
|
1545
|
+
const r = results[j];
|
|
1546
|
+
if (r && r.success) {
|
|
1547
|
+
chunkImported++;
|
|
1548
|
+
}
|
|
1549
|
+
else {
|
|
1550
|
+
chunkRefused++;
|
|
1551
|
+
if (r?.error && firstErrors.length < 3)
|
|
1552
|
+
firstErrors.push(r.error);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
imported += chunkImported;
|
|
1556
|
+
failed += chunkRefused;
|
|
1557
|
+
if (chunkRefused > 0) {
|
|
1558
|
+
chunkFailed = true;
|
|
1559
|
+
errors.push({
|
|
1560
|
+
batch: i,
|
|
1561
|
+
error: `${chunkRefused} row(s) failed${firstErrors.length ? `: ${firstErrors.join("; ")}` : ""}`,
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1731
1564
|
if (!options.json) {
|
|
1732
|
-
info(`Batch ${i + 1}/${chunks.length}: imported ${
|
|
1565
|
+
info(`Batch ${i + 1}/${chunks.length}: imported ${chunkImported}, failed ${chunkRefused}`);
|
|
1733
1566
|
}
|
|
1734
1567
|
}
|
|
1735
1568
|
catch (err) {
|
|
1569
|
+
// A chunk-level failure (a 400 for a malformed op): the whole
|
|
1570
|
+
// chunk was refused, so every row in it counts as failed.
|
|
1736
1571
|
const msg = err?.message || String(err);
|
|
1572
|
+
chunkFailed = true;
|
|
1737
1573
|
errors.push({ batch: i, error: msg });
|
|
1738
1574
|
failed += piece.length;
|
|
1739
1575
|
if (!options.json) {
|
|
1740
1576
|
warn(`Batch ${i + 1}/${chunks.length} failed: ${msg}`);
|
|
1741
1577
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1578
|
+
}
|
|
1579
|
+
if (chunkFailed && options.stopOnError) {
|
|
1580
|
+
aborted = true;
|
|
1581
|
+
break;
|
|
1746
1582
|
}
|
|
1747
1583
|
}
|
|
1748
1584
|
if (options.json) {
|
|
@@ -1760,7 +1596,7 @@ database-type-config' and the 'database-type-configs' group for inspection:
|
|
|
1760
1596
|
}
|
|
1761
1597
|
}
|
|
1762
1598
|
}
|
|
1763
|
-
// Non-zero exit if any chunk
|
|
1599
|
+
// Non-zero exit if any row or chunk failed.
|
|
1764
1600
|
if (errors.length > 0) {
|
|
1765
1601
|
process.exit(1);
|
|
1766
1602
|
}
|
|
@@ -1778,7 +1614,7 @@ database-type-config' and the 'database-type-configs' group for inspection:
|
|
|
1778
1614
|
.description("Manage the TOML schema attached to a database type");
|
|
1779
1615
|
schema
|
|
1780
1616
|
.command("generate")
|
|
1781
|
-
.description("Scaffold a starting TOML schema for <database-type> from
|
|
1617
|
+
.description("Scaffold a starting TOML schema for <database-type> from the models and fields its databases hold, and append it to the local database-type-configs/<type>.toml file.")
|
|
1782
1618
|
.argument("<database-type>", "Database type name")
|
|
1783
1619
|
.option("-f, --force", "Overwrite an existing [models.*] block in the local file")
|
|
1784
1620
|
.option("--json", "Output the generated TOML as JSON")
|
|
@@ -1817,10 +1653,10 @@ database-type-config' and the 'database-type-configs' group for inspection:
|
|
|
1817
1653
|
console.log(scaffoldedToml);
|
|
1818
1654
|
return;
|
|
1819
1655
|
}
|
|
1820
|
-
// 3. For each candidate,
|
|
1821
|
-
//
|
|
1822
|
-
//
|
|
1823
|
-
//
|
|
1656
|
+
// 3. For each candidate, append the scaffolded TOML at the end of the
|
|
1657
|
+
// file. Per the plan-of-record, textual insertion is deliberate —
|
|
1658
|
+
// smol-toml parse/stringify drops comments, so we splice as a raw
|
|
1659
|
+
// string.
|
|
1824
1660
|
for (const targetPath of candidateFiles) {
|
|
1825
1661
|
const original = readFileSync(targetPath, "utf-8");
|
|
1826
1662
|
// Detect existing [models.*] block. If present, refuse unless --force.
|
|
@@ -1829,26 +1665,9 @@ database-type-config' and the 'database-type-configs' group for inspection:
|
|
|
1829
1665
|
error(`${targetPath} already has [models.*] blocks. Re-run with --force to overwrite.`);
|
|
1830
1666
|
process.exit(1);
|
|
1831
1667
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
cleaned = stripModelsBlocks(original);
|
|
1836
|
-
}
|
|
1837
|
-
// Splice: insert before the first [[operations]] block, otherwise
|
|
1838
|
-
// at the end of file.
|
|
1839
|
-
let merged;
|
|
1840
|
-
const opsAnchor = cleaned.indexOf("\n[[operations]]");
|
|
1841
|
-
if (opsAnchor >= 0) {
|
|
1842
|
-
merged =
|
|
1843
|
-
cleaned.slice(0, opsAnchor + 1) +
|
|
1844
|
-
scaffoldedToml +
|
|
1845
|
-
"\n" +
|
|
1846
|
-
cleaned.slice(opsAnchor + 1);
|
|
1847
|
-
}
|
|
1848
|
-
else {
|
|
1849
|
-
const sep = cleaned.endsWith("\n") ? "" : "\n";
|
|
1850
|
-
merged = cleaned + sep + scaffoldedToml + "\n";
|
|
1851
|
-
}
|
|
1668
|
+
const merged = spliceScaffoldedSchema(original, scaffoldedToml, {
|
|
1669
|
+
force: !!options.force,
|
|
1670
|
+
});
|
|
1852
1671
|
writeFileSync(targetPath, merged);
|
|
1853
1672
|
success(`Inserted schema into ${targetPath}`);
|
|
1854
1673
|
}
|
|
@@ -1859,137 +1678,18 @@ database-type-config' and the 'database-type-configs' group for inspection:
|
|
|
1859
1678
|
process.exit(1);
|
|
1860
1679
|
}
|
|
1861
1680
|
});
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
try {
|
|
1875
|
-
// 0. Validate --lang up front so an unknown value fails clearly rather
|
|
1876
|
-
// than silently defaulting to the TS emitter.
|
|
1877
|
-
const lang = String(options.lang ?? "ts").toLowerCase();
|
|
1878
|
-
if (lang !== "ts" && lang !== "swift") {
|
|
1879
|
-
error(`Unknown --lang "${options.lang}". Use "ts" or "swift".`);
|
|
1880
|
-
process.exit(1);
|
|
1881
|
-
}
|
|
1882
|
-
// 1. The single source database-type-configs/ directory: the
|
|
1883
|
-
// environment's, and only ever the environment's (#1510, #3154).
|
|
1884
|
-
const dbTypesSourceDir = resolveCodegenSourceDir({
|
|
1885
|
-
subdir: "database-type-configs",
|
|
1886
|
-
});
|
|
1887
|
-
// 2. Collect the source .toml files (one per database type), filtered
|
|
1888
|
-
// to a single type when an argument is given.
|
|
1889
|
-
const inputs = [];
|
|
1890
|
-
for (const fileName of readdirSync(dbTypesSourceDir)) {
|
|
1891
|
-
if (!fileName.endsWith(".toml"))
|
|
1892
|
-
continue;
|
|
1893
|
-
const typeName = fileName.slice(0, -".toml".length);
|
|
1894
|
-
if (databaseType && typeName !== databaseType)
|
|
1895
|
-
continue;
|
|
1896
|
-
const tomlPath = path.join(dbTypesSourceDir, fileName);
|
|
1897
|
-
inputs.push({
|
|
1898
|
-
databaseType: typeName,
|
|
1899
|
-
tomlPath,
|
|
1900
|
-
tomlContent: readFileSync(tomlPath, "utf-8"),
|
|
1901
|
-
});
|
|
1902
|
-
}
|
|
1903
|
-
if (inputs.length === 0) {
|
|
1904
|
-
error(databaseType
|
|
1905
|
-
? `No database-type-configs/${databaseType}.toml found under primitive/<env>/.`
|
|
1906
|
-
: "No database-type-configs/*.toml files found to generate from.");
|
|
1907
|
-
process.exit(1);
|
|
1908
|
-
}
|
|
1909
|
-
// 3. Resolve the output directory. Default: a `generated/` subdir of
|
|
1910
|
-
// the first source dir (keeps generated output beside the schema).
|
|
1911
|
-
const outputDir = options.output
|
|
1912
|
-
? path.resolve(options.output)
|
|
1913
|
-
: path.join(path.dirname(inputs[0].tomlPath), "generated");
|
|
1914
|
-
// 4. Run codegen (or --check) with the selected emitter. Both the TS
|
|
1915
|
-
// and Swift generators return the same WriteOrCheckResult shape, so
|
|
1916
|
-
// the reporting below is emitter-agnostic; only the emitter (and the
|
|
1917
|
-
// regenerate hint) differs. When a single database-type filter
|
|
1918
|
-
// argument is given, the input set is partial — flag it so stale
|
|
1919
|
-
// cleanup is scoped to that type and sibling types' generated files
|
|
1920
|
-
// are left intact.
|
|
1921
|
-
const codegenResult = lang === "swift"
|
|
1922
|
-
? await generateDbSwiftTypes({
|
|
1923
|
-
inputs,
|
|
1924
|
-
outputDir,
|
|
1925
|
-
check: !!options.check,
|
|
1926
|
-
singleType: !!databaseType,
|
|
1927
|
-
})
|
|
1928
|
-
: await generateDbTypes({
|
|
1929
|
-
inputs,
|
|
1930
|
-
outputDir,
|
|
1931
|
-
check: !!options.check,
|
|
1932
|
-
singleType: !!databaseType,
|
|
1933
|
-
});
|
|
1934
|
-
if (options.check) {
|
|
1935
|
-
if (codegenResult.mismatches.length > 0) {
|
|
1936
|
-
if (options.json) {
|
|
1937
|
-
json({ ok: false, mismatches: codegenResult.mismatches });
|
|
1938
|
-
}
|
|
1939
|
-
else {
|
|
1940
|
-
error(`Check failed: ${codegenResult.mismatches.length} file(s) out of date.`);
|
|
1941
|
-
for (const m of codegenResult.mismatches) {
|
|
1942
|
-
error(` ${m.reason}: ${path.relative(process.cwd(), m.filePath)}`);
|
|
1943
|
-
}
|
|
1944
|
-
// Echo back the options that decide WHERE and WHAT was
|
|
1945
|
-
// checked, the way `workflows codegen` does (#2895). Suggesting
|
|
1946
|
-
// a bare `databases codegen` after a `--check -o <dir>` sent
|
|
1947
|
-
// people to regenerate into the default
|
|
1948
|
-
// <config-dir>/database-type-configs/generated/ instead of the
|
|
1949
|
-
// directory that is actually out of date, so the files stayed
|
|
1950
|
-
// stale and the next check failed the same way (#2911).
|
|
1951
|
-
const regenerate = ["primitive databases codegen"];
|
|
1952
|
-
if (databaseType)
|
|
1953
|
-
regenerate.push(databaseType);
|
|
1954
|
-
if (lang === "swift")
|
|
1955
|
-
regenerate.push("--lang swift");
|
|
1956
|
-
if (options.output)
|
|
1957
|
-
regenerate.push(`-o ${options.output}`);
|
|
1958
|
-
info(`Run \`${regenerate.join(" ")}\` to regenerate.`);
|
|
1959
|
-
}
|
|
1960
|
-
process.exit(1);
|
|
1961
|
-
}
|
|
1962
|
-
if (options.json) {
|
|
1963
|
-
json({ ok: true, checked: codegenResult.writtenFiles.length });
|
|
1964
|
-
}
|
|
1965
|
-
else {
|
|
1966
|
-
success(`Check passed: ${codegenResult.writtenFiles.length} file(s) up to date.`);
|
|
1967
|
-
}
|
|
1968
|
-
return;
|
|
1969
|
-
}
|
|
1970
|
-
if (options.json) {
|
|
1971
|
-
json({
|
|
1972
|
-
written: codegenResult.writtenFiles,
|
|
1973
|
-
deleted: codegenResult.deletedFiles,
|
|
1974
|
-
});
|
|
1975
|
-
return;
|
|
1976
|
-
}
|
|
1977
|
-
success(`Generated ${codegenResult.writtenFiles.length} file(s)` +
|
|
1978
|
-
(codegenResult.deletedFiles.length > 0
|
|
1979
|
-
? `, deleted ${codegenResult.deletedFiles.length} stale file(s).`
|
|
1980
|
-
: "."));
|
|
1981
|
-
for (const f of codegenResult.writtenFiles) {
|
|
1982
|
-
keyValue(" wrote", path.relative(process.cwd(), f));
|
|
1983
|
-
}
|
|
1984
|
-
for (const f of codegenResult.deletedFiles) {
|
|
1985
|
-
keyValue(" deleted", path.relative(process.cwd(), f));
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
catch (err) {
|
|
1989
|
-
error(err.message);
|
|
1990
|
-
process.exit(1);
|
|
1991
|
-
}
|
|
1992
|
-
});
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Append a scaffolded `[models.*]` schema to a database type file's text,
|
|
1684
|
+
* first stripping the file's existing `[models.*]` blocks when `force` is set.
|
|
1685
|
+
* Appended at the end of the file: #3189 retired the `[[operations]]` blocks
|
|
1686
|
+
* the scaffold used to be inserted ahead of.
|
|
1687
|
+
*/
|
|
1688
|
+
export function spliceScaffoldedSchema(original, scaffoldedToml, options = {}) {
|
|
1689
|
+
const hasModelsBlock = /\n\[models\./.test("\n" + original);
|
|
1690
|
+
const cleaned = hasModelsBlock && options.force ? stripModelsBlocks(original) : original;
|
|
1691
|
+
const sep = cleaned.endsWith("\n") ? "" : "\n";
|
|
1692
|
+
return cleaned + sep + scaffoldedToml + "\n";
|
|
1993
1693
|
}
|
|
1994
1694
|
/** Remove every existing `[models.*]` block from a TOML file body. */
|
|
1995
1695
|
function stripModelsBlocks(text) {
|