Skip to content

feat: Typescript#23

Merged
PuruVJ merged 24 commits into
masterfrom
feature/by-1465
Jun 26, 2025
Merged

feat: Typescript#23
PuruVJ merged 24 commits into
masterfrom
feature/by-1465

Conversation

@PuruVJ

@PuruVJ PuruVJ commented May 6, 2025

Copy link
Copy Markdown
Contributor

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

@leandrohlsilva leandrohlsilva left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM given comments

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

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

Comment thread lib/types.ts
export type RequestConfig = {
/** API endpoint */
endpoint: string | null;
/** HTTP method */

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

Comment thread tsup.config.js Outdated
Comment thread tsconfig.json Outdated
PuruVJ and others added 2 commits May 19, 2025 16:54
Co-authored-by: Leandro Silva <leandrohlsilva@gmail.com>
Co-authored-by: Leandro Silva <leandrohlsilva@gmail.com>
@PuruVJ

PuruVJ commented May 19, 2025

Copy link
Copy Markdown
Contributor Author

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

@antony antony left a comment

Copy link
Copy Markdown
Member

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

Comment thread package.json Outdated
"test": "mocha './!(node_modules)/**/**.+(spec).js'",
"lint": "eslint lib --ext .js"
"lint": "eslint lib --ext .js",
"compile": "tsup"

Copy link
Copy Markdown
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
Copy Markdown
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

Copy link
Copy Markdown

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
Copy Markdown
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

Comment thread package.json Outdated
"node": "18.15.0"
},
"packageManager": "pnpm@8.0.0"
"packageManager": "pnpm@9.15.0"

Copy link
Copy Markdown
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
Copy Markdown
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 😅

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

Copy link
Copy Markdown
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!

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

Copy link
Copy Markdown

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
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

School day. Thank you!

Comment thread package.json Outdated
"test": "mocha './!(node_modules)/**/**.+(spec).js'",
"lint": "eslint lib --ext .js"
"lint": "eslint lib --ext .js",
"compile": "tsup"

Copy link
Copy Markdown

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

Comment thread lib/index.js Outdated
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'.
@pkg-pr-new

pkg-pr-new Bot commented Jun 18, 2025

Copy link
Copy Markdown

Open in StackBlitz

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

commit: ca4ef9e

Comment thread .github/workflows/cr.yml
run: pnpm build

- name: Release
run: pnpm dlx pkg-pr-new publish No newline at end of file

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
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
Copy Markdown
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

@antony antony left a comment

Copy link
Copy Markdown
Member

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

antony commented Jun 25, 2025

Copy link
Copy Markdown
Member

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