Skip to content

core API

Everything the package exports, by the path you import it from. Open an export for its signature and options.

@evolve-framework/core

The application, the module system (Module, AbstractModule, CompositeModule), and health checks.

Classes

AbstractModuleThe base class of every module.
import { AbstractModule } from "@evolve-framework/core";
new AbstractModule()
MethodDescription
getGraphQLConfig(): GraphQLConfig | undefinedThe type definitions and resolvers this module adds to the GraphQL schema, or undefined when it serves no GraphQL.
getGraphQLSchema(): string | undefinedThe type definitions of getGraphQLConfig() printed as SDL, which codegen reads without running the service.
getHttpConfig(): HttpConfig | undefinedThe routes and route-scoped Fastify plugins this module registers on the HTTP server, or undefined when it has none.
getInternalEventConfig(): InternalEventConfig | undefinedThe internal events this module handles, if any.
init(): void | Promise<void>One-shot initialization hook for side-effecty setup that must not run at construction time — typically building SDK clients or registering module-scoped singletons.
close(): void | Promise<void>Optional cleanup hook called by Application.stop() after the HTTP server has closed.
ApplicationApplication composes a Module with its HTTP/GraphQL surface and runs it.
import { Application } from "@evolve-framework/core";

Lifecycle:

  1. new Application(options) — cheap, no side effects.
  2. await service.init() — runs module.init() then builds the GraphQL and Fastify instances. Idempotent.
  3. await service.start() — runs onStart, binds the port and installs signal handlers. A signal-driven shutdown runs stop(), then onShutdown.

Tests can use service.fastify.inject(...) after init() without calling start(). Use createApplication for the common one-liner that does steps 1 and 2 together.

The GraphQL user-context type lives on ApplicationOptions<TUserContext> (where it couples graphql.context to graphql.plugins), not on the service itself — the service exposes nothing typed by it.

new Application(options: ApplicationOptions<any>)
OptionTypeDescription
namestring

The name of the domain service, used for identification and logging.

moduleModule<any>

The module that encapsulates the domain's business logic, GraphQL schema, and resolvers. This is a required property as the domain service relies on the module to define its functionality.

httpHTTPServerOptions

HTTP server configuration. Required to start the HTTP server that handles both GraphQL and REST requests.

onStart?(() => void | Promise<void>)

Sets up what the modules share, such as the cache connection, when start() is called and before the server listens. A rejection is a failed startup: the process shuts down and exits.

onShutdown?(() => void | Promise<void>)

Releases what a finished onStart acquired, after stop(), when the process shuts down on a signal. A direct stop() does not run it.

graphql?GraphQLOptions<any>

Optional GraphQL server configuration. This is only used if the module provides a GraphQL configuration. If the module does not provide a GraphQL configuration, this property is ignored.

MethodDescription
init(): Promise<void>Runs module.init(), then builds the GraphQL and Fastify servers from the module and the options.
start(): Promise<void>Runs onStart, binds the HTTP server to http.address and installs signal handlers that run stop() and then onShutdown.
stop(): Promise<void>Closes HTTP (draining open requests), disposes GraphQL, then closes the module.
getYogaInstance(): GraphQLInstance<ServerContext, any> | undefinedThe GraphQL server, or undefined before init() or when the module serves no GraphQL.
CompositeModuleSeveral modules served as one.
import { CompositeModule } from "@evolve-framework/core";

Extends AbstractModule<TContext>

