From d0c1285dce1f3412902e8d6a17c5f3b2aa79b870 Mon Sep 17 00:00:00 2001 From: Ivan Kiral Date: Thu, 22 Aug 2024 10:16:05 +0200 Subject: [PATCH] rework API types --- src/commands/clean/clean.ts | 16 +++- src/commands/importExport/export.ts | 10 +- src/commands/importExport/import.ts | 10 +- src/commands/migrations/run/run.ts | 15 +-- src/commands/syncContent/run/run.ts | 95 ++++++------------- src/commands/syncModel/diff/diff.ts | 11 ++- src/modules/importExport/clean.ts | 9 +- src/modules/importExport/export.ts | 9 +- src/modules/importExport/import.ts | 11 +-- .../importExport/utils/includeExclude.ts | 23 ++++- src/modules/migrations/add.ts | 9 +- src/modules/migrations/run.ts | 25 ++--- src/modules/sync/diffEnvironments.ts | 19 ++-- src/modules/sync/syncModelExport.ts | 9 +- src/modules/sync/syncModelRun.ts | 13 +-- src/modules/syncContent/syncContent.ts | 11 ++- src/modules/syncContent/syncContentExport.ts | 10 +- src/modules/syncContent/syncContentRun.ts | 50 +++++----- src/utils/types.ts | 27 ++++++ .../unit/syncContent/deliveryHelpers.test.ts | 20 ++-- 20 files changed, 222 insertions(+), 180 deletions(-) diff --git a/src/commands/clean/clean.ts b/src/commands/clean/clean.ts index e7e474d0..e99a731c 100644 --- a/src/commands/clean/clean.ts +++ b/src/commands/clean/clean.ts @@ -1,10 +1,17 @@ import chalk from "chalk"; import { logError, LogOptions } from "../../log.js"; -import { CleanEntityChoices, cleanEntityChoices, cleanEnvironmentInternal } from "../../modules/importExport/clean.js"; +import { + CleanEntityChoices, + cleanEntityChoices, + cleanEnvironmentInternal, + CleanEnvironmentParams, +} from "../../modules/importExport/clean.js"; +import { resolveIncludeExcludeCliParams } from "../../modules/importExport/utils/includeExclude.js"; import { requestConfirmation } from "../../modules/sync/utils/consoleHelpers.js"; import { RegisterCommand } from "../../types/yargs.js"; import { createClient } from "../../utils/client.js"; +import { omit } from "../../utils/object.js"; export const commandName = "clean"; @@ -75,7 +82,7 @@ const cleanEnvironmentCli = async ( const client = createClient({ environmentId: params.environmentId, apiKey: params.apiKey, commandName }); try { - await cleanEnvironmentInternal(params, client); + await cleanEnvironmentInternal(resolveParams(params), client); } catch (e) { handleError(params, e); } @@ -92,3 +99,8 @@ const handleError = ( process.exit(1); }; + +const resolveParams = (params: CleanEnvironmentCliParams): CleanEnvironmentParams => ({ + ...omit(params, ["include", "exclude"]), + ...resolveIncludeExcludeCliParams(params), +}); diff --git a/src/commands/importExport/export.ts b/src/commands/importExport/export.ts index ef9b94c3..9bae1530 100644 --- a/src/commands/importExport/export.ts +++ b/src/commands/importExport/export.ts @@ -3,10 +3,13 @@ import { ExportEntityChoices, exportEntityChoices, exportEnvironmentInternal, + ExportEnvironmentParams, } from "../../modules/importExport/export.js"; +import { resolveIncludeExcludeCliParams } from "../../modules/importExport/utils/includeExclude.js"; import { RegisterCommand } from "../../types/yargs.js"; import { createClient } from "../../utils/client.js"; import { simplifyErrors } from "../../utils/error.js"; +import { omit } from "../../utils/object.js"; const commandName = "export"; @@ -68,9 +71,14 @@ const exportEnvironmentCli = async (params: ExportEnvironmentCliParams): Promise }); try { - await exportEnvironmentInternal(client, params); + await exportEnvironmentInternal(client, resolveParams(params)); } catch (e) { logError(params, `${JSON.stringify(e, Object.getOwnPropertyNames(e))}\nStopping export...`); process.exit(1); } }; + +const resolveParams = (params: ExportEnvironmentCliParams): ExportEnvironmentParams => ({ + ...omit(params, ["include", "exclude"]), + ...resolveIncludeExcludeCliParams(params), +}); diff --git a/src/commands/importExport/import.ts b/src/commands/importExport/import.ts index bd6552c6..2bc1fe86 100644 --- a/src/commands/importExport/import.ts +++ b/src/commands/importExport/import.ts @@ -3,10 +3,13 @@ import { ImportEntityChoices, importEntityChoices, importEnvironmentInternal, + ImportEnvironmentParams, } from "../../modules/importExport/import.js"; +import { resolveIncludeExcludeCliParams } from "../../modules/importExport/utils/includeExclude.js"; import { RegisterCommand } from "../../types/yargs.js"; import { createClient } from "../../utils/client.js"; import { simplifyErrors } from "../../utils/error.js"; +import { omit } from "../../utils/object.js"; const commandName = "import"; @@ -71,9 +74,14 @@ const importEnvironmentCli = async (params: ImportEnvironmentCliParams) => { }); try { - await importEnvironmentInternal(client, params); + await importEnvironmentInternal(client, resolveParams(params)); } catch (e: unknown) { logError(params, `${JSON.stringify(e, Object.getOwnPropertyNames(e))}\nStopping import...`); process.exit(1); } }; + +const resolveParams = (params: ImportEnvironmentCliParams): ImportEnvironmentParams => ({ + ...omit(params, ["include", "exclude"]), + ...resolveIncludeExcludeCliParams(params), +}); diff --git a/src/commands/migrations/run/run.ts b/src/commands/migrations/run/run.ts index bbf25ad6..67a30cbd 100644 --- a/src/commands/migrations/run/run.ts +++ b/src/commands/migrations/run/run.ts @@ -170,17 +170,20 @@ const runMigrationsCli = async (params: RunMigrationsCliParams) => { const resolveParams = ( params: RunMigrationsCliParams, -): WithErr => - match(params) +): WithErr => { + const emptyParams = { next: undefined, name: undefined, range: undefined, all: undefined }; + + return match(params) .returnType>() - .with({ next: P.nonNullable }, ({ next }) => ({ value: { ...params, next } })) + .with({ next: P.nonNullable }, ({ next }) => ({ value: { ...params, ...emptyParams, next } })) .with({ range: P.nonNullable }, ({ range }) => { const parsedRange = parseRange(range as string); if ("err" in parsedRange) { return parsedRange; } - return { value: { ...params, range: parsedRange.value } }; + return { value: { ...params, ...emptyParams, range: parsedRange.value } }; }) - .with({ name: P.nonNullable }, ({ name }) => ({ value: { ...params, name } })) - .with({ all: P.nonNullable }, ({ all }) => ({ value: { ...params, all } })) + .with({ name: P.nonNullable }, ({ name }) => ({ value: { ...params, ...emptyParams, name } })) + .with({ all: P.nonNullable }, ({ all }) => ({ value: { ...params, ...emptyParams, all } })) .otherwise(() => ({ err: "Invalid parameters" })); +}; diff --git a/src/commands/syncContent/run/run.ts b/src/commands/syncContent/run/run.ts index e1e070ba..2741f690 100644 --- a/src/commands/syncContent/run/run.ts +++ b/src/commands/syncContent/run/run.ts @@ -1,12 +1,7 @@ -import { createDeliveryClient } from "@kontent-ai/delivery-sdk"; -import { extractAsync, importAsync, migrateAsync } from "@kontent-ai/migration-toolkit"; -import chalk from "chalk"; import { match, P } from "ts-pattern"; -import { logError, logInfo, LogOptions } from "../../../log.js"; -import { requestConfirmation } from "../../../modules/sync/utils/consoleHelpers.js"; -import { getItemsCodenames } from "../../../modules/syncContent/syncContent.js"; -import { SyncContentRunParams } from "../../../modules/syncContent/syncContentRun.js"; +import { logError, LogOptions } from "../../../log.js"; +import { syncContentRunIntenal, SyncContentRunParams } from "../../../modules/syncContent/syncContentRun.js"; import { RegisterCommand } from "../../../types/yargs.js"; import { simplifyErrors } from "../../../utils/error.js"; import { omit } from "../../../utils/object.js"; @@ -140,62 +135,7 @@ type SyncContentRunCliParams = const syncContentRunCli = async (params: SyncContentRunCliParams) => { const resolvedParams = resolveParams(params); - if ("filename" in resolvedParams) { - const data = await extractAsync({ filename: params.filename }); - - await importAsync({ - data: data, - environmentId: params.targetEnvironmentId, - apiKey: params.targetApiKey, - }); - - process.exit(0); - } - - const deliveryClient = createDeliveryClient({ - environmentId: resolvedParams.sourceEnvironmentId, - previewApiKey: resolvedParams.sourceDeliveryPreviewKey, - defaultQueryConfig: { - usePreviewMode: true, - }, - }); - - const itemsCodenames = await getItemsCodenames(deliveryClient, resolvedParams); - - if (!itemsCodenames.length) { - logInfo(params, "standard", `No items to migrate`); - process.exit(0); - } - - logInfo( - params, - "standard", - `Syncing ${itemsCodenames.length} items from ${resolvedParams.sourceEnvironmentId} to ${resolvedParams.targetEnvironmentId} ${ - itemsCodenames.length < 100 ? `with codenames:\n${itemsCodenames.join("\n")}` : "" - }`, - ); - - const warningMessage = chalk.yellow( - `⚠ Running this operation may result in changes to the content in environment ${params.targetEnvironmentId}. OK to proceed y/n? (suppress this message with --skipConfirmation parameter)\n`, - ); - - const confirmed = !params.skipConfirmation ? await requestConfirmation(warningMessage) : true; - - if (!confirmed) { - logInfo(params, "standard", chalk.red("Operation aborted.")); - process.exit(1); - } - - await migrateAsync({ - targetEnvironment: { apiKey: params.targetApiKey, environmentId: params.targetEnvironmentId }, - sourceEnvironment: { - environmentId: resolvedParams.sourceEnvironmentId, - apiKey: resolvedParams.sourceApiKey, - items: itemsCodenames.map(i => ({ itemCodename: i, languageCodename: resolvedParams.language })), - }, - }); - - logInfo(params, "standard", `All items sucessfuly migrated`); + syncContentRunIntenal(resolvedParams, "sync-content-run"); }; const resolveParams = (params: SyncContentRunCliParams): SyncContentRunParams => { @@ -206,12 +146,31 @@ const resolveParams = (params: SyncContentRunCliParams): SyncContentRunParams => } const filterParams = match(params) - .with({ items: P.nonNullable }, ({ items }) => ({ ...ommited, items })) - .with({ byTypesCodenames: P.nonNullable }, ({ byTypesCodenames }) => ({ ...ommited, byTypesCodenames })) - .with({ filter: P.nonNullable }, ({ filter }) => ({ ...ommited, filter })) - .with({ last: P.nonNullable }, ({ last }) => ({ ...ommited, last })) + .with( + { items: P.nonNullable, depth: P.nonNullable, sourceDeliveryPreviewKey: P.nonNullable }, + ({ items, depth, sourceDeliveryPreviewKey }) => ({ ...ommited, items, depth, sourceDeliveryPreviewKey }), + ) + .with( + { items: P.nonNullable }, + ({ items }) => ({ ...ommited, items }), + ) + .with( + { byTypesCodenames: P.nonNullable, sourceDeliveryPreviewKey: P.nonNullable }, + ({ byTypesCodenames, sourceDeliveryPreviewKey }) => ({ ...ommited, byTypesCodenames, sourceDeliveryPreviewKey }), + ) + .with( + { filter: P.nonNullable, sourceDeliveryPreviewKey: P.nonNullable }, + ({ filter, sourceDeliveryPreviewKey }) => ({ ...ommited, filter, sourceDeliveryPreviewKey }), + ) + .with( + { last: P.nonNullable, sourceDeliveryPreviewKey: P.nonNullable }, + ({ last, sourceDeliveryPreviewKey }) => ({ ...ommited, last, sourceDeliveryPreviewKey }), + ) .otherwise(() => { - logError(params, "You need to provide exactly one from parameters: items, byTypesCodenames, filter or last"); + logError( + params, + "You need to provide exactly one from parameters: --items or --items with --depth, --filter, --byTypesCodenames, --last with --sourceDeliveryPeviewKey", + ); process.exit(1); }); diff --git a/src/commands/syncModel/diff/diff.ts b/src/commands/syncModel/diff/diff.ts index 5c9117cb..fb009206 100644 --- a/src/commands/syncModel/diff/diff.ts +++ b/src/commands/syncModel/diff/diff.ts @@ -85,8 +85,8 @@ const diffEnvironmentsCli = async (params: DiffEnvironmentsCliParams) => { await diffEnvironmentsInternal(resolvedParams, commandName); }; -const resolveParams = (params: DiffEnvironmentsCliParams): DiffEnvironmentsParams => - match(params) +const resolveParams = (params: DiffEnvironmentsCliParams): DiffEnvironmentsParams => { + const baseParams = match(params) .with( { sourceEnvironmentId: P.nonNullable, sourceApiKey: P.nonNullable }, ({ sourceEnvironmentId, sourceApiKey }) => ({ ...params, sourceEnvironmentId, sourceApiKey }), @@ -99,3 +99,10 @@ const resolveParams = (params: DiffEnvironmentsCliParams): DiffEnvironmentsParam ); process.exit(1); }); + + if (!params.advanced) { + return { ...baseParams, advanced: false }; + } + + return { ...baseParams, advanced: true }; +}; diff --git a/src/modules/importExport/clean.ts b/src/modules/importExport/clean.ts index 75a9aba4..1ec3de81 100644 --- a/src/modules/importExport/clean.ts +++ b/src/modules/importExport/clean.ts @@ -44,13 +44,14 @@ export const cleanEntityChoices = cleanEntityDefinitions.map(e => e.name); export type CleanEntityChoices = typeof cleanEntityChoices[number]; -export type CleanEnvironmentParams = - & Readonly<{ +export type CleanEnvironmentParams = Readonly< + & { environmentId: string; apiKey: string; - }> + } & IncludeExclude - & LogOptions; + & LogOptions +>; export const cleanEnvironment = async (params: CleanEnvironmentParams) => { const client = createClient({ environmentId: params.environmentId, apiKey: params.apiKey, commandName: "clean-API" }); diff --git a/src/modules/importExport/export.ts b/src/modules/importExport/export.ts index 6519222b..df059cc0 100644 --- a/src/modules/importExport/export.ts +++ b/src/modules/importExport/export.ts @@ -54,14 +54,15 @@ export const exportEntityChoices = exportEntityDefinitions.map(e => e.name); export type ExportEntityChoices = typeof exportEntityChoices[number]; -export type ExportEnvironmentParams = - & Readonly<{ +export type ExportEnvironmentParams = Readonly< + & { environmentId: string; fileName?: string; apiKey: string; - }> + } & IncludeExclude - & LogOptions; + & LogOptions +>; export const exportEnvironment = async (params: ExportEnvironmentParams) => { const client = createClient({ diff --git a/src/modules/importExport/import.ts b/src/modules/importExport/import.ts index 3084884e..6af08fc8 100644 --- a/src/modules/importExport/import.ts +++ b/src/modules/importExport/import.ts @@ -53,16 +53,15 @@ export const importEntityChoices = importEntityDefinitions.filter(e => !("isDepe export type ImportEntityChoices = typeof importEntityChoices[number]; -export type ImportEnvironmentParams = - & Readonly<{ +export type ImportEnvironmentParams = Readonly< + & { environmentId: string; fileName: string; apiKey: string; - include?: ReadonlyArray; - exclude?: ReadonlyArray; - }> + } & IncludeExclude - & LogOptions; + & LogOptions +>; export const importEnvironment = async (params: ImportEnvironmentParams) => { const client = createClient({ diff --git a/src/modules/importExport/utils/includeExclude.ts b/src/modules/importExport/utils/includeExclude.ts index a83d7c27..0cf6c8a4 100644 --- a/src/modules/importExport/utils/includeExclude.ts +++ b/src/modules/importExport/utils/includeExclude.ts @@ -1,6 +1,21 @@ -export type IncludeExclude = - | { include: ReadonlyArray } - | { exclude?: ReadonlyArray }; // Exclude is optional because it's possible to use the type without +import { match, P } from "ts-pattern"; + +export type IncludeExclude = Readonly< + | { include?: ReadonlyArray; exclude?: undefined } + | { exclude?: ReadonlyArray; include?: undefined } +>; export const includeExcludePredicate = (params: IncludeExclude) => (entity: Readonly<{ name: string }>) => - "include" in params ? params.include.includes(entity.name) : !params.exclude?.includes(entity.name); + match(params) + .with({ include: P.nonNullable }, ({ include }) => include.includes(entity.name)) + .with({ exclude: P.nonNullable }, ({ exclude }) => !exclude.includes(entity.name)) + .otherwise(() => true); + +export const resolveIncludeExcludeCliParams = ( + params: { include?: ReadonlyArray; exclude?: ReadonlyArray }, +): IncludeExclude => + match(params) + .returnType>() + .with({ include: P.nonNullable }, ({ include }) => ({ ...params, include, exclude: undefined })) + .with({ exclude: P.nonNullable }, ({ exclude }) => ({ ...params, exclude, include: undefined })) + .otherwise(() => ({ ...params, include: undefined, exclude: undefined })); diff --git a/src/modules/migrations/add.ts b/src/modules/migrations/add.ts index dba99f36..e1a6e8d0 100644 --- a/src/modules/migrations/add.ts +++ b/src/modules/migrations/add.ts @@ -12,14 +12,15 @@ import { getMigrationName, } from "./utils/migrationUtils.js"; -export type AddMigrationParams = - & Readonly<{ +export type AddMigrationParams = Readonly< + & { name: string; migrationsFolder?: string; timestamp: boolean; type: string; - }> - & LogOptions; + } + & LogOptions +>; export const addMigration = async (params: AddMigrationParams) => { if (params.type !== "js" && params.type !== "ts") { diff --git a/src/modules/migrations/run.ts b/src/modules/migrations/run.ts index 75a6630e..5e971133 100644 --- a/src/modules/migrations/run.ts +++ b/src/modules/migrations/run.ts @@ -3,6 +3,7 @@ import * as path from "path"; import { logInfo, LogOptions } from "../../log.js"; import { createClient } from "../../utils/client.js"; +import { AnyOnePropertyOf } from "../../utils/types.js"; import { Migration, MigrationOrder } from "./models/migration.js"; import { MigrationOperation, MigrationStatus, SaveStatus, Status, StatusPlugin } from "./models/status.js"; import { handleErr, WithErr } from "./utils/errUtils.js"; @@ -23,8 +24,8 @@ import { writeStatus, } from "./utils/statusUtils.js"; -export type RunMigrationsParams = - & Readonly<{ +export type RunMigrationsParams = Readonly< + & { environmentId: string; apiKey: string; migrationsFolder: string; @@ -32,20 +33,14 @@ export type RunMigrationsParams = statusPlugins?: string; continueOnError?: boolean; force?: boolean; - }> + } & RunMigrationFilterParams - & LogOptions; - -export type RunMigrationFilterParams = - | Readonly<{ name: string }> - | Readonly<{ - range: { - from: MigrationOrder; - to: MigrationOrder; - }; - }> - | Readonly<{ all: boolean }> - | Readonly<{ next: number }>; + & LogOptions +>; + +export type RunMigrationFilterParams = AnyOnePropertyOf< + { name: string; range: { from: MigrationOrder; to: MigrationOrder }; all: boolean; next: number } +>; export const runMigrations = async (params: RunMigrationsParams) => { const client = createClient({ diff --git a/src/modules/sync/diffEnvironments.ts b/src/modules/sync/diffEnvironments.ts index ea31cd28..fb2b26bc 100644 --- a/src/modules/sync/diffEnvironments.ts +++ b/src/modules/sync/diffEnvironments.ts @@ -11,23 +11,24 @@ import { readContentModelFromFolder, } from "./utils/getContentModel.js"; -export type DiffEnvironmentsParams = - & Readonly<{ +export type DiffEnvironmentsParams = Readonly< + & { targetEnvironmentId: string; targetApiKey: string; - }> + } & ( - | Readonly<{ folderName: string }> - | Readonly<{ sourceEnvironmentId: string; sourceApiKey: string }> + | { folderName: string } + | { folderName?: undefined; sourceEnvironmentId: string; sourceApiKey: string } ) & ( - Readonly<{ + { advanced: true; outPath?: string; noOpen?: boolean; - }> | { advanced?: false } + } | { advanced?: false } ) - & LogOptions; + & LogOptions +>; export const diffEnvironments = async (params: DiffEnvironmentsParams) => { await diffEnvironmentsInternal(params, "diff-API"); @@ -42,7 +43,7 @@ export const diffEnvironmentsInternal = async (params: DiffEnvironmentsParams, c } and target environment ${chalk.blue(params.targetEnvironmentId)}\n`, ); - const sourceModel = "folderName" in params + const sourceModel = "folderName" in params && params.folderName !== undefined ? await readContentModelFromFolder(params.folderName) : transformSyncModel( await fetchModel( diff --git a/src/modules/sync/syncModelExport.ts b/src/modules/sync/syncModelExport.ts index d7bd936e..1b96e23f 100644 --- a/src/modules/sync/syncModelExport.ts +++ b/src/modules/sync/syncModelExport.ts @@ -5,13 +5,14 @@ import { logInfo, LogOptions } from "../../log.js"; import { createClient } from "../../utils/client.js"; import { fetchModel, saveSyncModel, transformSyncModel } from "./generateSyncModel.js"; -export type SyncModelExportParams = - & Readonly<{ +export type SyncModelExportParams = Readonly< + & { environmentId: string; apiKey: string; folderName?: string; - }> - & LogOptions; + } + & LogOptions +>; export const syncModelExport = async (params: SyncModelExportParams) => { await syncModelExportInternal( diff --git a/src/modules/sync/syncModelRun.ts b/src/modules/sync/syncModelRun.ts index bba3123d..ddd49515 100644 --- a/src/modules/sync/syncModelRun.ts +++ b/src/modules/sync/syncModelRun.ts @@ -13,16 +13,17 @@ import { } from "./utils/getContentModel.js"; import { validateDiffedModel, validateSyncModelFolder } from "./validation.js"; -export type SyncModelRunParams = - & Readonly<{ +export type SyncModelRunParams = Readonly< + & { targetEnvironmentId: string; targetApiKey: string; - }> + } & ( - | Readonly<{ folderName: string }> - | Readonly<{ sourceEnvironmentId: string; sourceApiKey: string }> + | { folderName: string } + | { sourceEnvironmentId: string; sourceApiKey: string } ) - & LogOptions; + & LogOptions +>; export const syncModelRun = async (params: SyncModelRunParams) => { const commandName = "sync-model-run-API"; diff --git a/src/modules/syncContent/syncContent.ts b/src/modules/syncContent/syncContent.ts index b4619a1a..7e2f1d29 100644 --- a/src/modules/syncContent/syncContent.ts +++ b/src/modules/syncContent/syncContent.ts @@ -2,6 +2,7 @@ import { DeliveryClient, IContentItem, IContentItemElements, Responses } from "@ import { match, P } from "ts-pattern"; import { notNullOrUndefined } from "../../utils/typeguards.js"; +import { SuperiorOmit } from "../../utils/types.js"; import { SyncContentFilterParams } from "./syncContentRun.js"; import { createDeliveryUrlParameters } from "./utils/deliveryHelpers.js"; @@ -9,7 +10,9 @@ const deliveryApiItemsLimit = 2000; type SyncContentFilterDeliveryOnlyParams = | Exclude }> - | Readonly<{ items: ReadonlyArray; depth: number; limit?: number }>; + | Readonly< + { items: ReadonlyArray; depth: number; limit?: number; sourceDeliveryPreviewKey: string; language: string } + >; // { items: ReadonlyArray; depth: number; limit?: number } is assignable to { items: ReadonlyArray } // therefore it was also removed by Exclude @@ -39,8 +42,10 @@ export const getItemsCodenames = async ( .then(res => res.data.responses.flatMap(r => [...extractItemsCodenamesFromResponse(r.data)])); }; -export const getDeliveryUrlParams = (params: SyncContentFilterDeliveryOnlyParams) => { - const defaultParams = { ...params }; +export const getDeliveryUrlParams = ( + params: SuperiorOmit, +) => { + const defaultParams = { ...params, depth: params.depth ?? 0 }; return match(params) .with( diff --git a/src/modules/syncContent/syncContentExport.ts b/src/modules/syncContent/syncContentExport.ts index 158f84c5..57c9a43f 100644 --- a/src/modules/syncContent/syncContentExport.ts +++ b/src/modules/syncContent/syncContentExport.ts @@ -5,15 +5,15 @@ import { createClientDelivery } from "../../utils/client.js"; import { getItemsCodenames } from "./syncContent.js"; import { SyncContentFilterParams } from "./syncContentRun.js"; -export type SyncContentExportParams = - & Readonly<{ +export type SyncContentExportParams = Readonly< + & { sourceEnvironmentId: string; sourceApiKey: string; - language: string; filename?: string; - }> + } & SyncContentFilterParams - & LogOptions; + & LogOptions +>; export const syncContentExport = async (params: SyncContentExportParams) => { await syncContentExportInternal(params, "sync-content-export-API"); diff --git a/src/modules/syncContent/syncContentRun.ts b/src/modules/syncContent/syncContentRun.ts index 022c3a59..15a603da 100644 --- a/src/modules/syncContent/syncContentRun.ts +++ b/src/modules/syncContent/syncContentRun.ts @@ -4,39 +4,41 @@ import { logInfo, LogOptions } from "../../log.js"; import { createClientDelivery } from "../../utils/client.js"; import { getItemsCodenames } from "./syncContent.js"; -export type SyncContentRunParams = - & Readonly<{ +export type SyncContentRunParams = Readonly< + & { targetEnvironmentId: string; targetApiKey: string; skipFailedItems?: boolean; - }> + } & ( - | Readonly< - & { - sourceEnvironmentId: string; - sourceApiKey: string; - language: string; - } + | { + sourceEnvironmentId: string; + sourceApiKey: string; + } & SyncContentFilterParams - > - | Readonly<{ filename: string }> + | { filename: string } ) - & LogOptions; + & LogOptions +>; -export type SyncContentFilterParams = - | Readonly<{ items: ReadonlyArray }> - | ( - & ( - | Readonly<{ items: ReadonlyArray; depth: number; limit?: number }> - | ( - | Readonly<{ last: number }> - | Readonly<{ byTypesCodenames: ReadonlyArray }> - | Readonly<{ filter: string }> +export type SyncContentFilterParams = Readonly< + & ( + | { items: ReadonlyArray } + | ( + & ( + | { items: ReadonlyArray; depth: number; limit?: number } + | ( + | { last: number } + | { byTypesCodenames: ReadonlyArray } + | { filter: string } + ) + & { depth?: number; limit?: number } ) - & Readonly<{ depth?: number; limit?: number }> + & { sourceDeliveryPreviewKey: string } ) - & { sourceDeliveryPreviewKey: string } - ); + ) + & { language: string } +>; export const syncContentRun = async (params: SyncContentRunParams) => { await syncContentRunIntenal(params, "sync-content-run-API"); diff --git a/src/utils/types.ts b/src/utils/types.ts index f08fd7df..cfad1566 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -40,3 +40,30 @@ export type SuperiorPick = T extends any ? { * Original Omit type extended to work on Union type objects */ export type SuperiorOmit = T extends any ? SuperiorPick> : never; + +/** + * A utility type that takes an object type and returns an union type representing + * objects with one property from the `Obj` with all other properties set to `undefined`. + * + * The type ensures that only one property of the object can be defined at a time, + * with all other properties explicitly set to `undefined` or omitted. This is useful + * for cases where only one field is allowed to be set, commonly used in discriminated unions. + * + * @example + * // Given an object type + * type User = { + * id: number; + * name: string; + * email: string; + * }; + * + * // `AnyOnePropertyOf` will be equivalent to: + * // { id: number; name?: undefined; email?: undefined } | + * // { id?: undefined; name: string; email?: undefined } | + * // { id?: undefined; name?: undefined; email: string; } + */ +export type AnyOnePropertyOf = [keyof Obj, keyof Obj] extends + [infer Key, infer AllKeys extends keyof Obj] + ? Key extends keyof Obj ? { [K in Key]: Obj[K] } & { [K in Exclude]?: undefined } + : never + : never; diff --git a/tests/unit/syncContent/deliveryHelpers.test.ts b/tests/unit/syncContent/deliveryHelpers.test.ts index c42ecaa8..f7845f34 100644 --- a/tests/unit/syncContent/deliveryHelpers.test.ts +++ b/tests/unit/syncContent/deliveryHelpers.test.ts @@ -5,9 +5,7 @@ import { extractItemsCodenamesFromResponse, getDeliveryUrlParams, } from "../../../src/modules/syncContent/syncContent.ts"; -import { SyncContentFilterParams } from "../../../src/modules/syncContent/syncContentRun.ts"; import * as deliveryHelpers from "../../../src/modules/syncContent/utils/deliveryHelpers.ts"; -import { Replace } from "../../../src/utils/types.ts"; describe("createDeliveryUrlParameters", () => { it("should return correct parameters for language and last parameter", () => { @@ -48,9 +46,8 @@ describe("createDeliveryUrlParameters", () => { describe("getDeliveryUrlParams", () => { it("should return correct deliver url parameters for last cli param", () => { const last = 10; - const params: Replace = { language: "default", last, limit: last }; - const parameters = getDeliveryUrlParams(params); + const parameters = getDeliveryUrlParams({ language: "default", last, limit: last }); expect(parameters).toStrictEqual([ new Parameters.DepthParameter(0), @@ -62,13 +59,12 @@ describe("getDeliveryUrlParams", () => { it("should return correct deliver url parameters for byTypeCodenames cli param", () => { const byTypesCodenames = ["type1", "type2"]; - const params: Replace = { + + const parameters = getDeliveryUrlParams({ language: "default", limit: 100, byTypesCodenames, - }; - - const parameters = getDeliveryUrlParams(params); + }); expect(parameters).toStrictEqual([ new Parameters.DepthParameter(0), @@ -80,13 +76,13 @@ describe("getDeliveryUrlParams", () => { it("should return correct deliver url parameters for items cli param", () => { const items = ["item1", "item2"]; - const params: Replace = { + + const parameters = getDeliveryUrlParams({ language: "default", limit: 100, + depth: 0, items, - }; - - const parameters = getDeliveryUrlParams(params); + }); expect(parameters).toStrictEqual([ new Parameters.DepthParameter(0),