@beignet/core
Version:
Core framework primitives for Beignet
98 lines (90 loc) • 2.71 kB
text/typescript
/**
* PortsBuilder - Mutable builder for composing ports during app initialization
*
* This builder is a small composition helper for tests and custom bootstrapping
* code that wants to assemble ports incrementally.
*/
/**
* A mutable builder around a ports object.
* Used during app composition and provider registration.
*/
export interface PortsBuilder<Ports> {
/**
* The current ports object being built.
* This is mutated internally when extend/replace are called.
*/
ports: Ports;
/**
* Extend ports with a new key. If the key already exists, it's overwritten.
*
* Returns the same builder instance with an extended type:
* Ports & { [K in key]: Value }
*/
extend<K extends string, V>(
key: K,
value: V,
): PortsBuilder<Ports & { [P in K]: V }>;
/**
* Replace an existing key. Does not change the type, but updates the runtime value.
* Returns the same builder instance.
*/
replace<K extends keyof Ports>(key: K, value: Ports[K]): PortsBuilder<Ports>;
}
/**
* Create a new PortsBuilder from an initial ports object.
*
* The builder wraps a mutable object internally and provides type-safe methods
* to extend or replace ports. This is used during application composition
* helpers that want to modify ports before passing them to a server.
*
* @example
* ```ts
* const initialPorts = definePorts({ db: dbAdapter });
* const builder = createPortsBuilder(initialPorts);
*
* // Provider extends with cache
* builder.extend("cache", cacheAdapter);
*
* // Final ports includes both db and cache
* const finalPorts = builder.ports;
* ```
*/
export function createPortsBuilder<Ports>(
initialPorts: Ports,
): PortsBuilder<Ports> {
// Keep a mutable object internally
// biome-ignore lint/suspicious/noExplicitAny: internal mutable state needs any to accept arbitrary port extensions
const state: { ports: any } = {
ports: { ...initialPorts },
};
const builder: PortsBuilder<Ports> = {
get ports() {
// Could freeze in dev, but keep simple
return state.ports as Ports;
},
extend<K extends string, V>(
key: K,
value: V,
): PortsBuilder<Ports & { [P in K]: V }> {
state.ports[key] = value;
return builder as unknown as PortsBuilder<Ports & { [P in K]: V }>;
},
replace<K extends keyof Ports>(
key: K,
value: Ports[K],
): PortsBuilder<Ports> {
state.ports[key] = value;
return builder;
},
};
return builder;
}
/**
* Extract the Ports type from a PortsBuilder
*
* @example
* ```ts
* type MyPorts = PortsOf<typeof builder>;
* ```
*/
export type PortsOf<PB> = PB extends PortsBuilder<infer P> ? P : never;