export function requireNonEmptyString(value: unknown, message: string): string {
const stringValue = typeof value === 'string' ? value.trim() : ''
if (!stringValue) throw new Error(message)
return stringValue
}
export function chunkArray<T>(items: T[], size: number): T[][] {
const chunks: T[][] = []
for (let index = 0; index < items.length; index += size) {
chunks.push(items.slice(index, index + size))
}
return chunks
}
export function parseLimit(value: unknown, defaultValue: number, max: number): number {
const parsed = Number(value ?? defaultValue)
if (!Number.isFinite(parsed)) return defaultValue
return Math.min(max, Math.max(1, Math.round(parsed)))
}
export function normalizeSpotifyPlaylistUri(value: unknown): string {
const stringValue = requireNonEmptyString(
value,
'Spotify playlist IDs or URIs must be non-empty strings.',
)
if (stringValue.startsWith('spotify:playlist:')) return stringValue
const match = stringValue.match(/open\.spotify\.com\/playlist\/([^?/#]+)/)
const playlistId = match ? match[1] : stringValue
return 'spotify:playlist:' + playlistId
}
export function normalizeSpotifyTrackId(value: unknown): string {
const stringValue = requireNonEmptyString(
value,
'Spotify track IDs or URIs must be non-empty strings.',
)
if (stringValue.startsWith('spotify:track:')) {
return stringValue.split(':')[2] ?? stringValue
}
const match = stringValue.match(/open\.spotify\.com\/track\/([^?/#]+)/)
return match ? match[1] : stringValue
}
export function normalizeSpotifyTrackUri(value: unknown): string {
return 'spotify:track:' + normalizeSpotifyTrackId(value)
}
export function normalizeTrackUris(uris: unknown): string[] {
if (!Array.isArray(uris)) {
throw new Error('An array of Spotify track URIs is required.')
}
const withUris = uris.map((uri) =>
typeof uri === 'string' && uri.trim() ? normalizeSpotifyTrackUri(uri) : '',
)
const invalid = withUris
.map((uri, index) =>
uri && uri.startsWith('spotify:track:') ? null : JSON.stringify(uris[index] ?? null),
)
.filter((entry) => entry !== null)
if (invalid.length) {
throw new Error(
'Invalid Spotify track URI(s) (expected non-empty strings like spotify:track:xxx): ' +
invalid.join(', '),
)
}
return withUris
}
export function normalizeIdList(values: unknown, message: string, max?: number): string[] {
if (!Array.isArray(values)) throw new Error(message)
const invalid = values
.filter((value) => typeof value !== 'string' || !value.trim())
.map((value) => JSON.stringify(value ?? null))
if (invalid.length) {
throw new Error(message + ' Invalid entries: ' + invalid.join(', '))
}
const ids = values.map((value) => String(value).trim()).map(normalizeSpotifyTrackId)
if (max && ids.length > max) {
throw new Error('Spotify accepts at most ' + max + ' track ids.')
}
return ids
}
export function normalizeArtistNames(artists: unknown): string[] {
return Array.isArray(artists) ? artists.map((artist: any) => artist?.name).filter(Boolean) : []
}
export function isDryRun(params: { dryRun?: unknown } = {}): boolean {
return params.dryRun === true
}
export function dryRunPreview<T extends Record<string, unknown>>(payload: T) {
return { dryRun: true as const, ...payload }
}