Skip to content

Commit eb8ff3b

Browse files
vladfranguB4nan
andauthored
feat: key-value-store commands (#700)
Adds in `keys` to list the keys, `set-value` to set a value and `delete-value` to delete a value --------- Co-authored-by: Martin Adámek <[email protected]>
1 parent 75ad005 commit eb8ff3b

File tree

4 files changed

+227
-39
lines changed

4 files changed

+227
-39
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { Args } from '@oclif/core';
2+
import type { ApifyApiError } from 'apify-client';
3+
import chalk from 'chalk';
4+
5+
import { ApifyCommand } from '../../lib/apify_command.js';
6+
import { confirmAction } from '../../lib/commands/confirm.js';
7+
import { tryToGetKeyValueStore } from '../../lib/commands/storages.js';
8+
import { error, info } from '../../lib/outputs.js';
9+
import { getLoggedClientOrThrow } from '../../lib/utils.js';
10+
11+
export class KeyValueStoresDeleteValueCommand extends ApifyCommand<typeof KeyValueStoresDeleteValueCommand> {
12+
static override description = 'Delete a value from a key-value store.';
13+
14+
static override hiddenAliases = ['kvs:delete-value'];
15+
16+
static override args = {
17+
storeId: Args.string({
18+
description: 'The key-value store ID to delete the value from.',
19+
required: true,
20+
}),
21+
itemKey: Args.string({
22+
description: 'The key of the item in the key-value store.',
23+
required: true,
24+
}),
25+
};
26+
27+
async run() {
28+
const { storeId, itemKey } = this.args;
29+
30+
const apifyClient = await getLoggedClientOrThrow();
31+
const maybeStore = await tryToGetKeyValueStore(apifyClient, storeId);
32+
33+
if (!maybeStore) {
34+
error({
35+
message: `Key-value store with ID or name "${storeId}" not found.`,
36+
});
37+
38+
return;
39+
}
40+
41+
const { keyValueStoreClient: client } = maybeStore;
42+
43+
const existing = await client.getRecord(itemKey);
44+
45+
if (!existing) {
46+
error({
47+
message: `Item with key "${itemKey}" not found in the key-value store.`,
48+
});
49+
return;
50+
}
51+
52+
const confirm = await confirmAction({
53+
type: 'record',
54+
});
55+
56+
if (!confirm) {
57+
info({ message: 'Key-value store record deletion aborted.', stdout: true });
58+
return;
59+
}
60+
61+
try {
62+
await client.deleteRecord(itemKey);
63+
info({
64+
message: `Record with key "${chalk.yellow(itemKey)}" deleted from the key-value store.`,
65+
stdout: true,
66+
});
67+
} catch (err) {
68+
const casted = err as ApifyApiError;
69+
70+
error({
71+
message: `Failed to delete record with key "${itemKey}" from the key-value store.\n ${casted.message || casted}`,
72+
});
73+
}
74+
}
75+
}

src/commands/key-value-stores/get-value.ts

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Args, Flags } from '@oclif/core';
2-
import type { ApifyClient, KeyValueStore, KeyValueStoreClient } from 'apify-client';
32

43
import { ApifyCommand } from '../../lib/apify_command.js';
4+
import { tryToGetKeyValueStore } from '../../lib/commands/storages.js';
55
import { error, simpleLog } from '../../lib/outputs.js';
6-
import { getLocalUserInfo, getLoggedClientOrThrow } from '../../lib/utils.js';
6+
import { getLoggedClientOrThrow } from '../../lib/utils.js';
77

