zinkee 0.1.26 → 0.1.28

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.26",
3
+ "version": "0.1.28",
4
4
  "description": "CLI for Zinkee API v2",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,6 +27,7 @@ export interface RecordQueryInput {
27
27
  [key: string]: unknown;
28
28
  select?: string[];
29
29
  where?: RecordQueryExpression[];
30
+ search?: { term: string };
30
31
  sort?: string[];
31
32
  limit?: number;
32
33
  offset?: number;
@@ -196,6 +196,150 @@ describe("records commands", () => {
196
196
  });
197
197
  });
198
198
 
199
+ it("maps --search to a search.term object in the query body", async () => {
200
+ const configPath = createApiConfig();
201
+ const output = createStdoutBuffer();
202
+ const fetchMock = createFetchMock();
203
+ mockSchemaLookup(fetchMock);
204
+ fetchMock.mockResolvedValueOnce(
205
+ jsonResponse({
206
+ records: [{ recordId: "record-1", values: { name: "Ana Garcia" } }],
207
+ page: { offset: 0, limit: 25, total: 1, count: 1, hasMore: false },
208
+ }),
209
+ );
210
+ const program = buildProgram({ configPath, stdout: output.stdout });
211
+
212
+ await program.parseAsync([
213
+ "node",
214
+ "zinkee",
215
+ "--json",
216
+ "records",
217
+ "query",
218
+ "contacts",
219
+ "--select",
220
+ "id,name,status",
221
+ "--where",
222
+ "status in OPEN",
223
+ "--search",
224
+ "garcia",
225
+ "--limit",
226
+ "25",
227
+ ]);
228
+
229
+ const init = fetchMock.mock.calls[1]?.[1];
230
+
231
+ expect(JSON.parse(String(init?.body))).toEqual({
232
+ select: ["id", "name", "status"],
233
+ where: [{ field: "status", op: "in", value: ["OPEN"] }],
234
+ search: { term: "garcia" },
235
+ page: { limit: 25 },
236
+ });
237
+ });
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
+
199
343
  it("normalizes UUID selectors for records get", async () => {
200
344
  const configPath = createApiConfig();
201
345
  const output = createStdoutBuffer();
@@ -48,6 +48,7 @@ interface GlobalOptions extends RuntimeOverrides {}
48
48
  interface RecordsQueryCommandOptions {
49
49
  select?: string[];
50
50
  where?: ParsedExpression[];
51
+ search?: string;
51
52
  sort?: string[];
52
53
  limit?: number;
53
54
  offset?: number;
@@ -232,12 +233,20 @@ function parseJsonKeyValueAssignment(input: string): JsonKeyValueAssignment {
232
233
  }
233
234
  }
234
235
 
236
+ function dedupeStrings(values: string[]): string[] {
237
+ return [...new Set(values)];
238
+ }
239
+
235
240
  function collectStringValues(value: string, previous: string[] = []): string[] {
236
241
  return [...previous, value];
237
242
  }
238
243
 
244
+ function collectSelectValues(value: string, previous: string[] = []): string[] {
245
+ return dedupeStrings([...previous, ...parseCommaSeparatedValues(value, "--select")]);
246
+ }
247
+
239
248
  function collectSortValues(value: string, previous: string[] = []): string[] {
240
- return [...previous, ...parseCommaSeparatedValues(value, "--sort")];
249
+ return dedupeStrings([...previous, ...parseCommaSeparatedValues(value, "--sort")]);
241
250
  }
242
251
 
243
252
  function collectWhereExpressions(
@@ -375,6 +384,10 @@ function buildRecordQueryInput(
375
384
  input.where = where;
376
385
  }
377
386
 
387
+ if (options.search !== undefined) {
388
+ input.search = { term: options.search };
389
+ }
390
+
378
391
  if (options.sort && options.sort.length > 0) {
379
392
  input.sort = [...options.sort];
380
393
  }
@@ -573,10 +586,12 @@ export function registerRecordsCommands(
573
586
  .command("query")
574
587
  .description("Query records for a schema")
575
588
  .argument("<schema>", "schema uuid or slug", parseUuidOrSlugSelector)
576
- .option("--select <fields>", "comma-separated field selection", (value) => (
577
- parseCommaSeparatedValues(value, "--select")
578
- ))
589
+ .option("--select <fields>", "field selection (comma-separated, repeatable)", collectSelectValues, [])
579
590
  .option("--where <expression>", "where expression", collectWhereExpressions, [])
591
+ .option(
592
+ "--search <term>",
593
+ "free-text search term (matched across the schema's searchable fields)",
594
+ )
580
595
  .option("--sort <field:direction>", "sort field(s)", collectSortValues, [])
581
596
  .option("--limit <number>", "limit results", parseNonNegativeInteger)
582
597
  .option("--offset <number>", "offset results", parseNonNegativeInteger)
@@ -9,6 +9,12 @@ describe("getCommandExamples", () => {
9
9
 
10
10
  expect(examples.length).toBeGreaterThan(0);
11
11
  expect(examples[0]?.command).toContain("records query");
12
+
13
+ const searchExample = examples.find((example) =>
14
+ example.command.includes("--search"),
15
+ );
16
+ expect(searchExample?.command).toContain("--search garcia");
17
+ expect(searchExample?.raw).toContain('"search": { "term": "garcia" }');
12
18
  });
13
19
 
14
20
  it("returns fallback examples for simple commands", () => {
@@ -502,7 +502,7 @@ const specificExamples: Record<string, readonly CommandExample[]> = {
502
502
  {
503
503
  description: "Filter by status with a free-text search term.",
504
504
  command:
505
- `zinkee --json records query contacts --select id,contact-name,status --where "status in OPEN" --limit 25 --raw '{"search":{"term":"garcia"}}'`,
505
+ `zinkee --json records query contacts --select id,contact-name,status --where "status in OPEN" --search garcia --limit 25`,
506
506
  raw: `{
507
507
  "select": ["id", "contact-name", "status"],
508
508
  "where": [