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

Typescript Conversion #75

Draft
wants to merge 72 commits into
base: master
Choose a base branch
from
Draft

Conversation

ogrady
Copy link

@ogrady ogrady commented May 3, 2023

Migrate to Typescript.
Fixes #64

This is still a draft and far from done. Some parts are currently not even in a compileable state.

Most parts of the migration are rather straightforward, thanks to the well-structured nature of the current state of gw2-api-client.
The problematic part is the variety of schemas that not always follow a linear inheritance, but sometimes change the structure of the old schema entirely.

Accomodating the Various Schemas

My best guess as to how to solve this is currently the following:
Each endpoint has their various responses defined in the endpoints/schemas/responses directory. If a response can vary based on the selected schema, then that is reflected by exposing the type in various schema versions through namespaces1. See Category, which has changed significantly on 2022-03-23.

Schemas are then bundled in the endpoints/schemas directory.

Each schema files exports an interface Schema which provides the type information for all endpoints (or the encapsulating client) for that specific schema version. Calls to get, all, etc. change their result type accordingly.
Take the Cats response, which has changed from the original schema to the 2019-03-22 schema:

import { Schema as Schema_1970_01_01} from './endpoints/schemas/schema_1970_01_01'
import { Schema as Schema_2022_03_09} from './endpoints/schemas/schema_2022_03_09'
import { CatsEndpoint } from './endpoints/cats'

let oldEndpoint: CatsEndpoint<Schema_1970_01_01>
let newEndpoint: CatsEndpoint<Schema_2022_03_09>

oldEndpoint.get().then(res => res.hint)
//                        ↳ { hint, id }
newEndpoint.get().then(res => res)
//                        ↳ number

I believe this makes the most sense, but requires a bit of voodoo and probably a change in gw2api-clients API, as users would have to explicitely give a schema version while instantiating the client (see below).

As this requires quite a bit of manual work, could you please give your opinion on this to make sure this doesn't go in a completely unmaintainable direction, @queicherius?

EDIT: shoehorning a demonstration of how the instantiation with different schemas would look like and what issues would arise from it:

Different Schema Versions in Action

import { Schema as S1970 } from './endpoints/schemas/schema_1970_01_01'
import { Schema as S2022 } from './endpoints/schemas/schema_2022_03_09'

class Client<S extends Schema> {
    // overloads to enable the user to pass in exact timestamps to automatically select the matching schema
    schema (schema: '1970-01-01T00:00Z'): Client<S1970>;
    schema (schema: '2022-03-09T00:00Z'): Client<S2022>;
    // catchall
    schema <S extends Schema>(schema: string): Client<S>;
    schema <S extends Schema>(schema: string): Client<S> {
      // can't change generic parameter of existing instance → create new instance and return that instead.
      // Obvious drawback: references to old client become stale
      const result = new Client<S>()
      result.schemaVersion = schema
      //return this
      return result      
    }
}

const clientWithOldSchema = new Client<S1970>()
// for exact matches, the generic parameter is inferred from the overloads 👍
const clientWithNewSchema = clientWithOldSchema.schema('2022-03-09T00:00Z')
// for everything else (mind the hour which is off by one), the user has to explicitly pass a schema. 😕
// Note: the user is responsible for matching their string parameter with the schema version to receive correct types. See below. 
const clientWithManuallySetSchema = clientWithOldSchema.schema<S2022>('2022-03-09T01:00Z')
// user didn't match their string with their schema. Suggested types will match the 1970 schema, actual responses will match the S2022 schema 👎
const clientWithFaultySchema = clientWithOldSchema.schema<S1970>('2022-03-09T01:00Z')

clientWithOldSchema.cats().get(42).then(cat => cat)  // {id, hint} 👍
clientWithNewSchema.cats().get(42).then(cat => cat)  // number 👍
clientWithManuallySetSchema.cats().get(42).then(cat => cat)  // number 👍
clientWithFaultySchema.cats().get(42).then(cat => cat)  // {id, hint} 👎 is actually number at runtime

Footnotes

  1. although namespaces are kind of deprecated, I believe they make the most sense here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Migrate to TypeScript
2 participants