new CompositeModule(modules: AbstractModule<NoInfer<TContext>>[], options?: CompositeModuleOptions)
OptionTypeDescription
graphql?{ mergeTypeDefsOptions?: { useSchemaDefinition?: boolean | undefined; throwOnConflict?: boolean | undefined; reverseDirectives?: boolean | undefined; } | undefi…

mergeTypeDefsOptions is passed to the merge of the modules' type definitions and overrides its defaults.

HealthModuleReports this service's health as one field of a Health entity shared with every other service in the composition.
import { HealthModule } from "@evolve-framework/core";

Compose it into each service that should be reportable -- every instance of every service, since the check answers for the instance that received the query -- giving each one its own field name:

new HealthModule({
	name: "products",
	checks: {
		engine: () => client.ping(),
		cache: () => cache.get("health"),
	},
});

A single query then reports the whole deployment, which is what makes this usable as a promotion gate:

{ health { products { ok release } checkout { ok release } } }

Two HTTP routes carry the same information for callers that are not the router, and they answer different questions:

  • /healthcheck runs no checks and answers 200 with the component name and the running release for as long as the process can serve a request. This is the liveness probe, and the reason it checks nothing is that a probe fails on a timeout as surely as on a status code.
  • /readiness runs the checks and answers 503 when any of them failed. This is the one for a startup probe and for a deploy gate that can only read a status code.

A service that serves no GraphQL at all -- a payment provider, a sitemap builder, a search indexer -- composes this with graphql: false and gets the routes without becoming a subgraph.

This is deliberately not the router's own liveness or readiness probe: those answer for the router's HTTP layer and its selected supergraph, never reach a service, and keep reporting healthy while a service behind them is broken.

The report carries the running build so a gate can tell it reached the instances it is about to promote rather than the ones still serving traffic.

Extends AbstractModule<unknown>

new HealthModule(options: HealthModuleOptions)
OptionTypeDescription
namestring

The field this service contributes to the shared Health entity, e.g. products or checkout. It must be unique across the composition: two services contributing the same field name is a composition conflict, not a merge.

release?string

The build this instance runs, reported as release. Defaults to COMPONENT_VERSION, then GIT_SHA, then SENTRY_RELEASE, then unknown.

check?HealthCheck

Whether the service itself is healthy, for a reason that is not one of its dependencies. Its failure message lands on ServiceHealth.message.

checks?Record<string, HealthCheck | HealthCheckDefinition>

The dependencies to check, keyed by the name to report them under. They run concurrently on every query.

More

A bare function is reported without counting towards the verdict. Wrap it as { check, required: true } for a dependency the service cannot serve without, whose failure then sets ok to false and answers 503 on the readiness route.

checks: {
	engine: { check: () => client.ping(), required: true },
	cache: () => cache.get("health"),
}
readinessRoute?string | false

Where to serve the full report over plain HTTP, or false not to serve it. Defaults to /readiness.

More

It runs the checks and answers 200 when the service is healthy, 503 when it is not -- so unlike the GraphQL query it can be gated on a status code, where a federated response reports a partial failure as a 200 with a populated errors list. Point startup probes and deploy gates here.

Not a liveness probe. It reports on the service's dependencies, so a dependency outage answers 503 and would have every instance restarted for a fault none of them can fix. Liveness belongs on livenessRoute.

A readiness probe is worth a thought too, for the same reason in a quieter form: an outage of something every instance shares takes the whole deployment out of rotation, which turns a degraded service into an absent one. What this route is unambiguously right for is a startup probe -- invalid credentials or a missing scope then keep a new build from ever receiving traffic, which is the failure it can actually rule out.

livenessRoute?string | false

Where to serve the cheap liveness report, or false not to serve it. Defaults to /healthcheck, which is where the GraphQL server serves its own shallow probe, so a deployment that already points liveness there needs no change.

More

It answers 200 with the component name and the running release for as long as this process can serve a request, and deliberately runs no checks: a probe fails on a timeout as surely as on a status code, so a route meant to say "this process is alive" must not wait on anything that can hang.

graphql?boolean

Whether to contribute the Health entity to the service's GraphQL schema. Defaults to true.

More

Set it to false in a service that serves no GraphQL -- a payment provider, a sitemap builder, a search indexer. The module then contributes the HTTP routes only, where contributing type definitions would have made Application demand graphql options and turn the service into a subgraph.

timeout?number

How long a single check may take before it counts as failed, in milliseconds. Defaults to 2000.

More

A check that never settles is the case this exists for: without it a hung dependency hangs the health query itself, and a deploy gate waiting on the query learns nothing until the router's own timeout fires.

ProcessManagerRuns a service's start and shuts it down on SIGTERM or SIGINT: it awaits stop, flushes queued error reports and exits with 0.
import { ProcessManager } from "@evolve-framework/core";
new ProcessManager(args: Args)
OptionTypeDescription
start() => Promise<void>
stop?(() => Promise<void>)
MethodDescription
start(): Promise<void>

Functions

createApplicationConvenience factory: constructs an Application and awaits init().
import { createApplication } from "@evolve-framework/core";

The TUserContext generic is captured here so the options literal is checked against ApplicationOptions<TUserContext> — this is where plugin-type-safety against a specific context shape is enforced. The returned Application is non-generic.

function createApplication<TUserContext>(
options: ApplicationOptions<TUserContext>,
): Promise<Application>
OptionTypeDescription
namestring

The name of the domain service, used for identification and logging.

moduleModule<TUserContext>

The module that encapsulates the domain's business logic, GraphQL schema, and resolvers. This is a required property as the domain service relies on the module to define its functionality.

httpHTTPServerOptions

HTTP server configuration. Required to start the HTTP server that handles both GraphQL and REST requests.

onStart?(() => void | Promise<void>)

Sets up what the modules share, such as the cache connection, when start() is called and before the server listens. A rejection is a failed startup: the process shuts down and exits.

onShutdown?(() => void | Promise<void>)

Releases what a finished onStart acquired, after stop(), when the process shuts down on a signal. A direct stop() does not run it.

graphql?GraphQLOptions<TUserContext>

Optional GraphQL server configuration. This is only used if the module provides a GraphQL configuration. If the module does not provide a GraphQL configuration, this property is ignored.

Returns Promise<Application>

Types

ApplicationOptionsConfiguration options for creating an application.
import { type ApplicationOptions } from "@evolve-framework/core";
export type ApplicationOptions<TUserContext extends Record<string, unknown> = Record<string, never>> = {
name: string;
module: Module<TUserContext>;
onStart?: () => Promise<void> | void;
onShutdown?: () => Promise<void> | void;
graphql?: GraphQLOptions<TUserContext>;
http: HTTPServerOptions;
};
HealthCheckOne thing a service depends on, checked while it reports its own health.
import { type HealthCheck } from "@evolve-framework/core";

Returning false marks the dependency unhealthy; so does throwing, and the error message is reported. Anything else -- including returning nothing -- counts as healthy, so a bare read of the dependency is a complete check.

export type HealthCheck = () => unknown;
HttpConfigThe HTTP routes a module adds to the service, and the plugins scoped to them.
import { type HttpConfig } from "@evolve-framework/core";
export type HttpConfig = {
routes: (app: FastifyInstance) => void;
plugins?: [FastifyPluginCallback, FastifyPluginOptions][];
};
InternalEventConfigWhat a module answers to on the internal event bus.
import { type InternalEventConfig } from "@evolve-framework/core";

Internal events are the vendor-agnostic messages evolve services publish to each other — a category created, content modified, a payment authorized — each carrying a type, an origin and a timestamp, with one schema per type in @evolve-framework/schemas. They are deliberately not a provider's own delivery: a commercetools subscription, a Storyblok webhook or an Adyen notification arrives as HTTP on a route (getHttpConfig()) in whatever shape that provider sends, and the module owning that provider is what turns one into an internal event by publishing it.

So which commerce engine a category came from is not knowable from the event, and that is the point: a CMS module creating a page for a new category works unchanged behind commercetools or Medusa, and a storefront invalidating its cache on modified content does not care which CMS moved.

export type InternalEventConfig = {
handles: readonly string[];
handler: MessageHandler;
};
ModuleOne part of a service: the GraphQL schema and resolvers, the HTTP routes and the internal events it contributes.
import { type Module } from "@evolve-framework/core";
export interface Module<TContext = unknown> {
getGraphQLConfig(): GraphQLConfig | undefined;
getGraphQLSchema(): string | undefined;
getHttpConfig(): HttpConfig | undefined;
readonly dataLoaders?: DataLoaderDefinition<TContext>;
init?(): Promise<void> | void;
close?(): Promise<void> | void;
getInternalEventConfig?(): InternalEventConfig | undefined;
}

@evolve-framework/core/graphql/plugins

GraphQL server plugins for the client context and error logging.

Functions

useClientContextA Yoga plugin that settles the request's clientContext before resolvers run: it hands over the x-access-token header, if any, and awaits load().
import { useClientContext } from "@evolve-framework/core/graphql/plugins";
function useClientContext(): Plugin<ClientContextPlugin>

Returns Plugin<ClientContextPlugin>

useErrorLoggingLog GraphQL errors with the operation that produced them.
import { useErrorLogging } from "@evolve-framework/core/graphql/plugins";

GraphQL reports execution failures inside a 200 response, and Yoga only logs the ones it considers unexpected. Without this an error like a union that fails to resolve its type leaves no trace at all: the request log line says 200 and the field is simply null.

Errors carrying serviceName or code extensions are skipped — those come from a downstream service that has already logged them itself.

function useErrorLogging(): Plugin

Returns Plugin

@evolve-framework/core/messaging

The messaging port: the publisher and handler types, plus a Redis queue and a local development listener.

Classes

NoopPublisherA no-op publisher that logs and discards every message.
import { NoopPublisher } from "@evolve-framework/core/messaging";
new NoopPublisher()
MethodDescription
publish( message: Message, options?: PublishMessageOptions, ): Promise<void>Hand a message to the provider.
RedisPublisherPublishes internal events onto a Redis list, for local development, where startDevelopmentListener reads them back.
import { RedisPublisher } from "@evolve-framework/core/messaging";
new RedisPublisher(target: string)
MethodDescription
publish( data: Message, _options?: PublishMessageOptions, ): Promise<void>Hand a message to the provider.

Functions

isRedisQueueWhether target is a redis: or rediss: URL.
import { isRedisQueue } from "@evolve-framework/core/messaging";
function isRedisQueue(target: string): boolean

Returns boolean

startDevelopmentListenerPops messages from the Redis list target names and hands each, parsed as JSON, to callback, for running a consumer locally.
import { startDevelopmentListener } from "@evolve-framework/core/messaging";
function startDevelopmentListener<T>(
target: string,
callback: (event: T) => Promise<void>,
options?: Options,
): Promise<void>
OptionTypeDescription
continueOnError?boolean
dlq?string

Returns Promise<void>

Types

MessageA message as published and handled: a JSON object.
import { type Message } from "@evolve-framework/core/messaging";
export type Message = Record<string, unknown>;
MessageHandlerThe consume-side port.
import { type MessageHandler } from "@evolve-framework/core/messaging";
export type MessageHandler<T = Message> = (data: T) => Promise<void>;
MessagePublisherThe publish-side port.
import { type MessagePublisher } from "@evolve-framework/core/messaging";
export type MessagePublisher = {
publish(message: Message, options?: PublishMessageOptions): Promise<void>;
close?(): Promise<void>;
};
PublishMessageOptionsDelivery metadata a publisher maps onto its provider's own fields.
import { type PublishMessageOptions } from "@evolve-framework/core/messaging";
export type PublishMessageOptions = {
messageGroupId?: string;
origin?: string;
type?: string;
};

@evolve-framework/core/storage

The file storage port, the signed-URL port a browser uploads and downloads through, and a local-disk implementation for development.

Classes

LocalStorageClientStores files on the local disk, as <path>/<bucket>/<key>, for development and tests.
import { LocalStorageClient } from "@evolve-framework/core/storage";

A bucket or key that would land outside path, such as ../x, is refused: the cloud adapters treat both as plain object names, so a key that walks up the directory tree is a mistake and not a location.

new LocalStorageClient(path?: string)
MethodDescription
save( bucket: string, key: string, data: string, _options?: SaveOptions, ): Promise<void>Writes data as the object key in bucket, replacing what was stored there, with options.ContentType as its content type.

Functions

configureStorageClientResolves local filesystem storage; use a cloud adapter package otherwise.
import { configureStorageClient } from "@evolve-framework/core/storage";
function configureStorageClient(target: string): StorageClient

Returns StorageClient

Types

SaveOptions
import { type SaveOptions } from "@evolve-framework/core/storage";
export type SaveOptions = {
ContentType?: string;
};
SignedUploadOptions
import { type SignedUploadOptions } from "@evolve-framework/core/storage";
export type SignedUploadOptions = SignedUrlOptions & {
contentType: string;
};
SignedUrlOptionsWhat a signed URL is good for, and for how long.
import { type SignedUrlOptions } from "@evolve-framework/core/storage";
export type SignedUrlOptions = {
expiresInSeconds: number;
};
SignedUrlStorageClientA store that hands out time-limited URLs to one object, so a browser uploads or downloads it directly rather than through the service.
import { type SignedUrlStorageClient } from "@evolve-framework/core/storage";
export interface SignedUrlStorageClient {
signUpload: (bucket: string, key: string, options: SignedUploadOptions) => Promise<string>;
signRead: (bucket: string, key: string, options: SignedUrlOptions) => Promise<string>;
stat: (bucket: string, key: string) => Promise<StoredObject | undefined>;
}
StorageClientA store that writes objects by bucket and key.
import { type StorageClient } from "@evolve-framework/core/storage";
export interface StorageClient {
save: (bucket: string, key: string, data: string, options?: SaveOptions) => Promise<void>;
}
StoredObjectWhat a store knows of an object it holds.
import { type StoredObject } from "@evolve-framework/core/storage";
export type StoredObject = {
contentType?: string;
size?: number;
};

@evolve-framework/core/service/graphql

The GraphQL request context, the store context and the session and authentication guards resolvers use.

Classes

SessionExpiredErrorThrown when the commerce platform's access token has expired, or is within the safety buffer of expiring.
import { SessionExpiredError } from "@evolve-framework/core/service/graphql";

Extends GraphQLError

new SessionExpiredError(message?: string)

Functions

useClientContextA Yoga plugin that settles the request's clientContext before resolvers run: it hands over the x-access-token header, if any, and awaits load().
import { useClientContext } from "@evolve-framework/core/service/graphql";
function useClientContext(): Plugin<ClientContextPlugin>

Returns Plugin<ClientContextPlugin>

applyFieldResolversWraps each field resolver so a parent's own __fieldResolvers entry for the field answers instead, when it has one.
import { applyFieldResolvers } from "@evolve-framework/core/service/graphql";
function applyFieldResolvers<T>(resolvers: T): T

Returns T

assertActiveSessionThrows SESSION_REQUIRED (HTTP 401) unless the client has a session token.
import { assertActiveSession } from "@evolve-framework/core/service/graphql";
function assertActiveSession(context: ContextValue): void
OptionTypeDescription
clientContext{ isAuthenticated(): boolean; hasToken(): boolean; }

Returns void

assertAuthenticatedThrows UNAUTHENTICATED (HTTP 401) unless the client is signed in.
import { assertAuthenticated } from "@evolve-framework/core/service/graphql";
function assertAuthenticated(context: ContextValue): void
OptionTypeDescription
clientContext{ isAuthenticated(): boolean; hasToken(): boolean; }

Returns void

assertNotAuthenticatedThrows ALREADY_AUTHENTICATED (HTTP 403) when the client is signed in.
import { assertNotAuthenticated } from "@evolve-framework/core/service/graphql";
function assertNotAuthenticated(context: ContextValue): void
OptionTypeDescription
clientContext{ isAuthenticated(): boolean; hasToken(): boolean; }

Returns void

createRequestContextReads the client address from the X-Client-IP header, which the router fills from the X-Forwarded-For chain the ingress hands over.
import { createRequestContext } from "@evolve-framework/core/service/graphql";

Only the right end of the chain is the platform's: a caller can put anything in front of it, so keying a rate limit on the whole chain or its first entry would give a caller a fresh bucket per request. This relies on the service being reachable through the router only.

function createRequestContext(
request: Request,
options?: RequestContextOptions,
): RequestContext
OptionTypeDescription
trustedHops?number

How many entries at the right end of X-Client-IP the platform's own proxies appended, the client's own address included. ipAddress is the entry that many places from the right; the entries left of it are whatever the caller sent. Defaults to 1, the entry the nearest proxy appended. Behind a Google Cloud load balancer, which appends the client and then its own address, this is 2.

Returns RequestContext

createZodGraphqlErrorA helper function to create a GraphQLError with the correct extensions for the GraphQL Server.
import { createZodGraphqlError } from "@evolve-framework/core/service/graphql";
function createZodGraphqlError(...issues: $ZodIssue[]): GraphQLError

Returns GraphQLError

readStoreContextFromRequestRead the store context from the request HTTP headers.
import { readStoreContextFromRequest } from "@evolve-framework/core/service/graphql";

If not all required headers are present, an error is thrown. The market, X-StoreContext-Country, is optional and read upper-cased; one that is not a two-letter country code is an error too.

A service that carries more than the currency, locale and store key on its store context passes a reader per own field. They are read from the request the same way and end up on the returned store context:

const storeContext = readStoreContextFromRequest(request, {
	channel: {
		header: "X-StoreContext-Channel",
		read: (value) => value ?? "web",
	},
});
// StoreContext & { channel: string }
function readStoreContextFromRequest<ExtraFields>(
request: Request,
extraFields?: { [Field in keyof ExtraFields]: StoreContextFieldReader<ExtraFields[Field]>; },
): StoreContext & ExtraFields

Returns StoreContext & ExtraFields

throwInternalErrorRethrows err as an INTERNAL_SERVER_ERROR GraphQL error that keeps it as originalError, with its message, or "Internal error" for a non-Error.
import { throwInternalError } from "@evolve-framework/core/service/graphql";
function throwInternalError(err: unknown): never

Returns never

validateZodSchemaParses data with schema and returns the result.
import { validateZodSchema } from "@evolve-framework/core/service/graphql";
function validateZodSchema<T>(
schema: T,
data: unknown,
): z.infer<T>

Returns z.infer<T>

Types

ClientContextThe per-request session an engine keeps.
import { type ClientContext } from "@evolve-framework/core/service/graphql";
export interface ClientContext {
isAuthenticated(): boolean;
setAccessToken(token: string): void;
load(): Promise<void>;
}
ContextValueThe GraphQL context every resolver receives; packages extend it.
import { type ContextValue } from "@evolve-framework/core/service/graphql";
export interface ContextValue {
loaders?: Record<string, unknown>;
storeContext: StoreContext;
clientContext: ClientContext;
request: RequestContext;
[key: string]: unknown;
}
RequestContextThe incoming request, with the client address read from X-Client-IP.
import { type RequestContext } from "@evolve-framework/core/service/graphql";
export type RequestContext = Request & {
ipAddress: string;
};
ServerContextBase server context type that is passed to all GraphQL resolvers.
import { type ServerContext } from "@evolve-framework/core/service/graphql";
export type ServerContext = Record<string, unknown>;
StoreContextThe store a request is made in, from its X-StoreContext-* headers.
import { type StoreContext } from "@evolve-framework/core/service/graphql";
export type StoreContext = {
currency: string;
locale: string;
storeKey: string;
country?: string;
};
TypeWithInternalFieldsA resolver parent that can carry its own field resolvers under __fieldResolvers, keyed by its __typename, for applyFieldResolvers.
import { type TypeWithInternalFields } from "@evolve-framework/core/service/graphql";
export type TypeWithInternalFields<T, R> = {
__instance?: unknown;
__fieldResolvers?: R[ExtractTypeName<T> extends keyof R ? ExtractTypeName<T> : never];
} & T;

@evolve-framework/core/service/http

The HTTP server underneath an application.

Functions

fastifyLoggerFastify plugin to log incoming requests and outgoing responses.
import { fastifyLogger } from "@evolve-framework/core/service/http";
function fastifyLogger(
instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>,
opts: FastifyPluginOptions,
done: (err?: Error | undefined) => void,
): void

Returns void

startHttpServerListens on address, resolving once the server accepts connections, so a close() that follows is guaranteed to find it listening.
import { startHttpServer } from "@evolve-framework/core/service/http";
function startHttpServer(
server: Server<typeof IncomingMessage, typeof ServerResponse> | FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>,
address: Address,
): Promise<void>
OptionTypeDescription
hoststring
portnumber

Returns Promise<void>

Types

HttpServer
import { type HttpServer } from "@evolve-framework/core/service/http";
export type HttpServer = FastifyInstance<Server>;

@evolve-framework/core/cache

The Redis-backed cache with its in-memory tier.

Classes

LocalTierStoreA store that answers from memory before asking the store it wraps.
import { LocalTierStore } from "@evolve-framework/core/cache";

Every read a Keyv store performs is a network round trip, and a dataloader batch of one key is still a round trip. For data that a handful of keys covers and that barely changes — product types, the attributes highlighted on them, categories, the store itself — that round trip is the entire cost of the lookup: the value was already there, it just had to travel. This tier removes the travel for values that were read recently, and asks the wrapped store only for the keys it does not have.

What is held in memory is the encoded entry, exactly as the wrapped store returned it, not a deserialized value. Two things follow, and both matter:

  • Keyv decodes and checks expiry above this layer, so an entry can never be served past the expiry it was written with. The local ttl only decides how long a value may be answered from memory; it cannot extend its life.
  • Each caller gets a value decoded from scratch, so nothing here can be mutated by one caller and observed by another.

The cost is that eviction is local. delete reaches the wrapped store, so other instances see it, but their own memory is untouched and keeps answering from it until the local ttl runs out. Anything that must be uniformly fresh the moment it changes does not belong behind this tier; everything else trades a bounded window of staleness for not going over the network at all.

Extends EventEmitter<any>

new LocalTierStore(delegate: KeyvStoreAdapter, options: LocalTierOptions)
OptionTypeDescription
ttlnumber

How long a value may be served from memory, in milliseconds. This bounds how long an eviction on another instance goes unnoticed, so it wants to be short even when the value behind it lives much longer.

max?number

Maximum number of entries held in memory.

MethodDescription
get<Value>(key: string): Promise<StoredData<Value>>
getMany<Value>( keys: string[], ): Promise<StoredData<Value | undefined>[]>
set(key: string, value: any, ttl?: number | undefined): Promise<any>
setMany( values: { key: string; value: any; ttl?: number | undefined; }[], ): Promise<void>
delete(key: string): Promise<boolean>
deleteMany(keys: string[]): Promise<boolean>
clear(): Promise<void>
has(key: string): Promise<boolean>
disconnect(): Promise<void>

Functions

createKeyvMemoryStoreReturns a Keyv store that keeps its entries in this process.
import { createKeyvMemoryStore } from "@evolve-framework/core/cache";

Nothing is shared between instances, so anything that reads a cache another instance wrote — an invalidation in particular — behaves differently than it does against Redis. That makes this a store for tests and for a local run with no Redis to point at, not a fallback for a deployment.

function createKeyvMemoryStore<T>(
namespace: string,
options?: KeyvRedisStoreOptions,
): Keyv<T>
OptionTypeDescription
localTtl?number

When set, values are additionally held in memory for this many milliseconds, so a repeated read costs nothing rather than a round trip. Only for data that tolerates being briefly stale after another instance changes it — see LocalTierStore for what that window actually means.

Returns Keyv<T>

createKeyvRedisStoreReturns a Keyv store that uses Redis as a backend.
import { createKeyvRedisStore } from "@evolve-framework/core/cache";
function createKeyvRedisStore<T>(
redisClient: RedisClientType<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping> | undefined,
namespace: string,
options?: KeyvRedisStoreOptions,
): Keyv<T>
OptionTypeDescription
localTtl?number

When set, values are additionally held in memory for this many milliseconds, so a repeated read costs nothing rather than a round trip. Only for data that tolerates being briefly stale after another instance changes it — see LocalTierStore for what that window actually means.

Returns Keyv<T>

createRateLimitStoreAdapts a client from createRedisClient for RedisStore from @envelop/rate-limiter: new RedisStore(createRateLimitStore(cache.client)).
import { createRateLimitStore } from "@evolve-framework/core/cache";

It fails open. Each command is bounded by the cache's timeout, a read that fails or times out answers no earlier calls, and a write that does is dropped, both with a throttled warning. After a failure Redis is left alone for a few seconds, so a limit check during an outage answers at once. Limits are not enforced while Redis is away, which is the price of a checkout that keeps working through a failover.

function createRateLimitStore(
client: RedisClientType<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping>,
timeout?: number,
retryAfter?: number,
): RateLimitRedisClient

Returns RateLimitRedisClient

createRedisClientcreate a new redis.RedisClient instance
import { createRedisClient } from "@evolve-framework/core/cache";

A number of settings are passed to the constructor to ensure that the client handles network issues gracefully.

Authentication that requires a cloud-specific SDK (for example Microsoft Entra ID for Azure Managed Redis) is not handled here. Instead the caller injects a credentialsProvider built by the relevant cloud adapter (e.g. createEntraIdCredentialsProvider from @evolve-framework/cloud-adapter-azure). This keeps core free of any cloud provider SDK.

function createRedisClient(
redisUrl: string | undefined,
credentialsProvider?: CredentialsProvider,
): Promise<RedisClient | undefined>
OptionTypeDescription
type"async-credentials-provider"
credentials?() => Promise<BasicAuth>
subscribe?(listener: StreamingCredentialsListener<BasicAuth>) => Promise<[BasicAuth, Disposable]>

Provides initial credentials and subscribes to subsequent updates. This is used internally by the node-redis client to handle credential rotation and re-authentication.

More

Note: The node-redis client manages the subscription lifecycle automatically. Users only need to implement onReAuthenticationError if they want to be notified about authentication failures.

Error handling:

  • Errors received via onError indicate a fatal issue with the credentials stream
  • The stream is automatically closed(disposed) when onError occurs
  • onError typically mean the provider failed to fetch new credentials after retrying
onReAuthenticationError?(error: ReAuthenticationError) => void

Called when authentication fails or credentials cannot be renewed in time. Implement this to handle authentication errors in your application.

Returns Promise<RedisClient | undefined>

Constants

cacheThe process-wide cache.
import { cache } from "@evolve-framework/core/cache";
const cache: CacheHandler

Types

CredentialsProviderA Redis credentials provider, as accepted by the underlying @redis/client.
import { type CredentialsProvider } from "@evolve-framework/core/cache";

Cloud adapters build a concrete provider that authenticates against a specific platform — e.g. createEntraIdCredentialsProvider from @evolve-framework/cloud-adapter-azure for Azure Managed Redis — and the application injects it via cache.configure. Keeping this as the public port type means core never depends on a cloud provider SDK.

export type CredentialsProvider = NonNullable<RedisClientOptions["credentialsProvider"]>;
RateLimitRedisClientThe callback client RedisStore from @envelop/rate-limiter drives.
import { type RateLimitRedisClient } from "@evolve-framework/core/cache";
export type RateLimitRedisClient = {
get(key: string, callback: Callback<string>): void;
set(args: Array<string | number>, callback: (error: Error | null) => void): void;
};
RedisClientThe node-redis client createRedisClient returns.
import { type RedisClient } from "@evolve-framework/core/cache";
export type RedisClient = ReturnType<typeof createClient>;

@evolve-framework/core/utils

Building blocks for per-request dataloaders, and file utilities for build scripts.

Functions

copyFilesReturn a function that copies files to the specified directories.
import { copyFiles } from "@evolve-framework/core/utils";
function copyFiles(...directories: string[]): (...files: string[]) => void

Returns (...files: string[]) => void

createDeferredDataLoadersCreates request-scoped DataLoaders that are initialized only when first used.
import { createDeferredDataLoaders } from "@evolve-framework/core/utils";
function createDeferredDataLoaders<TLoaders>(
initializers: { [K in keyof TLoaders]: () => TLoaders[K]; },
): TLoaders

Returns TLoaders

defineDataLoadersDeclares a set of loaders that a module contributes to the request context.
import { defineDataLoaders } from "@evolve-framework/core/utils";

Curried so that TContext can be given explicitly while the loader names and their types are still inferred from the object literal.

function defineDataLoaders<TContext>(): <TInitializers extends Record<string, (context: TContext) => unknown>>(initializers: TInitializers) => DataLoaderDefinition<TContext, { [K in keyof TInitializers]: ReturnType<TInitializers[K]>; }>

Returns <TInitializers extends Record<string, (context: TContext) => unknown>>(initializers: TInitializers) => DataLoaderDefinition<TContext, { [K in keyof TInitializers]: ReturnType<TInitializers[K]>; }>

mergeDataLoaderDefinitionsCombines the loader definitions a composition contributes into one context.loaders.
import { mergeDataLoaderDefinitions } from "@evolve-framework/core/utils";

Definitions are deduplicated by identity, so the twelve modules of a single package may all point at the same module-scoped definition without that counting as a conflict. Two different definitions claiming the same name is a hard error: the names are a flat namespace and there is no way to tell which implementation a resolver meant. Silently letting one win is how the bug this design came from stayed invisible.

Nothing is constructed here. A definition's create runs on the first read of any loader it owns, and the loaders it returns are each built on first use.

function mergeDataLoaderDefinitions<TContext>(
definitions: Iterable<DataLoaderDefinition<TContext, Record<string, unknown>>>,
context: TContext,
): Record<string, unknown>

Returns Record<string, unknown>

overlayDataLoadersLays one set of loaders over another, with the overlay winning on any shared name.
import { overlayDataLoaders } from "@evolve-framework/core/utils";

Only needed while services still assemble loaders by hand: the modules contribute their declarations and whatever the service already put on the context keeps precedence, so a service can migrate one module at a time instead of in a single step. Both sets are enumerable, so this is a plain key merge — no proxy juggling, and still nothing is constructed until read.

function overlayDataLoaders(
base: Record<string, unknown>,
overlay: Record<string, unknown>,
): Record<string, unknown>

Returns Record<string, unknown>

Types

DataLoaderDefinitionA set of loaders a module contributes, declared once at module scope.
import { type DataLoaderDefinition } from "@evolve-framework/core/utils";

Declaration and construction are deliberately separate: keys is known as soon as the module is imported, so a composition can be checked for duplicates before a request is ever served, while create is only called per request and hands back loaders that are each still built on first use.

export type DataLoaderDefinition<TContext, TLoaders extends Record<string, unknown> = Record<string, unknown>> = {
readonly keys: readonly string[];
readonly create: (context: TContext) => TLoaders;
};
LoadersOfThe loaders a declaration — or a composition — produces.
import { type LoadersOf } from "@evolve-framework/core/utils";

A service that wants a typed context.loaders for its resolvers builds it from the declarations rather than asserting a shape:

type Loaders = LoadersOf<typeof commercetoolsDataLoaders> &
	LoadersOf<typeof myModuleDataLoaders>;

Adding a module to the composition and forgetting it here leaves its loaders untyped rather than missing, so this stays a reading convenience — what is actually present at runtime is decided by the modules, not by this type.

export type LoadersOf<TDefinition> = TDefinition extends DataLoaderDefinition<any, infer TLoaders> ? TLoaders : never;

@evolve-framework/core/http/client

A fetch-based HTTP client with retries and error helpers.

Functions

fetchClientCustom fetch function
import { fetchClient } from "@evolve-framework/core/http/client";
function fetchClient(
url: string | Request | URL,
options?: RequestInit,
spanName?: string,
): Promise<Response>

Returns Promise<Response>

isHttpErrorWhether error is an Error that carries an HTTP statusCode.
import { isHttpError } from "@evolve-framework/core/http/client";
function isHttpError(error: unknown): error is HttpError

Returns error is HttpError

joinURLJoins URL parts with single slashes, as url-join does.
import { joinURL } from "@evolve-framework/core/http/client";
function joinURL(...parts: string[]): string

Returns string

retrywill retry the fn in case of an exception with exponential backoff;
import { retry } from "@evolve-framework/core/http/client";
function retry<T, E>(
fn: () => Promise<T>,
options?: Options<T, E>,
): Promise<T>
OptionTypeDescription
maxNumberOfAttempts?number
baseDelay?number
maxDelay?number
errorHandler?((error: E) => () => Promise<T>)

Returns Promise<T>

@evolve-framework/core/observability

The logger, tracer and meter.

Functions

getActiveSpanThe span of the current async context, if any.
import { getActiveSpan } from "@evolve-framework/core/observability";
function getActiveSpan(): Span | undefined

Returns Span | undefined

resourceSpanNameName a span that wraps a logical call to a vendor.
import { resourceSpanName } from "@evolve-framework/core/observability";

The transport span under it already carries the method and the host, so a wrapper named after the request path only restates it -- and truncating that path to keep ids out of the name collapses unrelated calls together. The name says which resource was addressed; the request itself belongs in the span's attributes.

segments are the meaningful path segments, in order, with whatever scopes a request rather than names a resource already removed -- a project key, an API version, an in-store prefix. The first is the resource; anything after it selects one, so the name is singular.

function resourceSpanName(
vendor: string,
method: string,
segments: readonly string[],
): string

Returns string

setLogDestinationDirects log output to stdout or stderr.
import { setLogDestination } from "@evolve-framework/core/observability";

A process whose stdout carries a protocol -- an MCP server speaking JSON-RPC over stdio, for instance -- must move its logs to stderr, because a log line interleaved into that stream corrupts the protocol. Call this before the first log line is written; anything already flushed stays where it went.

function setLogDestination(destination: LogDestination): void

Returns void

urlResourceSegmentsThe meaningful path segments of a URL, for a caller with no vendor-specific knowledge of the path.
import { urlResourceSegments } from "@evolve-framework/core/observability";
function urlResourceSegments(path: string): string[]

Returns string[]

Constants

contextThe per-request logger store logger reads from once withLoggerContext has enabled it.
import { context } from "@evolve-framework/core/observability";
const context: { store: AsyncLocalStorage<Map<string, Logger>>; enabled: boolean; }
loggerThe logger to be used by all applications.
import { logger } from "@evolve-framework/core/observability";
const logger: Logger
meterThe OpenTelemetry meter, named after SERVICE_NAME.
import { meter } from "@evolve-framework/core/observability";
const meter: Meter
tracerInstrumentation scope for the spans the framework itself emits — the commercetools client middleware, the cache handler, the Lambda wrapper.
import { tracer } from "@evolve-framework/core/observability";

A scope names the code doing the instrumenting, not the service being instrumented; the service already travels on the resource as service.name. Naming it after SERVICE_NAME also read the variable at module load, before initObservability() applies its own fallback, so an unset SERVICE_NAME interpolated to the literal string "undefined".

const tracer: Tracer

Types

LogDestination
import { type LogDestination } from "@evolve-framework/core/observability";
export type LogDestination = "stdout" | "stderr";
Logger
import { type Logger } from "@evolve-framework/core/observability";
export type Logger<CustomLevels extends string = never, UseOnlyCustomLevels extends boolean = boolean> = BaseLogger & LoggerExtras<CustomLevels> & CustomLevelLogger<CustomLevels, UseOnlyCustomLevels>

@evolve-framework/core/observability/configure

Start-up configuration for tracing, metrics, Sentry and event-loop monitoring.

Functions

configureEventLoopBlockDetectionReport a blocked event loop to Sentry, with the stack trace of whatever was holding it.
import { configureEventLoopBlockDetection } from "@evolve-framework/core/observability/configure";

A stall is invisible in a trace. Every span that happens to be open absorbs it, so dozens of resolvers report an identical duration and none of them is the one at fault -- which is why the resolver span filter refuses to judge a span by its duration. This answers the question those spans cannot: a watchdog thread notices the main thread has stopped answering and captures where it is stuck.

On wherever there is a DSN to report to, unless SENTRY_EVENT_LOOP_BLOCK is false. SENTRY_EVENT_LOOP_BLOCK_THRESHOLD sets how long the loop must be blocked before an event is sent, in milliseconds.

What it costs is a watchdog thread, which is a V8 isolate of its own and measures around 35MB. The polling it asks of the main thread does not register: one poll per half threshold, at roughly 8 microseconds each, is two hundred-thousandths of a core at the default. Sentry also rate-limits itself to one such event per hour, so this reports that stalls are happening and what they look like, not how many there were.

Nothing here is allowed to prevent the service from starting. The native module resolves a prebuilt binary for the exact platform, libc and Node ABI, and throws while loading when there is no match -- on a Node release that Sentry has not built for yet, that would otherwise take down every service at startup, before any of them could report why. Now that this runs by default that is every service at once, which is why it is imported dynamically and a failure is a warning.

function configureEventLoopBlockDetection(): Promise<void>

Returns Promise<void>

configureSentryConfigure Sentry and all relevant integrations.
import { configureSentry } from "@evolve-framework/core/observability/configure";

This also makes sure that whenever we send an event to Sentry, we also include the current logger context.

function configureSentry({ tracing, redact }?: SentryOptions | undefined): void
OptionTypeDescription
tracing?boolean

Overrides SENTRY_TRACING for a call site that knows better than the environment does. See sentryOwnsTracing for what it selects.

redact?RedactOptions

Overrides the environment defaults for URL and span redaction.

Returns void

initObservabilityInitialize error reporting and tracing before the application loads.
import { initObservability } from "@evolve-framework/core/observability/configure";

This code is based on the documentation provided by Sentry, see: - https://docs.sentry.io/platforms/node/performance/instrumentation/opentelemetry/

function initObservability({ redact }?: ObservabilityOptions | undefined): void
OptionTypeDescription
redact?RedactOptions

Overrides the environment defaults for URL and span redaction.

Returns void

registerObservabilityStart observability for a whole process: initObservability, then the event-loop block watchdog.
import { registerObservability } from "@evolve-framework/core/observability/configure";
function registerObservability(
options?: ObservabilityOptions,
): Promise<void>
OptionTypeDescription
redact?RedactOptions

Overrides the environment defaults for URL and span redaction.

Returns Promise<void>

Types

RedactOptions
import { type RedactOptions } from "@evolve-framework/core/observability/configure";
export type RedactOptions = {
queryParams?: string[];
paths?: string[];
};

@evolve-framework/core/observability/logging

Logging only: the logger, its destination and error reporting.

Functions

setErrorReporterSend log events of error level and above to an error reporting backend.
import { setErrorReporter } from "@evolve-framework/core/observability/logging";

The backend registers itself here rather than being imported, so that this module carries no reporting SDK of its own. A process that only writes logs then pays neither the dependency nor the time it takes to load it, which for the Sentry SDK is close to seven hundred modules -- a cost that would otherwise fall on everything that imports a logger, including every test file. A process that does report registers the backend when it configures it.

Until a reporter is registered, error level logs are written and nothing else happens to them.

function setErrorReporter(reporter: ErrorReporter): void

Returns void

setLogDestinationDirects log output to stdout or stderr.
import { setLogDestination } from "@evolve-framework/core/observability/logging";

A process whose stdout carries a protocol -- an MCP server speaking JSON-RPC over stdio, for instance -- must move its logs to stderr, because a log line interleaved into that stream corrupts the protocol. Call this before the first log line is written; anything already flushed stays where it went.

function setLogDestination(destination: LogDestination): void

Returns void

withLoggerContextRuns the given function within a new logger context.
import { withLoggerContext } from "@evolve-framework/core/observability/logging";
function withLoggerContext<T>(child: Logger, fn: () => T | Promise<T>): void

Returns void

Constants

loggerThe logger to be used by all applications.
import { logger } from "@evolve-framework/core/observability/logging";
const logger: Logger
rootLoggerThe root logger, of which childs can be created.
import { rootLogger } from "@evolve-framework/core/observability/logging";
const rootLogger: Logger<never, boolean>

Types

ErrorReportOne log event of error level or above, as handed to the error reporter.
import { type ErrorReport } from "@evolve-framework/core/observability/logging";
export type ErrorReport = {
level: ErrorReportLevel;
message: string;
error?: Error;
context: Record<string, string>;
fingerprint: string[];
};
ErrorReporterSends an error report to a backend; registered with setErrorReporter.
import { type ErrorReporter } from "@evolve-framework/core/observability/logging";
export type ErrorReporter = (report: ErrorReport) => void;
ErrorReportLevelThe severity of a reported log event, using the names Sentry gives them.
import { type ErrorReportLevel } from "@evolve-framework/core/observability/logging";
export type ErrorReportLevel = "fatal" | "error" | "warning" | "info" | "debug";
LogDestination
import { type LogDestination } from "@evolve-framework/core/observability/logging";
export type LogDestination = "stdout" | "stderr";
Logger
import { type Logger } from "@evolve-framework/core/observability/logging";
export type Logger<CustomLevels extends string = never, UseOnlyCustomLevels extends boolean = boolean> = BaseLogger & LoggerExtras<CustomLevels> & CustomLevelLogger<CustomLevels, UseOnlyCustomLevels>

@evolve-framework/core/address

The city-lookup port an address-lookup provider implements, and officialCity, which an engine applies on an address write.

Functions

officialCityThe city to store for an address: the provider's spelling where it names the same city ignoring case, so "utrecht" is stored as "Utrecht", and otherwise the input unchanged.
import { officialCity } from "@evolve-framework/core/address";
function officialCity(
lookup: CityLookup | undefined,
address: { country?: string | null | undefined; postalCode?: string | null | undefined; streetNumber?: string | null | undefined; city?: string | null | undefined; },
options?: OfficialCityOptions,
): Promise<string | undefined>
OptionTypeDescription
country?string | null
postalCode?string | null
streetNumber?string | null
city?string | null
timeoutMs?number

How long the lookup may take before the input is kept. Defaults to 1.5 seconds.

Returns Promise<string | undefined>

Types

CityLookupThe official name of the city at a postcode and house number, from an address-lookup provider.
import { type CityLookup } from "@evolve-framework/core/address";
export type CityLookup = (query: CityLookupQuery) => Promise<string | undefined>;
CityLookupQueryThe part of an address a lookup by postcode and house number needs.
import { type CityLookupQuery } from "@evolve-framework/core/address";
export type CityLookupQuery = {
country: string;
postalCode: string;
streetNumber: string;
};

Signing a link that lets a buyer read one order until it expires, and reading one back.

Functions

createOrderLinkTokenSign a link to an order, valid until expiresAt.
import { createOrderLinkToken } from "@evolve-framework/core/order-link";
function createOrderLinkToken(link: OrderLink, secret: string): string
OptionTypeDescription
orderIdstring
expiresAtDate

Returns string

readOrderLinkTokenThe order a token links to, or undefined when the token is malformed, signed with another secret, or past its expiry.
import { readOrderLinkToken } from "@evolve-framework/core/order-link";
function readOrderLinkToken(
token: string,
secret: string,
now?: Date,
): OrderLink | undefined

Returns OrderLink | undefined

Types

@evolve-framework/core/site-revalidation

The sites' cache tags, and a signed purge that a CMS publish sends to each site's revalidate route.

Functions

articlesTagEvery article, overview or topic list query, and Product.articles.
import { articlesTag } from "@evolve-framework/core/site-revalidation";
function articlesTag(locale: string): string

Returns string

createSiteRevalidatorA revalidator that POSTs { tags }, signed with the shared secret, to each site URL.
import { createSiteRevalidator } from "@evolve-framework/core/site-revalidation";
function createSiteRevalidator(
options: SiteRevalidationOptions,
logger: RevalidationLogger,
): SiteRevalidator
OptionTypeDescription
urlsstring[]

Each site's revalidate route, the URL its createRevalidateRoute answers on, such as https://shop.example.com/api/revalidate.

secretstring

The secret shared with the sites, their EVOLVE_REVALIDATE_SECRET.

info(entry: Record<string, unknown>) => void
error(entry: Record<string, unknown>) => void
maxAttempts?number

Attempts per URL before a purge is given up on. Defaults to 3.

timeout?number

How long one attempt may take, in milliseconds. Defaults to 5000.

Returns SiteRevalidator

layoutTagNavigation and site settings.
import { layoutTag } from "@evolve-framework/core/site-revalidation";
function layoutTag(locale: string): string

Returns string

messagesTagThe store's fixed system copy and its content snippets.
import { messagesTag } from "@evolve-framework/core/site-revalidation";
function messagesTag(locale: string): string

Returns string

pagesTagEvery page fetch in a locale, for content embedded in pages nobody can list.
import { pagesTag } from "@evolve-framework/core/site-revalidation";
function pagesTag(locale: string): string

Returns string

pageTagThe tag a site puts on one page fetch, page:{locale}:{path}, with the home page as /.
import { pageTag } from "@evolve-framework/core/site-revalidation";
function pageTag(locale: string, path: string): string

Returns string

Types

SiteRevalidatorPurge the given cache tags on every site.
import { type SiteRevalidator } from "@evolve-framework/core/site-revalidation";
export type SiteRevalidator = (tags: string[]) => Promise<void>;

Deprecated

Still exported so existing code keeps working. Each one says what replaces it.

createDomainService
import { createDomainService } from "@evolve-framework/core";
function createDomainService<TUserContext>(
options: ApplicationOptions<TUserContext>,
): Promise<Application>
OptionTypeDescription
namestring

The name of the domain service, used for identification and logging.

moduleModule<TUserContext>

The module that encapsulates the domain's business logic, GraphQL schema, and resolvers. This is a required property as the domain service relies on the module to define its functionality.

httpHTTPServerOptions

HTTP server configuration. Required to start the HTTP server that handles both GraphQL and REST requests.

onStart?(() => void | Promise<void>)

Sets up what the modules share, such as the cache connection, when start() is called and before the server listens. A rejection is a failed startup: the process shuts down and exits.

onShutdown?(() => void | Promise<void>)

Releases what a finished onStart acquired, after stop(), when the process shuts down on a signal. A direct stop() does not run it.

graphql?GraphQLOptions<TUserContext>

Optional GraphQL server configuration. This is only used if the module provides a GraphQL configuration. If the module does not provide a GraphQL configuration, this property is ignored.

Returns Promise<Application>

DomainService
import { DomainService } from "@evolve-framework/core";

Extends Application

new DomainService(options: ApplicationOptions<any>)
OptionTypeDescription
namestring

The name of the domain service, used for identification and logging.

moduleModule<any>

The module that encapsulates the domain's business logic, GraphQL schema, and resolvers. This is a required property as the domain service relies on the module to define its functionality.

httpHTTPServerOptions

HTTP server configuration. Required to start the HTTP server that handles both GraphQL and REST requests.

onStart?(() => void | Promise<void>)

Sets up what the modules share, such as the cache connection, when start() is called and before the server listens. A rejection is a failed startup: the process shuts down and exits.

onShutdown?(() => void | Promise<void>)

Releases what a finished onStart acquired, after stop(), when the process shuts down on a signal. A direct stop() does not run it.

graphql?GraphQLOptions<any>

Optional GraphQL server configuration. This is only used if the module provides a GraphQL configuration. If the module does not provide a GraphQL configuration, this property is ignored.

Not listed: 8 options types. They appear as the tables under the constructors and functions they type.