zinkee 0.1.27 → 0.1.29
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/.claude/settings.local.json +14 -1
- package/dist/index.js +12 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/api/records.ts +0 -1
- package/src/commands/records.test.ts +183 -14
- package/src/commands/records.ts +15 -43
package/package.json
CHANGED
package/src/api/records.ts
CHANGED
|
@@ -236,6 +236,110 @@ describe("records commands", () => {
|
|
|
236
236
|
});
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
+
it("accumulates repeated --select flags into a single field selection", async () => {
|
|
240
|
+
const configPath = createApiConfig();
|
|
241
|
+
const output = createStdoutBuffer();
|
|
242
|
+
const fetchMock = createFetchMock();
|
|
243
|
+
mockSchemaLookup(fetchMock);
|
|
244
|
+
fetchMock.mockResolvedValueOnce(
|
|
245
|
+
jsonResponse({
|
|
246
|
+
records: [{ recordId: "record-1", values: { name: "Ana" } }],
|
|
247
|
+
page: { offset: 0, limit: 25, total: 1, count: 1, hasMore: false },
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
250
|
+
const program = buildProgram({ configPath, stdout: output.stdout });
|
|
251
|
+
|
|
252
|
+
await program.parseAsync([
|
|
253
|
+
"node",
|
|
254
|
+
"zinkee",
|
|
255
|
+
"--json",
|
|
256
|
+
"records",
|
|
257
|
+
"query",
|
|
258
|
+
"contacts",
|
|
259
|
+
"--select",
|
|
260
|
+
"campo-1",
|
|
261
|
+
"--select",
|
|
262
|
+
"campo-2",
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
const init = fetchMock.mock.calls[1]?.[1];
|
|
266
|
+
|
|
267
|
+
expect(JSON.parse(String(init?.body))).toEqual({
|
|
268
|
+
select: ["campo-1", "campo-2"],
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("mixes comma-separated and repeated --select flags preserving order", async () => {
|
|
273
|
+
const configPath = createApiConfig();
|
|
274
|
+
const output = createStdoutBuffer();
|
|
275
|
+
const fetchMock = createFetchMock();
|
|
276
|
+
mockSchemaLookup(fetchMock);
|
|
277
|
+
fetchMock.mockResolvedValueOnce(
|
|
278
|
+
jsonResponse({
|
|
279
|
+
records: [{ recordId: "record-1", values: { name: "Ana" } }],
|
|
280
|
+
page: { offset: 0, limit: 25, total: 1, count: 1, hasMore: false },
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
283
|
+
const program = buildProgram({ configPath, stdout: output.stdout });
|
|
284
|
+
|
|
285
|
+
await program.parseAsync([
|
|
286
|
+
"node",
|
|
287
|
+
"zinkee",
|
|
288
|
+
"--json",
|
|
289
|
+
"records",
|
|
290
|
+
"query",
|
|
291
|
+
"contacts",
|
|
292
|
+
"--select",
|
|
293
|
+
"a,b",
|
|
294
|
+
"--select",
|
|
295
|
+
"c",
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
const init = fetchMock.mock.calls[1]?.[1];
|
|
299
|
+
|
|
300
|
+
expect(JSON.parse(String(init?.body))).toEqual({
|
|
301
|
+
select: ["a", "b", "c"],
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("deduplicates repeated fields across --select and --sort flags", async () => {
|
|
306
|
+
const configPath = createApiConfig();
|
|
307
|
+
const output = createStdoutBuffer();
|
|
308
|
+
const fetchMock = createFetchMock();
|
|
309
|
+
mockSchemaLookup(fetchMock);
|
|
310
|
+
fetchMock.mockResolvedValueOnce(
|
|
311
|
+
jsonResponse({
|
|
312
|
+
records: [{ recordId: "record-1", values: { name: "Ana" } }],
|
|
313
|
+
page: { offset: 0, limit: 25, total: 1, count: 1, hasMore: false },
|
|
314
|
+
}),
|
|
315
|
+
);
|
|
316
|
+
const program = buildProgram({ configPath, stdout: output.stdout });
|
|
317
|
+
|
|
318
|
+
await program.parseAsync([
|
|
319
|
+
"node",
|
|
320
|
+
"zinkee",
|
|
321
|
+
"--json",
|
|
322
|
+
"records",
|
|
323
|
+
"query",
|
|
324
|
+
"contacts",
|
|
325
|
+
"--select",
|
|
326
|
+
"id",
|
|
327
|
+
"--select",
|
|
328
|
+
"id",
|
|
329
|
+
"--sort",
|
|
330
|
+
"name:asc",
|
|
331
|
+
"--sort",
|
|
332
|
+
"name:asc",
|
|
333
|
+
]);
|
|
334
|
+
|
|
335
|
+
const init = fetchMock.mock.calls[1]?.[1];
|
|
336
|
+
|
|
337
|
+
expect(JSON.parse(String(init?.body))).toEqual({
|
|
338
|
+
select: ["id"],
|
|
339
|
+
sort: [{ field: "name", direction: "ASC" }],
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
239
343
|
it("normalizes UUID selectors for records get", async () => {
|
|
240
344
|
const configPath = createApiConfig();
|
|
241
345
|
const output = createStdoutBuffer();
|
|
@@ -308,7 +412,7 @@ describe("records commands", () => {
|
|
|
308
412
|
});
|
|
309
413
|
});
|
|
310
414
|
|
|
311
|
-
it("
|
|
415
|
+
it("wraps raw-file fields into the set payload", async () => {
|
|
312
416
|
const configPath = createApiConfig();
|
|
313
417
|
const output = createStdoutBuffer();
|
|
314
418
|
const fetchMock = createFetchMock();
|
|
@@ -319,15 +423,9 @@ describe("records commands", () => {
|
|
|
319
423
|
writeFileSync(
|
|
320
424
|
rawFile,
|
|
321
425
|
JSON.stringify({
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
imported: true,
|
|
326
|
-
},
|
|
327
|
-
},
|
|
328
|
-
unset: ["legacyCode"],
|
|
329
|
-
audit: {
|
|
330
|
-
reason: "sync",
|
|
426
|
+
status: "inactive",
|
|
427
|
+
metadata: {
|
|
428
|
+
imported: true,
|
|
331
429
|
},
|
|
332
430
|
}),
|
|
333
431
|
"utf8",
|
|
@@ -368,9 +466,6 @@ describe("records commands", () => {
|
|
|
368
466
|
);
|
|
369
467
|
expect(init?.method).toBe("PATCH");
|
|
370
468
|
expect(JSON.parse(String(init?.body))).toEqual({
|
|
371
|
-
audit: {
|
|
372
|
-
reason: "sync",
|
|
373
|
-
},
|
|
374
469
|
set: {
|
|
375
470
|
status: "active",
|
|
376
471
|
metadata: {
|
|
@@ -380,7 +475,81 @@ describe("records commands", () => {
|
|
|
380
475
|
tier: "gold",
|
|
381
476
|
},
|
|
382
477
|
},
|
|
383
|
-
unset: ["
|
|
478
|
+
unset: ["tempField"],
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it("wraps bare raw fields into the set payload", async () => {
|
|
483
|
+
const configPath = createApiConfig();
|
|
484
|
+
const output = createStdoutBuffer();
|
|
485
|
+
const fetchMock = createFetchMock();
|
|
486
|
+
mockSchemaLookup(fetchMock);
|
|
487
|
+
fetchMock.mockResolvedValueOnce(
|
|
488
|
+
jsonResponse({
|
|
489
|
+
id: "550e8400-e29b-41d4-a716-446655440001",
|
|
490
|
+
uno: "prueba mod",
|
|
491
|
+
}),
|
|
492
|
+
);
|
|
493
|
+
const program = buildProgram({ configPath, stdout: output.stdout });
|
|
494
|
+
|
|
495
|
+
await program.parseAsync([
|
|
496
|
+
"node",
|
|
497
|
+
"zinkee",
|
|
498
|
+
"--json",
|
|
499
|
+
"records",
|
|
500
|
+
"update",
|
|
501
|
+
"contacts",
|
|
502
|
+
"550E8400-E29B-41D4-A716-446655440001",
|
|
503
|
+
"--raw",
|
|
504
|
+
'{"uno":"prueba mod"}',
|
|
505
|
+
]);
|
|
506
|
+
|
|
507
|
+
const init = fetchMock.mock.calls[1]?.[1];
|
|
508
|
+
|
|
509
|
+
expect(fetchMock.mock.calls[1]?.[0]).toBe(
|
|
510
|
+
"https://api.zinkee.test/api/v2/schemas/schema-1/records/550e8400-e29b-41d4-a716-446655440001",
|
|
511
|
+
);
|
|
512
|
+
expect(init?.method).toBe("PATCH");
|
|
513
|
+
expect(JSON.parse(String(init?.body))).toEqual({
|
|
514
|
+
set: {
|
|
515
|
+
uno: "prueba mod",
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it("lets --set override raw fields with the same key", async () => {
|
|
521
|
+
const configPath = createApiConfig();
|
|
522
|
+
const output = createStdoutBuffer();
|
|
523
|
+
const fetchMock = createFetchMock();
|
|
524
|
+
mockSchemaLookup(fetchMock);
|
|
525
|
+
fetchMock.mockResolvedValueOnce(
|
|
526
|
+
jsonResponse({
|
|
527
|
+
id: "550e8400-e29b-41d4-a716-446655440001",
|
|
528
|
+
}),
|
|
529
|
+
);
|
|
530
|
+
const program = buildProgram({ configPath, stdout: output.stdout });
|
|
531
|
+
|
|
532
|
+
await program.parseAsync([
|
|
533
|
+
"node",
|
|
534
|
+
"zinkee",
|
|
535
|
+
"--json",
|
|
536
|
+
"records",
|
|
537
|
+
"update",
|
|
538
|
+
"contacts",
|
|
539
|
+
"550E8400-E29B-41D4-A716-446655440001",
|
|
540
|
+
"--raw",
|
|
541
|
+
'{"uno":"raw value"}',
|
|
542
|
+
"--set",
|
|
543
|
+
"uno=set value",
|
|
544
|
+
]);
|
|
545
|
+
|
|
546
|
+
const init = fetchMock.mock.calls[1]?.[1];
|
|
547
|
+
|
|
548
|
+
expect(init?.method).toBe("PATCH");
|
|
549
|
+
expect(JSON.parse(String(init?.body))).toEqual({
|
|
550
|
+
set: {
|
|
551
|
+
uno: "set value",
|
|
552
|
+
},
|
|
384
553
|
});
|
|
385
554
|
});
|
|
386
555
|
|
package/src/commands/records.ts
CHANGED
|
@@ -233,12 +233,20 @@ function parseJsonKeyValueAssignment(input: string): JsonKeyValueAssignment {
|
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
function dedupeStrings(values: string[]): string[] {
|
|
237
|
+
return [...new Set(values)];
|
|
238
|
+
}
|
|
239
|
+
|
|
236
240
|
function collectStringValues(value: string, previous: string[] = []): string[] {
|
|
237
241
|
return [...previous, value];
|
|
238
242
|
}
|
|
239
243
|
|
|
244
|
+
function collectSelectValues(value: string, previous: string[] = []): string[] {
|
|
245
|
+
return dedupeStrings([...previous, ...parseCommaSeparatedValues(value, "--select")]);
|
|
246
|
+
}
|
|
247
|
+
|
|
240
248
|
function collectSortValues(value: string, previous: string[] = []): string[] {
|
|
241
|
-
return [...previous, ...parseCommaSeparatedValues(value, "--sort")];
|
|
249
|
+
return dedupeStrings([...previous, ...parseCommaSeparatedValues(value, "--sort")]);
|
|
242
250
|
}
|
|
243
251
|
|
|
244
252
|
function collectWhereExpressions(
|
|
@@ -413,50 +421,16 @@ function buildRecordCreateInput(
|
|
|
413
421
|
return { values };
|
|
414
422
|
}
|
|
415
423
|
|
|
416
|
-
function readRawSetPayload(raw: JsonObject): Record<string, unknown> {
|
|
417
|
-
const setValue = raw.set;
|
|
418
|
-
|
|
419
|
-
if (setValue === undefined) {
|
|
420
|
-
return {};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (!isJsonObject(setValue)) {
|
|
424
|
-
throw new CliError(
|
|
425
|
-
"invalid_cli_usage",
|
|
426
|
-
'Expected raw update payload field "set" to be a JSON object.',
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return { ...setValue };
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function readRawUnsetPayload(raw: JsonObject): string[] {
|
|
434
|
-
const unsetValue = raw.unset;
|
|
435
|
-
|
|
436
|
-
if (unsetValue === undefined) {
|
|
437
|
-
return [];
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (!Array.isArray(unsetValue) || !unsetValue.every((value) => typeof value === "string")) {
|
|
441
|
-
throw new CliError(
|
|
442
|
-
"invalid_cli_usage",
|
|
443
|
-
'Expected raw update payload field "unset" to be an array of strings.',
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
return [...unsetValue];
|
|
448
|
-
}
|
|
449
|
-
|
|
450
424
|
function buildRecordUpdateInput(
|
|
451
425
|
options: RecordsUpdateCommandOptions,
|
|
452
426
|
): UpdateRecordInput {
|
|
453
427
|
const raw = readRawObject(options.raw, options.rawFile);
|
|
454
|
-
const input: UpdateRecordInput = {
|
|
428
|
+
const input: UpdateRecordInput = {};
|
|
455
429
|
const setPayload = {
|
|
456
|
-
...
|
|
430
|
+
...raw,
|
|
457
431
|
...buildAssignmentObject(options.set, options.setJson),
|
|
458
432
|
};
|
|
459
|
-
const unsetPayload = [...
|
|
433
|
+
const unsetPayload = [...(options.unset ?? [])];
|
|
460
434
|
|
|
461
435
|
for (const key of Object.keys(setPayload)) {
|
|
462
436
|
const unsetIndex = unsetPayload.indexOf(key);
|
|
@@ -578,9 +552,7 @@ export function registerRecordsCommands(
|
|
|
578
552
|
.command("query")
|
|
579
553
|
.description("Query records for a schema")
|
|
580
554
|
.argument("<schema>", "schema uuid or slug", parseUuidOrSlugSelector)
|
|
581
|
-
.option("--select <fields>", "comma-separated
|
|
582
|
-
parseCommaSeparatedValues(value, "--select")
|
|
583
|
-
))
|
|
555
|
+
.option("--select <fields>", "field selection (comma-separated, repeatable)", collectSelectValues, [])
|
|
584
556
|
.option("--where <expression>", "where expression", collectWhereExpressions, [])
|
|
585
557
|
.option(
|
|
586
558
|
"--search <term>",
|
|
@@ -678,8 +650,8 @@ export function registerRecordsCommands(
|
|
|
678
650
|
.option("--set <field=value>", "set a string field", collectKeyValueAssignments, [])
|
|
679
651
|
.option("--set-json <field=json>", "set a structured field", collectJsonAssignments, [])
|
|
680
652
|
.option("--unset <field>", "unset a field", collectStringValues, [])
|
|
681
|
-
.option("--raw <json>", "raw JSON object
|
|
682
|
-
.option("--raw-file <path>", "path to a
|
|
653
|
+
.option("--raw <json>", "raw JSON object of fields to set")
|
|
654
|
+
.option("--raw-file <path>", "path to a JSON object of fields to set")
|
|
683
655
|
.action(async function action(
|
|
684
656
|
this: Command,
|
|
685
657
|
schema: NormalizedUuidOrSlug,
|