diff --git a/src/commands/key-value-stores/delete-value.ts b/src/commands/key-value-stores/delete-value.ts new file mode 100644 index 000000000..f66402f78 --- /dev/null +++ b/src/commands/key-value-stores/delete-value.ts @@ -0,0 +1,75 @@ +import { Args } from '@oclif/core'; +import type { ApifyApiError } from 'apify-client'; +import chalk from 'chalk'; + +import { ApifyCommand } from '../../lib/apify_command.js'; +import { confirmAction } from '../../lib/commands/confirm.js'; +import { tryToGetKeyValueStore } from '../../lib/commands/storages.js'; +import { error, info } from '../../lib/outputs.js'; +import { getLoggedClientOrThrow } from '../../lib/utils.js'; + +export class KeyValueStoresDeleteValueCommand extends ApifyCommand { + static override description = 'Delete a value from a key-value store.'; + + static override hiddenAliases = ['kvs:delete-value']; + + static override args = { + storeId: Args.string({ + description: 'The key-value store ID to delete the value from.', + required: true, + }), + itemKey: Args.string({ + description: 'The key of the item in the key-value store.', + required: true, + }), + }; + + async run() { + const { storeId, itemKey } = this.args; + + const apifyClient = await getLoggedClientOrThrow(); + const maybeStore = await tryToGetKeyValueStore(apifyClient, storeId); + + if (!maybeStore) { + error({ + message: `Key-value store with ID or name "${storeId}" not found.`, + }); + + return; + } + + const { keyValueStoreClient: client } = maybeStore; + + const existing = await client.getRecord(itemKey); + + if (!existing) { + error({ + message: `Item with key "${itemKey}" not found in the key-value store.`, + }); + return; + } + + const confirm = await confirmAction({ + type: 'record', + }); + + if (!confirm) { + info({ message: 'Key-value store record deletion aborted.', stdout: true }); + return; + } + + try { + await client.deleteRecord(itemKey); + info({ + message: `Record with key "${chalk.yellow(itemKey)}" deleted from the key-value store.`, + stdout: true, + }); + } catch (err) { + const casted = err as ApifyApiError; + + error({ + message: `Failed to delete record with key "${itemKey}" from the key-value store.\n ${casted.message || casted}`, + }); + } + } +} diff --git a/src/commands/key-value-stores/get-value.ts b/src/commands/key-value-stores/get-value.ts index c2e062a0a..1288209c1 100644 --- a/src/commands/key-value-stores/get-value.ts +++ b/src/commands/key-value-stores/get-value.ts @@ -1,9 +1,9 @@ import { Args, Flags } from '@oclif/core'; -import type { ApifyClient, KeyValueStore, KeyValueStoreClient } from 'apify-client'; import { ApifyCommand } from '../../lib/apify_command.js'; +import { tryToGetKeyValueStore } from '../../lib/commands/storages.js'; import { error, simpleLog } from '../../lib/outputs.js'; -import { getLocalUserInfo, getLoggedClientOrThrow } from '../../lib/utils.js'; +import { getLoggedClientOrThrow } from '../../lib/utils.js'; export class KeyValueStoresGetValueCommand extends ApifyCommand { static override description = 'Gets a value by key in the given key-value store.'; @@ -33,13 +33,14 @@ export class KeyValueStoresGetValueCommand extends ApifyCommand { - const byIdOrName = await client - .keyValueStore(keyValueStoreId) - .get() - .catch(() => undefined); - - if (byIdOrName) { - return { - store: byIdOrName, - storeClient: client.keyValueStore(byIdOrName.id), - }; - } - - const info = await getLocalUserInfo(); - - const byName = await client - .keyValueStore(`${info.username!}/${keyValueStoreId}`) - .get() - .catch(() => undefined); - - if (byName) { - return { - store: byName, - storeClient: client.keyValueStore(byName.id), - }; - } - - error({ message: `Key-value store with ID "${keyValueStoreId}" not found.` }); - - return null; - } } diff --git a/src/commands/key-value-stores/keys.ts b/src/commands/key-value-stores/keys.ts new file mode 100644 index 000000000..33a60f473 --- /dev/null +++ b/src/commands/key-value-stores/keys.ts @@ -0,0 +1,76 @@ +import { Args, Flags } from '@oclif/core'; + +import { ApifyCommand } from '../../lib/apify_command.js'; +import { prettyPrintBytes } from '../../lib/commands/pretty-print-bytes.js'; +import { CompactMode, ResponsiveTable } from '../../lib/commands/responsive-table.js'; +import { tryToGetKeyValueStore } from '../../lib/commands/storages.js'; +import { error, simpleLog } from '../../lib/outputs.js'; +import { getLoggedClientOrThrow } from '../../lib/utils.js'; + +const table = new ResponsiveTable({ + allColumns: ['Key', 'Size'], + mandatoryColumns: ['Key', 'Size'], +}); + +export class KeyValueStoresKeysCommand extends ApifyCommand { + static override description = 'Lists all keys in a key-value store.'; + + static override hiddenAliases = ['kvs:keys']; + + static override flags = { + limit: Flags.integer({ + description: 'The maximum number of keys to return.', + default: 20, + }), + 'exclusive-start-key': Flags.string({ + description: 'The key to start the list from.', + }), + }; + + static override args = { + storeId: Args.string({ + description: 'The key-value store ID to list keys for.', + required: true, + }), + }; + + static override enableJsonFlag = true; + + async run() { + const { storeId } = this.args; + const { limit, exclusiveStartKey } = this.flags; + + const apifyClient = await getLoggedClientOrThrow(); + const maybeStore = await tryToGetKeyValueStore(apifyClient, storeId); + + if (!maybeStore) { + error({ + message: `Key-value store with ID or name "${storeId}" not found.`, + }); + + return; + } + + const { keyValueStoreClient: client } = maybeStore; + + const keys = await client.listKeys({ limit, exclusiveStartKey }); + + if (this.flags.json) { + return keys; + } + + for (const keyData of keys.items) { + table.pushRow({ + Key: keyData.key, + Size: prettyPrintBytes({ bytes: keyData.size, shortBytes: true, precision: 0 }), + }); + } + + simpleLog({ + message: table.render(CompactMode.WebLikeCompact), + stdout: true, + }); + + return undefined; + } +} diff --git a/src/commands/key-value-stores/set-value.ts b/src/commands/key-value-stores/set-value.ts new file mode 100644 index 000000000..671b808a1 --- /dev/null +++ b/src/commands/key-value-stores/set-value.ts @@ -0,0 +1,71 @@ +import { Args, Flags } from '@oclif/core'; +import type { ApifyApiError } from 'apify-client'; + +import { ApifyCommand } from '../../lib/apify_command.js'; +import { tryToGetKeyValueStore } from '../../lib/commands/storages.js'; +import { error, success } from '../../lib/outputs.js'; +import { getLoggedClientOrThrow } from '../../lib/utils.js'; + +export class KeyValueStoresSetValueCommand extends ApifyCommand { + static override description = 'Sets a value in a key-value store.'; + + static override hiddenAliases = ['kvs:set-value']; + + static override flags = { + 'content-type': Flags.string({ + description: 'The MIME content type of the value. By default, "application/json" is assumed.', + default: 'application/json', + }), + }; + + static override args = { + storeId: Args.string({ + description: 'The key-value store ID to set the value in.', + required: true, + ignoreStdin: true, + }), + itemKey: Args.string({ + description: 'The key of the item in the key-value store.', + required: true, + ignoreStdin: true, + }), + value: Args.string({ + description: 'The value to set.', + ignoreStdin: true, + }), + }; + + async run() { + const { storeId, itemKey, value } = this.args; + const { contentType } = this.flags; + + const apifyClient = await getLoggedClientOrThrow(); + const maybeStore = await tryToGetKeyValueStore(apifyClient, storeId); + + if (!maybeStore) { + error({ + message: `Key-value store with ID or name "${storeId}" not found.`, + }); + + return; + } + + const { keyValueStoreClient: client } = maybeStore; + + try { + // TODO: again, the types need to be fixed -w- + await client.setRecord({ key: itemKey, value: (value || process.stdin) as string, contentType }); + + success({ + message: `Value with key "${itemKey}" set in the key-value store.`, + stdout: true, + }); + } catch (err) { + const casted = err as ApifyApiError; + + error({ + message: `Failed to set value with key "${itemKey}" in the key-value store.\n ${casted.message || casted}`, + }); + } + } +}