ts-essentials / ts-essentials

All essential TypeScript types in one place πŸ€™

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

autocompletable dictionary that's not SafeDictionary

xenoterracide opened this issue Β· comments

is it possible to have a dictionary where the IDE's would recognize the keys, and not allow any unknown keys without having to specify the keys twice. essentially the same as this

export type ErrorCodeKey = 'unknownJoi' | 'notFound' | 'badRequest' | 'ise' | 'npe'
export const errorCodes: Required<SafeDictionary<[number, string], ErrorCodeKey>> = {
  unknownJoi: [200000, 'Request validation error'],
  notFound: [200001, 'Requested resource not found'],
  badRequest: [200002, 'An error has occurred'],
  ise: [200099, 'Our system experienced an error. Please try again later.'],
  npe: [200099, 'System threw a null or undefined error. This should not be a client issue'],
}

except with no need to define ErrorCodeKey. e.g

export const errorCodes: Required<CheckedDictionary<[number, string]>> = {
  unknownJoi: [200000, 'Request validation error'],
  notFound: [200001, 'Requested resource not found'],
  badRequest: [200002, 'An error has occurred'],
  ise: [200099, 'Our system experienced an error. Please try again later.'],
  npe: [200099, 'System threw a null or undefined error. This should not be a client issue'],
}

this would be completable by an IDE and not compile if the key doesn't exist

errorCodes.npe // fine
errorCodes.foo // compile error

Hey @xenoterracide!

That's a great question (even though it's out of the scope for ts-essentials) because I already know the answer to it :)

You can define this utility:

const createFactoryWithConstrain =
    <Constrain>() =>
    <U extends Constrain>(value: U): U =>
        value;

and given it and the type that you defined Required<SafeDictionary<[number, string], string>>, you can then infer values for your errorCodes

Playground example – https://tsplay.dev/WJyxVm

Hope it helped!

Have a wonderful evening!

probably jumping back and forth between #314 and here... if I wanted to get the keys back out of the new const type...

const createFactoryWithConstraint =
  <CONSTRAINT>() =>
  <U extends CONSTRAINT>(value: U): U =>
    value

type ErrorCodeTuple = Readonly<[number, string]>

type ErrorCodeIndex = { [k: string]: ErrorCodeTuple }
type ErrorCodes = Readonly<Required<SafeDictionary<ErrorCodeTuple, string> & ErrorCodeIndex>>

const createErrorCodes = createFactoryWithConstraint<ErrorCodes>()

export const errorCodes = createErrorCodes({
  dataMissing: [100000, 'Some required data not defined after cleaning'],
  unknownJoi: [200000, 'Request validation error'],
  notFound: [200001, 'Requested resource not found'],
  badRequest: [200002, 'An error has occurred'],
  ise: [200099, 'Our system experienced an error. Please try again later.'],
  npe: [200099, 'System threw a null or undefined error. This should not be a client issue'],
} as const)

and still have the equivalent of

export type ErrorCodeKey = 'unknownJoi' | 'notFound' | 'badRequest' | 'ise' | 'npe'

obviously

export type ErrorCodeKey = keyof errorCodes

doesn't work.

Also, is there a reason we can't add this function to ts-essentials? seems super useful for type magic

You need keyof typeof errorCodes to make it work

Playground – https://tsplay.dev/mpnAzw

Also, is there a reason we can't add this function to ts-essentials? seems super useful for type magic

I've started using it a lot in tests recently, previously didn't think that much about it. Also not sure if people need it

We only have isExact which looks similar but not quite the same

export const isExact =
<ExpectedShape>() =>
<ActualShape>(x: Exact<ActualShape, ExpectedShape>): ExpectedShape => {
return x;
};

I could swear I tried that πŸ€¦πŸ»β€β™‚οΈ maybe you could add this function in a future release? with some of the examples we've gone through. I definitely plan on trying isExact soon because we really should be testing our types. That's a different problem from creating them though

haha, derp, now I have this problem

type ErrorCodes = Readonly<Required<SafeDictionary<ErrorCodeTuple, string> & ErrorCodeIndex>>

const createErrorCodes = createFactoryWithConstraint<ErrorCodes>()

export const errorCodes = createErrorCodes({
  dataMissing: [100000, 'Some required data not defined after cleaning'],
  unknownJoi: [200000, 'Request validation error'],
  notFound: [200001, 'Requested resource not found'],
  badRequest: [200002, 'An error has occurred'],
  ise: [200099, 'Our system experienced an error. Please try again later.'],
  npe: [200099, 'System threw a null or undefined error. This should not be a client issue'],
} as const)
src/framework/middleware/error-handler.ts:30:20 - error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ readonly dataMissing: readonly [100000, "Some required data not defined after cleaning"]; readonly unknownJoi: readonly [200000, "Request validation error"]; readonly notFound: readonly [200001, "Requested resource not found"]; readonly badRequest: readonly [...]; readonly ise: readonly [...]; readonly npe: readon...'.
  No index signature with a parameter of type 'string' was found on type '{ readonly dataMissing: readonly [100000, "Some required data not defined after cleaning"]; readonly unknownJoi: readonly [200000, "Request validation error"]; readonly notFound: readonly [200001, "Requested resource not found"]; readonly badRequest: readonly [...]; readonly ise: readonly [...]; readonly npe: readon...'.

