alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
1,325 lines • 102 kB
TypeScript
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