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

feat: improve function return types #60

Merged
merged 7 commits into from
Jun 16, 2022
Merged

feat: improve function return types #60

merged 7 commits into from
Jun 16, 2022

Conversation

alaister
Copy link
Member

@alaister alaister commented Apr 6, 2022

Fixes #55.

What is the current behaviour?

There are some type errors, and functions return everything with | null, making it difficult for end-users to work with.

What is the new behaviour?

Functions now return type unions, which are more correct and easier to work with.

Additional context

I've labelled this as feat instead of chore as it should be treated as a breaking change because it may show new type errors to existing users.

Copy link

@Jaaneek Jaaneek left a comment

Choose a reason for hiding this comment

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

  ): Promise<
    | {
        data: { message: string }
        error: null
      }
    | {
        data: null
        error: Error // in future we might add Error || OurOwnError
      }
> {
     const data = something
     return { data, error: null }
    } catch (error) {
        if(basicErrorTypeguard(error)) return { data: null, error }
        // In case we would need to add new Error type in future.
        //if(ourOwnErrorTypeguard(error)) return { data: null, error }
        throw error
    }
  }

Reasoning:

  1. If there will be any other error that is not handled by us, we shouldn't return it without a type.
  2. User won't be able to handle it, returning an unknown type as error isn't an option as well as that makes developer experience completely worthless. We might as well return a flag 'isError'

Why this solution:
Typeguard will ensure that we only ever return our 'Error' and users can handle it.
They will get an error in development that will give them information.
Bonus:
Backward compatibility.
With this code it's possible to add new ErrorTypes in future without breaking anything. Its maintanable

Another possible solution (not recommended):

catch (error) {
    const castedError = error as Error
      return { data: null, error }
    }

This will also work, but it will tell the developer that the error is of type 'Error'.
If any other error is thrown, the developer won't get information about it. This is bug prone.
For example:

  1. Developer shows information from the error to user (user of website).
  2. Any other error is thrown that doesn't match 'Error'.
  3. Javascript might crash because of accessing a property that doesn't exist.

@alaister
Copy link
Member Author

alaister commented Apr 7, 2022

Thanks, @Jaaneek!

I agree with your first option more than the second.

The only thing I don't feel great about is returning an error in the object, but having the function also be able to throw... If a user sees that error in the object, my intuition is that all errors would come through that error and not through a try/catch.

I'm keen to hear your thoughts!

@Jaaneek
Copy link

Jaaneek commented Apr 7, 2022

@alaister

My proposal:

Create custom error that will format any unknown error
Something like

try {
  throw new Error('Some supabase error')
} catch (error) {
  if (error instanceof Error) return error
// if(error instaceof SomeOtherError) return error
// if(error instaceof AnotherError) return error

return toStorageError(error,Reason.Unknown)
// Might be: return toStorageError(error,Reason.UpdateFail)
}
const toStorageError = (error:unknown,reason:Reason) => {
if(isErrorWithMessage) return new StorageError(error.message,reason,error)


// We might aswell do:
// return new StorageError('',reason,error)
try {
    return new StorageError(JSON.stringify(maybeError),reason,error)
  } catch {
    // fallback in case there's an error stringifying the maybeError
    // like with circular references for example.
    return new StorageError(String(maybeError),reason,error)
  }
}
export class StorageError extends Error {
  reason: Reason
  originalError:unknown
  constructor(message: string, reason: Reason,originalError:unknown) {
    super(message)
    this.name = 'StorageError'
    this.Reason= status
    this.,originalError: originalError
  }


  }
}
enum Reason {
  Unknown,
  OtherEasons...
}
function isErrorWithMessage(error: unknown): error is ErrorWithMessage {
  return (
    typeof error === 'object' &&
    error !== null &&
    'message' in error &&
    typeof (error as Record<string, unknown>).message === 'string'
  )
}

Why?
It'simportant to let the developer handle the final error.
With introducing 'StorageError' we will let developers always:

  • See a message
  • We will let him know the reason behind the error (if we know of that)
  • Let him handle the final error which will be always unknown.

Suggestions
Name it better, 'StorageError ' might be understood as something that we throw, which is not. We are just wrapping it for developers.

More to read about this pattern
https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript

@mircea-pavel-anton
Copy link
Contributor

I think that PRs #62 and #63 could also be closed and added here, as they also address the return types for some functions.

@alaister
Copy link
Member Author

alaister commented Apr 8, 2022

@mirceanton, I'll review your PRs now and try and get them in before this goes out. Thank you!

@alaister alaister mentioned this pull request Apr 8, 2022
@alaister alaister marked this pull request as ready for review April 8, 2022 16:06
@alaister alaister changed the base branch from main to next May 24, 2022 10:31
@kiwicopple
Copy link
Member

🎉 This PR is included in version 1.8.0-next.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@kiwicopple
Copy link
Member

🎉 This PR is included in version 2.0.0-rc.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

