/** Authoring helpers for one schema-bound remote method. */ import type { AuthPolicy, RouteBinding } from '@astrale-os/kernel-api/routed' import type { MethodClassKeys, ClassMethodConfig, NonSealedMethodKeys, OwnMethodKeys, } from '@astrale-os/kernel-core/domain' import type { Schema } from '@astrale-os/kernel-dsl' import type { TypedSelf } from '../dispatch/self.js' import type { DomainKernel, Kernel, KernelForAuth, RemoteAuthorizeContext, RemoteContext, } from './context.js' /** * Remote method handler. `execute` is optional only for binding stubs; any * handler that the dispatcher actually reaches must provide it. */ export type RemoteHandler< TParams, TResult, TSelf, TDeps, TAuth extends AuthPolicy = 'required', TKernel extends Kernel = Kernel, > = { /** The handler body. May be async or an async generator (for `output: 'stream'`). */ execute?: ( ctx: RemoteContext, TAuth>, ) => TResult | Promise | AsyncGenerator /** * Optional REST binding — attaches a native HTTP route to this method. * Uses the kernel's canonical `RouteBinding` type directly. */ route?: RouteBinding /** * Optional anchor URL — marks this method as anchored to a specific host. * May contain `{name}` placeholders matching fields in the input schema. * If absent, the method is ambient (mounts on whatever server loads the domain). */ remoteUrl?: string /** Optional human-readable description. Appears in generated docs. */ description?: string /** * Authentication policy. Defaults to `'required'` when absent. Captured as a * literal type so it drives `ctx.auth` and {@link KernelForAuth} on the * `execute`/`authorize` context: omit it (or set `'required'`) and both * `ctx.auth` and `ctx.kernel` are non-null; `'optional'` widens them to * `... | null`; `'public'` makes both `null`. */ auth?: TAuth /** * Optional business authorization after auth/_self resolution. Kernel * has_perm checks remain authoritative; this is additive gating. */ authorize?: ( ctx: RemoteAuthorizeContext, TAuth>, ) => void | Promise } // Post-binding indexes erase the concrete auth policy; authoring remains checked // at `remoteMethod`, while dispatcher/build-spec only need a policy-neutral bag. // oxlint-disable no-explicit-any export type AnyRemoteHandler = Omit< RemoteHandler, 'authorize' | 'execute' > & { execute?: (ctx: any) => any authorize?: (ctx: any) => void | Promise } // oxlint-enable no-explicit-any // Compile-time guard: every auth policy must fit the erased handler bag. type _AssertTrueH = T type _HandlerFitsBag = D extends AnyRemoteHandler ? true : false export type _MethodPolicyBagGuards = [ _AssertTrueH<_HandlerFitsBag>>, _AssertTrueH<_HandlerFitsBag>>, _AssertTrueH<_HandlerFitsBag>>, ] /** * Fully typed method implementation — resolves params/result/self from the * schema, wraps in `RemoteHandler` with deps. */ export type MethodImpl< S extends Schema, K extends MethodClassKeys & string, M extends string, TDeps = unknown, TAuth extends AuthPolicy = 'required', > = ClassMethodConfig extends { params: infer P result: infer R self: unknown isStatic: infer St } ? RemoteHandler< P, R, St extends true ? undefined : TypedSelf, TDeps, TAuth, DomainKernel > : never /** * `MethodParams` / `MethodResult` — a handler types its `params` as * `MethodParams` and its return as * `MethodResult<…>`, so it never re-declares (and can't drift from) the schema. * Resolved from whichever def — CLASS or INTERFACE — declares the method. */ export type { MethodParams, MethodResult } from '@astrale-os/kernel-core/domain' type ImplementableMethodName & string> = ( | OwnMethodKeys | NonSealedMethodKeys ) & string /** * Identity helper for authoring one remote method with full schema-driven typing. * * Two-form calling convention: * - `remoteMethod()` — curried form; captures deps type, returns a * per-schema helper. Use when you want deps typed. * - `remoteMethod(schema, className, methodName, impl)` — direct form with * `unknown` deps. */ export function remoteMethod(): < S extends Schema, K extends MethodClassKeys & string, M extends ImplementableMethodName, TAuth extends AuthPolicy = 'required', >( schema: S, className: K, methodName: M, impl: MethodImpl, ) => MethodImpl export function remoteMethod< S extends Schema, K extends MethodClassKeys & string, M extends ImplementableMethodName, TAuth extends AuthPolicy = 'required', >( schema: S, className: K, methodName: M, impl: MethodImpl, ): MethodImpl export function remoteMethod(...args: unknown[]) { if (args.length === 0) { return (...innerArgs: unknown[]) => innerArgs[3] } return args[3] }