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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zinkee",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "CLI for Zinkee API v2",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,7 +38,6 @@ export interface CreateRecordInput {
38
38
  }
39
39
 
40
40
  export interface UpdateRecordInput {
41
- [key: string]: unknown;
42
41
  set?: Record<string, unknown>;
43
42
  unset?: string[];
44
43
  }
@@ -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("updates records with the patch-style set and unset contract", async () => {
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
- set: {
323
- status: "inactive",
324
- metadata: {
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: ["legacyCode", "tempField"],
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
 
@@ -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 = { ...raw };
428
+ const input: UpdateRecordInput = {};
455
429
  const setPayload = {
456
- ...readRawSetPayload(raw),
430
+ ...raw,
457
431
  ...buildAssignmentObject(options.set, options.setJson),
458
432
  };
459
- const unsetPayload = [...readRawUnsetPayload(raw), ...(options.unset ?? [])];
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 field selection", (value) => (
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 payload")
682
- .option("--raw-file <path>", "path to a raw JSON object payload")
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,