service-configA 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
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.get throws on a key with no value and no
default; getOptional returns undefined. Never await either.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.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../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.setTestConfig({ KEY: "value" }) from ./testing, which
releases itself when the calling bun:test scope ends.