UNPKG

alepha

Version:

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

1,242 lines 68.7 kB
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