Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: key-value-store commands #700

Merged
merged 6 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions src/commands/key-value-stores/delete-value.ts
Original file line number Diff line number Diff line change
@@ -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<typeof KeyValueStoresDeleteValueCommand> {
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}`,
});
}
}
}
44 changes: 5 additions & 39 deletions src/commands/key-value-stores/get-value.ts
Original file line number Diff line number Diff line change
@@ -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<typeof KeyValueStoresGetValueCommand> {
static override description = 'Gets a value by key in the given key-value store.';
Expand Down Expand Up @@ -33,13 +33,14 @@ export class KeyValueStoresGetValueCommand extends ApifyCommand<typeof KeyValueS
const { keyValueStoreId, itemKey } = this.args;

const apifyClient = await getLoggedClientOrThrow();
const maybeStore = await this.tryToGetKeyValueStore(apifyClient, keyValueStoreId);
const maybeStore = await tryToGetKeyValueStore(apifyClient, keyValueStoreId);

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

const { storeClient } = maybeStore;
const { keyValueStoreClient: storeClient } = maybeStore;

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

Expand Down Expand Up @@ -83,39 +84,4 @@ export class KeyValueStoresGetValueCommand extends ApifyCommand<typeof KeyValueS
// pipe the output to stdout
itemRecord.value.pipe(process.stdout);
}

private async tryToGetKeyValueStore(
client: ApifyClient,
keyValueStoreId: string,
): Promise<{ store: KeyValueStore | undefined; storeClient: KeyValueStoreClient } | null> {
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;
}
}
76 changes: 76 additions & 0 deletions src/commands/key-value-stores/keys.ts
Original file line number Diff line number Diff line change
@@ -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<typeof KeyValueStoresKeysCommand> {
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;
}
}
71 changes: 71 additions & 0 deletions src/commands/key-value-stores/set-value.ts
Original file line number Diff line number Diff line change
@@ -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<typeof KeyValueStoresSetValueCommand> {
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}`,
});
}
}
}
Loading