alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
1,242 lines • 68.7 kB
TypeScript
import { Alepha, AlephaError, KIND, Primitive, SchemaValidator, Static, TNull, TObject, TOptional, TSchema, TUnion } from "alepha";
import { UserAccount } from "alepha/security";
import { RepositoryProvider } from "alepha/orm";
import { CryptoProvider } from "alepha/crypto";
import { DateTimeProvider } from "alepha/datetime";
import { LockProvider } from "alepha/lock";
import { BuildExtraConfigColumns, SQL } from "drizzle-orm";
import { PgColumnBuilderBase, PgSequenceOptions, PgTableExtraConfigValue, UpdateDeleteAction } from "drizzle-orm/pg-core";
import "drizzle-orm/pg-core/foreign-keys";
import "drizzle-orm/pg-core/session";
import "drizzle-kit/api";
import "drizzle-orm/d1";
import "drizzle-orm/sqlite-core";
//#region ../../src/api/parameters/audits/ParameterAudits.d.ts
/**
* Runtime parameter (config) audit events.
*
* Holds the `parameter` audit type. Using `$audit` pulls in the audits module
* automatically — the parameters module does not need to import it. Register
* as a variant and log via `parameterAudits.parameter.log("rollback", …)`.
*/
declare class ParameterAudits {
readonly parameter: import("alepha/api/audits").AuditPrimitive;
}
//#endregion
//#region ../../src/orm/core/schemas/insertSchema.d.ts
/**
* Transforms a TObject schema for insert operations.
* All default properties at the root level are made optional.
* Generated columns are excluded entirely.
*
* @example
* Before: { name: string; age: number(default=0); fullName: generated }
* After: { name: string; age?: number; }
*/
type TObjectInsert<T extends TObject> = TObject<{ [K in keyof T["shape"] as T["shape"][K] extends {
[PG_GENERATED]: any;
} ? never : K]: T["shape"][K] extends {
[PG_DEFAULT]: any;
} | {
[PG_ORGANIZATION]: any;
} ? TOptional<Extract<T["shape"][K], TSchema>> : T["shape"][K]; }>;
//#endregion
//#region ../../src/orm/core/schemas/updateSchema.d.ts
/**
* Transforms a TObject schema for update operations.
* All optional properties at the root level are made nullable (i.e., `T | null`).
* Generated columns are excluded entirely.
*
* @example
* Before: { name?: string; age: number; fullName: generated }
* After: { name?: string | null; age: number; }
*/
type TObjectUpdate<T extends TObject> = TObject<{ [K in keyof T["shape"] as T["shape"][K] extends {
[PG_GENERATED]: any;
} ? never : K]: T["shape"][K] extends TOptional<infer U extends TSchema> ? TOptional<TUnion<[U, TNull]>> : T["shape"][K]; }>;
//#endregion
//#region ../../src/orm/core/primitives/$entity.d.ts
interface EntityPrimitiveOptions<T extends TObject, Keys = keyof Static<T>> {
/**
* The database table name that will be created for this entity.
* If not provided, name will be inferred from the $repository variable name.
*/
name: string;
/**
* TypeBox schema defining the table structure and column types.
*/
schema: T;
/**
* Database indexes to create for query optimization.
*/
indexes?: (Keys | {
/**
* Single column to index.
*/
column: Keys;
/**
* Whether this should be a unique index (enforces uniqueness constraint).
*/
unique?: boolean;
/**
* Custom name for the index. If not provided, generates name automatically.
*/
name?: string;
/**
* Partial index condition. Only rows matching this SQL expression are indexed.
*/
where?: SQL;
} | {
/**
* Multiple columns for composite index (order matters for query optimization).
*/
columns: Keys[];
/**
* Whether this should be a unique index (enforces uniqueness constraint).
*/
unique?: boolean;
/**
* Custom name for the index. If not provided, generates name automatically.
*/
name?: string;
/**
* Partial index condition. Only rows matching this SQL expression are indexed.
*/
where?: SQL;
} | {
/**
* SQL expressions for expression-based indexes.
*
* Can include column references and SQL functions like `LOWER()`, `UPPER()`, etc.
* Columns and expressions can be mixed together.
*
* @example
* ```ts
* // Case-insensitive unique username per realm
* indexes: [{
* expressions: (self) => [self.realm, sql`LOWER(${self.username})`],
* unique: true,
* name: "users_realm_username_lower_idx",
* }]
* ```
*/
expressions: (self: Record<Keys & string, any>) => (SQL | any)[];
/**
* Whether this should be a unique index (enforces uniqueness constraint).
*/
unique?: boolean;
/**
* Custom name for the index. If not provided, generates name automatically.
*/
name: string;
/**
* Partial index condition. Only rows matching this SQL expression are indexed.
*/
where?: SQL;
})[];
/**
* Foreign key constraints to maintain referential integrity.
*/
foreignKeys?: Array<{
/**
* Optional name for the foreign key constraint.
*/
name?: string;
/**
* Local columns that reference the foreign table.
*/
columns: Array<keyof Static<T>>;
/**
* Referenced columns in the foreign table.
* Must be EntityColumn references from other entities.
*/
foreignColumns: Array<() => EntityColumn<any>>;
}>;
/**
* Additional table constraints for data validation.
*
* Constraints enforce business rules at the database level, providing
* an additional layer of data integrity beyond application validation.
*
* **Constraint Types**:
* - **Unique constraints**: Prevent duplicate values across columns
* - **Check constraints**: Enforce custom validation rules with SQL expressions
*
* @example
* ```ts
* constraints: [
* {
* name: "unique_user_email",
* columns: ["email"],
* unique: true
* },
* {
* name: "valid_age_range",
* columns: ["age"],
* check: sql`age >= 0 AND age <= 150`
* },
* {
* name: "unique_user_username_per_tenant",
* columns: ["tenantId", "username"],
* unique: true
* }
* ]
* ```
*/
constraints?: Array<{
/**
* Columns involved in this constraint.
*/
columns: Array<keyof Static<T>>;
/**
* Optional name for the constraint.
*/
name?: string;
/**
* Whether this is a unique constraint.
*/
unique?: boolean | {};
/**
* SQL expression for check constraint validation.
*/
check?: SQL;
}>;
/**
* Advanced Drizzle ORM configuration for complex table setups.
*/
config?: (self: BuildExtraConfigColumns<string, FromSchema<T>, "pg">) => PgTableExtraConfigValue[];
}
declare class EntityPrimitive<T extends TObject = TObject> {
readonly options: EntityPrimitiveOptions<T>;
constructor(options: EntityPrimitiveOptions<T>);
alias(alias: string): this;
get cols(): EntityColumns<T>;
get name(): string;
get schema(): T;
protected _insertSchema?: TObjectInsert<T>;
get insertSchema(): TObjectInsert<T>;
protected _updateSchema?: TObjectUpdate<T>;
get updateSchema(): TObjectUpdate<T>;
}
/**
* Convert a schema to columns.
*/
type FromSchema<T extends TObject> = { [key in keyof T["properties"]]: PgColumnBuilderBase; };
type EntityColumn<T extends TObject> = {
name: string;
entity: EntityPrimitive<T>;
};
type EntityColumns<T extends TObject> = { [key in keyof T["properties"]]: EntityColumn<T>; };
//#endregion
//#region ../../src/orm/core/constants/PG_SYMBOLS.d.ts
declare const PG_DEFAULT: unique symbol;
declare const PG_PRIMARY_KEY: unique symbol;
declare const PG_CREATED_AT: unique symbol;
declare const PG_UPDATED_AT: unique symbol;
declare const PG_DELETED_AT: unique symbol;
declare const PG_VERSION: unique symbol;
declare const PG_IDENTITY: unique symbol;
declare const PG_ENUM: unique symbol;
declare const PG_REF: unique symbol;
declare const PG_GENERATED: unique symbol;
declare const PG_ORGANIZATION: unique symbol;
/**
* @deprecated Use `PG_IDENTITY` instead.
*/
declare const PG_SERIAL: unique symbol;
type PgSymbols = {
[PG_DEFAULT]: {};
[PG_PRIMARY_KEY]: {};
[PG_CREATED_AT]: {};
[PG_UPDATED_AT]: {};
[PG_DELETED_AT]: {};
[PG_VERSION]: {};
[PG_IDENTITY]: PgIdentityOptions;
[PG_REF]: PgRefOptions;
[PG_ENUM]: PgEnumOptions;
[PG_GENERATED]: PgGeneratedOptions;
[PG_ORGANIZATION]: PgOrganizationOptions;
/**
* @deprecated Use `PG_IDENTITY` instead.
*/
[PG_SERIAL]: {};
};
type PgSymbolKeys = keyof PgSymbols;
interface PgOrganizationOptions {
/**
* Fail-closed tenant scoping. When `true`, the repository (a) refuses any
* read/write with no resolved tenant instead of falling through to an
* unfiltered "see/write everything" query, and (b) drops the
* `OR organizationId IS NULL` "global row" escape — a scoped tenant never
* sees NULL/global rows. Use for security-sensitive tables. Defaults to
* `false` (the historic fail-open semantics).
*/
strict?: boolean;
}
type PgIdentityOptions = {
mode: "always" | "byDefault";
} & PgSequenceOptions & {
name?: string;
};
interface PgEnumOptions {
name?: string;
description?: string;
}
interface PgGeneratedOptions {
/**
* SQL expression for the generated column.
*/
expression: SQL;
/**
* Storage mode for the generated column.
* - `"stored"` — value is computed on write and stored on disk (default for PostgreSQL).
* - `"virtual"` — value is computed on read (default for SQLite).
*/
mode?: "stored" | "virtual";
}
interface PgRefOptions {
ref: () => {
name: string;
entity: EntityPrimitive;
};
actions?: {
onUpdate?: UpdateDeleteAction;
onDelete?: UpdateDeleteAction;
};
}
//#endregion
//#region ../../src/orm/core/helpers/pgAttr.d.ts
/**
* Type representation.
*/
type PgAttr<T extends TSchema, TAttr extends PgSymbolKeys> = T & { [K in TAttr]: PgSymbols[K]; };
//#endregion
//#region ../../src/orm/core/schemas/databaseEnvSchema.d.ts
/**
* Base database environment schema.
*
* Defines the `DATABASE_URL` connection string used by all ORM providers
* to determine the database driver and connection target.
*
* Supported URL formats:
* - `sqlite://:memory:` or `sqlite://./path/to/db` — SQLite (Node.js or Bun)
* - `postgres://user:password@host:port/database` — PostgreSQL (Node.js or Bun)
* - `pglite://:memory:` or `pglite://./path` — PGlite (embedded Postgres)
* - `d1://BINDING_NAME` — Cloudflare D1
* - `hyperdrive://BINDING_NAME` — Cloudflare Hyperdrive
*/
declare const databaseEnvSchema: import("zod").ZodObject<{
DATABASE_URL: import("zod").ZodOptional<import("zod").ZodString>;
DATABASE_SYNC: import("zod").ZodOptional<import("zod").ZodBoolean>;
}, import("zod/v4/core").$strip>;
declare module "alepha" {
interface Env extends Partial<Static<typeof databaseEnvSchema>> {}
}
//#endregion
//#region ../../src/api/parameters/entities/parameters.d.ts
/**
* Configuration parameter entity for versioned configuration management.
*
* Stores all versions of configuration parameters with:
* - Status derived from activationDate at query time
* - Schema versioning for migrations
* - Activation scheduling
* - Audit trail (creator info)
*/
declare const parameters: import("alepha/orm").EntityPrimitive<import("zod").ZodObject<{
id: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("zod").ZodString, typeof import("alepha/orm").PG_PRIMARY_KEY>, typeof import("alepha/orm").PG_DEFAULT>;
createdAt: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("zod").ZodString, typeof import("alepha/orm").PG_CREATED_AT>, typeof import("alepha/orm").PG_DEFAULT>;
updatedAt: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("zod").ZodString, typeof import("alepha/orm").PG_UPDATED_AT>, typeof import("alepha/orm").PG_DEFAULT>;
organizationId: import("alepha/orm").PgAttr<import("zod").ZodOptional<import("zod").ZodString> | import("zod").ZodString, typeof import("alepha/orm").PG_ORGANIZATION>;
name: import("zod").ZodString;
content: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>;
schemaHash: import("zod").ZodString;
activationDate: import("zod").ZodString;
version: import("zod").ZodInt;
changeDescription: import("zod").ZodOptional<import("zod").ZodString>;
tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
creatorId: import("zod").ZodOptional<import("zod").ZodString>;
creatorName: import("zod").ZodOptional<import("zod").ZodString>;
previousContent: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
migrationLog: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
type Parameter = Static<typeof parameters.schema>;
//#endregion
//#region ../../src/api/parameters/primitives/$parameter.d.ts
/**
* Creates a versioned parameter primitive for managing application settings.
*
* Provides type-safe, versioned configuration with:
* - Schema validation with auto-migration detection
* - Default values for initial state
* - Status derived from activationDate (no stored status)
* - Database persistence with full version history
* - Cross-instance notification via topic
* - Tree view support via dot-notation naming (e.g., "app.features.flags")
* - Async `.get()` with lazy loading (works in Node and Cloudflare Workers)
*
* @example
* ```ts
* class AppConfig {
* features = $parameter({
* name: "app.features.flags",
* schema: z.object({
* enableBeta: z.boolean(),
* maxUploadSize: z.number()
* }),
* default: { enableBeta: false, maxUploadSize: 10485760 }
* });
*
* async checkBeta() {
* const config = await this.features.get();
* return config.enableBeta;
* }
*
* async enableBeta() {
* await this.features.set({ enableBeta: true, maxUploadSize: 20971520 });
* }
* }
* ```
*/
interface ParameterPrimitiveOptions<T extends TObject> {
/**
* Parameter name using dot notation for tree hierarchy.
* Examples: "app.features", "app.pricing.tiers", "system.limits"
*/
name?: string;
/**
* Human-readable description of the parameter.
*/
description?: string;
/**
* TypeBox schema defining the parameter structure.
*/
schema: T;
/**
* Default value used when no parameter exists in database.
*/
default: Static<T>;
/**
* Optional migration function for schema changes.
* Receives the raw DB value and returns a transformed value matching the new schema.
* Runs before validation — if the result is valid, it's used directly.
* If not provided or returns an invalid value, falls through to merge/default cascade.
*/
migrate?: (old: unknown) => Static<T>;
}
declare class ParameterPrimitive<T extends TObject> extends Primitive<ParameterPrimitiveOptions<T>> {
protected readonly provider: ParameterProvider;
/**
* Parameter name (uses property key if not specified).
*/
get name(): string;
/**
* The TypeBox schema for this parameter.
*/
get schema(): T;
/**
* Get the cached current content, falling back to default.
* Synchronous access for admin API.
*/
get cachedCurrentContent(): Static<T>;
/**
* Whether the parameter is using its default value (no DB value loaded).
*/
get isUsingDefault(): boolean;
/**
* Get the current parameter value asynchronously.
* Lazy-loads from database on first call.
* Checks if a cached next version has become current.
*/
get(): Promise<Static<T>>;
/**
* Load current and next values from database.
*/
load(): Promise<void>;
/**
* Set a new parameter value.
*
* @param value - The new parameter value
* @param options - Optional settings (activation date, creator info, etc.)
*/
set(value: Static<T>, options?: SetParameterOptions): Promise<void>;
/**
* Subscribe to parameter changes.
* Returns an unsubscribe function.
*/
sub(fn: (curr: Static<T>) => void): () => void;
/**
* Reload parameter from database.
* Called when sync notification received or for manual refresh.
*/
reload(): Promise<void>;
/**
* Get version history for this parameter.
*/
getHistory(options?: {
limit?: number;
offset?: number;
}): Promise<{
id: string;
createdAt: string;
updatedAt: string;
organizationId: string | undefined;
name: string;
content: Record<string, any>;
schemaHash: string;
activationDate: string;
version: number;
changeDescription?: string | undefined;
tags?: string[] | undefined;
creatorId?: string | undefined;
creatorName?: string | undefined;
previousContent?: Record<string, any> | undefined;
migrationLog?: string | undefined;
}[]>;
/**
* Get a specific version of this parameter.
*/
getVersion(version: number): Promise<{
id: string;
createdAt: string;
updatedAt: string;
organizationId: string | undefined;
name: string;
content: Record<string, any>;
schemaHash: string;
activationDate: string;
version: number;
changeDescription?: string | undefined;
tags?: string[] | undefined;
creatorId?: string | undefined;
creatorName?: string | undefined;
previousContent?: Record<string, any> | undefined;
migrationLog?: string | undefined;
} | null>;
/**
* Delete all versions of this parameter.
*/
delete(): Promise<void>;
/**
* Rollback to a specific version.
*/
rollback(version: number, options?: SetParameterOptions): Promise<void>;
/**
* Called after primitive creation to register with provider.
*/
protected onInit(): void;
}
declare const $parameter: {
<T extends TObject>(options: ParameterPrimitiveOptions<T>): ParameterPrimitive<T>;
[KIND]: typeof ParameterPrimitive;
};
interface SetParameterOptions {
/**
* User making the change (for audit trail).
*/
user?: Pick<UserAccount, "id" | "email" | "name">;
/**
* When this parameter should become active.
* Default is immediate (now).
*/
activationDate?: Date;
/**
* Description of the change.
*/
changeDescription?: string;
/**
* Tags for filtering/categorization.
*/
tags?: string[];
}
//#endregion
//#region ../../src/api/parameters/schemas/parameterStatusSchema.d.ts
/**
* Parameter status enum schema.
*/
declare const parameterStatusSchema: import("zod").ZodEnum<{
current: "current";
expired: "expired";
future: "future";
next: "next";
}>;
type ParameterStatus = Static<typeof parameterStatusSchema>;
//#endregion
//#region ../../src/api/parameters/schemas/parameterTreeNodeSchema.d.ts
/**
* Tree node schema for parameter tree navigation.
*/
declare const parameterTreeNodeSchema: import("zod").ZodObject<{
name: import("zod").ZodString;
path: import("zod").ZodString;
isLeaf: import("zod").ZodBoolean;
children: import("zod").ZodArray<import("zod").ZodAny>;
}, import("zod/v4/core").$strip>;
type ParameterTreeNode = Static<typeof parameterTreeNodeSchema>;
//#endregion
//#region ../../src/api/parameters/services/ParameterProvider.d.ts
/**
* Payload for parameter change events across instances.
*/
interface ParameterChangePayload {
name: string;
instanceId: string;
}
/**
* A parameter with a calculated status field.
*/
type ParameterWithStatus = Parameter & {
status: ParameterStatus;
};
/**
* ParameterProvider manages versioned parameter persistence, caching,
* migration, and synchronization.
*
* Features:
* - Stores all parameter versions in the database
* - Derives status from activationDate at query time (no stored status)
* - Provides cross-instance notification via topic
* - Supports schema migrations via hash comparison
* - Manages per-parameter caching, loading, and subscriber notification
*/
declare class ParameterProvider {
protected readonly log: import("alepha/logger").Logger;
protected readonly alepha: Alepha;
protected readonly dateTimeProvider: DateTimeProvider;
protected readonly crypto: CryptoProvider;
protected readonly lockProvider: LockProvider;
protected readonly schemaValidator: SchemaValidator;
protected readonly repositoryProvider: RepositoryProvider;
protected readonly repo: import("alepha/orm").Repository<import("zod").ZodObject<{
id: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("zod").ZodString, typeof import("alepha/orm").PG_PRIMARY_KEY>, typeof import("alepha/orm").PG_DEFAULT>;
createdAt: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("zod").ZodString, typeof import("alepha/orm").PG_CREATED_AT>, typeof import("alepha/orm").PG_DEFAULT>;
updatedAt: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("zod").ZodString, typeof import("alepha/orm").PG_UPDATED_AT>, typeof import("alepha/orm").PG_DEFAULT>;
organizationId: import("alepha/orm").PgAttr<import("zod").ZodOptional<import("zod").ZodString> | import("zod").ZodString, typeof import("alepha/orm").PG_ORGANIZATION>;
name: import("zod").ZodString;
content: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>;
schemaHash: import("zod").ZodString;
activationDate: import("zod").ZodString;
version: import("zod").ZodInt;
changeDescription: import("zod").ZodOptional<import("zod").ZodString>;
tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
creatorId: import("zod").ZodOptional<import("zod").ZodString>;
creatorName: import("zod").ZodOptional<import("zod").ZodString>;
previousContent: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
migrationLog: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
/**
* Unique identifier for this instance (to avoid self-updates).
*/
protected get instanceId(): string;
protected _instanceId: string | undefined;
/**
* Resolve the active tenant for cache keying — MIRRORS the Repository's
* `resolveOrganizationValue` (tenant atom → user org) so the in-memory
* value caches partition exactly the way the DB rows do. Returns a sentinel
* for the org-less (single-tenant / no-request) case.
*
* Why this matters: the DB table is now org-scoped, but these process-global
* Maps are keyed by parameter NAME — without folding the org into the key, a
* pooled multi-tenant worker would hand org A's cached `club.settings` to a
* request for org B. (Cross-instance topic sync + the `ready` preload run
* with no request atom → the sentinel key; they refresh only org-less rows,
* leaving per-org caches to lazy-load + the immediate local update on `set`.
* That is bounded staleness, never a cross-tenant read.)
*/
protected orgKey(): string;
/** Per-org cache key for the value caches (`${org}:${name}`). */
protected cacheKey(name: string): string;
/**
* In-memory cache of registered parameter primitives. Keyed by NAME only —
* the `$parameter` definition (schema + default) is identical for every
* tenant; only the stored VALUE is per-org (see the value caches below).
*/
protected readonly primitives: Map<string, ParameterPrimitive<any>>;
/**
* In-memory cached current content per parameter.
*/
protected readonly cachedCurrent: Map<string, unknown>;
/**
* In-memory cached next version info per parameter.
*/
protected readonly cachedNext: Map<string, {
content: unknown;
activationDate: string;
}>;
/**
* Set of parameter names that have completed initial load.
*/
protected readonly loaded: Set<string>;
/**
* Epoch millis of the last successful load (or local set) per cache key.
* Drives the serverless revalidation TTL (see `revalidateAfterMs`).
*/
protected readonly loadedAt: Map<string, number>;
/**
* How long a cached value may serve before `get()` re-reads the DB.
*
* The in-memory caches are PER ISOLATE. On serverless runtimes
* (Cloudflare Workers) many isolates serve the same app and the
* cross-instance `syncTopic` rides the queue provider — in-memory by
* default, so a `set()` handled by one isolate never reaches the others:
* without a TTL they serve the stale value until they are recycled.
*
* Defaults: 30 s on serverless, 0 (= never revalidate, the historical
* behaviour) elsewhere. Override with the `PARAMETERS_CACHE_TTL_MS` env.
*/
protected get revalidateAfterMs(): number;
/**
* Shared promises for deduplicating concurrent load() calls.
*/
protected readonly loadPromises: Map<string, Promise<void>>;
/**
* Generation counter per parameter — incremented on each doLoad call.
* Used to discard results from superseded loads.
*/
protected readonly loadGeneration: Map<string, number>;
/**
* Subscriber callbacks per parameter name.
*/
protected readonly subscribers: Map<string, ((v: unknown) => void)[]>;
/**
* Set of parameter names that have already been checked for migration.
*/
protected readonly migrationChecked: Set<string>;
/**
* Computed schema hashes per parameter name.
*/
protected readonly schemaHashes: Map<string, string>;
/**
* Pre-load all registered parameters on ready (non-serverless only).
*/
protected readonly onReady: import("alepha").HookPrimitive<"ready">;
/**
* Topic for cross-instance change notification.
* Payload is minimal — receivers call load() to fetch fresh data.
*/
readonly syncTopic: import("alepha/topic").TopicPrimitive<{
payload: import("zod").ZodObject<{
name: import("zod").ZodString;
instanceId: import("zod").ZodString;
}, import("zod/v4/core").$strip>;
}>;
/**
* Register a parameter primitive with the provider.
* Computes and stores the schema hash.
*/
register(param: ParameterPrimitive<any>): void;
/**
* Get the current parameter value asynchronously.
* Lazy-loads from database on first call.
* Checks if a cached next version has become current.
*/
get(name: string): Promise<unknown>;
/**
* Set a new parameter value.
*/
set(name: string, value: unknown, options?: SaveParameterOptions): Promise<void>;
/** Whether the cached value outlived the revalidation TTL. */
protected isStale(ck: string): boolean;
/**
* Subscribe to parameter changes.
* Returns an unsubscribe function.
*/
sub(name: string, fn: (v: unknown) => void): () => void;
/**
* Load current and next values from database.
* Deduplicates concurrent calls via shared promise.
*/
load(name: string): Promise<void>;
/**
* Get the cached current content, falling back to default.
* Synchronous access for admin API.
*/
getCachedCurrentContent(name: string): unknown;
/**
* Whether the parameter is using its default value (no DB value loaded).
*/
isUsingDefault(name: string): boolean;
/**
* Load the current and next parameter values from database.
* Current: latest version with activationDate <= now.
* Next: earliest version with activationDate > now.
*/
loadCurrentAndNext(name: string): Promise<{
current: Parameter | null;
next: Parameter | null;
now: Date;
}>;
/**
* Calculate statuses for a list of parameter versions.
* Derives status from activationDate relative to now:
* - The latest version with activationDate <= now is "current"
* - The earliest version with activationDate > now is "next"
* - Other future versions are "future"
* - Other past versions are "expired"
*/
calculateStatuses<T extends Parameter>(versions: T[], now?: Date): Array<T & {
status: ParameterStatus;
}>;
/**
* Save a new parameter version.
*
* @param name - Parameter name (e.g., "app.features.flags")
* @param content - New parameter content
* @param schemaHash - Hash of the schema for migration detection
* @param options - Additional options (activation date, creator info, etc.)
*/
save<T extends TObject>(name: string, content: Static<T>, schemaHash: string, options?: SaveParameterOptions): Promise<ParameterWithStatus>;
/**
* Best-effort left join embedding the creating user on each version, so the
* admin UI can render a human-readable identifier (live, not snapshotted)
* instead of the bare `creatorId`. Joins `parameters.creatorId` → `users.id`.
*
* The `users` entity is resolved from the repository registry at runtime
* rather than imported: that keeps the parameters module free of any
* dependency on the users module (no import, no circular-import risk). When
* the users module is not registered the join is skipped and `creator` comes
* back undefined.
*/
protected resolveCreatorJoin(): {
creator: {
join: import("alepha/orm").EntityPrimitive<TObject>;
on: ["creatorId", {
name: string;
}];
};
} | undefined;
/**
* Get all versions of a parameter, each with the creating user embedded
* (best-effort, see {@link resolveCreatorJoin}).
*/
getHistory(name: string, options?: {
limit?: number;
offset?: number;
}): Promise<Parameter[]>;
/**
* Delete all versions of a parameter.
*/
delete(name: string): Promise<void>;
/**
* Delete all versions of many parameters by name in one repository call.
*/
deleteMany(names: string[]): Promise<string[]>;
/** Drop every per-org value cache entry for `name` in the active org. */
protected evictCaches(name: string): void;
/**
* Get a specific version of a parameter.
*/
getVersion(name: string, version: number): Promise<Parameter | null>;
/**
* Rollback to a previous version by creating a new version with old content.
*/
rollback(name: string, targetVersion: number, options?: SaveParameterOptions): Promise<ParameterWithStatus>;
/**
* Get current parameter value with fallback to default from registered primitive.
* Returns the in-memory current value which may be the default if never saved.
*/
getCurrentValue(name: string): {
content: unknown;
isDefault: boolean;
} | null;
/**
* Get parameter info including current value with default fallback.
*
* When no version exists in the DB yet but a primitive is registered, this
* lazily materializes v1 from the primitive's `default`. After this call,
* the parameter has a concrete current row admins can edit / roll back /
* compare against, instead of running on phantom defaults-from-code state.
* Idempotent: subsequent calls return the same row without re-creating.
*/
getCurrentWithDefault(name: string): Promise<{
current: ParameterWithStatus | null;
next: ParameterWithStatus | null;
defaultValue: unknown | null;
currentValue: unknown | null;
schema: Record<string, unknown> | null;
}>;
/**
* Get all unique parameter names (for tree view).
*/
getParameterNames(): Promise<string[]>;
/**
* Build a tree structure from parameter names for UI.
* Includes both database parameters and registered (but not yet saved) parameters.
*/
getParameterTree(): Promise<ParameterTreeNode[]>;
/**
* Internal load implementation.
* Fetches current and next from database, updates cache.
*/
protected doLoad(name: string): Promise<void>;
/**
* Run migration with a distributed lock (Node.js only).
* Ensures only one instance performs the migration while others wait.
*/
protected migrateWithLock(name: string): Promise<void>;
/**
* Poll until a lock is released (or TTL expires).
* Uses a probe-only SET NX with minimal TTL to detect release
* without holding the lock longer than necessary.
*/
protected waitForLock(lockKey: string): Promise<void>;
/**
* Attempt to migrate a DB value to the current schema.
* Returns the migrated value if successful, or null if no migration needed.
*
* Cascade:
* 1. Run user migrate() if provided
* 2. Validate result against schema
* 3. If invalid, shallow merge DB value with defaults
* 4. Validate merged result
* 5. If still invalid, use defaults
*/
protected migrateValue(name: string, dbValue: unknown, dbSchemaHash: string): {
value: unknown;
description: string;
} | null;
/**
* Reload next version info in background (non-blocking).
*/
protected reloadNextInBackground(name: string): void;
/**
* Notify all subscribers of a value change.
*/
protected notifySubscribers(name: string): void;
/**
* Probe whether a value matches the schema, without throwing or mutating.
*/
protected isValid(schema: TObject, value: unknown): boolean;
/**
* Return a new object containing only keys present in the schema.
*/
protected pickSchemaKeys(obj: Record<string, unknown>, schemaKeys: Set<string>): Record<string, unknown>;
/**
* Calculate a hash of the schema for migration detection.
* Uses CryptoProvider for proper SHA-256 hashing.
*/
protected calculateSchemaHash(schema: TObject): string;
/**
* Publish change notification to other instances.
*/
protected publishChange(name: string): Promise<void>;
/**
* Handle incoming change notification from other instances.
* Reloads the parameter from DB.
*/
protected handleChangeNotification(payload: ParameterChangePayload): Promise<void>;
/**
* Build tree structure from dot-notation names.
*/
protected buildTree(names: string[]): ParameterTreeNode[];
}
interface SaveParameterOptions {
activationDate?: Date;
changeDescription?: string;
tags?: string[];
creatorId?: string;
creatorName?: string;
}
//#endregion
//#region ../../src/api/parameters/controllers/AdminParameterController.d.ts
/**
* REST API controller for versioned parameter management.
*
* Provides endpoints for:
* - Listing all parameters (tree view support)
* - Getting parameter history (all versions with calculated status)
* - Getting current/next parameter values
* - Creating new parameter versions (immediate or scheduled)
* - Rolling back to previous versions
* - Activating scheduled versions immediately
*/
declare class AdminParameterController {
protected readonly url = "/parameters";
protected readonly group = "admin:parameters";
protected readonly provider: ParameterProvider;
protected readonly alepha: Alepha;
/**
* Get tree structure of all parameter names.
* Useful for admin UI navigation.
*/
getParameterTree: import("alepha/server").ActionPrimitiveFn<{
response: import("zod").ZodArray<import("zod").ZodObject<{
name: import("zod").ZodString;
path: import("zod").ZodString;
isLeaf: import("zod").ZodBoolean;
children: import("zod").ZodArray<import("zod").ZodAny>;
}, import("zod/v4/core").$strip>>;
}>;
/**
* List all unique parameter names.
*/
listParameterNames: import("alepha/server").ActionPrimitiveFn<{
response: import("zod").ZodObject<{
names: import("zod").ZodArray<import("zod").ZodString>;
}, import("zod/v4/core").$strip>;
}>;
/**
* Get version history for a specific parameter.
* Returns all versions with calculated status.
*/
getHistory: import("alepha/server").ActionPrimitiveFn<{
params: import("zod").ZodObject<{
name: import("zod").ZodString;
}, import("zod/v4/core").$strip>;
query: import("zod").ZodObject<{
limit: import("zod").ZodOptional<import("zod").ZodInt>;
offset: import("zod").ZodOptional<import("zod").ZodInt>;
}, import("zod/v4/core").$strip>;
response: import("zod").ZodObject<{
versions: import("zod").ZodArray<import("zod").ZodObject<{
id: PgAttr<PgAttr<import("zod").ZodString, typeof PG_PRIMARY_KEY>, typeof PG_DEFAULT>;
createdAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_CREATED_AT>, typeof PG_DEFAULT>;
updatedAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_UPDATED_AT>, typeof PG_DEFAULT>;
organizationId: PgAttr<import("zod").ZodOptional<import("zod").ZodString> | import("zod").ZodString, typeof PG_ORGANIZATION>;
name: import("zod").ZodString;
content: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>;
schemaHash: import("zod").ZodString;
activationDate: import("zod").ZodString;
version: import("zod").ZodInt;
changeDescription: import("zod").ZodOptional<import("zod").ZodString>;
tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
creatorId: import("zod").ZodOptional<import("zod").ZodString>;
creatorName: import("zod").ZodOptional<import("zod").ZodString>;
previousContent: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
migrationLog: import("zod").ZodOptional<import("zod").ZodString>;
status: import("zod").ZodEnum<{
current: "current";
expired: "expired";
future: "future";
next: "next";
}>;
creator: import("zod").ZodOptional<import("zod").ZodObject<{
id: import("zod").ZodString;
email: import("zod").ZodOptional<import("zod").ZodString>;
username: import("zod").ZodOptional<import("zod").ZodString>;
firstName: import("zod").ZodOptional<import("zod").ZodString>;
lastName: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
}, import("zod/v4/core").$strip>>;
}, import("zod/v4/core").$strip>;
}>;
/**
* Get current and next values for a parameter.
* Includes defaultValue and currentValue from the registered primitive
* even if no versions exist in the database yet.
*/
getCurrent: import("alepha/server").ActionPrimitiveFn<{
params: import("zod").ZodObject<{
name: import("zod").ZodString;
}, import("zod/v4/core").$strip>;
response: import("zod").ZodObject<{
current: import("zod").ZodOptional<import("zod").ZodObject<{
id: PgAttr<PgAttr<import("zod").ZodString, typeof PG_PRIMARY_KEY>, typeof PG_DEFAULT>;
createdAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_CREATED_AT>, typeof PG_DEFAULT>;
updatedAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_UPDATED_AT>, typeof PG_DEFAULT>;
organizationId: PgAttr<import("zod").ZodOptional<import("zod").ZodString> | import("zod").ZodString, typeof PG_ORGANIZATION>;
name: import("zod").ZodString;
content: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>;
schemaHash: import("zod").ZodString;
activationDate: import("zod").ZodString;
version: import("zod").ZodInt;
changeDescription: import("zod").ZodOptional<import("zod").ZodString>;
tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
creatorId: import("zod").ZodOptional<import("zod").ZodString>;
creatorName: import("zod").ZodOptional<import("zod").ZodString>;
previousContent: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
migrationLog: import("zod").ZodOptional<import("zod").ZodString>;
status: import("zod").ZodEnum<{
current: "current";
expired: "expired";
future: "future";
next: "next";
}>;
creator: import("zod").ZodOptional<import("zod").ZodObject<{
id: import("zod").ZodString;
email: import("zod").ZodOptional<import("zod").ZodString>;
username: import("zod").ZodOptional<import("zod").ZodString>;
firstName: import("zod").ZodOptional<import("zod").ZodString>;
lastName: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
}, import("zod/v4/core").$strip>>;
next: import("zod").ZodOptional<import("zod").ZodObject<{
id: PgAttr<PgAttr<import("zod").ZodString, typeof PG_PRIMARY_KEY>, typeof PG_DEFAULT>;
createdAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_CREATED_AT>, typeof PG_DEFAULT>;
updatedAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_UPDATED_AT>, typeof PG_DEFAULT>;
organizationId: PgAttr<import("zod").ZodOptional<import("zod").ZodString> | import("zod").ZodString, typeof PG_ORGANIZATION>;
name: import("zod").ZodString;
content: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>;
schemaHash: import("zod").ZodString;
activationDate: import("zod").ZodString;
version: import("zod").ZodInt;
changeDescription: import("zod").ZodOptional<import("zod").ZodString>;
tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
creatorId: import("zod").ZodOptional<import("zod").ZodString>;
creatorName: import("zod").ZodOptional<import("zod").ZodString>;
previousContent: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
migrationLog: import("zod").ZodOptional<import("zod").ZodString>;
status: import("zod").ZodEnum<{
current: "current";
expired: "expired";
future: "future";
next: "next";
}>;
creator: import("zod").ZodOptional<import("zod").ZodObject<{
id: import("zod").ZodString;
email: import("zod").ZodOptional<import("zod").ZodString>;
username: import("zod").ZodOptional<import("zod").ZodString>;
firstName: import("zod").ZodOptional<import("zod").ZodString>;
lastName: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
}, import("zod/v4/core").$strip>>;
defaultValue: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
currentValue: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
schema: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
}, import("zod/v4/core").$strip>;
}>;
/**
* Get a specific version of a parameter.
*/
getVersion: import("alepha/server").ActionPrimitiveFn<{
params: import("zod").ZodObject<{
name: import("zod").ZodString;
version: import("zod").ZodInt;
}, import("zod/v4/core").$strip>;
response: import("zod").ZodObject<{
parameter: import("zod").ZodOptional<import("zod").ZodObject<{
id: PgAttr<PgAttr<import("zod").ZodString, typeof PG_PRIMARY_KEY>, typeof PG_DEFAULT>;
createdAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_CREATED_AT>, typeof PG_DEFAULT>;
updatedAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_UPDATED_AT>, typeof PG_DEFAULT>;
organizationId: PgAttr<import("zod").ZodOptional<import("zod").ZodString> | import("zod").ZodString, typeof PG_ORGANIZATION>;
name: import("zod").ZodString;
content: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>;
schemaHash: import("zod").ZodString;
activationDate: import("zod").ZodString;
version: import("zod").ZodInt;
changeDescription: import("zod").ZodOptional<import("zod").ZodString>;
tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
creatorId: import("zod").ZodOptional<import("zod").ZodString>;
creatorName: import("zod").ZodOptional<import("zod").ZodString>;
previousContent: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
migrationLog: import("zod").ZodOptional<import("zod").ZodString>;
status: import("zod").ZodEnum<{
current: "current";
expired: "expired";
future: "future";
next: "next";
}>;
creator: import("zod").ZodOptional<import("zod").ZodObject<{
id: import("zod").ZodString;
email: import("zod").ZodOptional<import("zod").ZodString>;
username: import("zod").ZodOptional<import("zod").ZodString>;
firstName: import("zod").ZodOptional<import("zod").ZodString>;
lastName: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
}, import("zod/v4/core").$strip>>;
}, import("zod/v4/core").$strip>;
}>;
/**
* Create a new parameter version.
*/
createVersion: import("alepha/server").ActionPrimitiveFn<{
params: import("zod").ZodObject<{
name: import("zod").ZodString;
}, import("zod/v4/core").$strip>;
body: import("zod").ZodObject<{
content: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>;
schemaHash: import("zod").ZodString;
changeDescription: import("zod").ZodOptional<import("zod").ZodString>;
tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
activationDate: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>;
response: import("zod").ZodObject<{
id: PgAttr<PgAttr<import("zod").ZodString, typeof PG_PRIMARY_KEY>, typeof PG_DEFAULT>;
createdAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_CREATED_AT>, typeof PG_DEFAULT>;
updatedAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_UPDATED_AT>, typeof PG_DEFAULT>;
organizationId: PgAttr<import("zod").ZodOptional<import("zod").ZodString> | import("zod").ZodString, typeof PG_ORGANIZATION>;
name: import("zod").ZodString;
content: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>;
schemaHash: import("zod").ZodString;
activationDate: import("zod").ZodString;
version: import("zod").ZodInt;
changeDescription: import("zod").ZodOptional<import("zod").ZodString>;
tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
creatorId: import("zod").ZodOptional<import("zod").ZodString>;
creatorName: import("zod").ZodOptional<import("zod").ZodString>;
previousContent: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
migrationLog: import("zod").ZodOptional<import("zod").ZodString>;
status: import("zod").ZodEnum<{
current: "current";
expired: "expired";
future: "future";
next: "next";
}>;
creator: import("zod").ZodOptional<import("zod").ZodObject<{
id: import("zod").ZodString;
email: import("zod").ZodOptional<import("zod").ZodString>;
username: import("zod").ZodOptional<import("zod").ZodString>;
firstName: import("zod").ZodOptional<import("zod").ZodString>;
lastName: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
}, import("zod/v4/core").$strip>;
}>;
/**
* Rollback to a previous version.
*/
rollback: import("alepha/server").ActionPrimitiveFn<{
params: import("zod").ZodObject<{
name: import("zod").ZodString;
}, import("zod/v4/core").$strip>;
body: import("zod").ZodObject<{
changeDescription: import("zod").ZodOptional<import("zod").ZodString>;
targetVersion: import("zod").ZodInt;
}, import("zod/v4/core").$strip>;
response: import("zod").ZodObject<{
id: PgAttr<PgAttr<import("zod").ZodString, typeof PG_PRIMARY_KEY>, typeof PG_DEFAULT>;
createdAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_CREATED_AT>, typeof PG_DEFAULT>;
updatedAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_UPDATED_AT>, typeof PG_DEFAULT>;
organizationId: PgAttr<import("zod").ZodOptional<import("zod").ZodString> | import("zod").ZodString, typeof PG_ORGANIZATION>;
name: import("zod").ZodString;
content: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>;
schemaHash: import("zod").ZodString;
activationDate: import("zod").ZodString;
version: import("zod").ZodInt;
changeDescription: import("zod").ZodOptional<import("zod").ZodString>;
tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
creatorId: import("zod").ZodOptional<import("zod").ZodString>;
creatorName: import("zod").ZodOptional<import("zod").ZodString>;
previousContent: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
migrationLog: import("zod").ZodOptional<import("zod").ZodString>;
status: import("zod").ZodEnum<{
current: "current";
expired: "expired";
future: "future";
next: "next";
}>;
creator: import("zod").ZodOptional<import("zod").ZodObject<{
id: import("zod").ZodString;
email: import("zod").ZodOptional<import("zod").ZodString>;
username: import("zod").ZodOptional<import("zod").ZodString>;
firstName: import("zod").ZodOptional<import("zod").ZodString>;
lastName: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
}, import("zod/v4/core").$strip>;
}>;
/**
* Activate a scheduled version immediately.
* Creates a new version with the same content but immediate activation.
*/
activateNow: import("alepha/server").ActionPrimitiveFn<{
params: import("zod").ZodObject<{
name: import("zod").ZodString;
}, import("zod/v4/core").$strip>;
body: import("zod").ZodObject<{
version: import("zod").ZodInt;
}, import("zod/v4/core").$strip>;
response: import("zod").ZodObject<{
id: PgAttr<PgAttr<import("zod").ZodString, typeof PG_PRIMARY_KEY>, typeof PG_DEFAULT>;
createdAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_CREATED_AT>, typeof PG_DEFAULT>;
updatedAt: PgAttr<PgAttr<import("zod").ZodString, typeof PG_UPDATED_AT>, typeof PG_DEFAULT>;
organizationId: PgAttr<import("zod").ZodOptional<import("zod").ZodString> | import("zod").ZodString, typeof PG_ORGANIZATION>;
name: import("zod").ZodStr