@cfxlabsinc/b2b-services
    Preparing search index...

    Module @cfxlabsinc/service-config

    service-config

    A services package declares the config keys it reads, plain data that a Pulumi program, the CLI and tests can read without running the package, and reads them through a typed handle over one flat map. Operating it: docs/SECRETS.md.

    // packages/virtual-account-services/src/configNamespace.ts
    import { defineConfigKeys } from "@cfxlabsinc/service-config/keys";

    export const victorConfig = defineConfigKeys({
    group: "victor",
    keys: {
    VICTOR_CLIENT_ID: { owner: "operator" },
    VICTOR_BASE_URL: { owner: "operator", plain: true },
    VICTOR_TIMEOUT_MS: { owner: "operator", plain: true, default: "10000" },
    },
    });

    const clientId = victorConfig.get("VICTOR_CLIENT_ID"); // synchronous
    • Keys keep their full env-style names, unique across every declaration. group names only the SSM path. owner picks the tree (operator: /cfx/secrets/<group>/<KEY>, stack: /cfx/stack/<stage>/<group>/<KEY>). required defaults to true, plain to false (a secret). Every value is a string.
    • Reads are synchronous. get throws on a key with no value and no default; getOptional returns undefined. Never await either.
    • Deployed (CONFIG_SERVICE set on Lambda), the process serves the config.json its deploy baked beside bootstrap ($LAMBDA_TASK_ROOT/config.json, CONFIG_FILE), the SST dashboards' server included; with no file it throws. A read older than 30 s fires one background refresh of cfg:svc:{<service>:<stage>}; a failed or refused one keeps serving. Locally, and in next build, a key reads process.env[KEY], then its default.
    • resolveConfig (./resolve.ts, on the root) is the one resolution: stack value, then operator value, then default. A deploy bakes with it and config-refresher builds bundles with it, so the two never differ.
    • Deploy keys (owner: "deploy": VALKEY_URL, DATABASE_URL, CONFIG_STAGE, CONFIG_SERVICE, …) have no SSM path: each function's deploy bakes its own into config.json (values, deploy, functions), and a read is the same synchronous <group>Config.get. A deployed environment holds only what a library reads (libraryEnvironment); Lambda's own variables are lambdaEnv(...).
    • memoize(build) backs every static instance(): one instance per argument set, and a throw caches nothing.
    • Subpaths keep a declaration light. ./keys (and the root, which exports types only from it) load no Valkey client, so a Pulumi program or sst.config.ts can import them. ./service holds ServiceConfigService and the bundle sources.
    • manifest.json is every declaration as JSON, generated by nx run service-config:gen-manifest and bundled into config-refresher. test/manifest.test.ts fails when it drifts from the declarations, and test/declarations.test.ts when a key is declared twice or gives a deploy key an SSM owner.
    • readers.json maps each declared key to the services whose config list (WORKLOAD_CONFIGS in pulumi-templates' workloadConfig.ts) names its group, generated by the same target through pulumi-templates' scripts/gen-readers.ts; its test/readers.test.ts fails when it drifts.
    • ./inspect is the read model behind cfx config show and the admin dashboard's /config: per key its owner, path, whether it is set, version and last change, and the value of a plain key only; per service (inspectService) its bundle and the key list its last deploy wrote. Listing never decrypts. ./inspect-ssm backs it with an SSMClient the caller builds.
    • Tests call setTestConfig({ KEY: "value" }) from ./testing, which releases itself when the calling bun:test scope ends.
    ConfigKeys
    BakedConfig
    ConfigNamespaceName
    KeySpec
    KeysSpec
    Manifest
    ManifestNamespace
    ReadPath
    ResolvedConfig
    CONFIG_FILE
    CONFIG_NAMESPACE_NAMES
    buildManifest
    configPaths
    consumersPath
    keysHash
    manifestHash
    memoize
    parseBakedConfig
    parseConsumerList
    parseManifest
    requiredKeys
    resolveConfig
    serializeBakedConfig