88
export class KeyValueStoresGetValueCommand extends ApifyCommand<typeof KeyValueStoresGetValueCommand> {
99
static override description = 'Gets a value by key in the given key-value store.';
@@ -33,13 +33,14 @@ export class KeyValueStoresGetValueCommand extends ApifyCommand<typeof KeyValueS
3333
const { keyValueStoreId, itemKey } = this.args;
3434

3535
const apifyClient = await getLoggedClientOrThrow();
36-
const maybeStore = await this.tryToGetKeyValueStore(apifyClient, keyValueStoreId);
36+
const maybeStore = await tryToGetKeyValueStore(apifyClient, keyValueStoreId);
3737

3838
if (!maybeStore) {
39+
error({ message: `Key-value store with ID "${keyValueStoreId}" not found.` });
3940
return;
4041
}
4142

42-
const { storeClient } = maybeStore;
43+
const { keyValueStoreClient: storeClient } = maybeStore;
4344

4445
const itemRecord = await storeClient.getRecord(itemKey, { stream: true });
4546

@@ -83,39 +84,4 @@ export class KeyValueStoresGetValueCommand extends ApifyCommand<typeof KeyValueS
8384
// pipe the output to stdout
8485
itemRecord.value.pipe(process.stdout);
8586
}
86-
87-
private async tryToGetKeyValueStore(
88-
client: ApifyClient,
89-
keyValueStoreId: string,
90-
): Promise<{ store: KeyValueStore | undefined; storeClient: KeyValueStoreClient } | null> {
91-
const byIdOrName = await client
92-
.keyValueStore(keyValueStoreId)
93-
.get()
94-
.catch(() => undefined);
95-
96-
if (byIdOrName) {
97-
return {
98-
store: byIdOrName,
99-
storeClient: client.keyValueStore(byIdOrName.id),
100-
};
101-
}
102-
103-
const info = await getLocalUserInfo();
104-
105-
const byName = await client
106-
.keyValueStore(`${info.username!}/${keyValueStoreId}`)
107-
.get()
108-
.catch(() => undefined);
109-
110-
if (byName) {
111-
return {
112-
store: byName,
113-
storeClient: client.keyValueStore(byName.id),
114-
};
115-
}
116-
117-
error({ message: `Key-value store with ID "${keyValueStoreId}" not found.` });
118-
119-
return null;
120-
}
12187
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { Args, Flags } from '@oclif/core';
2+
3+
import { ApifyCommand } from '../../lib/apify_command.js';
4+
import { prettyPrintBytes } from '../../lib/commands/pretty-print-bytes.js';
5+
import { CompactMode, ResponsiveTable } from '../../lib/commands/responsive-table.js';
6+
import { tryToGetKeyValueStore } from '../../lib/commands/storages.js';
7+
import { error, simpleLog } from '../../lib/outputs.js';
8+
import { getLoggedClientOrThrow } from '../../lib/utils.js';
9+
10+
const table = new ResponsiveTable({
11+
allColumns: ['Key', 'Size'],
12+
mandatoryColumns: ['Key', 'Size'],
13+
});
14+
15+
export class KeyValueStoresKeysCommand extends ApifyCommand<typeof KeyValueStoresKeysCommand> {
16+
static override description = 'Lists all keys in a key-value store.';
17+
18+
static override hiddenAliases = ['kvs:keys'];
19+
20+
static override flags = {
21+
limit: Flags.integer({
22+
description: 'The maximum number of keys to return.',
23+
default: 20,
24+
}),
25+
'exclusive-start-key': Flags.string({
26+
description: 'The key to start the list from.',
27+
}),
28+
};
29+
30+
static override args = {
31+
storeId: Args.string({
32+
description: 'The key-value store ID to list keys for.',
33+
required: true,
34+
}),
35+
};
36+
37+
static override enableJsonFlag = true;
38+
39+
async run() {
40+
const { storeId } = this.args;
41+
const { limit, exclusiveStartKey } = this.flags;
42+
43+
const apifyClient = await getLoggedClientOrThrow();
44+
const maybeStore = await tryToGetKeyValueStore(apifyClient, storeId);
45+
46+
if (!maybeStore) {
47+
error({
48+
message: `Key-value store with ID or name "${storeId}" not found.`,
49+
});
50+
51+
return;
52+
}
53+
54+
const { keyValueStoreClient: client } = maybeStore;
55+
56+
const keys = await client.listKeys({ limit, exclusiveStartKey });
57+
58+
if (this.flags.json) {
59+
return keys;
60+
}
61+
62+
for (const keyData of keys.items) {
63+
table.pushRow({
64+
Key: keyData.key,
65+
Size: prettyPrintBytes({ bytes: keyData.size, shortBytes: true, precision: 0 }),
66+
});
67+
}
68+
69+
simpleLog({
70+
message: table.render(CompactMode.WebLikeCompact),
71+
stdout: true,
72+
});
73+
74+
return undefined;
75+
}
76+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { Args, Flags } from '@oclif/core';
2+
import type { ApifyApiError } from 'apify-client';
3+
4+
import { ApifyCommand } from '../../lib/apify_command.js';
5+
import { tryToGetKeyValueStore } from '../../lib/commands/storages.js';
6+
import { error, success } from '../../lib/outputs.js';
7+
import { getLoggedClientOrThrow } from '../../lib/utils.js';
8+
9+
export class KeyValueStoresSetValueCommand extends ApifyCommand<typeof KeyValueStoresSetValueCommand> {
10+
static override description = 'Sets a value in a key-value store.';
11+
12+
static override hiddenAliases = ['kvs:set-value'];
13+
14+
static override flags = {
15+
'content-type': Flags.string({
16+
description: 'The MIME content type of the value. By default, "application/json" is assumed.',
17+
default: 'application/json',
18+
}),
19+
};
20+
21+
static override args = {
22+
storeId: Args.string({
23+
description: 'The key-value store ID to set the value in.',
24+
required: true,
25+
ignoreStdin: true,
26+
}),
27+
itemKey: Args.string({
28+
description: 'The key of the item in the key-value store.',
29+
required: true,
30+
ignoreStdin: true,
31+
}),
32+
value: Args.string({
33+
description: 'The value to set.',
34+
ignoreStdin: true,
35+
}),
36+
};
37+
38+
async run() {
39+
const { storeId, itemKey, value } = this.args;
40+
const { contentType } = this.flags;
41+
42+
const apifyClient = await getLoggedClientOrThrow();
43+
const maybeStore = await tryToGetKeyValueStore(apifyClient, storeId);
44+
45+
if (!maybeStore) {
46+
error({
47+
message: `Key-value store with ID or name "${storeId}" not found.`,
48+
});
49+
50+
return;
51+
}
52+
53+
const { keyValueStoreClient: client } = maybeStore;
54+
55+
try {
56+
// TODO: again, the types need to be fixed -w-
57+
await client.setRecord({ key: itemKey, value: (value || process.stdin) as string, contentType });
58+
59+
success({
60+
message: `Value with key "${itemKey}" set in the key-value store.`,
61+
stdout: true,
62+
});
63+
} catch (err) {
64+
const casted = err as ApifyApiError;
65+
66+
error({
67+
message: `Failed to set value with key "${itemKey}" in the key-value store.\n ${casted.message || casted}`,
68+
});
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)