alaister added a commit that referenced this pull request Oct 11, 2022
* feat: improve function return types (#60)

* feat: improve types

* chore: better error handling

* feat: custom storage api error

* chore: use custom error type

* chore: update custom error type

* chore: replace instanceof with isStorageError

* chore: move isStorageError to a non-static method

* Improve `uploadOrUpdate` returned data. (#63)

* fix: typo in updateBucket jsdoc

* fix: import cross-fetch conditionally

* fix: es2020

* Also return the clean path for the uploaded file.

* Update readme.

* Add bucket id in returned data.

* Update readme

* Fix return type for `upload` and `update`.

* Fix typo in code snippet comment.

* Rename `downloadPath` to `path`

* Update return types

Co-authored-by: Jonathan Picques <jonathan.picques@gmail.com>
Co-authored-by: Bobbie Soedirgo <bobbie@soedirgo.dev>
Co-authored-by: Mircea-Pavel Anton <mircea.anton99@yahoo.com>

* Improve `getPublicUrl` (#62)

* Improve `getPublicUrl`

* Make return types consistent across all functions.

* Undo return type change

* Change the `getPublicUrl` method to return only the url as string.

* Remove redundant method.

* Update tests.

Co-authored-by: Mircea-Pavel Anton <mircea.anton99@yahoo.com>

* upload, update: don't return bucketId

as a return parameter and in the path. Just returning the path without the bucket id makes it easier to pass the value in to other storage-js functions. Also not calling it Key -- was uppercase and not as clear as just calling it path.

* add docs folder to .gitignore

* Merge branch 'master' into chore/merge-master-04-08-22 (#82)

* fix: remove release config from package.json (#83)

* chore: merge main into next (#85)

* fix: typo in updateBucket jsdoc

* fix: import cross-fetch conditionally

* fix: es2020

* chore: add search param to SearchOptions jsdoc on list function (#59)

* build(release-next): sets up the next branch as an npm prerelease (#80)

* fix: rename main release branch (#84)

Co-authored-by: Jonathan Picques <jonathan.picques@gmail.com>
Co-authored-by: Bobbie Soedirgo <bobbie@soedirgo.dev>

* fix: encode all urls

fixes #78

this should ideally be done in the api server, but doing this breaking change at the client library first, so when we do it at the backend, only folks who are using the api directly will need to upgrade.

* return values is always wrapped by data

signedURL used to return a url directly and inside the data object. This is inconsistent. Now we always return values inside a data object only.

* fix: pass through all values returned by backend

instead of cherry picking only name - we still do this in the types though.

also ensures data is always an object.

Fixes #6

* fix tests

* fix metadata type

Previously accessing metada.contentType would throw a type error since we set the type to an empty object.

* only export StorageClient

#46

removing the change we added for backward compatibility

* update ci to node 16

* upgrade typedoc to latest version

* fix: move bucket and file api to package folder

docs look better when we do this and add the packages to the entrypoint to the typedoc command

* modify publicurl to always return data

all methods return data and an error

* dont return error for getPublicUrl

* fix: signed url is returned as signedUrl

matches the method name createSignedUrl

* downgrade typedoc to 0.22.16

our doc generation pipeline only works with 0.22.16 for now

* exclude protected properties from typedoc

* fix: typedocs

* chore: merge main into next (#99)

* fix: typo in updateBucket jsdoc

* fix: import cross-fetch conditionally

* fix: es2020

* chore: add search param to SearchOptions jsdoc on list function (#59)

* build(release-next): sets up the next branch as an npm prerelease (#80)

* fix: rename main release branch (#84)

* build(release-rc): sets up the rc branch as an npm prerelease (#98)

Co-authored-by: Jonathan Picques <jonathan.picques@gmail.com>
Co-authored-by: Bobbie Soedirgo <bobbie@soedirgo.dev>

* feat: Release V2 RC

BREAKING CHANGE: Release V2

* update docs (#102)

* Fix docs typo (#104)

* chore: increased test coverage (#106)

* fix: consistent return types for copy (#110)

* feat: download file via url (#112)

* fix: merge main into rc (#113)

* fix: typo in updateBucket jsdoc

* fix: import cross-fetch conditionally

* fix: es2020

* chore: add search param to SearchOptions jsdoc on list function (#59)

* build(release-next): sets up the next branch as an npm prerelease (#80)

* fix: rename main release branch (#84)

* build(release-rc): sets up the rc branch as an npm prerelease (#98)

* Adds v1 docs (#107)

* docs: ci

Co-authored-by: Jonathan Picques <jonathan.picques@gmail.com>
Co-authored-by: Bobbie Soedirgo <bobbie@soedirgo.dev>
Co-authored-by: Copple <10214025+kiwicopple@users.noreply.github.com>

Co-authored-by: Mircea-Pavel Anton <contact@mirceanton.com>
Co-authored-by: Jonathan Picques <jonathan.picques@gmail.com>
Co-authored-by: Bobbie Soedirgo <bobbie@soedirgo.dev>
Co-authored-by: Mircea-Pavel Anton <mircea.anton99@yahoo.com>
Co-authored-by: Inian <inian1234@gmail.com>
Co-authored-by: Fabrizio <fabri.feno@gmail.com>
Co-authored-by: Copple <10214025+kiwicopple@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use enum to narrow down return type
5 participants