UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

1,325 lines 102 kB
import { AsyncLocalStorage } from "node:async_hooks"; import { z as z$1 } from "zod"; import { Middleware as Middleware$1 } from "alepha"; import { Readable } from "node:stream"; import { ReadableStream as ReadableStream$1 } from "node:stream/web"; //#region ../../src/core/constants/KIND.d.ts /** * Used for identifying primitives. * * @internal */ declare const KIND: unique symbol; //#endregion //#region ../../src/core/constants/MODULE.d.ts /** * Used for identifying modules. * * @internal */ declare const MODULE: unique symbol; //#endregion //#region ../../src/core/interfaces/Service.d.ts /** * In Alepha, a service is a class that can be instantiated or an abstract class. Nothing more, nothing less... */ type Service<T extends object = any> = InstantiableClass<T> | AbstractClass<T> | RunFunction<T>; type RunFunction<T extends object = any> = (...args: any[]) => T | undefined; type InstantiableClass<T extends object = any> = new (...args: any[]) => T; /** * Abstract class is a class that cannot be instantiated directly! * It widely used for defining interfaces. */ type AbstractClass<T extends object = any> = abstract new (...args: any[]) => T; /** * Service substitution allows you to register a class as a different class. * Providing class A, but using class B instead. * This is useful for testing, mocking, or providing a different implementation of a service. * * class A is mostly an AbstractClass, while class B is an InstantiableClass. */ interface ServiceSubstitution<T extends object = any> { /** * Every time someone asks for this class, it will be provided with the 'use' class. */ provide: Service<T>; /** * Service to use instead of the 'provide' service. * * Syntax is inspired by Angular's DI system. */ use: Service<T>; /** * If true, if the service already exists -> just ignore the substitution and do not throw an error. * Mostly used for plugins to enforce a substitution without throwing an error. */ optional?: boolean; } /** * Every time you register a service, you can use this type to define it. * * alepha.with( ServiceEntry ) * or * alepha.with( provide: ServiceEntry, use: MyOwnServiceEntry ) * * And yes, you declare the *type* of the service, not the *instance*. */ type ServiceEntry<T extends object = any> = Service<T> | ServiceSubstitution<T>; declare function isClass(func: any): func is InstantiableClass; //#endregion //#region ../../src/core/helpers/primitive.d.ts interface PrimitiveArgs<T extends object = {}> { options: T; alepha: Alepha; service: InstantiableClass<Service>; module?: Service; } interface PrimitiveConfig { propertyKey: string; service: InstantiableClass<Service>; module?: Service; } declare abstract class Primitive<T extends object = {}> { protected readonly alepha: Alepha; readonly options: T; readonly config: PrimitiveConfig; constructor(args: PrimitiveArgs<T>); /** * Called automatically by Alepha after the primitive is created. */ protected onInit(): void; /** * Override (partially) the options of this primitive. * * Options are shallow-merged into the existing options — fields not present * in `partial` are preserved. Must be called BEFORE `alepha.start()`; * throws once the container has started because options are consumed during * start-up (route registration, etc.) and later mutations would have no effect. * * Typical use case: customize a built-in primitive from a module without * forking the whole module. * * @example Instance-level override * ```ts * const auth = alepha.inject(AuthRouter); * auth.login.override({ lazy: () => import("./MyLogin.tsx") }); * ``` * * @example Subclass override (constructor body, after super()) * ```ts * class MyAuthRouter extends AuthRouter { * constructor() { * super(); * this.login.override({ lazy: () => import("./MyLogin.tsx") }); * } * } * ``` */ override(partial: Partial<T>): this; } type PrimitiveFactoryLike<T extends object = any> = { (options: T): any; [KIND]: any; }; declare const createPrimitive: <TPrimitive extends Primitive>(primitive: InstantiableClass<TPrimitive> & { [MODULE]?: Service; }, options: TPrimitive["options"]) => TPrimitive; //#endregion //#region ../../src/core/interfaces/Async.d.ts /** * Represents a value that can be either a value or a promise of value. */ type Async<T> = T | Promise<T>; /** * Represents a function that returns an async value. */ type AsyncFn = (...args: any[]) => Async<any>; /** * Transforms a type T into a promise if it is not already a promise. */ type MaybePromise<T> = T extends Promise<any> ? T : Promise<T>; //#endregion //#region ../../src/core/interfaces/LoggerInterface.d.ts type LogLevel = "ERROR" | "WARN" | "INFO" | "DEBUG" | "TRACE" | "SILENT"; interface LoggerInterface { trace(message: string, data?: unknown): void; debug(message: string, data?: unknown): void; info(message: string, data?: unknown): void; warn(message: string, data?: unknown): void; error(message: string, data?: unknown): void; } //#endregion //#region ../../src/core/helpers/FileLike.d.ts interface FileLike { /** * Filename. * @default "file" */ name: string; /** * Mandatory MIME type of the file. * @default "application/octet-stream" */ type: string; /** * Size of the file in bytes. * * Always 0 for streams, as the size is not known until the stream is fully read. * * @default 0 */ size: number; /** * Last modified timestamp in milliseconds since epoch. * * Always the current timestamp for streams, as the last modified time is not known. * We use this field to ensure compatibility with File API. * * @default Date.now() */ lastModified: number; /** * Returns a ReadableStream or Node.js Readable stream of the file content. * * For streams, this is the original stream. */ stream(): StreamLike; /** * Returns the file content as an ArrayBuffer. * * For streams, this reads the entire stream into memory. */ arrayBuffer(): Promise<ArrayBuffer>; /** * Returns the file content as a string. * * For streams, this reads the entire stream into memory and converts it to a string. */ text(): Promise<string>; /** * Optional file path, if the file is stored on disk. * * This is not from the File API, but rather a custom field to indicate where the file is stored. */ filepath?: string; } /** * TypeBox view of FileLike. */ type TFile = TUnsafe<FileLike>; declare const isTypeFile: (value: TSchema) => value is TFile; declare const isFileLike: (value: any) => value is FileLike; type StreamLike = ReadableStream | ReadableStream$1 | Readable | NodeJS.ReadableStream; type TStream = TUnsafe<StreamLike>; //#endregion //#region ../../src/core/providers/zodAugment.d.ts /** * Legacy-compatibility shims so typebox-era structural accessors keep working * on zod schemas (the "short path" for the migration — avoids rewriting * hundreds of schema-walking call-sites): * * typebox zod alias added here * ---------------- ------------ ---------------- * obj.properties obj.shape .properties -> .shape * array.items array.element .items -> .element * union.anyOf union.options .anyOf -> .options * * Both the runtime (prototype getters) and the type-level (`declare module` * interface merge) are provided, so `schema.properties` resolves and typechecks. * * Imported for side-effects by ZodProvider so it loads with the `z` core. */ declare module "zod" { interface ZodObject { /** typebox-compat alias for {@link ZodObject.shape}. */ readonly properties: this["shape"]; } interface ZodArray { /** typebox-compat alias for {@link ZodArray.element}. */ readonly items: this["element"]; } interface ZodUnion { /** typebox-compat alias for {@link ZodUnion.options}. */ readonly anyOf: this["options"]; } interface ZodTuple { /** typebox-compat alias for the tuple's element schemas. */ readonly items: readonly import("zod").ZodType[]; } } //#endregion //#region ../../src/core/providers/ZodProvider.d.ts /** * Alepha's `z` — a thin, enhanceable wrapper over zod 4 that mirrors the * ergonomics of the legacy typebox `t` provider (same call-shapes, so existing * `t.*` call-sites become a near-mechanical rename), while delegating all * validation + static-type inference to zod. * * Design notes: * - No encode/decode. string -> string, number -> number. Validation is a plain * `schema.parse(value)` — zod does NOT use `Function`/`eval`, so this is safe * inside Cloudflare Workers (unlike typebox's `Compile`). * - Metadata (`title`, `description`, `format`, custom `~options`) rides on zod's * native `.meta()` registry, so `z.toJSONSchema()` round-trips it for OpenAPI * + form generation. Defaults use `.default()`. */ type ZType = z$1.ZodType; type ZObject<T extends z$1.ZodRawShape = any> = z$1.ZodObject<T>; /** Replaces typebox `Static<T>`. */ type Infer<T extends z$1.ZodType> = z$1.infer<T>; /** * Shallow output-extraction for `T extends SomeSchema ? Static<T> : Fallback` * conditional positions. * * Reads zod's `_zod.output` directly (a single shallow conditional) instead of * first testing `T extends TRequestBody` / `TResponseBody` — those tests compare * `T` against 5-6 member unions of deeply-recursive zod class types, and that * structural assignability check is what trips TypeScript's instantiation-depth * limit (TS2589) inside `$action`'s generics. The extracted type is identical to * `Static<T>` for any real schema; non-schema `T` (e.g. `undefined`) yields * `Fallback`. */ type SchemaOutput<T, Fallback = any> = [T] extends [{ _zod: { output: infer O; }; }] ? O : Fallback; interface SchemaOptions { title?: string; description?: string; default?: unknown; format?: string; /** Alepha string transforms, kept for JSON-Schema round-trip parity. */ "~options"?: { trim?: boolean; lowercase?: boolean; }; [key: string]: unknown; } interface StringOptions extends SchemaOptions { minLength?: number; maxLength?: number; pattern?: string | RegExp; } interface NumberOptions extends SchemaOptions { minimum?: number; maximum?: number; } interface TextOptions extends StringOptions { size?: "short" | "regular" | "long" | "rich"; trim?: boolean; lowercase?: boolean; } /** Defaults mirroring TypeProvider's static length caps. */ declare const Z_LIMITS: { short: number; regular: number; long: number; rich: number; arrayMaxItems: number; }; declare const z: { /** * Native zod coercion namespace (`z.coerce.number()`, `z.coerce.boolean()`…). * Used at the HTTP/string boundary (query, headers, env) where everything * arrives as a string; request bodies and the ORM stay strict (no coercion). */ coerce: typeof z$1.coerce; object: typeof z$1.object; array: typeof z$1.array; record: typeof z$1.record; tuple: typeof z$1.tuple; union: typeof z$1.union; string: typeof z$1.string; number: typeof z$1.number; boolean: typeof z$1.boolean; null: typeof z$1.null; any: typeof z$1.any; void: typeof z$1.void; undefined: typeof z$1.undefined; literal: typeof z$1.literal; /** Alias for `zod.literal` (legacy `t.const`). */ const: typeof z$1.literal; enum: typeof z$1.enum; /** Integer — native `z.int()` (format `safeint`, drives PG INT column). */ integer: typeof z$1.int; /** Free-form JSON object (`Record<string, any>`). */ json: () => z$1.ZodRecord<z$1.ZodString, z$1.ZodAny>; text: (options?: TextOptions) => z$1.ZodString; shortText: (options?: TextOptions) => z$1.ZodString; longText: (options?: TextOptions) => z$1.ZodString; richText: (options?: TextOptions) => z$1.ZodString; email: () => z$1.ZodString; uuid: () => z$1.ZodString; url: () => z$1.ZodString; datetime: () => z$1.ZodString; date: () => z$1.ZodString; time: () => z$1.ZodString; /** bigint as a validated string (no codec). */ bigint: () => z$1.ZodString; binary: () => z$1.ZodString; duration: () => z$1.ZodString; e164: () => z$1.ZodString; bcp47: () => z$1.ZodString; constantCase: () => z$1.ZodString; int32: () => z$1.ZodInt; int64: () => z$1.ZodInt; file: (options?: { maxSize?: number; }) => z$1.ZodAny; stream: () => z$1.ZodAny; valueLabel: () => z$1.ZodObject<{ value: z$1.ZodString; label: z$1.ZodString; description: z$1.ZodOptional<z$1.ZodString>; }, z$1.core.$strip>; /** Pagination wrapper, mirrors `pageSchema(item)`. */ page: <T extends z$1.ZodType>(item: T) => z$1.ZodObject<{ content: z$1.ZodArray<T>; page: z$1.ZodObject<{ number: z$1.ZodInt; size: z$1.ZodInt; offset: z$1.ZodInt; numberOfElements: z$1.ZodInt; totalElements: z$1.ZodOptional<z$1.ZodInt>; totalPages: z$1.ZodOptional<z$1.ZodInt>; isEmpty: z$1.ZodBoolean; isFirst: z$1.ZodBoolean; isLast: z$1.ZodBoolean; }, z$1.core.$strip>; }, z$1.core.$strip>; /** Export a schema to JSON Schema (OpenAPI / form generation). */ toJSONSchema: typeof z$1.toJSONSchema; /** Runtime type guards (`z.schema.*`) — schema introspection for ORM + forms. */ schema: { isOptional: (s: any) => s is z$1.ZodOptional<z$1.core.$ZodType<unknown, unknown, z$1.core.$ZodTypeInternals<unknown, unknown>>>; isDefault: (s: any) => s is z$1.ZodDefault<z$1.core.$ZodType<unknown, unknown, z$1.core.$ZodTypeInternals<unknown, unknown>>>; isObject: (s: any) => s is z$1.ZodObject<z$1.core.$ZodLooseShape, z$1.core.$strip>; isString: (s: any) => boolean; isNumber: (s: any) => boolean; isBoolean: (s: any) => s is z$1.ZodBoolean; isArray: (s: any) => s is z$1.ZodArray<z$1.core.$ZodType<unknown, unknown, z$1.core.$ZodTypeInternals<unknown, unknown>>>; isUnion: (s: any) => s is z$1.ZodUnion<readonly z$1.core.$ZodType<unknown, unknown, z$1.core.$ZodTypeInternals<unknown, unknown>>[]>; isNull: (s: any) => s is z$1.ZodNull; isRecord: (s: any) => s is z$1.ZodRecord<z$1.core.$ZodRecordKey, z$1.core.$ZodType<unknown, unknown, z$1.core.$ZodTypeInternals<unknown, unknown>>>; isEnum: (s: any) => boolean; isLiteral: (s: any) => boolean; isAny: (s: any) => s is z$1.ZodAny; isNullable: (s: any) => s is z$1.ZodNullable<z$1.core.$ZodType<unknown, unknown, z$1.core.$ZodTypeInternals<unknown, unknown>>>; isInteger: (s: any) => boolean; isText: (s: any) => boolean; isDateTime: (s: any) => boolean; isDate: (s: any) => boolean; isTime: (s: any) => boolean; isBigInt: (s: any) => boolean; isUuid: (s: any) => boolean; isEmail: (s: any) => boolean; isUrl: (s: any) => boolean; isBinary: (s: any) => boolean; isUUID: (s: any) => boolean; isScalar: (s: any) => boolean; isSchema: (s: any) => s is z$1.ZodType<unknown, unknown, z$1.core.$ZodTypeInternals<unknown, unknown>>; isTuple: (s: any) => s is z$1.ZodTuple<readonly z$1.core.$ZodType<unknown, unknown, z$1.core.$ZodTypeInternals<unknown, unknown>>[], z$1.core.$ZodType<unknown, unknown, z$1.core.$ZodTypeInternals<unknown, unknown>> | null>; isUndefined: (s: any) => s is z$1.ZodUndefined; isUnsafe: (s: any) => s is z$1.ZodAny | z$1.ZodUnknown; isVoid: (s: any) => s is z$1.ZodVoid; format: (s: any) => string | undefined; /** The string values of a `ZodEnum` (typebox-compat for `value.enum`). */ enumValues: (s: any) => string[]; /** Peel optional / nullable / default wrappers to the inner schema. */ unwrap: (s: any) => any; /** * Read a schema's default value (peeling optional/nullable wrappers), or * `undefined` if there is none. In zod, defaults are a `ZodDefault` wrapper * (not a `default` data property as in typebox), so `"default" in schema` * is NOT a valid test — every schema has a `.default()` method. */ getDefault: (s: any) => unknown; /** typebox-compat: the object's required (non-optional) field names. */ requiredKeys: (s: any) => string[]; }; }; type Z = typeof z; //#endregion //#region ../../src/core/providers/TypeProvider.d.ts /** Raw zod namespace (legacy `Type` escape hatch). */ declare const Type: typeof z$1; type Static<T extends ZType> = Infer<T>; type StaticDecode<T extends ZType> = Infer<T>; type StaticEncode<T extends ZType> = Infer<T>; type TSchema = ZType; type TObject<T extends z$1.ZodRawShape = any> = ZObject<T>; type TProperties = z$1.ZodRawShape; type TString = z$1.ZodString; type TNumber = z$1.ZodNumber; type TInteger = z$1.ZodNumber; type TBoolean = z$1.ZodBoolean; type TArray<T extends ZType = ZType> = z$1.ZodArray<T>; type TUnion<_T extends ZType[] = ZType[]> = z$1.ZodUnion; type TRecord = z$1.ZodRecord<any, any>; type TTuple = ZType; type TNull = z$1.ZodNull; type TAny = z$1.ZodAny; type TVoid = z$1.ZodVoid; type TBigInt = z$1.ZodString; type TUnsafe<_T = unknown> = ZType; type TOptional<T extends ZType = ZType> = z$1.ZodOptional<T>; type TOptionalAdd<T extends ZType = ZType> = z$1.ZodOptional<T>; type TPick<T extends ZObject = ZObject, _K = unknown> = T; type TOmit<T extends ZObject = ZObject, _K = unknown> = T; type TPartial<_T extends ZObject = ZObject> = ZObject; type TInterface<_T = unknown, _U = unknown> = ZObject; type TKeysToIndexer<_T = unknown> = any; type TSchemaOptions = SchemaOptions; type TStringOptions = StringOptions; type TNumberOptions = NumberOptions; type TObjectOptions = SchemaOptions; type TArrayOptions = SchemaOptions; type TextLength = "short" | "regular" | "long" | "rich"; type TTextOptions = TextOptions; interface TEnumOptions extends TextOptions { /** `"text"` = TEXT column; omitted = a real PostgreSQL ENUM type. */ mode?: "text"; /** Custom PG ENUM type name (shared across tables). */ name?: string; } /** Legacy `TypeProvider` — kept for its static config knobs. */ declare class TypeProvider { static DEFAULT_STRING_MAX_LENGTH: number | undefined; static DEFAULT_SHORT_STRING_MAX_LENGTH: number | undefined; static DEFAULT_LONG_STRING_MAX_LENGTH: number | undefined; static DEFAULT_RICH_STRING_MAX_LENGTH: number | undefined; static DEFAULT_ARRAY_MAX_ITEMS: number; static translateError: (error: { message?: string; }, _locale?: string) => string; static setLocale: (_locale: string) => void; static isValidBigInt: (value: string | number) => boolean; } //#endregion //#region ../../src/core/primitives/$atom.d.ts /** * Define an atom for state management. * * Atom lets you define a piece of state with a name, schema, and default value. * * By default, Alepha state is just a simple key-value store. * Using atoms allows you to have type safety, validation, and default values for your state. * * You control how state is structured and validated. * * Features: * - Schema validation on every write (invalid writes throw) * - Default value for initial state * - Automatic getter access in services with {@link $state} * - SSR support (server state automatically serialized and hydrated on client) * - React integration (useStore / useSelector hooks for automatic re-renders) * - Derived values with {@link $computed} (useComputed hook) * - Persistence adapters: cookie (SSR-safe), localStorage, sessionStorage * - `serverOnly` flag to keep an atom out of the hydration payload * - reset / watch helpers on the state manager * - Documentation generation & devtools integration (mutation log) * * Common use cases: * - user preferences * - feature flags * - configuration options * - session data * * Atom must contain only serializable data. * Avoid storing complex objects like class instances, functions, or DOM elements. * If you need to store complex data, consider using identifiers or references instead. */ declare const $atom: { <T extends ZType, N extends string>(options: AtomOptions<T, N>): Atom<T, N>; [KIND]: string; }; /** * Persistence adapter for an atom. */ type AtomPersist = "cookie" | "localStorage" | "sessionStorage"; type AtomOptions<T extends ZType, N extends string> = { name: N; schema: T; description?: string; /** * Persist this atom outside the in-memory store. * * - `"cookie"` — synced to an HTTP cookie by the `alepha/server/cookies` * module. Works on the server AND in the browser, so it is the only * correct adapter for SSR apps: the server reads it while rendering and * the first paint matches the persisted state. * - `"localStorage"` / `"sessionStorage"` — browser Web Storage. Fine for * pure SPA apps. The server cannot see these values; registering such * an atom during SSR logs a warning. * * Values are validated against the schema when read back; invalid or * corrupted entries are discarded and the default is used. * * **Security: cookie-persisted atoms are attacker-controlled.** The * cookie written by `persist: "cookie"` is unsigned, unencrypted, and * freely writable by any client-side script (`AtomCookiePersistence`'s * `cookieOptions()` sets no `sign`, `encrypt`, or `httpOnly`) — a request * can present any value it wants for it. Never persist trust-bearing * state — user ids, roles, entitlements, permissions — in a * `persist: "cookie"` atom. If you need a signed, encrypted, and/or * `httpOnly` cookie, declare an explicit * `$cookie({ key: atom.key, sign: true, encrypt: true, httpOnly: true, ... })` * binding instead — that is the escape hatch for trust-bearing values. */ persist?: AtomPersist; /** * Exclude this atom from the SSR hydration export * (`StateManager.exportAtoms()`). * * This is the ONLY guarantee `serverOnly` makes: the value is never * written into the `<script id="__ssr">` JSON block serialized into the * HTML payload. It says nothing about any other channel a value can * reach the browser through. * * **Cannot be combined with `persist`.** Every persistence adapter * (`"cookie"`, `"localStorage"`, `"sessionStorage"`) targets the browser * by definition — cookie persistence ships the value in a `Set-Cookie` * response header, and web-storage persistence IS the browser. Declaring * `serverOnly: true` together with any `persist` value throws * `AlephaError` at `$atom()` call time, because the persistence channel * would leak the exact value `serverOnly` is meant to keep server-side. * * Use `serverOnly` for atoms that may hold secrets or internal * request-scoped state that must never leave the server, and never pair * it with `persist`. * * **Never make a `serverOnly` atom a dependency of a `$computed` that the * browser reads through `useComputed`.** A computed is re-derived, never * serialized: the server derives it from the atom's real value, and the * browser — which never received that value — re-derives it from the * atom's default. The two disagree, producing a React hydration mismatch * and a silently wrong value from then on. Keep such a computed * server-side (`alepha.store.get`), or derive it from atoms that ship. */ serverOnly?: boolean; } & (T extends z$1.ZodOptional<any> ? { default?: Static<T>; } : { default: Static<T>; }); declare class Atom<T extends ZType = TObject, N extends string = string> { readonly options: AtomOptions<T, N>; get schema(): T; get key(): N; constructor(options: AtomOptions<T, N>); } type TAtomObject = ZType; type AtomStatic<T extends ZType> = T extends z$1.ZodOptional<any> ? Static<T> | undefined : Static<T>; //#endregion //#region ../../src/core/primitives/$computed.d.ts /** * Define a derived, read-only value computed from one or more atoms * (or other computed values). * * Dependencies are declared statically, which keeps the data flow explicit * and lets subscribers know exactly which mutations invalidate the value. * * Computed values are never stored, serialized, hydrated, or persisted — * they are recomputed from their dependencies on every read, so they are * always correct inside request-scoped (fork) state on the server. * * ```ts * const cartTotal = $computed({ * name: "cartTotal", * deps: [cartAtom], * get: (cart) => cart.items.reduce((sum, it) => sum + it.price, 0), * }); * * alepha.store.get(cartTotal); // number * ``` * * ### Footgun: a `serverOnly` dependency breaks hydration * * A computed whose deps include a `serverOnly` atom will NOT agree across the * SSR boundary. `serverOnly` keeps the atom out of the hydration payload, so * the server derives the value from the atom's real value while the browser * re-derives it from the atom's *default* — React then hydrates a DOM that * does not match the markup it received (a hydration mismatch, and a * silently wrong value afterwards). * * Either keep the computed server-side only (read it via `alepha.store.get`, * never through `useComputed`), or derive it from atoms that ship to the * browser. Note this cuts the other way too: if the value is safe to send to * the browser, the dependency should not have been `serverOnly` to begin * with. */ declare const $computed: { <const D extends ReadonlyArray<AnyDep>, R, N extends string = string>(options: ComputedOptions<D, R, N>): Computed<R, N>; [KIND]: string; }; type AnyDep = Atom<any, any> | Computed<any, any>; type DepValues<D extends ReadonlyArray<AnyDep>> = { [K in keyof D]: D[K] extends Atom<infer T, any> ? AtomStatic<T> : D[K] extends Computed<infer R2, any> ? R2 : never; }; interface ComputedOptions<D extends ReadonlyArray<AnyDep>, R, N extends string = string> { name: N; deps: D; get: (...values: DepValues<D>) => R; description?: string; } declare class Computed<R = unknown, N extends string = string> { readonly options: ComputedOptions<any, R, N>; constructor(options: ComputedOptions<any, R, N>); get key(): N; /** * Reentrancy guard for {@link keys}. Deps are declared as a `readonly` * array at construction time, so a cycle can't be built through the * public API — but the array itself isn't frozen, so nothing stops a * caller from reaching in and creating one anyway. Without this guard, * a cycle would recurse until the JS engine throws a native * `RangeError`, which violates the repo's "never throw a bare `Error`" * convention. */ protected resolvingKeys: boolean; /** * Reentrancy guard for {@link compute}, same rationale as * {@link resolvingKeys}. */ protected computing: boolean; /** * Transitive atom keys this computed value depends on. Subscribers * (useComputed, StateManager.watch) use this to know which `state:mutate` * events invalidate the value. */ keys(): string[]; /** * Resolve the value using the given dependency resolver. */ compute(resolve: (dep: AnyDep) => unknown): R; } //#endregion //#region ../../src/core/primitives/$inject.d.ts /** * Get the instance of the specified type from the context. * * ```ts * class A { } * class B { * a = $inject(A); * } * ``` */ declare const $inject: <T extends object>(type: Service<T>, opts?: InjectOptions<T>) => T; declare class InjectPrimitive extends Primitive {} interface InjectOptions<T extends object = any> { /** * - 'transient' → Always a new instance on every inject. Zero caching. * - 'singleton' → One instance per Alepha runtime (per-thread). Never disposed until Alepha shuts down. (default) * - 'scoped' → One instance per AsyncLocalStorage context. * - A new scope is created when Alepha handles a request, a scheduled job, a queue worker task... * - You can also start a manual scope via alepha.context.run(() => { ... }). * - When the scope ends, the scoped registry is discarded. * * @default "singleton" */ lifetime?: "transient" | "singleton" | "scoped"; /** * Constructor arguments to pass when creating a new instance. */ args?: ConstructorParameters<InstantiableClass<T>>; /** * Parent that requested the instance. * * @internal */ parent?: Service | null; } //#endregion //#region ../../src/core/primitives/$module.d.ts /** * Wrap Services and Primitives into a Module. * * - A module is just a Service with some extra {@link Module}. * - You must attach a `name` to it. * - Name must follow the pattern: `project.module.submodule`. (e.g. `myapp.users.auth`). * * @example * ```ts * import { $module } from "alepha"; * import { MyService } from "./MyService.ts"; * * export default $module({ * name: "my.project.module", * services: [MyService], * }); * ``` * * ### Slots * * - `services[]` — always auto-injected. Module metadata attached. * - `variants[]` — module metadata attached but NOT auto-injected. Two typical uses: * (1) alternative implementations picked at register-time via `alepha.with({ provide, use })`; * (2) services whose instantiation is driven externally (e.g., the framework core). * - `imports[]` — other modules this one depends on. Wired before `register()` runs. * - `atoms[]` — registered on the store. * - `primitives[]` — tagged with module metadata. * - `register(alepha)` — purely additive side-effect hook. Runs AFTER `imports[]` * are wired and BEFORE `services[]` are auto-injected — so substitutions it * records (e.g. `alepha.with({ provide, use })`) apply to the subsequent * auto-injection. It can never suppress auto-registration. * * ### Why Modules? * * #### Logging * * By default, AlephaLogger will log the module name in the logs. * This helps to identify where the logs are coming from. * * You can also set different log levels for different modules. * * #### Modulith * * Force to structure your application in modules, even if it's a single deployable unit. * It helps to keep a clean architecture and avoid monolithic applications. * * ### When not to use Modules? * * Small applications do not need modules. Modules earn their keep when the application * grows — as a rule of thumb, once a module has 30+ `$actions`, consider splitting it. */ declare const $module: (options: ModulePrimitiveOptions) => Service<Module>; interface ModulePrimitiveOptions { /** * Name of the module. * * It should be in the format of `project.module.submodule`. */ name: string; /** * Services that belong to this module. All services listed here are: * - tagged with module metadata (used for logging, boundary checks) * - auto-injected when the module is registered * * If you need an alternative implementation that should NOT be eagerly instantiated * (picked at register-time instead), list it under `variants` instead. */ services?: Array<Service>; /** * Alternative implementations that belong to this module but are NOT auto-injected. * Module metadata is still attached. * * Typical use: abstract provider in `services[]`, concrete impls here, with * `register()` choosing one via `alepha.with({ provide, use })`. * * @example * ```ts * $module({ * name: "alepha.email", * services: [EmailProvider], * variants: [MemoryEmailProvider, SmtpEmailProvider], * register: (alepha) => { * alepha.with({ * provide: EmailProvider, * use: alepha.isTest() ? MemoryEmailProvider : SmtpEmailProvider, * }); * }, * }); * ``` */ variants?: Array<Service>; /** * Other modules this module depends on. They are wired via `alepha.with(Module)` * before `register()` runs and before `services[]` are injected. * * Prefer this over calling `alepha.with(OtherModule)` manually inside `register()`. */ imports?: Array<Service<Module>>; /** * List of $primitives to register in the module. */ primitives?: Array<PrimitiveFactoryLike>; /** * Additive side-effect hook. Runs AFTER `imports[]` are wired and BEFORE * `services[]` are auto-injected — so substitutions it records apply to * the subsequent injection. Use it for: * - variant substitution: `alepha.with({ provide, use })` * - env parsing: `alepha.parseEnv(schema)` * - state seeding: `alepha.store.set(...)` * * It cannot suppress auto-registration — `services[]` are always injected. */ register?: (alepha: Alepha) => void; /** * List of atoms to register in the module. */ atoms?: Array<Atom<any>>; } /** * Base class for all modules. */ declare abstract class Module { abstract readonly options: ModulePrimitiveOptions; abstract register(alepha: Alepha): void; static NAME_REGEX: RegExp; /** * Check if a Service is a Module. */ static is(ctor: Service): boolean; /** * Get the Module of a Service. * * Returns undefined if the Service is not part of a Module. */ static of(ctor: Service): Service<Module> | undefined; } /** * Helper type to add Module metadata to a Service. */ type WithModule<T extends object = any> = T & { [MODULE]?: Service; }; //#endregion //#region ../../src/core/providers/AlsProvider.d.ts type AsyncLocalStorageData = any; type StateScope = "current" | "app" | "parent"; declare const ALS_PARENT: unique symbol; declare class AlsProvider { static create: () => AsyncLocalStorage<AsyncLocalStorageData> | undefined; als?: AsyncLocalStorage<AsyncLocalStorageData>; constructor(); createContextId(): string; run<R>(callback: () => R, data?: Record<string, any>): R; exists(): boolean; get<T>(key: string, scope?: StateScope): T | undefined; /** * Return the raw ALS layer object that owns `key`, walking from the * current layer up through parent forks — the same resolution order as * the default-scope branch of {@link get}. Returns `undefined` when no * active ALS layer (current or any ancestor) has the key. * * Used by `StateManager.register()` to decode a value in place, in * whichever fork layer it physically lives, instead of flattening * fork-scoped state into the app-level store. */ getLayer(key: string): AsyncLocalStorageData | undefined; has(key: string): boolean; set<T>(key: string, value: T): void; } //#endregion //#region ../../src/core/providers/Json.d.ts /** * Mimics the JSON global object with stringify and parse methods. * * Used across the codebase via dependency injection. */ declare class Json { stringify: { (value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (number | string)[] | null, space?: string | number): string; }; parse: (text: string, reviver?: (this: any, key: string, value: any) => any) => any; } //#endregion //#region ../../src/core/providers/SchemaCodec.d.ts declare abstract class SchemaCodec { /** * Encode the value to a string format. */ abstract encodeToString<T extends TSchema>(schema: T, value: Static<T>): string; /** * Encode the value to a binary format. */ abstract encodeToBinary<T extends TSchema>(schema: T, value: Static<T>): Uint8Array; /** * Decode string, binary, or other formats to the schema type. */ abstract decode<T>(schema: TSchema, value: unknown): T; } //#endregion //#region ../../src/core/providers/JsonSchemaCodec.d.ts declare class JsonSchemaCodec extends SchemaCodec { protected readonly json: Json; protected readonly encoder: TextEncoder; protected readonly decoder: TextDecoder; encodeToString<T extends TSchema>(schema: T, value: Static<T>): string; encodeToBinary<T extends TSchema>(schema: T, value: Static<T>): Uint8Array; decode<T>(schema: TSchema, value: unknown): T; } //#endregion //#region ../../src/core/providers/KeylessJsonSchemaCodec.d.ts interface KeylessCodec<T = any> { encode: (value: T) => string; decode: (str: string) => T; } interface KeylessCodecOptions { /** * Whether to use `new Function()` for code compilation. * When false, uses an interpreter-based approach (safer but slower). * * @default Auto-detected: false in browser (CSP compatibility), true on server */ useFunctionCompilation?: boolean; } /** * KeylessJsonSchemaCodec provides schema-driven JSON encoding without keys. * * It uses the schema to determine field order, allowing the encoded output * to be a simple JSON array instead of an object with keys. */ declare class KeylessJsonSchemaCodec extends SchemaCodec { protected readonly cache: Map<import("alepha").ZType, KeylessCodec<any>>; protected readonly textEncoder: TextEncoder; protected readonly textDecoder: TextDecoder; protected varCounter: number; protected useFunctionCompilation: boolean; /** * Configure codec options. */ configure(options: KeylessCodecOptions): this; /** * Hook to auto-detect safe mode on configure. * Disables function compilation in browser by default. */ protected onConfigure: import("alepha").HookPrimitive<"configure">; /** * Encode value to a keyless JSON string. */ encodeToString<T extends TSchema>(schema: T, value: Static<T>): string; /** * Encode value to binary (UTF-8 encoded keyless JSON). */ encodeToBinary<T extends TSchema>(schema: T, value: Static<T>): Uint8Array; /** * Decode keyless JSON string or binary to value. */ decode<T>(schema: TSchema, value: unknown): T; /** * Test if `new Function()` is available (not blocked by CSP). */ protected canUseFunction(): boolean; /** * Get a compiled codec for the given schema. * Codecs are cached for reuse. */ protected getCodec<T>(schema: TSchema): KeylessCodec<T>; protected nextVar(): string; /** * Compile codec using `new Function()` for maximum performance. * Only used when CSP allows and useFunctionCompilation is true. */ protected compileWithFunction(schema: TSchema): KeylessCodec; /** * Compile codec using interpreter-based approach. * Safer (no eval/Function) but slower. Used in browser by default. */ protected compileInterpreted(schema: TSchema): KeylessCodec; protected interpretEncode(schema: TSchema, value: any): any; protected interpretDecode(schema: TSchema, ctx: { arr: any[]; i: number; }): any; protected interpretDecodeFromValue(schema: TSchema, value: any): any; protected genEnc(schema: TSchema, ve: string): string; protected genDec(schema: TSchema): { code: string; result: string; }; protected genDecFromValue(schema: TSchema, expr: string): string; protected isLeaf(schema: TSchema): boolean; protected getObjectFields(schema: TObject): Array<{ key: string; isOpt: boolean; isNullable: boolean; inner: TSchema; }>; protected isEnum(schema: TSchema): boolean; protected isNullable(schema: TSchema): boolean; protected unwrap(schema: TSchema): TSchema; /** * Reconstruct an object from a parsed array (for when input is already parsed). */ protected reconstructObject(schema: TSchema, arr: any[]): any; } //#endregion //#region ../../src/core/providers/SchemaValidator.d.ts interface ValidateOptions { trim?: boolean; nullToUndefined?: boolean; deleteUndefined?: boolean; } /** * Validates + coerces a value against a zod schema. * * Trimming / lowercasing / defaults / unknown-key stripping all live in the * schema itself now (zod), so this is a thin wrapper over `schema.parse`. * No typebox `Compile`, no `eval` — safe inside Cloudflare Workers. */ declare class SchemaValidator { validate<T extends TSchema>(schema: T, value: unknown, _options?: ValidateOptions): Static<T>; safeValidate<T extends TSchema>(schema: T, value: unknown): import("zod").ZodSafeParseResult<import("zod").output<T>>; /** Zod schemas are immutable — clone is a pass-through. */ clone<T extends TSchema>(schema: T): T; /** * Legacy pre-processing entry point. Coercion now happens inside `parse`, * so this is a no-op kept for call-site compatibility. */ beforeParse(_schema: unknown, value: unknown, _options?: ValidateOptions): unknown; } //#endregion //#region ../../src/core/providers/CodecManager.d.ts type Encoding = "object" | "string" | "binary"; interface EncodeOptions<T extends Encoding = Encoding> { /** * The output encoding format: * - 'string': Returns JSON string * - 'binary': Returns Uint8Array (for protobuf, msgpack, etc.) * * @default "string" */ as?: T; /** * The encoder to use (e.g., 'json', 'protobuf', 'msgpack') * * @default "json" */ encoder?: string; /** * Validation options to apply before encoding. */ validation?: ValidateOptions | false; } type EncodeResult<T extends TSchema, E extends Encoding> = E extends "string" ? string : E extends "binary" ? Uint8Array : StaticEncode<T>; interface DecodeOptions { /** * The encoder to use (e.g., 'json', 'protobuf', 'msgpack') * * @default "json" */ encoder?: string; /** * Validation options to apply before encoding. */ validation?: ValidateOptions | false; } /** * CodecManager manages multiple codec formats and provides a unified interface * for encoding and decoding data with different formats. */ declare class CodecManager { protected readonly codecs: Map<string, SchemaCodec>; protected readonly jsonCodec: JsonSchemaCodec; protected readonly keylessCodec: KeylessJsonSchemaCodec; protected readonly schemaValidator: SchemaValidator; default: string; constructor(); /** * Register a new codec format. */ register(opts: CodecRegisterOptions): void; /** * Get a specific codec by name. * * @param name - The name of the codec * @returns The codec instance * @throws {AlephaError} If the codec is not found */ getCodec(name: string): SchemaCodec; /** * Encode data using the specified codec and output format. */ encode<T extends TSchema, E extends Encoding = "object">(schema: T, value: unknown, options?: EncodeOptions<E>): EncodeResult<T, E>; /** * Decode data using the specified codec. */ decode<T extends TSchema>(schema: T, data: any, options?: DecodeOptions): Static<T>; /** * Validate decoded data against the schema. * * This is automatically called before encoding or after decoding. */ validate<T extends TSchema>(schema: T, value: unknown, options?: ValidateOptions): Static<T>; } interface CodecRegisterOptions { name: string; codec: SchemaCodec; default?: boolean; } //#endregion //#region ../../src/core/providers/EventManager.d.ts /** * Compiled event executor - optimized for hot paths. * Returns void for sync-only chains, Promise<void> for chains with async hooks. */ type CompiledEventExecutor<T extends keyof Hooks> = (payload: Hooks[T]) => void | Promise<void>; /** * Options for compiled event executors. */ interface CompileOptions { /** * If true, errors will be caught and logged instead of throwing. * @default false */ catch?: boolean; } declare class EventManager { logFn?: () => LoggerInterface | undefined; /** * Three-list storage for hooks, split by priority tier. */ protected events: Record<string, { first: Array<Hook>; normal: Array<Hook>; last: Array<Hook>; }>; /** * Cache of compiled executors, auto-built on first emit per event. */ protected cache: Map<string, CompiledEventExecutor<any>>; /** * Cache of resolved (topo-sorted) hook arrays per event. */ protected sortedCache: Map<string, Hook<any>[]>; constructor(logFn?: () => LoggerInterface | undefined); protected get log(): LoggerInterface | undefined; clear(): void; /** * Registers a hook for the specified event. */ on<T extends keyof Hooks>(event: T, hookOrFunc: Hook<T> | ((payload: Hooks[T]) => Async<void>)): () => void; protected invalidateCache(event: string): void; /** * Resolves the final ordered hook list for an event. * Applies topological sort (Kahn's algorithm) within each priority tier * based on before/after constraints. */ protected resolve(event: string): Array<Hook>; /** * Topological sort using Kahn's algorithm. * Hooks with no constraints maintain registration order (stable). */ protected topoSort(hooks: Array<Hook>): Array<Hook>; /** * Compiles an event into an optimized executor function. * * Called automatically by emit() on first use. Can also be called * manually for direct access to the executor. */ compile<T extends keyof Hooks>(event: T, options?: CompileOptions): CompiledEventExecutor<T>; /** * Emits the specified event with the given payload. * * Auto-compiles and caches an optimized executor on first call per event. * Use `{ log: true }` for startup events that need timing information. */ emit<T extends keyof Hooks>(event: T, payload: Hooks[T], options?: { /** * If true, the hooks will be logged with their execution time. * * @default false */ log?: boolean; /** * If true, errors will be caught and logged instead of throwing. * * @default false */ catch?: boolean; }): Promise<void>; } //#endregion //#region ../../src/core/providers/StateManager.d.ts interface AtomWithValue { atom: Atom; value: unknown; } declare class StateManager<State$1 extends object = State> { protected readonly als: AlsProvider; protected readonly events: EventManager; protected readonly codec: JsonSchemaCodec; protected readonly validator: SchemaValidator; protected readonly atoms: Map<keyof State$1, Atom<TObject, string>>; protected store: Partial<State$1>; constructor(store?: Partial<State$1>); /** * Export all registered atoms as a plain object. * * @param scope - Controls which store layer to read from: * - `undefined` (default): Resolved value (walks fork tree then app store). * - `"current"`: Only the current ALS layer — intentionally does NOT walk * the parent chain so that SSR serialisation captures exactly what the * current request set, without leaking parent-fork state. * - `"app"`: Only the root (app-level) store. */ exportAtoms(scope?: "current" | "app"): Record<string, unknown>; getAtoms(context?: boolean): Array<AtomWithValue>; /** * Return the registered atom for a state key, if any. */ getAtom(key: string): Atom | undefined; /** * Every atom registered so far, whatever store layer (if any) currently * holds its value. * * This is the order-independent way to discover atoms. The * `"state:register"` event fires exactly once per key and `EventManager` * has no replay buffer, so a service instantiated after an atom registered * never hears about it — a real hazard, since `$module.register()` * registers `atoms[]` BEFORE it wires `imports[]` and injects `services[]`. * Services that act on atoms (e.g. the cookie persistence adapter) must * read this registry on demand rather than build their own map from the * event. */ listAtoms(): Array<Atom>; register(atom: Atom<any>): this; /** * Install the atom's declared default into the app-level store. * * Deliberately bypasses {@link set}, for two reasons: * * 1. `set()` short-circuits when the new value equals the currently * *resolved* value (`prevValue === value` for non-objects) — inside a * fork that already holds the same primitive value, the app-store write * would simply never happen. * 2. A registration seed is initialisation, NOT a mutation, so it must not * emit `state:mutate`. Persistence adapters listen on that event: an * atom registering lazily mid-request would otherwise emit a mutation * carrying its *default*, and the cookie adapter would dutifully write * it back as a `Set-Cookie` — overwriting the very cookie the request * arrived with. * * A `undefined` default (only reachable for an optional schema) is not * written at all, so the key stays absent from the store — matching the * previous behaviour of `set(key, undefined)`. */ protected seedDefault(atom: Atom<any>, key: keyof State$1): void; /** * A fresh, schema-validated copy of the atom's declared default. * * `atom.options.default` is a module-level object shared by every * container and every request in the process. Handing that exact reference * to the store would let an ordinary `store.mut(atom, s => ...)` mutate the * declaration itself, permanently and process-wide. Every other write path * round-trips through the validator (zod always returns a new object), so * these must too. */ protected cloneDefault(atom: Atom<any>): unknown; /** * Decode a value that already exists under an atom's key at registration * time, in place, in whichever layer it physically lives (an ALS fork * layer or the app store) — never flattening a fork-scoped value into the * app-level store. * * On schema mismatch, falls back to the atom's default (written into that * same layer) and emits a warning: a bad seed silently reverting a whole * atom to its defaults should be visible, not indistinguishable from * "nothing was ever set". */ protected decodeExisting(atom: Atom<any>, key: keyof State$1, layer: Record<string, any>): void; /** * Web-storage persistence (localStorage / sessionStorage) for atoms * declared with `persist`. Best-effort: quota errors and privacy modes * are swallowed. Cookie persistence is NOT handled here — it needs the * HTTP request cycle and lives in `alepha/server/cookies` * (AtomCookiePersistence), which discovers its atoms through * {@link listAtoms}. Both adapters are therefore registration-order * independent: one `persist` option, one reliability contract. */ protected bindWebStorage(atom: Atom<any>): void; /** * Best-effort `Storage#removeItem`. Some privacy modes throw on every * Storage method, including `removeItem` — this must never escape a * recovery `catch`, or persistence stops bein