Files
AstralRinth/packages/api-client/src/modules/index.ts
T
Truman Gao 681ae5d1d8 refactor: removing useAsyncData for tanstack query (#5262)
* refactor: most places with useAsyncData replaced with tanstack query

* refactor report list and report view

* refactor organization page to use tanstack query

* fix types

* refactor collection page and include proper loading state

* fix followed projects proper loading state

* fix 404 handling

* fix organization loading and 404 states

* pnpm prepr

* refactor: remove useAsyncData on newsletter button

* refactor: remove useAsyncData on auth globals fetch

* refactor: settings/billing/index.vue to useQuery instead of useAsyncData

* refactor: user page to remove useAsyncData

* pnpm prepr

* fix reports pages

* fix notifications page

* fix billing page cannot read properties of null and prop warnings

* fix refresh causing 404 by removing useBaseFetch and use api-client

* fix stale data after removing organization from project

* pnpm prepr

* fix news erroring in build

* fix: project page loads header only after content

* fix: user page tanstack problems (start on migrating away from useBaseFetch)

* fix: start swapping useBaseFetch usages to api-client

* Revert "fix: start swapping useBaseFetch usages to api-client"

This reverts commit 3df3fab11d535159132b1288dd7cacc38282b553.

* fix: remove debug logging

* fix: lint

---------

Co-authored-by: Calum H. <calum@modrinth.com>
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
2026-03-16 19:10:29 +00:00

154 lines
5.7 KiB
TypeScript

import type { AbstractModrinthClient } from '../core/abstract-client'
import type { AbstractModule } from '../core/abstract-module'
import { ArchonBackupsV1Module } from './archon/backups/v1'
import { ArchonContentV1Module } from './archon/content/v1'
import { ArchonOptionsV1Module } from './archon/options/v1'
import { ArchonPropertiesV1Module } from './archon/properties/v1'
import { ArchonServersV0Module } from './archon/servers/v0'
import { ArchonServersV1Module } from './archon/servers/v1'
import { ISO3166Module } from './iso3166'
import { KyrosContentV1Module } from './kyros/content/v1'
import { KyrosFilesV0Module } from './kyros/files/v0'
import { LabrinthVersionsV2Module, LabrinthVersionsV3Module } from './labrinth'
import { LabrinthAuthInternalModule } from './labrinth/auth/internal'
import { LabrinthBillingInternalModule } from './labrinth/billing/internal'
import { LabrinthCollectionsModule } from './labrinth/collections'
import { LabrinthOrganizationsV3Module } from './labrinth/organizations/v3'
import { LabrinthPayoutV3Module } from './labrinth/payout/v3'
import { LabrinthProjectsV2Module } from './labrinth/projects/v2'
import { LabrinthProjectsV3Module } from './labrinth/projects/v3'
import { LabrinthServerPingInternalModule } from './labrinth/server-ping/internal'
import { LabrinthStateModule } from './labrinth/state'
import { LabrinthTechReviewInternalModule } from './labrinth/tech-review/internal'
import { LabrinthThreadsV3Module } from './labrinth/threads/v3'
import { LabrinthUsersV2Module } from './labrinth/users/v2'
import { LauncherMetaManifestV0Module } from './launcher-meta/v0'
import { PaperVersionsV3Module } from './paper/v3'
import { PurpurVersionsV2Module } from './purpur/v2'
type ModuleConstructor = new (client: AbstractModrinthClient) => AbstractModule
/**
* To add a new module:
* 1. Create your module class extending AbstractModule
* 2. Add one line here: `<api>_<module>: YourModuleClass`
*
* TypeScript will automatically infer the client's field structure from this registry.
*
* TODO: Better way? Probably not
*/
export const MODULE_REGISTRY = {
archon_backups_v1: ArchonBackupsV1Module,
archon_content_v1: ArchonContentV1Module,
archon_options_v1: ArchonOptionsV1Module,
archon_properties_v1: ArchonPropertiesV1Module,
archon_servers_v0: ArchonServersV0Module,
archon_servers_v1: ArchonServersV1Module,
iso3166_data: ISO3166Module,
launchermeta_manifest_v0: LauncherMetaManifestV0Module,
kyros_content_v1: KyrosContentV1Module,
kyros_files_v0: KyrosFilesV0Module,
labrinth_auth_internal: LabrinthAuthInternalModule,
labrinth_billing_internal: LabrinthBillingInternalModule,
labrinth_collections: LabrinthCollectionsModule,
labrinth_organizations_v3: LabrinthOrganizationsV3Module,
labrinth_payout_v3: LabrinthPayoutV3Module,
labrinth_projects_v2: LabrinthProjectsV2Module,
labrinth_projects_v3: LabrinthProjectsV3Module,
labrinth_server_ping_internal: LabrinthServerPingInternalModule,
labrinth_state: LabrinthStateModule,
labrinth_tech_review_internal: LabrinthTechReviewInternalModule,
labrinth_threads_v3: LabrinthThreadsV3Module,
labrinth_users_v2: LabrinthUsersV2Module,
labrinth_versions_v2: LabrinthVersionsV2Module,
labrinth_versions_v3: LabrinthVersionsV3Module,
paper_versions_v3: PaperVersionsV3Module,
purpur_versions_v2: PurpurVersionsV2Module,
} as const satisfies Record<string, ModuleConstructor>
export type ModuleID = keyof typeof MODULE_REGISTRY
/**
* Parse a module ID into [api, moduleName] tuple
*
* @param id - Module ID in format `<api>_<module>` (e.g., 'labrinth_projects_v2')
* @returns Tuple of [api, moduleName] (e.g., ['labrinth', 'projects_v2'])
* @throws Error if module ID doesn't match expected format
*/
export function parseModuleID(id: string): [string, string] {
const parts = id.split('_')
if (parts.length < 2) {
throw new Error(
`Invalid module ID "${id}". Expected format: <api>_<module> (e.g., "labrinth_projects_v2")`,
)
}
const api = parts[0]
const moduleName = parts.slice(1).join('_')
return [api, moduleName]
}
/**
* Build nested module structure from flat registry
*
* Transforms:
* ```
* { labrinth_projects_v2: Constructor, labrinth_users_v2: Constructor }
* ```
* Into:
* ```
* { labrinth: { projects_v2: Constructor, users_v2: Constructor } }
* ```
*
* @returns Nested structure organized by API namespace
*/
export function buildModuleStructure(): Record<string, Record<string, ModuleConstructor>> {
const structure: Record<string, Record<string, ModuleConstructor>> = {}
for (const [id, constructor] of Object.entries(MODULE_REGISTRY)) {
const [api, moduleName] = parseModuleID(id)
if (!structure[api]) {
structure[api] = {}
}
structure[api][moduleName] = constructor
}
return structure
}
/**
* Extract API name from module ID
* @example ParseAPI<'labrinth_projects_v2'> = 'labrinth'
*/
type ParseAPI<T extends string> = T extends `${infer API}_${string}` ? API : never
/**
* Extract module name for a given API
* @example ParseModule<'labrinth_projects_v2', 'labrinth'> = 'projects_v2'
*/
type ParseModule<T extends string, API extends string> = T extends `${API}_${infer Module}`
? Module
: never
/**
* Group registry modules by API namespace
*
* Transforms flat registry into nested structure at the type level:
* ```
* { labrinth_projects_v2: ModuleClass } → { labrinth: { projects_v2: ModuleInstance } }
* ```
*/
type GroupByAPI<Registry extends Record<string, ModuleConstructor>> = {
[API in ParseAPI<keyof Registry & string>]: {
[Module in ParseModule<keyof Registry & string, API>]: InstanceType<
Registry[`${API}_${Module}`]
>
}
}
/**
* Inferred client module structure
**/
export type InferredClientModules = GroupByAPI<typeof MODULE_REGISTRY>