Skip to content

feat: Typescript #23

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

Merged
merged 24 commits into from
Jun 26, 2025
Merged

feat: Typescript #23

merged 24 commits into from
Jun 26, 2025

Conversation

PuruVJ
Copy link
Contributor

@PuruVJ PuruVJ commented May 6, 2025

CleanShot 2025-05-06 at 15 00 21

A few things about this PR:

  • Keeps the codebase in plain JS, and adding types via JSDoc
  • Adds tsup for bundling(More on that later)
  • Simplifies the codebase into a single file(More on that later)
  • Moved all error handlers to the Api class(Ditto)

Why build step?

I wanna start this sentence with: We do not need a build step. The files are in .js, and will work in runtime. If you import a .js file exposed from a package, into a .js file in a (say sveltekit) app, the typescript will work well there.

The build step however, is there for two possible future cases:

  • We wanna rewrite the package to TypeScript
  • We wanna use this .js package in a .ts package. TypeScript files are strict, and do not infer the types from .js coming from an external package. It strictly demands a .d.ts file attached there, which is where the build step comes in.

So, we actually do not need the build step in the present at all, but if in any of the above cases, a build step generating .d.ts files is must.

Why We Migrated From Mixin Composition to Direct Class Implementation

Our HTTP API client previously used the mixin composition pattern, which created several challenges when integrating TypeScript types.

The Problem

The previous approach:

  • Split error handlers across multiple files
  • Used a complex composition pattern (compose(MixinA, MixinB, ...)(BaseClass))
  • Created a convoluted inheritance chain hard for TypeScript to track
  • Caused bundling issues with tsup/esbuild (like the line 229 error)

TypeScript struggled with this pattern because:

  • It couldn't properly track types through the composition chain
  • this typing became complex across multiple inheritance layers
  • The interplay between JSDoc annotations and dynamic composition confused the bundler, causing it to mysteriously error while generating d.ts file.

The Solution

I consolidated everything into a direct class implementation:

  • Single class with all error handler methods directly defined
  • No complex composition or inheritance chains
  • Grouped related functionality together

This approach works much better with TypeScript because:

  • It follows standard class patterns TypeScript excels at typing
  • Properties and methods can be directly typed without complex inference
  • IDE tooling provides better autocomplete and type hints
  • Bundlers like tsup can process the code more reliably.
  • Slightly smaller bundler size 😁

The runtime behavior remains identical, but we get better developer experience, easier maintenance, and reliable bundling.

@PuruVJ PuruVJ requested a review from tomlewis0 May 6, 2025 09:57
Copy link
Member

@leandrohlsilva leandrohlsilva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM given comments

lib/types.ts Outdated
/** API endpoint */
endpoint: string | null;
/** HTTP method */
method: 'get' | 'GET' | 'post' | 'POST' | 'put' | 'PUT' | 'patch' | 'PATCH' | 'delete' | 'DELETE';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just uppercase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just keeping parity with how fetch itself works

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whilst this is a reasonable goal, I don't think we don't need to strive for fetch parity here. I'd go for one or the other - flexibility is the devil!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha happy to fix it to uppercase

export type RequestConfig = {
/** API endpoint */
endpoint: string | null;
/** HTTP method */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments are irrelevant and can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would digress, these comments show up in the hover popup in IDEs, so lets leave these here. Can be filled with useful info later on

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So these are TSDoc comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Hover wil show it. U can even put code snippets in these

PuruVJ and others added 2 commits May 19, 2025 16:54
Co-authored-by: Leandro Silva <[email protected]>
Co-authored-by: Leandro Silva <[email protected]>
@PuruVJ
Copy link
Contributor Author

PuruVJ commented May 19, 2025

BTW I forgot to add, to be able to take advantage of this, tsconfig of the consumer projects will have to be changed to not ignore the node_modules. It didn't break anything, just starts including type data from node_modules which is precisely is what we need here

Copy link
Member

@antony antony left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just adding some comments. I think it's hard to vote with approvals, so I'd suggest a thumbs up or thumbs down as go/no-go

package.json Outdated
@@ -32,7 +39,8 @@
},
"scripts": {
"test": "mocha './!(node_modules)/**/**.+(spec).js'",
"lint": "eslint lib --ext .js"
"lint": "eslint lib --ext .js",
"compile": "tsup"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused at the concept of no build step, but there is a compile step here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No no, it can have a build or no-build. Its what everyone shud decide. I wrote up in the description

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure compile is the right word here. I'd suggest build instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced it with build

package.json Outdated
@@ -49,5 +57,5 @@
"volta": {
"node": "18.15.0"
},
"packageManager": "pnpm@8.0.0"
"packageManager": "pnpm@9.15.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally this shouldn't be done as part of a PR, but it's a lib so I'm fine with it. Why 9 and not 10?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just put the one which is installed on my machine 😅

lib/types.ts Outdated
/** API endpoint */
endpoint: string | null;
/** HTTP method */
method: 'get' | 'GET' | 'post' | 'POST' | 'put' | 'PUT' | 'patch' | 'PATCH' | 'delete' | 'DELETE';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whilst this is a reasonable goal, I don't think we don't need to strive for fetch parity here. I'd go for one or the other - flexibility is the devil!

lib/index.js Outdated
* @returns {Promise<QueryResult>} Response data
* @throws {HttpError} If the request fails
*/
async #doQuery(attempt, client, endpoint, options) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's with this # syntax? For internal methods I'm more used to seeing _

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

School day. Thank you!

package.json Outdated
@@ -32,7 +39,8 @@
},
"scripts": {
"test": "mocha './!(node_modules)/**/**.+(spec).js'",
"lint": "eslint lib --ext .js"
"lint": "eslint lib --ext .js",
"compile": "tsup"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure compile is the right word here. I'd suggest build instead

PuruVJ added 6 commits June 18, 2025 16:45
- Introduced a new ESLint configuration file that extends recommended and mocha rules, setting ECMAScript version to 2025 and source type to module.
- Created a pnpm workspace configuration to specify only built dependencies, including 'unrs-resolver'.
Copy link

pkg-pr-new bot commented Jun 18, 2025

Open in StackBlitz

npm i https://pkg.pr.new/beyonk-group/http/@beyonk/http@23

commit: ca4ef9e

run: pnpm build

- name: Release
run: pnpm dlx pkg-pr-new publish
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a workflow for this already, it's better that we use it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this: https://github.com/beyonk-group/workflows/blob/main/.github/workflows/canary-libs.yml

Any examples I can check on how to integrate this? Or is it copy-pasta? 😁

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Started using this, now the Action doesn't run 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah IG its because workflows is a private repo while this one is public

Copy link
Member

@antony antony left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a link to show you how to use the shared workflows

@PuruVJ PuruVJ requested a review from antony June 24, 2025 09:43
@antony
Copy link
Member

antony commented Jun 25, 2025

All looks good, but it occurred to me that as you say - a public repo can't use a private workflow!

Might have to revert to the existing pkg.pr.new build file for this, sorry! :/

@PuruVJ PuruVJ merged commit 1ee53b7 into master Jun 26, 2025
3 checks passed
@PuruVJ PuruVJ deleted the feature/by-1465 branch June 26, 2025 10:41
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.

5 participants