UNPKG

alepha

Version:

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

457 lines 17.7 kB
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