/** * Define a remote domain. Views/functions are first-class domain members; their * serving URL is stamped later by spec producers and is also the worker issuer. */ import type { FunctionBinding } from '@astrale-os/kernel-api/routed' import type { BoundMethod, CompiledDomain, FunctionSchema, ViewSchema, } from '@astrale-os/kernel-core/domain' import type { Core, Schema } from '@astrale-os/kernel-dsl' import { assertKernel, bindMethods, compileDomain } from '@astrale-os/kernel-core/domain' import type { DomainManifest } from '../config/define-domain.js' import type { AnyRemoteFunctionDef, ViewDef } from '../define/index.js' import type { SchemaMethodsImpl } from '../method/class.js' import type { AnyRemoteHandler } from '../method/single.js' import { assertFunctionsMatchSchema, buildFunctionSchemas, DEFAULT_FUNCTIONS_FOLDER, } from './extend-functions.js' import { buildViewDeclarations, buildViewSchemas, DEFAULT_VIEWS_FOLDER } from './extend-views.js' export type RemoteDomainConfig = { schema: S methods: SchemaMethodsImpl core?: Core views?: Record> viewsFolder?: string remoteFunctions?: Record functionsFolder?: string /** Presentation metadata for `/meta` (logo / entry view). Carried verbatim. */ manifest?: DomainManifest /** Physical tree path for the Domain node (absolute, e.g. `/domains/acme`); default `/domains/`. */ path?: string } /** Effective bindings + folder layout + member schemas for auxiliary routes, resolved against `url`. */ export type AuxiliaryMetadata = { url: string viewsFolder: string functionsFolder: string viewBindings: Record remoteFunctionBindings: Record /** Function members' impl/binding half (`function.`) for `serialize`. */ functionSchemas: FunctionSchema[] /** View members' impl/binding half (`view.`) for `serialize`. */ viewSchemas: ViewSchema[] } export type RemoteDomain = { /** Define-time compile has structure only; bindings are stamped at materialization. */ compiled: CompiledDomain methods: BoundMethod[] // oxlint-disable-next-line no-explicit-any views?: Record> remoteFunctions?: Record /** Presentation metadata for `/meta` (logo / entry view); `createRemoteServer` stamps it. */ manifest?: DomainManifest /** The original config, so `materializeRemoteDomain` re-materializes from source. */ config: MaterializeInputs /** * Physical tree path for the Domain node (absolute string, e.g. `/domains/acme`). * Read by `buildInstallGraph` and passed to `serialize` as `treeMount`; unset => * `/domains/` (applied in build-spec). Addressing is unaffected. */ path?: string } /** The defineRemoteDomain inputs `materializeRemoteDomain` needs to re-materialize. */ type MaterializeInputs = { userCore?: Core viewsFolder: string functionsFolder: string } export function defineRemoteDomain() { return function (config: RemoteDomainConfig): RemoteDomain { const viewsFolder = config.viewsFolder ?? DEFAULT_VIEWS_FOLDER const functionsFolder = config.functionsFolder ?? DEFAULT_FUNCTIONS_FOLDER // Standalone function handlers must match schema-declared function members. assertFunctionsMatchSchema(config.schema, config.remoteFunctions) // Standalone function handlers must match schema-declared function members. assertFunctionsMatchSchema(config.schema, config.remoteFunctions) const compiled = compileDomain( config.schema, config.core, undefined, config.views ? buildViewDeclarations(config.views) : undefined, ) assertKernel(compiled) const methods = bindMethods( compiled.$.schema, compiled.$.methods, // oxlint-disable-next-line no-explicit-any config.methods as any, ) return Object.freeze({ compiled, methods, ...(config.views ? { views: config.views } : {}), ...(config.remoteFunctions ? { remoteFunctions: config.remoteFunctions } : {}), ...(config.manifest ? { manifest: config.manifest } : {}), ...(config.path ? { path: config.path } : {}), config: { userCore: config.core, viewsFolder, functionsFolder, }, }) } } /** Materialize aux member bindings against the real serving URL. Pure and repeatable. */ export function materializeRemoteDomain( domain: RemoteDomain, url: string, ): { compiled: CompiledDomain; auxiliary: AuxiliaryMetadata | undefined } { const hasAux = Boolean(domain.views || domain.remoteFunctions) if (!hasAux) return { compiled: domain.compiled, auxiliary: undefined } const { config } = domain const schema = domain.compiled.$.schema as S const compiled = compileDomain( schema, config.userCore as Core | undefined, undefined, domain.views ? buildViewDeclarations(domain.views) : undefined, ) const fns = domain.remoteFunctions ? buildFunctionSchemas(domain.remoteFunctions, url, config.functionsFolder) : { schemas: [], bindings: {} } const vws = domain.views ? buildViewSchemas(domain.views, url, config.viewsFolder, schema) : { schemas: [], bindings: {} } return { compiled, auxiliary: { url, viewsFolder: config.viewsFolder, functionsFolder: config.functionsFolder, viewBindings: vws.bindings, remoteFunctionBindings: fns.bindings, functionSchemas: fns.schemas, viewSchemas: vws.schemas, }, } }