UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

129 lines 5.37 kB
import type { StandardSchemaV1 } from "@standard-schema/spec"; /** * Any Standard Schema compatible validator. */ export type StandardSchema = StandardSchemaV1<unknown, unknown>; /** * Infer the parsed output type from a Standard Schema. */ export type InferOutput<T extends StandardSchemaV1> = StandardSchemaV1.InferOutput<T>; type AnyMethods = Record<string, any>; /** * Base entity instance passed to the `.methods(...)` builder: validated props * plus the `with` and `toJSON` helpers, before domain methods attach. * * `with(...)` is typed as returning the base instance here because the final * methods object is still being defined while the builder runs. At runtime * the returned instance carries the entity's methods, and `EntityInstance` * maps base-instance returns back to the full instance type so consumers keep * typed method chaining. */ export type EntityBaseInstance<Schema extends StandardSchema> = InferOutput<Schema> & { /** * Create a new validated entity instance with patched properties. */ with(patch: Partial<InferOutput<Schema>>): EntityBaseInstance<Schema> | Promise<EntityBaseInstance<Schema>>; /** * Convert the entity to a plain JSON object for persistence. */ toJSON(): InferOutput<Schema>; }; /** * Replace base-instance returns with the full entity instance type, unwrapping * promises, so methods that return `self` or `self.with(...)` chain with the * entity's methods attached. */ type WithEntityInstance<R, Instance, Schema extends StandardSchema> = R extends Promise<infer P> ? Promise<WithEntityInstance<P, Instance, Schema>> : R extends EntityBaseInstance<Schema> ? Instance : R; /** * Entity instance with validated props, attached methods, and immutable update * helpers. Method returns that are typed as the base instance (`self` or * `self.with(...)`) are mapped to the full instance type so chaining keeps * the entity's methods. */ export type EntityInstance<Name extends string, Schema extends StandardSchema, Methods extends AnyMethods> = InferOutput<Schema> & { /** * Create a new validated entity instance with patched properties. */ with(patch: Partial<InferOutput<Schema>>): EntityInstance<Name, Schema, Methods> | Promise<EntityInstance<Name, Schema, Methods>>; /** * Convert the entity to a plain JSON object for persistence. */ toJSON(): InferOutput<Schema>; } & { [K in keyof Methods]: Methods[K] extends (...args: infer A) => infer R ? (...args: A) => WithEntityInstance<R, EntityInstance<Name, Schema, Methods>, Schema> : Methods[K]; }; /** * Entity definition returned by `defineEntity(...).build()`. */ export interface EntityDef<Name extends string, Schema extends StandardSchema, Methods extends AnyMethods> { /** Name used for debugging and introspection. */ name: Name; /** Standard Schema used to validate entity props. */ schema: Schema; /** * Create a new frozen entity instance from props. * * Returns a promise when the underlying Standard Schema validates async. */ create(props: InferOutput<Schema>): EntityInstance<Name, Schema, Methods> | Promise<EntityInstance<Name, Schema, Methods>>; /** * Reconstruct an entity instance from JSON, usually from persistence. */ fromJSON(json: InferOutput<Schema>): EntityInstance<Name, Schema, Methods> | Promise<EntityInstance<Name, Schema, Methods>>; /** Type-only alias for the entity instance type. Undefined at runtime. */ Type: EntityInstance<Name, Schema, Methods>; } /** * Builder class for creating Entities/Aggregates. */ declare class EntityBuilder<Name extends string, Schema extends StandardSchema, Methods extends AnyMethods> { private readonly cfg; constructor(cfg: { name: Name; schema?: Schema; methods?: (self: EntityBaseInstance<Schema>) => Methods; }); /** * Define the schema for this entity using any Standard Schema compatible validator. */ props<S extends StandardSchema>(schema: S): EntityBuilder<Name, S, Methods>; /** * Define methods to attach to entity instances. * The method builder receives the typed base instance: validated props plus * `with` and `toJSON`. */ methods<M extends AnyMethods>(build: (self: EntityBaseInstance<Schema>) => M): EntityBuilder<Name, Schema, M>; /** * Finalize and build the entity definition. */ build(): EntityDef<Name, Schema, Methods>; } /** * Create a new entity builder. * * Entities validate props, attach domain methods, and return frozen immutable * instances. `with(...)` revalidates the merged props. `.Type` is type-only and * should be used with `typeof Entity.Type`. * * @example * ```ts * const Todo = defineEntity("Todo") * .props(z.object({ * id: z.string(), * title: z.string(), * assigneeIds: z.array(z.string()).default([]), * })) * .methods((self) => ({ * addAssignee(id: string) { * if (self.assigneeIds.includes(id)) return self; * return self.with({ assigneeIds: [...self.assigneeIds, id] }); * }, * })) * .build(); * * type Todo = typeof Todo.Type; * ``` */ export declare function defineEntity<Name extends string>(name: Name): EntityBuilder<Name, StandardSchema, Record<string, never>>; export {}; //# sourceMappingURL=entity.d.ts.map