30       const code = errorCodes[detail.type] != null ? errorCodes[detail.type] : errorCodes.unknownJoi
                      ~~~~~~~~~~~~~~~~~~~~~~~

related to #316 πŸ€¦πŸ»β€β™‚οΈ

urgh.. I was thinking I could do the same thing with this, but then I realized it isn't an object with values

export type EnvKey =
  | 'TIMEOUT_DURATION'
  | 'RETRY_ATTEMPTS'
  | 'DEVEX_URL'
  | 'SSO_BASE_URL'
  | 'IGNORE_RBAC'
  | 'HTTPS_PROXY'
  | 'QS_AWS_REGION'
  | 'QS_AWS_ACCOUNT_ID'
  | 'QS_ROLE_ARN'
  | 'CERTIFICATE_DASH_ID'
  | 'LOBREPORT_DASH_ID'
  | 'SERVICENOW_ENV'
  | 'S3_BUCKET_NAME'
  | 'IMPERSONATION_GROUP'
  | 'NODE_ENV'
  | 'SHOULD_LOG_AXIOS_BODY'

export type Env = SafeDictionary<Exclude<Primitive, symbol>> & {
  TIMEOUT_DURATION: number
  RETRY_ATTEMPTS: number
  LOCAL_S3: boolean
  IGNORE_RBAC: boolean
  SHOULD_LOG_AXIOS_BODY: boolean
  DEVEX_URL?: string
  SSO_BASE_URL?: string
  QS_AWS_REGION?: string
  QS_AWS_ACCOUNT_ID?: string
  HTTPS_PROXY?: string
  QS_ROLE_ARN?: string
  CERTIFICATE_DASH_ID?: string
  LOBREPORT_DASH_ID?: string
  SERVICENOW_ENV?: string
  S3_BUCKET_NAME?: string
  IMPERSONATION_GROUP?: string
  NODE_ENV?: string
}

still trying to get rid of all the duplicate effort. Simply doing type EnvKey = keyof Env just results in type string, but I want the more stringy enum like. By the way, I really appreciate all your help.

ok ^ that one is dumb, I forget why I was doing it that way, but using a plain old interface and keyof should be fine πŸ€¦πŸ»β€β™‚οΈ

I could swear I tried that πŸ€¦πŸ»β€β™‚οΈ

yeah, it's useful for different case, so don't worry

maybe you could add this function in a future release?

yeah, I think so, I've created an issue here to add it – #317

haha, derp, now I have this problem

type ErrorCodes = Readonly<Required<SafeDictionary<ErrorCodeTuple, string> & ErrorCodeIndex>>

const createErrorCodes = createFactoryWithConstraint<ErrorCodes>()

export const errorCodes = createErrorCodes({
  dataMissing: [100000, 'Some required data not defined after cleaning'],
  unknownJoi: [200000, 'Request validation error'],
  notFound: [200001, 'Requested resource not found'],
  badRequest: [200002, 'An error has occurred'],
  ise: [200099, 'Our system experienced an error. Please try again later.'],
  npe: [200099, 'System threw a null or undefined error. This should not be a client issue'],
} as const)
src/framework/middleware/error-handler.ts:30:20 - error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ readonly dataMissing: readonly [100000, "Some required data not defined after cleaning"]; readonly unknownJoi: readonly [200000, "Request validation error"]; readonly notFound: readonly [200001, "Requested resource not found"]; readonly badRequest: readonly [...]; readonly ise: readonly [...]; readonly npe: readon...'.
  No index signature with a parameter of type 'string' was found on type '{ readonly dataMissing: readonly [100000, "Some required data not defined after cleaning"]; readonly unknownJoi: readonly [200000, "Request validation error"]; readonly notFound: readonly [200001, "Requested resource not found"]; readonly badRequest: readonly [...]; readonly ise: readonly [...]; readonly npe: readon...'.

30       const code = errorCodes[detail.type] != null ? errorCodes[detail.type] : errorCodes.unknownJoi
                      ~~~~~~~~~~~~~~~~~~~~~~~

related to #316 πŸ€¦πŸ»β€β™‚οΈ

You need to validate, that detail.type is of type keyof typeof errorCodes, then it will work for you