UNPKG

alepha

Version:

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

1,457 lines 179 kB
import { Alepha, AlephaError, Page, PageQuery, Primitive, SchemaValidator, Static, StaticEncode, TNull, TObject, TOptional, TSchema, TUnion } from "alepha"; import { CryptoProvider, IssuerPrimitive, IssuerPrimitiveOptions, SecurityProvider, SigningConfig, UserAccount } from "alepha/security"; import { Page as Page$1, Repository } from "alepha/orm"; import { VerificationController, VerificationService } from "alepha/api/verifications"; import { CaptchaProvider } from "alepha/captcha"; import { OAuth2Profile, ServerAuthProvider, WithLinkFn, WithLoginFn } from "alepha/server/auth"; import { CacheProvider } from "alepha/cache"; import { DateTime, DateTimeProvider } from "alepha/datetime"; import { FileSystemProvider } from "alepha/system"; import { ParameterPrimitive } from "alepha/api/parameters"; import { BuildExtraConfigColumns, SQL, SQLWrapper } from "drizzle-orm"; import { LockConfig, LockStrength, PgColumn, PgColumnBuilderBase, PgDatabase, PgInsertValue, PgSelectBase, PgSequenceOptions, PgTableExtraConfigValue, PgTableWithColumns, PgTransaction, UpdateDeleteAction } from "drizzle-orm/pg-core"; import "drizzle-orm/pg-core/foreign-keys"; import { PgTransactionConfig } from "drizzle-orm/pg-core/session"; import { CryptoProvider as CryptoProvider$1 } from "alepha/crypto"; import * as DrizzleKit from "drizzle-kit/api"; import "drizzle-orm/d1"; import "drizzle-orm/sqlite-core"; import { FileController } from "alepha/api/files"; //#region ../../src/api/users/atoms/realmAuthSettingsAtom.d.ts /** * Tri-state field requirement for realm auth settings. * * - `"none"`: Field is disabled and not shown. * - `"optional"`: Field is shown but not required. * - `"required"`: Field is shown and required. */ type FieldRequirement = "none" | "optional" | "required"; /** * Username-specific field requirement, extending {@link FieldRequirement} * with an additional auto-derivation mode. * * - `"none"` / `"optional"` / `"required"`: same as {@link FieldRequirement}. * - `"email"`: Field is hidden in the registration UI and the value is * auto-derived from the user's email at signup. Same handling on * credentials and OAuth flows. See `UsernameSlugger` for the rule and * collision behavior. */ type UsernameFieldRequirement = FieldRequirement | "email"; declare const realmAuthSettingsAtom: import("alepha").Atom<import("zod").ZodObject<{ displayName: import("zod").ZodOptional<import("zod").ZodString>; description: import("zod").ZodOptional<import("zod").ZodString>; logoUrl: import("zod").ZodOptional<import("zod").ZodString>; registrationAllowed: import("zod").ZodBoolean; email: import("zod").ZodUnion<readonly [import("zod").ZodLiteral<"none">, import("zod").ZodLiteral<"optional">, import("zod").ZodLiteral<"required">]>; username: import("zod").ZodUnion<readonly [import("zod").ZodLiteral<"none">, import("zod").ZodLiteral<"optional">, import("zod").ZodLiteral<"required">, import("zod").ZodLiteral<"email">]>; usernameRegExp: import("zod").ZodString; usernameBlocklist: import("zod").ZodArray<import("zod").ZodString>; phoneNumber: import("zod").ZodUnion<readonly [import("zod").ZodLiteral<"none">, import("zod").ZodLiteral<"optional">, import("zod").ZodLiteral<"required">]>; verifyEmailRequired: import("zod").ZodBoolean; verifyPhoneRequired: import("zod").ZodBoolean; firstNameLastName: import("zod").ZodUnion<readonly [import("zod").ZodLiteral<"none">, import("zod").ZodLiteral<"optional">, import("zod").ZodLiteral<"required">]>; resetPasswordAllowed: import("zod").ZodBoolean; captchaRequired: import("zod").ZodBoolean; adminEmails: import("zod").ZodArray<import("zod").ZodString>; adminUsernames: import("zod").ZodArray<import("zod").ZodString>; defaultRoles: import("zod").ZodArray<import("zod").ZodString>; verifyEmailUrl: import("zod").ZodOptional<import("zod").ZodString>; passwordPolicy: import("zod").ZodObject<{ minLength: import("zod").ZodDefault<import("zod").ZodInt>; requireUppercase: import("zod").ZodBoolean; requireLowercase: import("zod").ZodBoolean; requireNumbers: import("zod").ZodBoolean; requireSpecialCharacters: import("zod").ZodBoolean; }, import("zod/v4/core").$strip>; loginRateLimit: import("zod").ZodObject<{ ipMaxAttempts: import("zod").ZodDefault<import("zod").ZodInt>; accountMaxAttempts: import("zod").ZodDefault<import("zod").ZodInt>; windowMs: import("zod").ZodDefault<import("zod").ZodInt>; }, import("zod/v4/core").$strip>; registrationIpMaxAttempts: import("zod").ZodDefault<import("zod").ZodInt>; refreshToken: import("zod").ZodObject<{ expirationIdle: import("zod").ZodOptional<import("zod").ZodInt>; }, import("zod/v4/core").$strip>; }, import("zod/v4/core").$strip>, "alepha.api.users.realmAuthSettings">; type RealmAuthSettings = Static<typeof realmAuthSettingsAtom.schema>; //#endregion //#region ../../src/api/users/audits/SessionAudits.d.ts /** * Authentication & session-security audit events. * * Holds two audit types: * - `auth` — login / logout / token refresh / MFA. * - `security` — rate limiting, session invalidation, and related guards. * * Failed events are logged with `success: false`; severity (`warning`) is * derived centrally in `AuditService.create`. Register as a module variant * and log via the exposed primitives: * `sessionAudits(realm)?.auth.log("login", { success: false, … })`. */ declare class SessionAudits { readonly auth: import("alepha/api/audits").AuditPrimitive; readonly security: import("alepha/api/audits").AuditPrimitive; } //#endregion //#region ../../src/api/users/audits/UserAudits.d.ts /** * User-management audit events. * * Holds the `user` audit type. Mirrors the `$notification`/`$job` holder * pattern (see {@link UserNotifications}) — register as a module variant and * log via the exposed primitive: `userAudits(realm)?.user.log("create", …)`. */ declare class UserAudits { readonly user: import("alepha/api/audits").AuditPrimitive; } //#endregion //#region ../../src/api/users/buckets/UserBuckets.d.ts /** * User-specific file storage wrapper service. * * This service provides file storage for user-related files such as: * - User avatars/profile pictures * * Declared as a module variant — not auto-injected. It is instantiated * lazily the first time something calls `alepha.inject(UserBuckets)`. */ declare class UserBuckets { /** * Bucket for user avatar storage. */ readonly avatars: import("alepha/bucket").BucketPrimitive; } //#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 SchemaToTableConfig<T extends TObject> = { name: string; schema: string | undefined; columns: { [key in keyof T["properties"]]: PgColumn; }; dialect: string; }; 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/errors/DbError.d.ts declare class DbError extends AlephaError { name: string; constructor(message: string, cause?: unknown); } //#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]; }; interface PgAttrField { key: string; type: TSchema; data: any; nested?: any[]; one?: boolean; } //#endregion //#region ../../src/orm/core/interfaces/FilterOperators.d.ts interface FilterOperators<TValue> { /** * Test that two values are equal. * * Remember that the SQL standard dictates that * two NULL values are not equal, so if you want to test * whether a value is null, you may want to use * `isNull` instead. * * ## Examples * * ```ts * // Select cars made by Ford * db.select().from(cars) * .where(eq(cars.make, 'Ford')) * ``` * * @see isNull for a way to test equality to NULL. */ eq?: TValue; /** * Test that two values are not equal. * * Remember that the SQL standard dictates that * two NULL values are not equal, so if you want to test * whether a value is not null, you may want to use * `isNotNull` instead. * * ## Examples * * ```ts * // Select cars not made by Ford * db.select().from(cars) * .where(ne(cars.make, 'Ford')) * ``` * * @see isNotNull for a way to test whether a value is not null. */ ne?: TValue; /** * Test that the first expression passed is greater than * the second expression. * * ## Examples * * ```ts * // Select cars made after 2000. * db.select().from(cars) * .where(gt(cars.year, 2000)) * ``` * * @see gte for greater-than-or-equal */ gt?: TValue; /** * Test that the first expression passed is greater than * or equal to the second expression. Use `gt` to * test whether an expression is strictly greater * than another. * * ## Examples * * ```ts * // Select cars made on or after 2000. * db.select().from(cars) * .where(gte(cars.year, 2000)) * ``` * * @see gt for a strictly greater-than condition */ gte?: TValue; /** * Test that the first expression passed is less than * the second expression. * * ## Examples * * ```ts * // Select cars made before 2000. * db.select().from(cars) * .where(lt(cars.year, 2000)) * ``` * * @see lte for greater-than-or-equal */ lt?: TValue; /** * Test that the first expression passed is less than * or equal to the second expression. * * ## Examples * * ```ts * // Select cars made before 2000. * db.select().from(cars) * .where(lte(cars.year, 2000)) * ``` * * @see lt for a strictly less-than condition */ lte?: TValue; /** * Test whether the first parameter, a column or expression, * has a value from a list passed as the second argument. * * ## Throws * * The argument passed in the second array can't be empty: * if an empty is provided, this method will throw. * * ## Examples * * ```ts * // Select cars made by Ford or GM. * db.select().from(cars) * .where(inArray(cars.make, ['Ford', 'GM'])) * ``` * * @see notInArray for the inverse of this test */ inArray?: TValue[]; /** * Test whether the first parameter, a column or expression, * has a value that is not present in a list passed as the * second argument. * * ## Throws * * The argument passed in the second array can't be empty: * if an empty is provided, this method will throw. * * ## Examples * * ```ts * // Select cars made by any company except Ford or GM. * db.select().from(cars) * .where(notInArray(cars.make, ['Ford', 'GM'])) * ``` * * @see inArray for the inverse of this test */ notInArray?: TValue[]; /** * Test whether an expression is not NULL. By the SQL standard, * NULL is neither equal nor not equal to itself, so * it's recommended to use `isNull` and `notIsNull` for * comparisons to NULL. * * ## Examples * * ```ts * // Select cars that have been discontinued. * db.select().from(cars) * .where(isNotNull(cars.discontinuedAt)) * ``` * * @see isNull for the inverse of this test */ isNotNull?: true; /** * Test whether an expression is NULL. By the SQL standard, * NULL is neither equal nor not equal to itself, so * it's recommended to use `isNull` and `notIsNull` for * comparisons to NULL. * * ## Examples * * ```ts * // Select cars that have no discontinuedAt date. * db.select().from(cars) * .where(isNull(cars.discontinuedAt)) * ``` * * @see isNotNull for the inverse of this test */ isNull?: true; /** * Test whether an expression is between two values. This * is an easier way to express range tests, which would be * expressed mathematically as `x <= a <= y` but in SQL * would have to be like `a >= x AND a <= y`. * * Between is inclusive of the endpoints: if `column` * is equal to `min` or `max`, it will be TRUE. * * ## Examples * * ```ts * // Select cars made between 1990 and 2000 * db.select().from(cars) * .where(between(cars.year, 1990, 2000)) * ``` * * @see notBetween for the inverse of this test */ between?: [number, number]; /** * Test whether an expression is not between two values. * * This, like `between`, includes its endpoints, so if * the `column` is equal to `min` or `max`, in this case * it will evaluate to FALSE. * * ## Examples * * ```ts * // Exclude cars made in the 1970s * db.select().from(cars) * .where(notBetween(cars.year, 1970, 1979)) * ``` * * @see between for the inverse of this test */ notBetween?: [number, number]; /** * Compare a column to a pattern, which can include `%` and `_` * characters to match multiple variations. Including `%` * in the pattern matches zero or more characters, and including * `_` will match a single character. * * ## Examples * * ```ts * // Select all cars with 'Turbo' in their names. * db.select().from(cars) * .where(like(cars.name, '%Turbo%')) * ``` * * @see ilike for a case-insensitive version of this condition */ like?: string; /** * The inverse of like - this tests that a given column * does not match a pattern, which can include `%` and `_` * characters to match multiple variations. Including `%` * in the pattern matches zero or more characters, and including * `_` will match a single character. * * ## Examples * * ```ts * // Select all cars that don't have "ROver" in their name. * db.select().from(cars) * .where(notLike(cars.name, '%Rover%')) * ``` * * @see like for the inverse condition * @see notIlike for a case-insensitive version of this condition */ notLike?: string; /** * Case-insensitively compare a column to a pattern, * which can include `%` and `_` * characters to match multiple variations. Including `%` * in the pattern matches zero or more characters, and including * `_` will match a single character. * * Unlike like, this performs a case-insensitive comparison. * * ## Examples * * ```ts * // Select all cars with 'Turbo' in their names. * db.select().from(cars) * .where(ilike(cars.name, '%Turbo%')) * ``` * * @see like for a case-sensitive version of this condition */ ilike?: string; /** * The inverse of ilike - this case-insensitively tests that a given column * does not match a pattern, which can include `%` and `_` * characters to match multiple variations. Including `%` * in the pattern matches zero or more characters, and including * `_` will match a single character. * * ## Examples * * ```ts * // Select all cars that don't have "Rover" in their name. * db.select().from(cars) * .where(notLike(cars.name, '%Rover%')) * ``` * * @see ilike for the inverse condition * @see notLike for a case-sensitive version of this condition */ notIlike?: string; /** * Syntactic sugar for case-insensitive substring matching. * Automatically wraps the value with `%` wildcards on both sides. * * Equivalent to: `ilike: '%value%'` * * ## Examples * * ```ts * // Select all cars with "Turbo" anywhere in their name. * db.select().from(cars) * .where({ name: { contains: 'Turbo' } }) * // Same as: .where(ilike(cars.name, '%Turbo%')) * ``` * * @see ilike for manual pattern matching * @see startsWith for prefix matching * @see endsWith for suffix matching */ contains?: string; /** * Syntactic sugar for case-insensitive prefix matching. * Automatically appends a `%` wildcard to the end of the value. * * Equivalent to: `ilike: 'value%'` * * ## Examples * * ```ts * // Select all cars whose names start with "Ford". * db.select().from(cars) * .where({ name: { startsWith: 'Ford' } }) * // Same as: .where(ilike(cars.name, 'Ford%')) * ``` * * @see ilike for manual pattern matching * @see contains for substring matching * @see endsWith for suffix matching */ startsWith?: string; /** * Syntactic sugar for case-insensitive suffix matching. * Automatically prepends a `%` wildcard to the beginning of the value. * * Equivalent to: `ilike: '%value'` * * ## Examples * * ```ts * // Select all cars whose names end with "Turbo". * db.select().from(cars) * .where({ name: { endsWith: 'Turbo' } }) * // Same as: .where(ilike(cars.name, '%Turbo')) * ``` * * @see ilike for manual pattern matching * @see contains for substring matching * @see startsWith for prefix matching */ endsWith?: string; /** * Test that a column or expression contains all elements of * the list passed as the second argument. * * ## Throws * * The argument passed in the second array can't be empty: * if an empty is provided, this method will throw. * * ## Examples * * ```ts * // Select posts where its tags contain "Typescript" and "ORM". * db.select().from(posts) * .where(arrayContains(posts.tags, ['Typescript', 'ORM'])) * ``` * * @see arrayContained to find if an array contains all elements of a column or expression * @see arrayOverlaps to find if a column or expression contains any elements of an array */ arrayContains?: TValue; /** * Test that the list passed as the second argument contains * all elements of a column or expression. * * ## Throws * * The argument passed in the second array can't be empty: * if an empty is provided, this method will throw. * * ## Examples * * ```ts * // Select posts where its tags contain "Typescript", "ORM" or both, * // but filtering posts that have additional tags. * db.select().from(posts) * .where(arrayContained(posts.tags, ['Typescript', 'ORM'])) * ``` * * @see arrayContains to find if a column or expression contains all elements of an array * @see arrayOverlaps to find if a column or expression contains any elements of an array */ arrayContained?: TValue; /** * Test that a column or expression contains any elements of * the list passed as the second argument. * * ## Throws * * The argument passed in the second array can't be empty: * if an empty is provided, this method will throw. * * ## Examples * * ```ts * // Select posts where its tags contain "Typescript", "ORM" or both. * db.select().from(posts) * .where(arrayOverlaps(posts.tags, ['Typescript', 'ORM'])) * ``` * * @see arrayContains to find if a column or expression contains all elements of an array * @see arrayContained to find if an array contains all elements of a column or expression */ arrayOverlaps?: TValue; } //#endregion //#region ../../src/orm/core/interfaces/PgQuery.d.ts /** * Order direction for sorting */ type OrderDirection = "asc" | "desc"; /** * Single order by clause with column and direction */ interface OrderByClause<T> { column: keyof T; direction?: OrderDirection; } /** * Order by parameter - supports 3 modes: * 1. String: orderBy: "name" (defaults to ASC) * 2. Single object: orderBy: { column: "name", direction: "desc" } * 3. Array: orderBy: [{ column: "name", direction: "asc" }, { column: "age", direction: "desc" }] */ type OrderBy<T> = keyof T | OrderByClause<T> | Array<OrderByClause<T>>; /** * Generic query interface for PostgreSQL entities */ interface PgQuery<T extends TObject = TObject> { distinct?: (keyof Static<T>)[]; columns?: (keyof Static<T>)[]; where?: PgQueryWhereOrSQL<T>; limit?: number; offset?: number; orderBy?: OrderBy<Static<T>>; groupBy?: (keyof Static<T>)[]; } type PgStatic<T extends TObject, Relations extends PgRelationMap<T>> = Static<T> & { [K in keyof Relations]: Static<Relations[K]["join"]["schema"]> & (Relations[K]["with"] extends PgRelationMap<TObject> ? PgStatic<Relations[K]["join"]["schema"], Relations[K]["with"]> : {}); }; interface PgQueryRelations<T extends TObject = TObject, Relations extends PgRelationMap<T> | undefined = undefined> extends PgQuery<T> { with?: Relations; where?: PgQueryWhereOrSQL<T, Relations>; } type PgRelationMap<Base extends TObject> = Record<string, PgRelation<Base>>; type PgRelation<Base extends TObject> = { type?: "left" | "inner" | "right"; join: { schema: TObject; name: string; }; on: SQLWrapper | readonly [keyof Static<Base>, { name: string; }]; with?: PgRelationMap<TObject>; }; //#endregion //#region ../../src/orm/core/interfaces/PgQueryWhere.d.ts type PgQueryWhere<T extends TObject, Relations extends PgRelationMap<TObject> | undefined = undefined> = (PgQueryWhereOperators<T> & PgQueryWhereConditions<T>) | (PgQueryWhereRelations<Relations> & PgQueryWhereOperators<T> & PgQueryWhereConditions<T, Relations>); type PgQueryWhereOrSQL<T extends TObject, Relations extends PgRelationMap<TObject> | undefined = undefined> = SQLWrapper | PgQueryWhere<T, Relations>; type PgQueryWhereOperators<T extends TObject> = { [Key in keyof Static<T>]?: FilterOperators<Static<T>[Key]> | Static<T>[Key]; }; type PgQueryWhereConditions<T extends TObject, Relations extends PgRelationMap<TObject> | undefined = undefined> = { /** * Combine a list of conditions with the `and` operator. Conditions * that are equal `undefined` are automatically ignored. * * ## Examples * * ```ts * db.select().from(cars) * .where( * and( * eq(cars.make, 'Volvo'), * eq(cars.year, 1950), * ) * ) * ``` */ and?: Array<PgQueryWhereOrSQL<T, Relations>>; /** * Combine a list of conditions with the `or` operator. Conditions * that are equal `undefined` are automatically ignored. * * ## Examples * * ```ts * db.select().from(cars) * .where( * or( * eq(cars.make, 'GM'), * eq(cars.make, 'Ford'), * ) * ) * ``` */ or?: Array<PgQueryWhereOrSQL<T, Relations>>; /** * Negate the meaning of an expression using the `not` keyword. * * ## Examples * * ```ts * // Select cars _not_ made by GM or Ford. * db.select().from(cars) * .where(not(inArray(cars.make, ['GM', 'Ford']))) * ``` */ not?: PgQueryWhereOrSQL<T, Relations>; /** * Test whether a subquery evaluates to have any rows. * * ## Examples * * ```ts * // Users whose `homeCity` column has a match in a cities * // table. * db * .select() * .from(users) * .where( * exists(db.select() * .from(cities) * .where(eq(users.homeCity, cities.id))), * ); * ``` * * @see notExists for the inverse of this test */ exists?: SQLWrapper; /** * Test whether a subquery evaluates to have no rows. * * @see exists for the inverse of this test */ notExists?: SQLWrapper; }; type PgQueryWhereRelations<Relations extends PgRelationMap<TObject> | undefined = undefined> = Relations extends PgRelationMap<TObject> ? { [K in keyof Relations]?: PgQueryWhere<Relations[K]["join"]["schema"], Relations[K]["with"]>; } : {}; //#endregion //#region ../../src/orm/core/interfaces/AggregateQuery.d.ts type AggregateOp = "count" | "sum" | "avg" | "min" | "max"; /** * Select definition for aggregate queries. * - `true` means select the column value as-is (used for groupBy columns). * - `{ sum: true, avg: true, ... }` means compute those aggregations. */ type AggregateColumnSelect = true | Partial<Record<AggregateOp, true>>; type AggregateSelect<T extends TObject> = { [K in keyof Static<T>]?: AggregateColumnSelect; }; /** * Maps a single column's select definition to its result type. * - `true` → original column type * - `{ sum: true, avg: true }` → `{ sum: number; avg: number }` */ type AggregateColumnResult<TValue, TSelect> = TSelect extends true ? TValue : { [Op in AggregateOp as TSelect extends Record<Op, true> ? Op : never]: number; }; /** * Result type for an aggregate query. */ type AggregateResult<T extends TObject, S extends AggregateSelect<T>> = { [K in keyof S & keyof Static<T>]: AggregateColumnResult<Static<T>[K], NonNullable<S[K]>>; }; /** * HAVING clause for aggregate queries. * Only applies to columns with aggregate operations (not `true`). */ type AggregateHaving<T extends TObject, S extends AggregateSelect<T>> = { [K in keyof S & keyof Static<T>]?: S[K] extends true ? never : { [Op in AggregateOp as S[K] extends Record<Op, true> ? Op : never]?: { gt?: number; gte?: number; lt?: number; lte?: number; eq?: number; ne?: number; }; }; }; /** * Full aggregate query definition. */ interface AggregateQuery<T extends TObject, S extends AggregateSelect<T>> { /** * Columns and aggregate operations to select. */ select: S; /** * WHERE clause to filter rows before aggregation. */ where?: PgQueryWhereOrSQL<T>; /** * Columns to group by. */ groupBy?: (keyof Static<T>)[]; /** * HAVING clause to filter groups after aggregation. */ having?: AggregateHaving<T, S>; /** * Order results. Supports dot notation for aggregate columns (e.g. "amount.sum"). */ orderBy?: string | { column: string; direction: "asc" | "desc"; } | Array<{ column: string; direction: "asc" | "desc"; }>; /** * Limit the number of results. */ limit?: number; /** * Offset for pagination. */ offset?: number; } //#endregion //#region ../../src/orm/core/providers/DbCacheProvider.d.ts /** * Database query cache using a simple in-memory Map. * * Uses `{tableName}:{cacheKey}` as the storage key. * Provides per-table invalidation for write-through cache busting. * * This is intentionally self-contained (no external cache dependencies) * so the ORM module does not force `AlephaCache` on all consumers. */ declare class DbCacheProvider { protected readonly dateTime: DateTimeProvider; protected readonly store: Map<string, { value: unknown; expiresAt?: number; }>; protected storeKey(tableName: string, cacheKey: string): string; /** * Get a cached query result. */ get<T>(tableName: string, cacheKey: string): Promise<T | undefined>; /** * Store a query result in the cache. */ set<T>(tableName: string, cacheKey: string, value: T, ttl?: number): Promise<void>; /** * Invalidate all cached queries for a table. */ invalidateTable(tableName: string): Promise<void>; } //#endregion //#region ../../src/orm/core/providers/SequenceProvider.d.ts /** * Portable, scoped numeric sequence provider — works identically on Postgres, * SQLite, and Cloudflare D1. * * Implementation: a single `alepha_sequences` table holds one row per * `(name, scope)` pair. Every call to {@link advance} runs an * `INSERT ... ON CONFLICT (name, scope) DO UPDATE SET value = value + step` * with `RETURNING value`, which is atomic on every supported driver: * * - **Postgres**: row-level lock on `ON CONFLICT DO UPDATE`. * - **SQLite / D1**: writes are serialized, atomic by construction. * * The repository pattern is the same one used by `DatabaseCacheProvider.incr()` * — see that file for the proof-of-design. * * Callers never instantiate this directly. They declare a {@link SequencePrimitive} * via `$sequence()` and call `.next(scope?)` on the primitive — this provider is * the engine behind that call. */ declare class SequenceProvider { protected readonly repository: Repository$1<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>; name: import("zod").ZodString; scope: import("zod").ZodString; value: import("zod").ZodInt; }, import("zod/v4/core").$strip>>; protected readonly crypto: CryptoProvider$1; /** * Atomically advance the counter for `(name, scope)` and return the new value. * * If the row doesn't exist yet, it's inserted with `value = start` and that * value is returned. Subsequent calls increment by `step`. * * Scope defaults to "default" — pass any string to namespace per * tenant / campaign / parent entity / etc. */ advance(name: string, scope?: string, opts?: { startWith?: number; incrementBy?: number; }): Promise<number>; /** * Read the current value without advancing it. Returns `null` if the * `(name, scope)` pair has never been advanced. */ peek(name: string, scope?: string): Promise<number | null>; /** * Reset the counter for `(name, scope)` to an explicit value. Creates the row * if it doesn't exist. * * Mostly useful in tests and ops scripts — production code should rarely call * this, since it can produce duplicate IDs if anything still holds a previously * advanced value. */ reset(name: string, scope: string | undefined, value: number): Promise<void>; } //#endregion //#region ../../src/orm/core/primitives/$sequence.d.ts interface SequencePrimitiveOptions { /** * Sequence name. Defaults to the primitive's property key. Two primitives * sharing the same name share the same counter table rows, which is rarely * what you want — prefer letting the property key drive the name. */ name?: string; /** * Starting value used on the very first `.next()` for a `(name, scope)` pair. * Defaults to `1`. */ startWith?: number; /** * Amount added on every call to `.next()`. Defaults to `1`. */ incrementBy?: number; } declare class SequencePrimitive extends Primitive<SequencePrimitiveOptions> { protected readonly provider: SequenceProvider; get name(): string; /** * Atomically advance the counter and return the new value. * * Scope defaults to "default". Pass any string to keep an independent counter * per tenant / campaign / parent entity / etc. * * **Transaction semantics:** when called inside a `$transactional` block, the * increment participates in that transaction — commit advances the counter, * rollback unwinds it. This is intentionally different from PG-native * `nextval()` (which leaves gaps on rollback). It means a failed insert that * consumed a `shortId` returns the value to the pool instead of burning it. */ next(scope?: string): Promise<number>; /** * Read the current value without advancing it. Returns `null` if the * `(name, scope)` pair has never been advanced. */ current(scope?: string): Promise<number | null>; /** * Reset the counter for the given scope to an explicit value. Creates the row * if it doesn't exist. Mostly useful in tests and ops scripts. */ reset(value: number, scope?: string): Promise<void>; } //#endregion //#region ../../src/orm/core/services/ModelBuilder.d.ts /** * Database-specific table configuration functions */ interface TableConfigBuilders<TConfig> { index: (name: string) => { on: (...columns: any[]) => TConfig; }; uniqueIndex: (name: string) => { on: (...columns: any[]) => TConfig; }; unique: (name: string) => { on: (...columns: any[]) => TConfig; }; check: (name: string, sql: SQL) => TConfig; foreignKey: (config: { name: string; columns: any[]; foreignColumns: any[]; }) => TConfig; } /** * Abstract base class for transforming Alepha Primitives (Entity, Sequence, etc...) * into drizzle models (tables, enums, sequences, etc...). */ declare abstract class ModelBuilder { /** * Build a table from an entity primitive. */ abstract buildTable(entity: EntityPrimitive, options: { tables: Map<string, unknown>; enums: Map<string, unknown>; schemas: Map<string, unknown>; schema: string; }): void; /** * Build a sequence from a sequence primitive. */ abstract buildSequence(sequence: SequencePrimitive, options: { sequences: Map<string, unknown>; schema: string; }): void; /** * Convert camelCase to snake_case for column names. */ protected toColumnName(str: string): string; /** * Build the table configuration function for any database. * This includes indexes, foreign keys, constraints, and custom config. * * @param entity - The entity primitive * @param builders - Database-specific builder functions * @param tableResolver - Function to resolve entity references to table columns * @param customConfigHandler - Optional handler for custom config */ protected buildTableConfig<TConfig, TSelf>(entity: EntityPrimitive, builders: TableConfigBuilders<TConfig>, tableResolver?: (entityName: string) => any, customConfigHandler?: (config: any, self: TSelf) => TConfig[]): ((self: TSelf) => TConfig[]) | undefined; } //#endregion //#region ../../src/orm/core/providers/DrizzleKitProvider.d.ts declare class DrizzleKitProvider { protected readonly log: import("alepha/logger").Logger; protected readonly dateTime: DateTimeProvider; protected readonly alepha: Alepha; /** * Push-based synchronization using Drizzle Kit's introspection API. * * Reads the actual database state, diffs against current entity definitions, * and applies changes. No stored snapshots — no drift, no corruption. * * - SQLite: uses `pushSQLiteSchema` (requires sync driver — node:sqlite shim or bun-sqlite) * - PostgreSQL: uses `pushSchema` with schema filters * * Does nothing in production mode — use file-based migrations instead. */ synchronize(provider: DatabaseProvider): Promise<void>; /** * Generate SQL migration statements by diffing two schema states. * * Used by tests (schema validation) and CLI (`alepha db migrations generate`). * Not part of the push sync flow. * * When `withoutSchema` is true, models are rebuilt without schema qualifiers * so the generated SQL is portable across different PostgreSQL schemas. */ generateMigration(provider: DatabaseProvider, prevSnapshot?: any, options?: { withoutSchema?: boolean; }): Promise<{ statements: string[]; models: Record<string, unknown>; snapshot?: any; }>; /** * Load all tables, enums, sequences, etc. from the provider's repositories. */ getModels(provider: DatabaseProvider): Record<string, unknown>; /** * Build schema-free models for migration generation. * * Rebuilds all entities with `schema = "public"` so Drizzle produces * SQL without schema qualifiers (e.g. `CREATE TABLE "users"` instead * of `CREATE TABLE "myschema"."users"`). * * The actual schema is applied at migration execution time via `search_path`. */ getModelsWithoutSchema(provider: DatabaseProvider): Record<string, unknown>; /** * Preview schema push without executing any statements. * * Returns the SQL statements that would be executed, warnings, and * whether data loss would occur. Does NOT execute any SQL. */ dryRunPush(provider: DatabaseProvider): Promise<{ statements: string[]; warnings: string[]; hasDataLoss: boolean; }>; protected pushSqlite(kit: typeof DrizzleKit, models: Record<string, unknown>, provider: DatabaseProvider): Promise<void>; /** * Push schema changes to PostgreSQL using Drizzle Kit's pushSchema with schema filters. */ protected pushPostgres(kit: typeof DrizzleKit, models: Record<string, unknown>, provider: DatabaseProvider): Promise<void>; /** * Execute a list of SQL statements against the provider. */ protected executeStatements(statements: string[], provider: DatabaseProvider): Promise<void>; /** * Execute SQL statements, ignoring "already exists" errors. * * Used by the fallback migration path where push may have partially * applied changes before erroring, leaving some objects already created. */ protected executeStatementsLenient(statements: string[], provider: DatabaseProvider): Promise<void>; protected createSchemaIfNotExists(provider: DatabaseProvider, schemaName: string): Promise<void>; /** * Wrap a Drizzle PgDatabase instance for compatibility with Drizzle Kit. * * Drizzle Kit's pushSchema expects execute() to return { rows: T[] } * (node-postgres/pg format), but postgres.js returns a Result that * extends Array directly — no .rows property. */ protected wrapDbForDrizzleKit(db: any): any; /** * Try to load the official Drizzle Kit API. */ importDrizzleKit(): typeof DrizzleKit; } //#endregion //#region ../../src/orm/core/providers/drivers/DatabaseProvider.d.ts type SQLLike = SQLWrapper | string; declare abstract class DatabaseProvider { protected readonly alepha: Alepha; protected readonly dateTime: DateTimeProvider; protected readonly log: import("alepha/logger").Logger; protected abstract readonly builder: ModelBuilder; protected readonly kit: DrizzleKitProvider; abstract readonly db: PgDatabase<any>; abstract readonly dialect: "postgresql" | "sqlite"; abstract readonly url: string; readonly enums: Map<string, unknown>; readonly tables: Map<string, unknown>; readonly sequences: Map<string, unknown>; readonly schemas: Map<string, unknown>; protected readonly entityPrimitives: EntityPrimitive[]; protected readonly sequencePrimitives: SequencePrimitive[]; get name(): string; get driver(): string; /** * Whether this driver supports SQL-level transactions (BEGIN/COMMIT/ROLLBACK). * * Drivers that do not (e.g. PGlite, Cloudflare D1) should override to `false`. */ get supportsTransactions(): boolean; /** * Raw database connection handle (e.g. DatabaseSync, bun:sqlite Database). * Override in providers that expose native connections for introspection. */ get nativeConnection(): unknown; get schema(): string; /** * Migration tracking table name, scoped by schema. * * Returns `migrations_{schema}` so that multiple schemas sharing the same * database each get their own migration history (e.g. `migrations_lore`, * `migrations_public`). */ get migrationsTable(): string; /** * Log a database query with structured metadata for devtools inspection. */ protected logQuery(sql: string, params: unknown[], duration: number, rowCount: number, error?: string): void; protected parseOperation(sql: string): string; table<T extends TObject>(entity: EntityPrimitive<T>): PgTableWithColumns<SchemaToTableConfig<T>>; registerEntity(entity: EntityPrimitive): void; registerSequence(sequence: SequencePrimitive): void; /** * Rebuild all models into fresh maps using a different schema. * * When called with `"public"`, produces schema-free models suitable for * migration generation (no schema qualifiers in the SQL output). */ rebuildModels(targetSchema: string): { tables: Map<string, unknown>; enums: Map<string, unknown>; sequences: Map<string, unknown>; schemas: Map<string, unknown>; }; /** * Run a function inside a database transaction with implicit tx propagation. * * The transaction object is stored in `alepha.store` so that all Repository * operations within `fn` automatically participate in the transaction without * explicit `{ tx }` drilling. * * Nesting is safe — if already inside a `transactional()` block, the inner * call reuses the outer transaction (no nested PG transactions / savepoints). */ transactional<R>(fn: () => Promise<R>, config?: PgTransactionConfig): Promise<R>; abstract execute(statement: SQLLike): Promise<Record<string, unknown>[]>; run<T extends TObject>(statement: SQLLike, schema: T): Promise<Array<Static<T>>>; /** * Get migrations folder path - can be overridden */ protected getMigrationsFolder(): string; /** * Migration orchestration. * * - Production: file-based migrations from the migrations folder * - Dev / Test: push-based sync (introspects actual DB, no snapshots) * - Serverless: skipped (migrations should be applied during deployment) */ migrate(): Promise<void>; /** * Provider-specific migration execution * MUST be implemented by each provider */ protected abstract executeMigrations(migrationsFolder: string): Promise<void>; /** * For testing purposes, generate a unique schema name. * * Format: `test_alepha_{epoch_seconds}_{random8}` * Example: `test_alepha_1739871618_k3m9x2p1` */ protected generateTestSchemaName(): string; } //#endregion //#region ../../src/orm/core/services/QueryManager.d.ts declare class QueryManager { protected readonly alepha: Alepha; /** * Convert a query object to a SQL query. */ toSQL(query: PgQueryWhereOrSQL<TObject>, options: { schema: TObject; col: (key: string) => PgColumn; joins?: PgJoin[]; dialect: "postgresql" | "sqlite"; }): SQL | undefined; /** * Check if an object has any filter operator properties. */ protected hasFilterOperatorProperties(obj: any): boolean; /** * Map a filter operator to a SQL query. */ mapOperatorToSql(operator: FilterOperators<any> | any, column: PgColumn, columnSchema?: TObject, columnName?: string, dialect?: "postgresql" | "sqlite"): SQL | undefined; /** * Parse pagination sort string to orderBy format. * Format: "firstName,-lastName" -> [{ column: "firstName", direction: "asc" }, { column: "lastName", direction: "desc" }] * - Columns separated by comma * - Prefix with '-' for DESC direction * * @param sort Pagination sort string * @returns