I have a custom module acting as a config file. It...
# suitescript
d
I have a custom module acting as a config file. It returns an object with all the config options. I'm trying to adapt it so it'll survive a sandbox refresh by checking the environment with `N/runtime`'s
.envType
. However I'm getting the error
SUITESCRIPT_API_UNAVAILABLE_IN_DEFINE
. My instinct is to wrap it up in a 'getConfig' function, but it feels wrong. Is there a way I can set this up so I can still do this:
config module looks similar to this
b
make your properties have getter functions using Object.defineProperty
it allows you to initialize your config in your entrypoint instead of in the define
e
Three different approaches I use for enumeration/config modules: https://gitlab.com/-/snippets/2384329 The first one won't work for your use case, but the latter two should.
👍🏻 1
s
I recommend using separate config files for separate environments.
e
Could you expand on that? I'm curious to see what that looks like in practice.
s
We have a build step replace the file (either automated or manual) prior to deployment. So if you have
config.js
in your
deploy.xml
simply copying
config_sb1.js
or
config_prod.js
to
config.js
prior to running the deploy. It helps keep the configs declarative (no
if sandbox else blah
sort of imperative logic.
This has worked so well that sometimes we even have functional consultants (non-developers) populate the values directly in this declarative style JS (well TS in our case) file.
IMHO SDF should probably support something like this natively. A couple other tools that handle things similarly IIRC was ASP.NET web.config and Angular environment files support.
e
I see; I sort of assumed there had to be something additional in there as conditionally including different files based on the environment didn't seem like a great option to me. If you've got the tooling in place, it's a great approach.
b
usually you want something that survives a sandbox refresh
e
Well I'd love to learn from your example; in the meantime, I've found these approaches to only require a single update on the first Sandbox refresh, and after that they're stable, so the maintenance effort is trivial.
b
automatic building before deployment only actually works if you deploy
sandbox refreshes dont trigger a sdf deploy, so you are left with a sandbox with production config
@erictgrubaugh approach is sane, it makes the properties a function so that you can have a config for different environments
you can use fancier things like a Proxy if you want to force 2.1, but the general idea is that you need to get a function in there somewhere
d
thank you all so much for the discussion. I've gone with a singleton class using getters, although I've learnt something new with proxy objects/handlers. Shawn's system seems good for a team/consulting environment, and I like the idea of the config being purely declarative. But being by myself in-house and wanting it to cope with SB refreshes, it wasn't suitable.
1
e
Glad you've got it working! Just for fun, for a (slightly) more declarative version of this approach:
Copy code
class ApprovalList {
    get REJECTED() {
      return (runtime.envType === runtime.EnvType.PRODUCTION) ? 4 : 3;
    }

    get CANCELED() {
      return (runtime.envType === runtime.EnvType.PRODUCTION) ? 3 : 4;
    }
  }
one could just use plain ol' Objects to map Account ID to value:
Copy code
class ApprovalList {
    get REJECTED() {
      return ({
        '12345678': 4,
        '12345678_SB1': 3,
        '12345678_SB2': 5,
      })[runtime.accountId];
    }

    get CANCELED() {
      return ({
        '12345678': 3,
        '12345678_SB1': 4,
        '12345678_SB2': 5,
      })[runtime.accountId];
    }
  }
Gets a bit repetitive, and I'd probably make an enumeration module for the Account IDs themselves as well, but it's workable. I'm also playing around with
Proxy
now, which I wasn't aware of previously.
s
notable, a sandbox refresh almost always requires touching - at least in my experience. This is partly due to NS (understandably) resetting tokens/keys upon refresh. We used to do environment-aware code like shown, but find the declarative to make sense, especially for mapping symbolic names to system-specific NS internal ids.
netsuite 1
regarding proxies - we've considered it for NFT - it's still on the table but not sure it would be an improvement to the current approach. I am glad that's standardized in the language now.
d
Yeah, the tokens/keys and password GUIDs are going to be manual no matter what
@erictgrubaugh so the consumer script would have to reference runtime module like
enum.REJECTED[runtime.accountid]
, seems a pain
🫤 didn't see the
[runtime.accountId]
at the end facepalm I think I've got this proxy right:
e
I did something really similar with
Proxy
. The keys of my
configEnums
were the Account IDs and the values were the config Objects. Then my
Proxy
handler was:
Copy code
get(target, prop, receiver) {
  return target[runtime.accountId]?.prop;
}
👍 1
b
the primary disadvantage of @erictgrubaugh get syntax is the copy/paste nature inherent to it, you can tell that you would have to copy and paste a new get for every new key you want to add
e
Indeed. Do any of the other approaches avoid this?
b
you can actually get past that by using Object.defineProperty or the related Object.defineProperties, both of which allow you to programmatically create getters. You can write code that creates a getter for every property on your config enums. its why i recommended Object.defineProperty first, it works in 2.0 and allows you to eventually get rid of adding code if you get tired of adding more Object.defineProperty calls when you want a new config
I generally would recommend one config object per account id like what eric has going in his proxy, it works nicely when you want to move the config to a separate file so that others can edit the config without seeing actual code
it also works if you actually want to store the config in a custom record and have one config record per environment
🤔 1