`state` is not a property in `StateFunc` or `State...
# pact-js
m
state
is not a property in
StateFunc
or
StateFuncWithSetup
. The state is the key to the function. so:
Copy code
stateHandlers: {
        'Customers are available': (params) => Promise.resolve(),
      } as StateHandlers,
Should work. If you don’t need the params, that’s optional also
é
that what I wrote first, but the error is the same
t
you shouldn’t need
as StateHandlers
I don’t think that’s the problem, but there’s a good chance it’s masking the problem.
@Matt (pactflow.io / pact-js / pact-go) There’s definitely something going on here that is worth looking in to (and will be a breaking change to fix). For some reason, there are two definitions of
StateHandlers
, one for messages and one for the verifier. They’re both interfaces, and so they’re being merged.
1
Oh, actually, that’s definitely the problem. It looks like the
as StateHandlers
is the wrong
StateHandlers
.
@Édouard Lopez what error (if any) do you get if you remove the
as StateHandlers
?
@Matt (pactflow.io / pact-js / pact-go) One of those
StateHandlers
needs to be removed or renamed, as the definitions are still being inappropriately merged even just within the pact-js code.
é
@Timothy Jones When I remove both
VerifierOptions
and
as StateHandlers
, from below code, I don't have any errors, but…:
Copy code
const opts: VerifierOptions = {
      provider: 'ms.pact-provider-example-for-typescript',
      providerVersion: packageJson.version,
      providerBaseUrl: '<http://localhost:8081>',
      pactUrls: [path.resolve('./pact/pacts/')],
      pactBrokerUrl:
        process.env.PACT_BROKER_BASE_URL || 'BROKER URL IS UNDEFINED',
      publishVerificationResult:
        !!<http://process.env.CI|process.env.CI> ||
        !!process.env.PACT_BROKER_PUBLISH_VERIFICATION_RESULTS,

      stateHandlers: {
        'Customers are available': (params) =>
          Promise.reject({ reason: `params: ${params}` }),
        'Customers orders are available': (params) =>
          Promise.reject({ reason: `params: ${params}` }),
      } as StateHandlers,
      logLevel: (LOG_LEVEL as LogLevel) || 'debug',
    }
If I use
as StateHandlers
I have indeed different errors: Import from
@pact_foundation/pact/src/dsl/verifier/proxy/types.js
Copy code
import { StateHandlers } from '@pact-foundation/pact/src/dsl/verifier/proxy/types.js'

Type 'StateHandlers' is not assignable to type 'StateHandlers & StateHandlers'.
  Type 'import("/workspaces/ms.pact-provider-example-for-typescript/node_modules/@pact-foundation/pact/src/dsl/verifier/proxy/types").StateHandlers' is not assignable to type 'import("/workspaces/ms.pact-provider-example-for-typescript/node_modules/@pact-foundation/pact/src/dsl/message").StateHandlers'.
    'string' index signatures are incompatible.
      Type 'StateHandler' is not assignable to type '(state: string, params?: { [name: string]: string; } | undefined) => Promise<unknown>'.
        Type 'StateFuncWithSetup' is not assignable to type '(state: string, params?: { [name: string]: string; } | undefined) => Promise<unknown>'.
          Type 'StateFuncWithSetup' provides no match for the signature '(state: string, params?: { [name: string]: string; } | undefined): Promise<unknown>'.
Import from
@pact-foundation/pact
Copy code
import { LogLevel, StateHandlers, Verifier, VerifierOptions } from '@pact-foundation/pact'

    Type 'StateHandlers' is not assignable to type 'StateHandlers & StateHandlers'.
  Type 'import("/workspaces/ms.pact-provider-example-for-typescript/node_modules/@pact-foundation/pact/src/dsl/message").StateHandlers' is not assignable to type 'import("/workspaces/ms.pact-provider-example-for-typescript/node_modules/@pact-foundation/pact/src/dsl/verifier/proxy/types").StateHandlers'.
    'string' index signatures are incompatible.
      Type '(state: string, params?: { [name: string]: string; } | undefined) => Promise<unknown>' is not assignable to type 'StateHandler'.
        Type '(state: string, params?: { [name: string]: string; } | undefined) => Promise<unknown>' is not assignable to type 'StateFunc'.
          Types of parameters 'state' and 'parameters' are incompatible.
            Type 'AnyJson | undefined' is not assignable to type 'string'.
              Type 'undefined' is not assignable to type 'string'.
t
There’s a lot of
as
in your code. Ideally, you should use this sparingly
when you use
as
, you’re telling the compiler not to believe what it knows, and overriding its ability to reason about the types.
logLevel: (LOG_LEVEL as LogLevel) || 'debug',
<-- this is definitely not right, for example.
1
Copy code
stateHandlers: {
        'Customers are available': (params) =>
          Promise.reject({ reason: `params: ${params}` }),
        'Customers orders are available': (params) =>
          Promise.reject({ reason: `params: ${params}` }),
      } as StateHandlers,
^ To fix this, replace with:
Copy code
stateHandlers: {
        'Customers are available': (params) =>
          Promise.reject({ reason: `params: ${params}` }),
        'Customers orders are available': (params) =>
          Promise.reject({ reason: `params: ${params}` }),
      },
I can’t tell you how to fix the
LOG_LEVEL
line without seeing how it is defined, but probably you want something like:
Copy code
const LOG_LEVEL = 'debug' as const;
(
as const
is different to the type assertion)
as const
means “this is a constant string”, so the type of
LOG_LEVEL
will be
'debug'
and not
string
as <Type>
is only for use sparingly, in situations where typescript can’t reason about the types. Usually this happens only if someone is converting code from JS that “happened to work” rather than was well-designed. Like relying on falsy values, or convoluted array operations
Copy code
const opts: VerifierOptions = {
      provider: 'ms.pact-provider-example-for-typescript',
      providerVersion: packageJson.version,
      providerBaseUrl: '<http://localhost:8081>',
      pactUrls: [path.resolve('./pact/pacts/')],
      pactBrokerUrl:
        process.env.PACT_BROKER_BASE_URL || 'BROKER URL IS UNDEFINED',
      publishVerificationResult:
        !!<http://process.env.CI|process.env.CI> ||
        !!process.env.PACT_BROKER_PUBLISH_VERIFICATION_RESULTS,

      stateHandlers: {
        'Customers are available': (params) =>
          Promise.reject({ reason: `params: ${params}` }),
        'Customers orders are available': (params) =>
          Promise.reject({ reason: `params: ${params}` }),
      },
      logLevel: LOG_LEVEL  || 'debug',
    }
1
^ This should work
oh, wait
it won’t because of the
!!
or maybe it will
I don’t know
This looks like JS to me
é
Using
VerifierOptions
trigger an error on
stateHandlers
and remove the
as LogLevel
trigger one on
logLevel
field It's defined like this
Copy code
const LOG_LEVEL = process.env.LOG_LEVEL || 'trace'
t
can you tell us what the error is please?
Right, so that line won’t work because typescript can only say that’s a string
You can force it to think it is with
as LogLevel
but you shouldn’t, because then you’re just turning off typescript
one moment, I’ll write you something
Copy code
const pactLogLevel = (
  maybeLogLevel: string | undefined,
  defaultLevel: LogLevel = 'info'
): LogLevel => {
  if (
    (maybeLogLevel !== undefined && maybeLogLevel === 'trace') ||
    maybeLogLevel === 'debug' ||
    maybeLogLevel === 'info' ||
    maybeLogLevel === 'warn' ||
    maybeLogLevel === 'error'
  )
    return maybeLogLevel;
  return defaultLevel;
};

const LOG_LEVEL = pactLogLevel(process.env.LOG_LEVEL, 'trace');
probably that function should be in pact, I guess
The following code has no errors at all for me:
Copy code
const pactLogLevel = (
  maybeLogLevel: string | undefined,
  defaultLevel: LogLevel = 'info'
): LogLevel => {
  if (
    (maybeLogLevel !== undefined && maybeLogLevel === 'trace') ||
    maybeLogLevel === 'debug' ||
    maybeLogLevel === 'info' ||
    maybeLogLevel === 'warn' ||
    maybeLogLevel === 'error'
  )
    return maybeLogLevel;
  return defaultLevel;
};

const LOG_LEVEL = pactLogLevel(process.env.LOG_LEVEL, 'trace');

const opts: VerifierOptions = {
  provider: 'ms.pact-provider-example-for-typescript',
  providerVersion: 'packageJson.version',
  providerBaseUrl: '<http://localhost:8081>',
  pactUrls: [path.resolve('./pact/pacts/')],
  pactBrokerUrl: process.env.PACT_BROKER_BASE_URL || 'BROKER URL IS UNDEFINED',
  publishVerificationResult:
    !!<http://process.env.CI|process.env.CI> || !!process.env.PACT_BROKER_PUBLISH_VERIFICATION_RESULTS,

  stateHandlers: {
    'Customers are available': (params) =>
      Promise.reject({ reason: `params: ${params}` }),
    'Customers orders are available': (params) =>
      Promise.reject({ reason: `params: ${params}` }),
  },
  logLevel: LOG_LEVEL || 'debug',
};
(note I had to put quotes around
packageJson.version
, because I don’t have that defined)
é
If you had
pactLogLevel
method, consider normalizing the string as you have example in uppercase
t
I guess. Change it as you like. I wrote it to only accept the actual values of the type, because originally I was going to write a proper type guard
That example is not correct.
😅 1
I think it was probably written before typescript had
as const
1
é
Still got the error on
stateHandlers
t
What is the error, please
you have sent the error several times with
as StateHandlers
present. I’d like to see it without that line, because that line is definitely causing the error that you have sent.
1
é
Copy code
Type '{ 'Customers are available': (params: string) => Promise<unknown>; 'Customers orders are available': (params: string) => Promise<unknown>; }' is not assignable to type 'StateHandlers & StateHandlers'.
  Type '{ 'Customers are available': (params: string) => Promise<unknown>; 'Customers orders are available': (params: string) => Promise<unknown>; }' is not assignable to type 'StateHandlers'.
    Property ''Customers are available'' is incompatible with index signature.
      Type '(params: string) => Promise<unknown>' is not assignable to type 'StateHandler'.
        Type '(params: string) => Promise<unknown>' is not assignable to type 'StateFunc'.
          Types of parameters 'params' and 'parameters' are incompatible.
            Type 'AnyJson | undefined' is not assignable to type 'string'.
              Type 'undefined' is not assignable to type 'string'.
types.d.ts(42, 5): The expected type comes from property 'stateHandlers' which is declared here on type 'VerifierOptions'
t
Are you certain you’re not still forcing it to be
StateHandlers
?
Because that looks like the error that you would get
Are you using an old version of TS?
Pact uses 4.7.4
é
I'm using
"typescript": "^4.9.4"
and
"@pact-foundation/pact": "^10.4.0",
👍 1
t
I would recommend pinning typescript, since they don’t follow semver
but I don’t think that’s your problem
So, if I paste that file in to one of my projects, it compiles fine
How are you getting this error?
is it in VSCode?
For some reason, vscode likes to use its own version of typescript instead of defaulting to the project’s one.
That can cause weirdness
é
yep in VSCode
t
If you do
F1
then “Select Typescript Version” you can choose which version it’s using. Usually you want “Project”
I have no idea why they didn’t make that the default
é
It's using vscode version
5.0.0
but select the one from the project I still get the error
t
I can get something similar if I try typing the
params
é
There is an option to prompt use for workspace version, but disable by default
t
Oh, awesome! Thank you
Give me a moment, I think I’ll have a workaround for you
é
Removing
VerifierOptions
on the
opts
declaration remove the error
t
Yeah, but that’s not ideal
it’ll remove the error because typescript then doesn’t know what it is.
Probably you’d have problems when you go to verify it
é
Yes, that's just a workaround
t
So, one of the problems is the
Promise.reject
which is a
Promise<unknown>
which Pact doesn’t accept.
It probably should.
Copy code
stateHandlers: {
        'Customers are available': (params: Record<string, string> | string) =>
          Promise.reject<void>({
            reason: `params: ${params}`,
          }),
        'Customers orders are available': (
          params: Record<string, string> | string
        ) => Promise.reject<void>({ reason: `params: ${params}` }),
      },
So this will compile for you
but it’s… not nice, or really even right.
The problem is the merging of two different
StateHandler
definitions. Frankly I can’t see how this ever worked.
I think Pact should: 1) Unmerge those definitions 2) Widen the return type for that Promise (really we should accept
unknown
)
é
Yep, that why I went for the workaround
the snippet
Copy code
stateHandlers: {
        'Customers are available': (params: Record<string, string> | string) =>
          Promise.reject<void>({
            reason: `params: ${params}`,
          }),
        'Customers orders are available': (
          params: Record<string, string> | string
        ) => Promise.reject<void>({ reason: `params: ${params}` }),
      },
fails
Copy code
Type '{ 'Customers are available': (params: Record<string, string> | string) => Promise<void>; 'Customers orders are available': (params: Record<string, string> | string) => Promise<void>; }' is not assignable to type 'StateHandlers & StateHandlers'.
  Type '{ 'Customers are available': (params: Record<string, string> | string) => Promise<void>; 'Customers orders are available': (params: Record<string, string> | string) => Promise<void>; }' is not assignable to type 'StateHandlers'.
    Property ''Customers are available'' is incompatible with index signature.
      Type '(params: Record<string, string> | string) => Promise<void>' is not assignable to type 'StateHandler'.
        Type '(params: Record<string, string> | string) => Promise<void>' is not assignable to type 'StateFunc'.
          Types of parameters 'params' and 'parameters' are incompatible.
            Type 'AnyJson | undefined' is not assignable to type 'string | Record<string, string>'.
              Type 'undefined' is not assignable to type 'string | Record<string, string>'.
t
That’s so weird. I don’t know why it works for me and not you.
We are both using TS 4.9.4 and Pact 10.4.0
Can you share your tsconfig?
Also, I have to apologise for
AnyJson
. I brought that in when I was a maintainer - I thought it was a good idea (designed to stop people putting non-json things in a body), but it turns out to be more trouble than it was worth. I don’t think it’s the problem here - I think the problem is the inappropriately merged declaration.
but since it comes up in every error message, I … am sorry
🙏 1
Copy code
const customersAreAvailable: StateFunc = (params) =>
    Promise.reject<void>({
      reason: `params: ${params}`,
    });

  const customerOrdersAreAvailable: StateFunc = (params) =>
    Promise.reject({
      reason: `params: ${params}`,
    });

  it('verify our app can provide responses expected by ALL our consumers', () => {
    const opts: VerifierOptions = {
      provider: 'ms.pact-provider-example-for-typescript',
      providerVersion: packageJson.version,
      providerBaseUrl: '<http://localhost:8081>',
      pactUrls: [path.resolve('./pact/pacts/')],
      pactBrokerUrl:
        process.env.PACT_BROKER_BASE_URL || 'BROKER URL IS UNDEFINED',
      publishVerificationResult:
        !!<http://process.env.CI|process.env.CI> ||
        !!process.env.PACT_BROKER_PUBLISH_VERIFICATION_RESULTS,
      stateHandlers: {
        'Customers are available': customersAreAvailable,
        'Customers orders are available': customerOrdersAreAvailable,
      },
      logLevel: LOG_LEVEL || 'debug',
    };
^ This also works for me, maybe it will work for you?
é
That works! 🎉
Thanks for the help and the time ❤️
t
You’re welcome. I’ll open an issue for this one, it definitely needs to be fixed
❤️ 1