Files
pages/packages/api-client/src/modules/iso3166/index.ts
Calum H. 7ccc32675b feat: start of cross platform page system (#4731)
* feat: abstract api-client DI into ui package

* feat: cross platform page system

* feat: tanstack as cross platform useAsyncData

* feat: archon servers routes + labrinth billing routes

* fix: dont use partial

* feat: migrate server list page to tanstack + api-client + re-enabled broken features!

* feat: migrate servers manage page to api-client before page system

* feat: migrate manage page to page system

* fix: type issues

* fix: upgrade wrapper bugs

* refactor: move state types into api-client

* feat: disable financial stuff on app frontend

* feat: finalize cross platform page system for now

* fix: lint

* fix: build issues

* feat: remove papaparse

* fix: lint

* fix: interface error

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2025-11-14 17:15:09 +00:00

122 lines
3.2 KiB
TypeScript

import { $fetch } from 'ofetch'
import { AbstractModule } from '../../core/abstract-module'
import type { ISO3166 } from './types'
export type { ISO3166 } from './types'
const ISO3166_REPO = 'https://raw.githubusercontent.com/ipregistry/iso3166/master'
/**
* Parse CSV string into array of objects
* @param csv - CSV string to parse
* @returns Array of objects with header keys mapped to row values
*/
function parseCSV(csv: string): Record<string, string>[] {
const lines = csv
.trim()
.split('\n')
.filter((line) => line.trim() !== '')
if (lines.length === 0) return []
const headerLine = lines[0]
const headers = (headerLine.startsWith('#') ? headerLine.slice(1) : headerLine).split(',')
return lines.slice(1).map((line) => {
const values = line.split(',')
const row: Record<string, string> = {}
headers.forEach((header, index) => {
row[header] = values[index] || ''
})
return row
})
}
/**
* Module for fetching ISO 3166 country and subdivision data
* Data from https://github.com/ipregistry/iso3166 (Licensed under CC BY-SA 4.0)
* @platform Not for use in Tauri or Nuxt environments, only node.
*/
export class ISO3166Module extends AbstractModule {
public getModuleID(): string {
return 'iso3166_data'
}
/**
* Build ISO 3166 country and subdivision data from the ipregistry repository
*
* @returns Promise resolving to countries and subdivisions data
*
* @example
* ```typescript
* const data = await client.iso3166.data.build()
* console.log(data.countries) // Array of country data
* console.log(data.subdivisions['US']) // Array of US state data
* ```
*/
public async build(): Promise<ISO3166.State> {
try {
const [countriesCSV, subdivisionsCSV] = await Promise.all([
$fetch<string>(`${ISO3166_REPO}/countries.csv`, {
// @ts-expect-error supports text
responseType: 'text',
}),
$fetch<string>(`${ISO3166_REPO}/subdivisions.csv`, {
// @ts-expect-error supports text
responseType: 'text',
}),
])
const countriesData = parseCSV(countriesCSV)
const subdivisionsData = parseCSV(subdivisionsCSV)
const countries: ISO3166.Country[] = countriesData.map((c) => ({
alpha2: c.country_code_alpha2,
alpha3: c.country_code_alpha3,
numeric: c.numeric_code,
nameShort: c.name_short,
nameLong: c.name_long,
}))
// Group subdivisions by country code
const subdivisions: Record<string, ISO3166.Subdivision[]> = subdivisionsData.reduce(
(acc, sub) => {
const countryCode = sub.country_code_alpha2
if (!countryCode || typeof countryCode !== 'string' || countryCode.trim() === '') {
return acc
}
if (!acc[countryCode]) acc[countryCode] = []
acc[countryCode].push({
code: sub['subdivision_code_iso3166-2'],
name: sub.subdivision_name,
localVariant: sub.localVariant || null,
category: sub.category,
parent: sub.parent_subdivision || null,
language: sub.language_code,
})
return acc
},
{} as Record<string, ISO3166.Subdivision[]>,
)
return {
countries,
subdivisions,
}
} catch (err) {
console.error('Error fetching ISO3166 data:', err)
return {
countries: [],
subdivisions: {},
}
}
}
}