zinkee 0.1.32 → 0.1.34
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/README.md +9 -3
- package/dist/index.js +197 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/api/records.ts +21 -11
- package/src/commands/records.test.ts +236 -10
- package/src/commands/records.ts +169 -22
- package/src/utils/examples.ts +60 -25
package/README.md
CHANGED
|
@@ -279,13 +279,16 @@ Notes:
|
|
|
279
279
|
### CRUD operations
|
|
280
280
|
|
|
281
281
|
```bash
|
|
282
|
-
# Create
|
|
282
|
+
# Create a single record (--set/--set-json/--raw/--raw-file build one record)
|
|
283
283
|
zinkee records create projects \
|
|
284
284
|
--set name="Website Redesign" \
|
|
285
285
|
--set budget=25000 \
|
|
286
286
|
--set start-date="2026-01-15T00:00:00.000Z" \
|
|
287
287
|
--set status=1
|
|
288
288
|
|
|
289
|
+
# Create many records from a JSON array file (one object per record; mutually exclusive with the single-record flags)
|
|
290
|
+
zinkee --json records create projects --records-file ./projects.json
|
|
291
|
+
|
|
289
292
|
# List and query
|
|
290
293
|
zinkee --json records list projects --limit 50
|
|
291
294
|
zinkee --json records query projects --limit 10 --sort "_id:desc"
|
|
@@ -299,8 +302,11 @@ zinkee --json records get projects <record-uuid>
|
|
|
299
302
|
# Update
|
|
300
303
|
zinkee records update projects <record-uuid> --set status=2
|
|
301
304
|
|
|
302
|
-
# Delete
|
|
303
|
-
zinkee records delete projects <record-uuid>
|
|
305
|
+
# Delete one or more records (--record is repeatable)
|
|
306
|
+
zinkee records delete projects --record <record-uuid> --record <record-uuid>
|
|
307
|
+
|
|
308
|
+
# Delete many records listing one uuid per line in a file (merged with --record, de-duplicated)
|
|
309
|
+
zinkee records delete projects --records-file ./record-ids.txt
|
|
304
310
|
|
|
305
311
|
# Clear all records from a schema (destructive — irreversible)
|
|
306
312
|
zinkee records clear projects
|
package/dist/index.js
CHANGED
|
@@ -2832,35 +2832,55 @@ var specificExamples = {
|
|
|
2832
2832
|
]),
|
|
2833
2833
|
"records create": buildCommandExamples([
|
|
2834
2834
|
{
|
|
2835
|
-
description: "
|
|
2835
|
+
description: "Create a single record with --set/--set-json (the single-record flags build one record, sent as a one-element 'records' array).",
|
|
2836
2836
|
command: `zinkee --json records create contacts --set contact-name="Ada Lovelace" --set contact-email=ada@example.com`,
|
|
2837
2837
|
raw: `{
|
|
2838
|
-
"
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2838
|
+
"records": [
|
|
2839
|
+
{
|
|
2840
|
+
"contact-name": "Ada Lovelace",
|
|
2841
|
+
"contact-email": "ada@example.com"
|
|
2842
|
+
}
|
|
2843
|
+
]
|
|
2842
2844
|
}`,
|
|
2843
2845
|
response: `{
|
|
2844
|
-
"
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
}
|
|
2846
|
+
"created": [
|
|
2847
|
+
"550e8400-e29b-41d4-a716-446655440000"
|
|
2848
|
+
],
|
|
2849
|
+
"errors": []
|
|
2849
2850
|
}`
|
|
2850
2851
|
},
|
|
2851
2852
|
{
|
|
2852
|
-
description: "Create a
|
|
2853
|
-
command: `zinkee --json records create contacts --
|
|
2853
|
+
description: "Create many records from a JSON array file (one object per record, slug -> value). Best-effort: valid records are created and invalid ones are reported per index in 'errors'. ReferenceField values must match the target's current targetDisplayField.",
|
|
2854
|
+
command: `zinkee --json records create contacts --records-file ./records.json`,
|
|
2854
2855
|
raw: `{
|
|
2855
|
-
"
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2856
|
+
"records": [
|
|
2857
|
+
{
|
|
2858
|
+
"contact-name": "Ada Lovelace",
|
|
2859
|
+
"contact-email": "ada@example.com"
|
|
2860
|
+
},
|
|
2861
|
+
{
|
|
2862
|
+
"contact-name": "Grace Hopper",
|
|
2863
|
+
"contact-email": "grace@example.com",
|
|
2864
|
+
"status": 2,
|
|
2865
|
+
"amount": 1999.99,
|
|
2866
|
+
"due-date": "2026-06-01T09:00:00Z",
|
|
2867
|
+
"customer-ref": "8ad602a1-f6e3-43f6-9ab2-f1c5afc99621",
|
|
2868
|
+
"owner": "6c8a3e0d-4e5f-4a1b-9c2d-3e4f5a6b7c8d"
|
|
2869
|
+
}
|
|
2870
|
+
]
|
|
2871
|
+
}`,
|
|
2872
|
+
response: `{
|
|
2873
|
+
"created": [
|
|
2874
|
+
"550e8400-e29b-41d4-a716-446655440000"
|
|
2875
|
+
],
|
|
2876
|
+
"errors": [
|
|
2877
|
+
{
|
|
2878
|
+
"index": 1,
|
|
2879
|
+
"reason": "record_field_not_found",
|
|
2880
|
+
"message": "Field 'contact-emial' was not found in schema '2eb8bdcf-6d9c-4fbb-9018-7fa8180f4c5e'.",
|
|
2881
|
+
"action": "Use a valid field slug from the schema and retry."
|
|
2882
|
+
}
|
|
2883
|
+
]
|
|
2864
2884
|
}`
|
|
2865
2885
|
}
|
|
2866
2886
|
]),
|
|
@@ -4533,10 +4553,24 @@ var specificExamples = {
|
|
|
4533
4553
|
]),
|
|
4534
4554
|
"records delete": buildCommandExamples([
|
|
4535
4555
|
{
|
|
4536
|
-
description: "Delete
|
|
4537
|
-
command: `zinkee --json records delete contacts
|
|
4556
|
+
description: "Delete one or more records by id (--record is repeatable).",
|
|
4557
|
+
command: `zinkee --json records delete contacts --record 550e8400-e29b-41d4-a716-446655440000 --record 6c8a3e0d-4e5f-4a1b-9c2d-3e4f5a6b7c8d`,
|
|
4538
4558
|
response: `{
|
|
4539
|
-
"
|
|
4559
|
+
"recordIds": [
|
|
4560
|
+
"550e8400-e29b-41d4-a716-446655440000",
|
|
4561
|
+
"6c8a3e0d-4e5f-4a1b-9c2d-3e4f5a6b7c8d"
|
|
4562
|
+
],
|
|
4563
|
+
"deleted": true
|
|
4564
|
+
}`
|
|
4565
|
+
},
|
|
4566
|
+
{
|
|
4567
|
+
description: "Delete many records listing one uuid per line in a file (merged with any --record flags and de-duplicated).",
|
|
4568
|
+
command: `zinkee --json records delete contacts --records-file ./record-ids.txt`,
|
|
4569
|
+
response: `{
|
|
4570
|
+
"recordIds": [
|
|
4571
|
+
"550e8400-e29b-41d4-a716-446655440000",
|
|
4572
|
+
"6c8a3e0d-4e5f-4a1b-9c2d-3e4f5a6b7c8d"
|
|
4573
|
+
],
|
|
4540
4574
|
"deleted": true
|
|
4541
4575
|
}`
|
|
4542
4576
|
}
|
|
@@ -10721,10 +10755,10 @@ var RecordsApi = class {
|
|
|
10721
10755
|
`/api/v2/schemas/${selectorValue8(schema)}/records/${selectorValue8(record)}`
|
|
10722
10756
|
);
|
|
10723
10757
|
}
|
|
10724
|
-
|
|
10758
|
+
createRecords(schema, records) {
|
|
10725
10759
|
return this.client.requestJson(
|
|
10726
10760
|
`/api/v2/schemas/${selectorValue8(schema)}/records`,
|
|
10727
|
-
jsonOptions6(
|
|
10761
|
+
jsonOptions6({ records }, "POST")
|
|
10728
10762
|
);
|
|
10729
10763
|
}
|
|
10730
10764
|
updateRecord(schema, record, input) {
|
|
@@ -10733,10 +10767,10 @@ var RecordsApi = class {
|
|
|
10733
10767
|
jsonOptions6(input, "PATCH")
|
|
10734
10768
|
);
|
|
10735
10769
|
}
|
|
10736
|
-
|
|
10770
|
+
deleteRecords(schema, recordIds) {
|
|
10737
10771
|
return this.client.requestJson(
|
|
10738
|
-
`/api/v2/schemas/${selectorValue8(schema)}/records
|
|
10739
|
-
{
|
|
10772
|
+
`/api/v2/schemas/${selectorValue8(schema)}/records:delete`,
|
|
10773
|
+
jsonOptions6({ recordIds }, "POST")
|
|
10740
10774
|
);
|
|
10741
10775
|
}
|
|
10742
10776
|
clearRecords(schema) {
|
|
@@ -10960,6 +10994,61 @@ function parseJsonKeyValueAssignment(input) {
|
|
|
10960
10994
|
function dedupeStrings(values) {
|
|
10961
10995
|
return [...new Set(values)];
|
|
10962
10996
|
}
|
|
10997
|
+
function toRecordUuid(value) {
|
|
10998
|
+
try {
|
|
10999
|
+
const normalized = normalizeUuidOrSlug(value);
|
|
11000
|
+
return normalized.kind === "uuid" ? normalized.value : null;
|
|
11001
|
+
} catch {
|
|
11002
|
+
return null;
|
|
11003
|
+
}
|
|
11004
|
+
}
|
|
11005
|
+
function collectRecordIds(value, previous = []) {
|
|
11006
|
+
const uuid = toRecordUuid(value.trim());
|
|
11007
|
+
if (!uuid) {
|
|
11008
|
+
throw new InvalidArgumentError8(`Expected a record UUID, received "${value}".`);
|
|
11009
|
+
}
|
|
11010
|
+
return [...previous, uuid];
|
|
11011
|
+
}
|
|
11012
|
+
function readRecordIdsFromFile(path) {
|
|
11013
|
+
let contents;
|
|
11014
|
+
try {
|
|
11015
|
+
contents = readFileSync8(path, "utf8");
|
|
11016
|
+
} catch (error) {
|
|
11017
|
+
const message = error instanceof Error ? error.message : "Unable to read file.";
|
|
11018
|
+
throw new CliError(
|
|
11019
|
+
"invalid_cli_usage",
|
|
11020
|
+
`Failed to read record ids from ${path}: ${message}`
|
|
11021
|
+
);
|
|
11022
|
+
}
|
|
11023
|
+
const ids = [];
|
|
11024
|
+
contents.split(/\r?\n/).forEach((line, index) => {
|
|
11025
|
+
const value = line.trim();
|
|
11026
|
+
if (!value) {
|
|
11027
|
+
return;
|
|
11028
|
+
}
|
|
11029
|
+
const uuid = toRecordUuid(value);
|
|
11030
|
+
if (!uuid) {
|
|
11031
|
+
throw new CliError(
|
|
11032
|
+
"invalid_cli_usage",
|
|
11033
|
+
`Invalid record UUID "${value}" at ${path}:${index + 1}.`
|
|
11034
|
+
);
|
|
11035
|
+
}
|
|
11036
|
+
ids.push(uuid);
|
|
11037
|
+
});
|
|
11038
|
+
return ids;
|
|
11039
|
+
}
|
|
11040
|
+
function resolveDeleteRecordIds(options) {
|
|
11041
|
+
const flagIds = options.record ?? [];
|
|
11042
|
+
const fileIds = options.recordsFile ? readRecordIdsFromFile(options.recordsFile) : [];
|
|
11043
|
+
const recordIds = dedupeStrings([...flagIds, ...fileIds]);
|
|
11044
|
+
if (recordIds.length === 0) {
|
|
11045
|
+
throw new CliError(
|
|
11046
|
+
"invalid_cli_usage",
|
|
11047
|
+
'Provide at least one record id via "--record <uuid>" (repeatable) or "--records-file <path>".'
|
|
11048
|
+
);
|
|
11049
|
+
}
|
|
11050
|
+
return recordIds;
|
|
11051
|
+
}
|
|
10963
11052
|
function collectStringValues(value, previous = []) {
|
|
10964
11053
|
return [...previous, value];
|
|
10965
11054
|
}
|
|
@@ -11071,7 +11160,60 @@ function buildRecordQueryInput(options, allowRaw) {
|
|
|
11071
11160
|
}
|
|
11072
11161
|
return input;
|
|
11073
11162
|
}
|
|
11074
|
-
function
|
|
11163
|
+
function readRecordsFromFile(path) {
|
|
11164
|
+
let contents;
|
|
11165
|
+
try {
|
|
11166
|
+
contents = readFileSync8(path, "utf8");
|
|
11167
|
+
} catch (error) {
|
|
11168
|
+
const message = error instanceof Error ? error.message : "Unable to read file.";
|
|
11169
|
+
throw new CliError(
|
|
11170
|
+
"invalid_cli_usage",
|
|
11171
|
+
`Failed to read records from ${path}: ${message}`
|
|
11172
|
+
);
|
|
11173
|
+
}
|
|
11174
|
+
let parsed;
|
|
11175
|
+
try {
|
|
11176
|
+
parsed = JSON.parse(contents);
|
|
11177
|
+
} catch (error) {
|
|
11178
|
+
const message = error instanceof Error ? error.message : "Invalid JSON.";
|
|
11179
|
+
throw new CliError(
|
|
11180
|
+
"invalid_cli_usage",
|
|
11181
|
+
`Failed to parse JSON from --records-file ${path}: ${message}`
|
|
11182
|
+
);
|
|
11183
|
+
}
|
|
11184
|
+
if (!Array.isArray(parsed)) {
|
|
11185
|
+
throw new CliError(
|
|
11186
|
+
"invalid_cli_usage",
|
|
11187
|
+
`Expected --records-file ${path} to contain a JSON array of record objects.`
|
|
11188
|
+
);
|
|
11189
|
+
}
|
|
11190
|
+
if (parsed.length === 0) {
|
|
11191
|
+
throw new CliError(
|
|
11192
|
+
"invalid_cli_usage",
|
|
11193
|
+
`--records-file ${path} must contain at least one record.`
|
|
11194
|
+
);
|
|
11195
|
+
}
|
|
11196
|
+
return parsed.map((record, index) => {
|
|
11197
|
+
if (!isJsonObject6(record)) {
|
|
11198
|
+
throw new CliError(
|
|
11199
|
+
"invalid_cli_usage",
|
|
11200
|
+
`Record at index ${index} in --records-file ${path} must be a JSON object.`
|
|
11201
|
+
);
|
|
11202
|
+
}
|
|
11203
|
+
return record;
|
|
11204
|
+
});
|
|
11205
|
+
}
|
|
11206
|
+
function buildCreateRecords(options) {
|
|
11207
|
+
const hasSingleRecordFlags = (options.set?.length ?? 0) > 0 || (options.setJson?.length ?? 0) > 0 || options.raw !== void 0 || options.rawFile !== void 0;
|
|
11208
|
+
if (options.recordsFile !== void 0) {
|
|
11209
|
+
if (hasSingleRecordFlags) {
|
|
11210
|
+
throw new CliError(
|
|
11211
|
+
"invalid_cli_usage",
|
|
11212
|
+
'Provide either "--records-file" or the single-record flags (--set/--set-json/--raw/--raw-file), not both.'
|
|
11213
|
+
);
|
|
11214
|
+
}
|
|
11215
|
+
return readRecordsFromFile(options.recordsFile);
|
|
11216
|
+
}
|
|
11075
11217
|
const values = {
|
|
11076
11218
|
...readRawObject3(options.raw, options.rawFile),
|
|
11077
11219
|
...buildAssignmentObject(options.set, options.setJson)
|
|
@@ -11079,10 +11221,10 @@ function buildRecordCreateInput(options) {
|
|
|
11079
11221
|
if (isEmptyObject5(values)) {
|
|
11080
11222
|
throw new CliError(
|
|
11081
11223
|
"invalid_cli_usage",
|
|
11082
|
-
'Provide record fields via "--set", "--set-json", "--raw", or "--
|
|
11224
|
+
'Provide record fields via "--set", "--set-json", "--raw", "--raw-file", or load multiple records with "--records-file".'
|
|
11083
11225
|
);
|
|
11084
11226
|
}
|
|
11085
|
-
return
|
|
11227
|
+
return [values];
|
|
11086
11228
|
}
|
|
11087
11229
|
function buildRecordUpdateInput(options) {
|
|
11088
11230
|
const raw = readRawObject3(options.raw, options.rawFile);
|
|
@@ -11205,19 +11347,26 @@ function registerRecordsCommands(program, options = {}) {
|
|
|
11205
11347
|
}
|
|
11206
11348
|
emitTable4(stdout, data);
|
|
11207
11349
|
});
|
|
11208
|
-
records.command("create").description("Create
|
|
11350
|
+
records.command("create").description("Create one or more records").argument("<schema>", "schema uuid or slug", parseUuidOrSlugSelector8).option("--set <field=value>", "set a string field", collectKeyValueAssignments, []).option("--set-json <field=json>", "set a structured field", collectJsonAssignments, []).option("--raw <json>", "raw JSON object payload").option("--raw-file <path>", "path to a raw JSON object payload").option(
|
|
11351
|
+
"--records-file <path>",
|
|
11352
|
+
"path to a JSON array of record objects (mutually exclusive with --set/--set-json/--raw/--raw-file)"
|
|
11353
|
+
).action(async function action(schema, commandOptions) {
|
|
11209
11354
|
const { api, schemasApi, json, readOnly } = createRecordsApi(this, filePath);
|
|
11210
11355
|
ensureWritable7("records create", readOnly, "write");
|
|
11356
|
+
const records2 = buildCreateRecords(commandOptions);
|
|
11211
11357
|
const resolvedSchema = await resolveSchemaSelector(schemasApi, schema);
|
|
11212
|
-
const data = await api.
|
|
11213
|
-
resolvedSchema,
|
|
11214
|
-
buildRecordCreateInput(commandOptions)
|
|
11215
|
-
);
|
|
11358
|
+
const data = await api.createRecords(resolvedSchema, records2);
|
|
11216
11359
|
if (json) {
|
|
11217
11360
|
emitJson10(stdout, "records create", data, filePath);
|
|
11218
11361
|
return;
|
|
11219
11362
|
}
|
|
11220
|
-
writeLine10(stdout, `Created record "${
|
|
11363
|
+
writeLine10(stdout, `Created ${data.created.length} record(s) in schema "${schema.value}".`);
|
|
11364
|
+
if (data.errors.length > 0) {
|
|
11365
|
+
writeLine10(stdout, `${data.errors.length} record(s) rejected:`);
|
|
11366
|
+
for (const error of data.errors) {
|
|
11367
|
+
writeLine10(stdout, ` - index ${error.index}: ${error.message}`);
|
|
11368
|
+
}
|
|
11369
|
+
}
|
|
11221
11370
|
});
|
|
11222
11371
|
records.command("update").description("Update a record").argument("<schema>", "schema uuid or slug", parseUuidOrSlugSelector8).argument("<record>", "record uuid or slug", parseUuidOrSlugSelector8).option("--set <field=value>", "set a string field", collectKeyValueAssignments, []).option("--set-json <field=json>", "set a structured field", collectJsonAssignments, []).option("--unset <field>", "unset a field", collectStringValues, []).option("--raw <json>", "raw JSON object of fields to set").option("--raw-file <path>", "path to a JSON object of fields to set").action(async function action(schema, record, commandOptions) {
|
|
11223
11372
|
const { api, schemasApi, json, readOnly } = createRecordsApi(this, filePath);
|
|
@@ -11234,21 +11383,23 @@ function registerRecordsCommands(program, options = {}) {
|
|
|
11234
11383
|
}
|
|
11235
11384
|
writeLine10(stdout, `Updated record "${getRecordIdentifier(data)}".`);
|
|
11236
11385
|
});
|
|
11237
|
-
records.command("delete").description("Delete
|
|
11386
|
+
records.command("delete").description("Delete one or more records").argument("<schema>", "schema uuid or slug", parseUuidOrSlugSelector8).option("--record <uuid>", "record uuid to delete (repeatable)", collectRecordIds, []).option(
|
|
11387
|
+
"--records-file <path>",
|
|
11388
|
+
"path to a file with one record uuid per line (merged with --record)"
|
|
11389
|
+
).action(async function action(schema, commandOptions) {
|
|
11238
11390
|
const { api, schemasApi, json, readOnly } = createRecordsApi(this, filePath);
|
|
11239
11391
|
ensureWritable7("records delete", readOnly, "destructive");
|
|
11392
|
+
const recordIds = resolveDeleteRecordIds(commandOptions);
|
|
11240
11393
|
const resolvedSchema = await resolveSchemaSelector(schemasApi, schema);
|
|
11241
|
-
await api.
|
|
11242
|
-
const data = {
|
|
11243
|
-
schema: resolvedSchema.value,
|
|
11244
|
-
record: record.value,
|
|
11245
|
-
deleted: true
|
|
11246
|
-
};
|
|
11394
|
+
const data = await api.deleteRecords(resolvedSchema, recordIds);
|
|
11247
11395
|
if (json) {
|
|
11248
11396
|
emitJson10(stdout, "records delete", data, filePath);
|
|
11249
11397
|
return;
|
|
11250
11398
|
}
|
|
11251
|
-
writeLine10(
|
|
11399
|
+
writeLine10(
|
|
11400
|
+
stdout,
|
|
11401
|
+
`Deleted ${recordIds.length} record(s) from schema "${schema.value}".`
|
|
11402
|
+
);
|
|
11252
11403
|
});
|
|
11253
11404
|
records.command("clear").description("Delete all records from a schema's table").argument("<schema>", "schema uuid or slug", parseUuidOrSlugSelector8).action(async function action(schema) {
|
|
11254
11405
|
const { api, schemasApi, json, readOnly } = createRecordsApi(this, filePath);
|