alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
457 lines • 17.7 kB
TypeScript
import { Alepha, Async, KIND, Primitive, Static } from "alepha";
import { ActionPrimitive, ClientRequestEntry, ClientRequestOptions, ClientRequestResponse, FetchResponse, HttpClient, RequestConfigSchema, ServerRequest, ServerRequestConfigEntry, ServerResponseBody, ServerTimingProvider, SseConfigSchema, SseEventData, SsePrimitive, SseRequestEntry, SseStream } from "alepha/server";
import { ProxyPrimitiveOptions, ServerProxyProvider } from "alepha/server/proxy";
import { SecureOptions, SecurityProvider, ServiceAccountPrimitive, UserAccountToken } from "alepha/security";
//#region ../../src/server/links/schemas/apiLinksResponseSchema.d.ts
declare const apiActionSchema: import("zod").ZodObject<{
path: import("zod").ZodString;
method: import("zod").ZodOptional<import("zod").ZodString>;
contentType: import("zod").ZodOptional<import("zod").ZodString>;
kind: import("zod").ZodOptional<import("zod").ZodString>;
service: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>;
declare const apiRegistryResponseSchema: import("zod").ZodObject<{
prefix: import("zod").ZodOptional<import("zod").ZodString>;
actions: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodObject<{
path: import("zod").ZodString;
method: import("zod").ZodOptional<import("zod").ZodString>;
contentType: import("zod").ZodOptional<import("zod").ZodString>;
kind: import("zod").ZodOptional<import("zod").ZodString>;
service: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
permissions: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
}, import("zod/v4/core").$strip>;
type ApiRegistryResponse = Static<typeof apiRegistryResponseSchema>;
type ApiAction = Static<typeof apiActionSchema>;
/**
* @deprecated Use `apiRegistryResponseSchema` and `ApiRegistryResponse` instead.
*/
declare const apiLinksResponseSchema: import("zod").ZodObject<{
prefix: import("zod").ZodOptional<import("zod").ZodString>;
actions: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodObject<{
path: import("zod").ZodString;
method: import("zod").ZodOptional<import("zod").ZodString>;
contentType: import("zod").ZodOptional<import("zod").ZodString>;
kind: import("zod").ZodOptional<import("zod").ZodString>;
service: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
permissions: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
}, import("zod/v4/core").$strip>;
/**
* @deprecated Use `ApiRegistryResponse` instead.
*/
type ApiLinksResponse = ApiRegistryResponse;
/**
* @deprecated Use `ApiAction` instead.
*/
type ApiLink = ApiAction;
//#endregion
//#region ../../src/server/links/atoms/apiLinksAtom.d.ts
declare const apiLinksAtom: import("alepha").Atom<import("zod").ZodOptional<import("zod").ZodObject<{
prefix: import("zod").ZodOptional<import("zod").ZodString>;
actions: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodObject<{
path: import("zod").ZodString;
method: import("zod").ZodOptional<import("zod").ZodString>;
contentType: import("zod").ZodOptional<import("zod").ZodString>;
kind: import("zod").ZodOptional<import("zod").ZodString>;
service: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
permissions: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
}, import("zod/v4/core").$strip>>, "alepha.server.request.apiLinks">;
//#endregion
//#region ../../src/server/links/atoms/linkOptionsAtom.d.ts
declare const linkOptionsAtom: import("alepha").Atom<import("zod").ZodObject<{
batch: import("zod").ZodDefault<import("zod").ZodBoolean>;
}, import("zod/v4/core").$strip>, "alepha.server.links.options">;
//#endregion
//#region ../../src/server/links/services/BatchCollector.d.ts
/**
* Collects browser-side action calls within a microtask and
* sends them as a single `POST /api/_batch` request.
*
* Key behaviors:
* - Single call in the window → direct HTTP call (no batch overhead)
* - Multiple calls → coalesced into one batch request
* - Same action + same params/query/body → deduplicated, result shared
* - Exceeding MAX_BATCH_SIZE → split into multiple batch calls
* - Transport failure → all pending promises reject
*/
declare class BatchCollector {
protected static readonly MAX_BATCH_SIZE = 20;
protected readonly log: import("alepha/logger").Logger;
protected readonly httpClient: HttpClient;
protected pending: PendingBatchEntry[];
protected scheduled: boolean;
/**
* Add an action call to the batch. Returns the result when the batch resolves.
*/
add(entry: BatchEntry): Promise<any>;
protected flush(): Promise<void>;
protected dedupe(batch: PendingBatchEntry[]): {
unique: PendingBatchEntry[];
indexMap: number[];
};
protected hasBinaryBody(body: unknown): boolean;
protected chunk<T>(arr: T[], size: number): T[][];
}
interface BatchEntry {
action: string;
params?: Record<string, any>;
query?: Record<string, any>;
body?: Record<string, any>;
directCall: () => Promise<any>;
}
interface PendingBatchEntry {
entry: BatchEntry;
resolve: (value: any) => void;
reject: (reason: any) => void;
}
//#endregion
//#region ../../src/server/links/providers/LinkProvider.d.ts
/**
* Browser, SSR friendly, service to handle links.
*/
declare class LinkProvider {
static path: {
apiLinks: string;
};
protected readonly log: import("alepha/logger").Logger;
protected readonly alepha: Alepha;
protected readonly httpClient: HttpClient;
protected serverLinkMap: Map<string, HttpClientLink>;
protected actionMap: Map<string, HttpClientLink>;
protected permissions: Set<string>;
protected lastLoadedRegistry: ApiRegistryResponse | null;
protected batchCollector?: BatchCollector;
protected readonly options: Readonly<{
batch: boolean;
}>;
/**
* Get applicative links registered on the server.
* This does not include lazy-loaded remote links.
*/
getServerLinks(): HttpClientLink[];
/**
* Register a new link for the application.
*/
registerLink(link: HttpClientLink): void;
/**
* Load the registry response into internal stores (actionMap, permissions, definitions).
* Called when storing from atom/fetch/SSR.
*/
protected loadRegistry(registry: ApiRegistryResponse): void;
get links(): HttpClientLink[];
/**
* Force browser to refresh links from the server.
*/
fetchLinks(): Promise<HttpClientLink[]>;
/**
* Create a virtual client that can be used to call actions.
*
* Use js Proxy under the hood.
*/
client<T extends object>(scope?: ClientScope): HttpVirtualClient<T>;
/**
* Check if a link with the given name exists or a permission matches.
*
* Action names never contain colons. Permission names always do.
* - `can("getUsers")` → O(1) map lookup
* - `can("admin:*")` → wildcard match against permissions set
* - `can("admin:user:read")` → O(1) set lookup
*/
can(name: string): boolean;
/**
* Resolve a link by its name and call it.
* - If link is local, it will call the local handler.
* - If link is remote, it will make a fetch request to the remote server.
*/
follow(name: string, config?: Partial<ServerRequestConfigEntry>, options?: ClientRequestOptions & ClientScope): Promise<any>;
protected createVirtualAction<T extends RequestConfigSchema>(name: string, scope?: ClientScope): VirtualAction<T>;
protected followRemote(link: HttpClientLink, config?: Partial<ServerRequestConfigEntry>, options?: ClientRequestOptions): Promise<FetchResponse>;
protected getLinkByName(name: string, options?: ClientScope): Promise<HttpClientLink>;
}
interface HttpClientLink {
name: string;
path: string;
method?: string;
kind?: string;
contentType?: string;
service?: string;
secured?: boolean | SecureOptions;
prefix?: string;
group?: string;
host?: string;
schema?: RequestConfigSchema;
handler?: (request: ServerRequest, options: ClientRequestOptions) => Async<ServerResponseBody>;
}
interface ClientScope {
service?: string;
hostname?: string;
}
type HttpVirtualClient<T> = { [K in keyof T as T[K] extends ActionPrimitive<RequestConfigSchema> ? K : never]: T[K] extends ActionPrimitive<infer Schema> ? VirtualAction<Schema> : never; } & { [K in keyof T as T[K] extends SsePrimitive<SseConfigSchema> ? K : never]: T[K] extends SsePrimitive<infer Schema> ? VirtualSse<Schema> : never; };
interface VirtualAction<T extends RequestConfigSchema> extends Pick<ActionPrimitive<T>, "name" | "run" | "fetch"> {
(config?: ClientRequestEntry<T>, opts?: ClientRequestOptions): Promise<ClientRequestResponse<T>>;
can: () => boolean;
}
interface VirtualSse<T extends SseConfigSchema> {
(config?: SseRequestEntry<T>): Promise<SseStream<SseEventData<T>>>;
name: string;
can: () => boolean;
}
//#endregion
//#region ../../src/server/links/primitives/$client.d.ts
/**
* Create a new client.
*/
declare const $client: {
<T extends object>(scope?: ClientScope): HttpVirtualClient<T>;
[KIND]: string;
};
//#endregion
//#region ../../src/server/links/primitives/$remote.d.ts
/**
* $remote is a primitive that allows you to define remote service access.
*
* Use it only when you have 2 or more services that need to communicate with each other.
*
* All remote services can be exposed as actions, ... or not.
*
* You can add a service account if you want to use a security layer.
*/
declare const $remote: {
(options: RemotePrimitiveOptions): RemotePrimitive;
[KIND]: typeof RemotePrimitive;
};
interface RemotePrimitiveOptions {
/**
* The URL of the remote service.
* You can use a function to generate the URL dynamically.
* You probably should use $env(env) to get the URL from the environment.
*
* @example
* ```ts
* import { $remote } from "alepha/server";
* import { $inject, z } from "alepha";
*
* class App {
* env = $env(z.object({
* REMOTE_URL: z.text({default: "http://localhost:3000"}),
* }));
* remote = $remote({
* url: this.env.REMOTE_URL,
* });
* }
* ```
*/
url: string | (() => string);
/**
* The name of the remote service.
*
* @default Member of the class containing the remote service.
*/
name?: string;
/**
* If true, all methods of the remote service will be exposed as actions in this context.
* > Note: Proxy will never use the service account, it just... proxies the request.
*/
proxy?: boolean | Partial<ProxyPrimitiveOptions & {
/**
* If true, the remote service won't be available internally, only through the proxy.
*/
noInternal: boolean;
}>;
/**
* For communication between the server and the remote service with a security layer.
* This will be used for internal communication and will not be exposed to the client.
*/
serviceAccount?: ServiceAccountPrimitive;
}
declare class RemotePrimitive extends Primitive<RemotePrimitiveOptions> {
get name(): string;
}
//#endregion
//#region ../../src/server/links/providers/RemotePrimitiveProvider.d.ts
declare class RemotePrimitiveProvider {
protected readonly serverApi: Readonly<{
prefix: string;
}>;
protected readonly alepha: Alepha;
protected readonly proxyProvider: ServerProxyProvider;
protected readonly linkProvider: LinkProvider;
protected readonly remotes: Array<ServerRemote>;
protected readonly log: import("alepha/logger").Logger;
getRemotes(): ServerRemote[];
readonly configure: import("alepha").HookPrimitive<"configure">;
readonly start: import("alepha").HookPrimitive<"start">;
registerRemote(value: RemotePrimitive): Promise<void>;
protected readonly fetchLinks: import("alepha").PipelinePrimitiveFn<(opts: FetchLinksOptions) => Promise<ApiRegistryResponse>>;
}
interface FetchLinksOptions {
/**
* Name of the remote service.
*/
service: string;
/**
* URL to fetch links from.
*/
url: string;
/**
* Authorization header containing access token.
*/
authorization?: string;
}
interface ServerRemote {
/**
* URL of the remote service.
*/
url: string;
/**
* Name of the remote service.
*/
name: string;
/**
* Expose links as endpoint. It's not only internal.
*/
proxy: boolean;
/**
* It's only used inside the application.
*/
internal: boolean;
/**
* Links fetcher.
*/
links: (args: {
authorization?: string;
}) => Promise<ApiRegistryResponse>;
/**
* Fetches schema for the remote service.
*/
schema: (args: {
name: string;
authorization?: string;
}) => Promise<any>;
/**
* Force a default access token provider when not provided.
*/
serviceAccount?: ServiceAccountPrimitive;
/**
* Prefix for the remote service links.
*/
prefix: string;
}
//#endregion
//#region ../../src/server/links/providers/ServerLinksProvider.d.ts
declare class ServerLinksProvider {
protected readonly serverApi: Readonly<{
prefix: string;
}>;
protected readonly alepha: Alepha;
protected readonly linkProvider: LinkProvider;
protected readonly remoteProvider: RemotePrimitiveProvider;
protected readonly serverTimingProvider: ServerTimingProvider;
protected readonly log: import("alepha/logger").Logger;
/**
* Resolved once on start. Undefined when alepha/security is not loaded.
*/
protected securityProvider: SecurityProvider | undefined;
/**
* Cache of serialized JSON by role key.
* Key = sorted roles joined by comma (empty string for unauthenticated).
*/
protected registryCache: Map<string, RegistryCacheEntry>;
get prefix(): string;
readonly onRoute: import("alepha").HookPrimitive<"configure">;
protected readonly onStart: import("alepha").HookPrimitive<"start">;
/**
* API registry endpoint — returns all available actions for the user.
*
* Response is filtered by the user's permissions.
* Cached by role set with ETag support.
*/
readonly links: import("alepha/server").RoutePrimitive<RequestConfigSchema>;
/**
* On-demand schemas endpoint — returns JSON Schemas for requested actions.
*
* Schemas are filtered by the user's permissions (same logic as the registry).
*/
readonly schemas: import("alepha/server").RoutePrimitive<{
body: import("zod").ZodObject<{
actions: import("zod").ZodArray<import("zod").ZodString>;
}, import("zod/v4/core").$strip>;
response: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodObject<{
body: import("zod").ZodOptional<import("zod").ZodString>;
response: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
}>;
/**
* Batch endpoint — execute multiple actions in a single HTTP request.
* Each sub-request is independent: errors in one don't affect others.
*/
readonly batch: import("alepha/server").RoutePrimitive<{
body: import("zod").ZodArray<import("zod").ZodObject<{
action: import("zod").ZodString;
params: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
query: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
body: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
}, import("zod/v4/core").$strip>>;
response: import("zod").ZodArray<import("zod").ZodObject<{
action: import("zod").ZodString;
status: import("zod").ZodInt;
data: import("zod").ZodOptional<import("zod").ZodAny>;
error: import("zod").ZodOptional<import("zod").ZodString>;
}, import("zod/v4/core").$strip>>;
}>;
protected static readonly MAX_BATCH_SIZE = 20;
/**
* Retrieves API registry for the user based on their permissions.
* Will check on local links and remote links.
*/
getUserApiLinks(options: GetApiLinksOptions): Promise<ApiRegistryResponse>;
/**
* Check if a link is accessible by the given user based on security rules.
*/
protected isLinkAccessible(link: HttpClientLink, user?: UserAccountToken): boolean;
}
interface GetApiLinksOptions {
user?: UserAccountToken;
authorization?: string;
}
interface RegistryCacheEntry {
json: string;
etag: string;
}
//#endregion
//#region ../../src/server/links/index.d.ts
declare module "alepha" {
interface State {
/**
* API registry attached to the server request state.
*
* @see {@link ApiRegistryResponse}
* @internal
*/
"alepha.server.request.apiLinks"?: ApiRegistryResponse;
/**
* Configuration options for the links module.
*/
"alepha.server.links.options": {
batch: boolean;
};
}
}
/**
* Type-safe API client with request deduplication.
*
* **Features:**
* - Virtual HTTP client for type-safe API calls
* - Remote action definitions
* - Type inference from action schemas
* - Request deduplication
* - Automatic error handling
*
* @module alepha.server.links
*/
declare const AlephaServerLinks: import("alepha").Service<import("alepha").Module>;
//#endregion
export { $client, $remote, AlephaServerLinks, ApiAction, ApiLink, ApiLinksResponse, ApiRegistryResponse, BatchCollector, BatchEntry, ClientScope, FetchLinksOptions, GetApiLinksOptions, HttpClientLink, HttpVirtualClient, LinkProvider, RemotePrimitive, RemotePrimitiveOptions, RemotePrimitiveProvider, ServerLinksProvider, ServerRemote, VirtualAction, VirtualSse, apiActionSchema, apiLinksAtom, apiLinksResponseSchema, apiRegistryResponseSchema, linkOptionsAtom };
//# sourceMappingURL=index.d.ts.map