import { Server } from 'node:http'; import * as hono from 'hono'; import { Context, MiddlewareHandler } from 'hono'; import { OpenAPIHono } from '@hono/zod-openapi'; import { MongoAbility, AbilityBuilder, RawRuleOf } from '@casl/ability'; import * as _gzl10_nexus_sdk from '@gzl10/nexus-sdk'; import { PluginManifest, ModuleManifest, LocaleConfig, SeedContext, SSESender, SSEOptions, EntityQuery, PaginatedResult, EntityDefinition, ModuleContext, CreateEntityServiceOptions, BaseEntityService as BaseEntityService$1, CollectionEntityDefinition, EntityServiceHooks, SingleEntityDefinition, ViewEntityDefinition, ComputedEntityDefinition, ExternalEntityDefinition, ServerEntityQuery, TreeEntityDefinition, TreeNode, DagEntityDefinition, EntityChangePayload, BridgeRule } from '@gzl10/nexus-sdk'; export { AuthRequest, AuthorizationParams, Category, ContextHelpers, CookieOptions, DomainFilterOptions, DomainFilterResult, EntityDefinition, EntityQuery, IdTokenClaims, ModuleAbilities, ModuleContext, ModuleManifest, ModuleMiddlewares, ModuleRequirements, NextFunction, OidcClient, OidcDiscoveryDocument, OidcState, OidcTokens, OidcUserInfo, PaginatedResult, PaginationParams, PluginManifest, Request, RequestHandler, Response, Router, SessionResult, StateManager, TokenExchangeParams, TokenValidationConfig, ValidateSchemas, assertAllowedDomain, checkAllowedDomain, createOidcClient, createStateManager, entityRoom, getOidcClient } from '@gzl10/nexus-sdk'; import { Knex } from 'knex'; import { Server as Server$1 } from 'socket.io'; import { Server as Server$2 } from 'http'; export { A as AppError, a as AppErrorParams, C as ConflictError, b as ErrorCode, E as ErrorCodes, F as ForbiddenError, N as NotFoundError, U as UnauthorizedError, c as ValidationDetail, V as ValidationError } from './app-error-DMO71vXw.js'; import * as hono_utils_types from 'hono/utils/types'; import * as zod_v4 from 'zod/v4'; import * as zod_v4_core from 'zod/v4/core'; import * as zod from 'zod'; import { ZodSchema } from 'zod'; import pino from 'pino'; import pkg from 'eventemitter2'; /** Minimal User shape for CASL subject matching */ interface CaslUser { id: string; type?: string; email?: string | null; name?: string; } /** Minimal Role shape for CASL subject matching */ interface CaslRole { id: string; name?: string; } /** * CASL subject registry. * Core modules define their subjects here. * Plugins can extend via declaration merging: * * @example * ```typescript * // my-plugin/src/types/abilities.d.ts * declare module '@gzl10/nexus-backend' { * interface SubjectRegistry { * MyCustomEntity: true * } * } * ``` */ interface SubjectRegistry { User: true; Role: true; all: true; } /** Subjects as strings (derived from the registry, extensible) */ type SubjectStrings = keyof SubjectRegistry; /** * CASL actions. Includes standard CRUD + execute for entity actions. * The `| (string & {})` allows custom action strings (e.g., 'change-password') * while preserving autocomplete for the known actions. */ type Actions$1 = 'manage' | 'create' | 'read' | 'update' | 'delete' | 'execute' | (string & {}); /** Valid subjects: registry strings + object instances */ type Subjects = SubjectStrings | CaslUser | CaslRole | (string & {}); type AppAbility = MongoAbility<[Actions$1, Subjects]>; /** * Local type for user data - auth does not import from users module. * Defines the fields auth needs for authentication. * Note: roles are loaded separately via users service (multi-role support). */ interface AuthUserRecord { id: string; type: 'human' | 'bot' | 'service'; email: string | null; password: string | null; name: string; created_at: Date; updated_at: Date; created_by: string | null; updated_by: string | null; [key: string]: unknown; } /** * Refresh token for JWT rotation */ interface RefreshToken { id: string; token: string; user_id: string; expires_at: Date; device_id?: string | null; device_name?: string | null; created_at?: Date; } /** * Hono HTTP layer types. * * `NexusEnv` is the environment generic passed to `Hono()` and every * `Context`. Variables set via `c.set(key, value)` are type-checked * against this shape. */ type NexusEnv = { Variables: { /** Authenticated user (set by auth middleware). Undefined on anonymous routes. */ user?: AuthUserRecord; /** CASL ability built from user's roles. Always set when auth middleware runs (even anonymous → empty ability). */ ability?: AppAbility; /** Per-request UUID. Always set by request-id middleware. */ requestId: string; /** Propagated correlation ID (falls back to requestId). */ correlationId: string; /** Tenant ID resolved from token/header. Null for single-tenant. */ tenantId: string | null; /** PAT token ID when request authenticated via PAT. */ patTokenId?: string; /** Custom JWT claims added by onTokenCreate hook. */ claims?: Record; /** Impersonation context when an admin acts on behalf of another user. */ impersonation?: { originalUserId: string; isImpersonating: boolean; }; }; }; type HonoApp = OpenAPIHono; type NexusContext = Context; type NexusMiddleware = MiddlewareHandler; /** * CASL action types (duplicated from core/abilities to avoid config/ → core/ dependency). * Must stay in sync with core/abilities/ability.types.ts. */ type Actions = 'manage' | 'create' | 'read' | 'update' | 'delete' | 'execute' | (string & {}); /** * Function to define custom CASL rules. * Runs after loading permissions from entity definitions. * Receives user as generic record to avoid circular deps. * * Uses `any` for Subjects to avoid importing SubjectRegistry from core/. * The concrete Subjects type is applied in core/abilities/ where this is called. */ type CaslRulesFunction = (user: Record, builder: Pick>, 'can' | 'cannot'>) => void | Promise; /** * Hook called before signing a JWT access token. * Return an object with custom claims to merge into the token. * System claims (userId, email, roleIds, roleNames, impersonatedBy, exp, iat) * are protected and cannot be overwritten. * * Called on every token creation: login, register, refresh, and impersonate. * On impersonate, `user` is the target user (not the admin). * * @param payload - JWT payload with system claims (read-only reference) * @param user - The database user record * @returns Object with custom claims to merge into the token */ type TokenCreateHook = (payload: { userId: string; email: string; roleIds: string[]; roleNames: string[]; impersonatedBy?: string; }, user?: Record) => Record | Promise>; /** * Options for the serveSPA helper */ interface ServeSPAOptions { /** Cache max-age (string or ms). Default: '1d' */ maxAge?: string | number; /** Enable ETag. Default: true */ etag?: boolean; /** Enable immutable directive. Default: false */ immutable?: boolean; /** Index file name. Default: 'index.html' */ index?: string; /** If true, distPath is treated as absolute (no cwd resolution). Default: false */ absolute?: boolean; } /** * Helper function to serve a SPA (Single Page Application) * * Mounts static files + fallback to index.html for SPA routing. * Paths are resolved relative to the project directory. * * @example * ```typescript * serveSPA('/app', '../frontend/dist') * serveSPA('/admin', '../admin/dist', { maxAge: '7d', immutable: true }) * ``` */ type ServeSPAFunction = (endpoint: string, distPath: string, options?: ServeSPAOptions & { viteSrc?: string; }) => void | Promise; /** * Configuration for a frontend SPA. * * Framework-agnostic: works with any frontend stack (nexus-ui, Vue, React, vanilla JS). * The backend serves static files and/or allows CORS regardless of the frontend framework. * For nexus-ui frontends, use `bootstrap()`. For other frameworks, use `@gzl10/nexus-client` * directly or any HTTP client pointing to the backend's API. * * Two usage patterns: * - Served by backend: provide `endpoint` + `path` (static files + SPA fallback) * - External server: provide `origin` (CORS allowlist only) * - Both: served SPA also accessible via a custom domain * * @example Served by backend (nexus-ui) * { endpoint: '/', path: '../frontend/dist' } * * @example External server, CORS only (any framework) * { origin: 'http://localhost:5174' } * * @example React app on its own server * { origin: 'http://localhost:5173' } * * @example Served + custom domain * { endpoint: '/admin', path: '../admin/dist', origin: 'https://admin.miapp.com' } */ interface SpaEntry extends ServeSPAOptions { /** Mount endpoint for backend-served SPAs (e.g. '/', '/admin'). Required when `path` is set. */ endpoint?: string; /** Relative path to the compiled dist/ directory. Required when `endpoint` is set. */ path?: string; /** Origin URL for CORS allowlist. Required for external SPAs; optional for served SPAs with a custom domain. */ origin?: string; /** * Path to frontend source directory (dev only). * When set in development, mounts Vite dev server as middleware with HMR instead of serving static files. * Ignored in production — uses `path` for static files. * Requires `endpoint` to be set. Requires `vite` as a peer dependency. * * @example { endpoint: '/', viteSrc: '../ui', path: '../ui/dist' } */ viteSrc?: string; } /** * Discovered plugins and modules. * * Populated automatically by auto-discovery: * - Plugins from package.json dependencies matching `nexus-plugin-*` * - Modules from `src/modules/` directory convention * * Can also be passed programmatically to `start()`. */ interface NexusConfig { /** * Plugins to register before starting the server. * Auto-discovered from package.json + passed programmatically to start(). */ plugins?: PluginManifest[]; /** * Standalone modules to register before starting the server. * Auto-discovered from src/modules/ + passed programmatically to start(). */ modules?: ModuleManifest[]; /** Supported locales. Default: DEFAULT_LOCALES (en + es) */ locales?: LocaleConfig[]; } /** * Runtime options for `start()`. * * Extends NexusConfig with lifecycle hooks, event handlers, and CASL rules. * These options only make sense at runtime, not in a config file. * * @example * ```typescript * import { start } from '@gzl10/nexus-backend' * * await start({ * plugins: [cmsPlugin], * beforeRoutes: (app) => { app.get('/health', (c) => c.json({ ok: true })) }, * onReady: (_app, port) => { console.log(`Ready on ${port}`) } * }) * ``` */ interface StartOptions extends NexusConfig { /** * Callback executed BEFORE mounting module routes. * Ideal for: custom health checks, webhooks, public routes, serving SPAs. */ beforeRoutes?: (app: HonoApp, serveSPA: ServeSPAFunction) => void | Promise; /** * Callback executed AFTER mounting all routes. * Ideal for: custom routes with Nexus services, serving additional SPAs. */ afterRoutes?: (app: HonoApp, serveSPA: ServeSPAFunction) => void | Promise; /** * System event subscriptions. * Allows subscribing to internal Nexus events or custom events. */ events?: { [eventName: string]: ((data: any) => void | Promise) | undefined; }; /** * Advanced CASL rules for cases not covered by `seed.roles.add({ permissions })`. * Runs as step 3 in the ability factory (after entity definition permissions). * * **Prefer `onSeed` → `seed.roles.add({ permissions })` for simple role-based permissions.** * Use `casl` only when you need: * - Conditions: `can('update', 'Post', { authorId: user.id })` * - Field-level: `can('read', 'User', ['name', 'email'])` * - Denials: `cannot('delete', 'User')` * - Dynamic logic based on user properties * * @example * ```typescript * casl: (user, { can, cannot }) => { * if ((user as any).roleNames?.includes('EDITOR')) { * can('update', 'Post', { authorId: (user as any).id }) * cannot('delete', 'Post') * } * } * ``` */ casl?: CaslRulesFunction; /** * Hook to add custom claims to JWT tokens before signing. * * @example * ```typescript * await start({ * onTokenCreate: (_payload, user) => ({ * tenantId: user?.tenantId, * plan: user?.subscriptionPlan ?? 'free' * }) * }) * ``` */ onTokenCreate?: TokenCreateHook; /** * Callback executed after all module seeds have run. * Receives a `SeedContext` with typed helpers — no direct DB access needed. * * - `seed.masters.register()` — lazy, flushed after callback completes * - `seed.roles.add()` — eager, supports `permissions` for CASL assignment * - `seed.users.add()` — eager, supports role assignment by name * - `seed.plugin(name).entity(name).upsert()` — resolves table prefix automatically * - `seed.raw()` — escape hatch for custom tables * * @example * ```typescript * onSeed: async (seed) => { * seed.masters.register('sources', [ * { code: 'web', label: { en: 'Website', es: 'Sitio web' } } * ]) * await seed.roles.add({ * name: 'SALES', * permissions: { 'contacts': ['read', 'create', 'update'] } * }) * await seed.users.add({ email: 'admin@acme.com', password: 'temp', roles: ['ADMIN'] }) * } * ``` */ onSeed?: (seed: SeedContext) => void | Promise; /** * Callback executed when the server is fully ready. * Runs after listen() and Socket.IO initialization. */ onReady?: (app: HonoApp, port: number, host: string) => void | Promise; /** * Callback executed BEFORE closing the server. * Ideal for: closing external connections, saving state. */ beforeClose?: () => void | Promise; /** * Frontend SPAs to configure. Single point of configuration for all frontends. * - Entries with `endpoint` + `path`: served by the backend (static files + SPA fallback) * - Entries with `origin`: added to the CORS allowlist automatically * - Entries with both: served + CORS for custom domain * * Served SPAs are mounted after API routes, sorted by specificity (longest endpoint first). * If a `spas` entry uses the same endpoint as `NEXUS_UI_*` env vars, `spas` takes priority. * * @example * ```typescript * start({ * spas: [ * { endpoint: '/', path: '../frontend/dist' }, // served by backend * { origin: 'http://localhost:5174' } // external server, CORS only * ] * }) * ``` */ spas?: SpaEntry[]; } interface ResolvedConfig { nodeEnv: 'development' | 'production' | 'test'; port: number; host: string; ui: { enabled: boolean; base: string; path: string; }; timezone: string; corsOrigin: string; databaseUrl: string; adminEmail?: string; adminPassword?: string; } /** * Hono server lifecycle orchestrator (NEX-346 Tarea 4). * * Drop-in replacement for `core/server.ts` over Hono. Coexists with the * Express-based `core/server.ts` until Tarea 7 retires it. The full pipeline * (logger, DB, migrations, seeds, module/plugin registration, HTTP listen, * Socket.IO, tunnel, lifecycle hooks, graceful shutdown) is preserved 1:1. */ /** * Starts the Nexus server on top of Hono. * * @returns The underlying `http.Server` (so Socket.IO and tests can attach). * @throws If the server is already running (call `stop()` first). */ declare function start(config?: StartOptions): Promise; /** * Stops the Hono server gracefully. Idempotent. */ declare function stop(): Promise; /** * Restarts the server. Reuses previous config if none provided. */ declare function restart(config?: StartOptions): Promise; /** Returns `true` if the server is currently running. */ declare function isRunning(): boolean; /** * Database connection management. * * Handles Knex instance lifecycle: initialization, access, and cleanup. */ /** * Gets the current DB instance (lazy init). * Use instead of `db` to support restarts. */ declare function getDb(): Knex; /** * @deprecated Use getDb() to support restart. * Proxy provides lazy initialization to avoid circular imports at module load time. */ declare const db: Knex; /** * Closes the DB connection (for stop/cleanup) */ declare function destroyDb(): Promise; /** * Database configuration. * * Builds Knex configuration based on DATABASE_URL. * Supports: SQLite, PostgreSQL, MySQL. */ /** * Gets the database type from DATABASE_URL */ declare function getDatabaseType(): 'sqlite' | 'postgresql' | 'mysql'; declare function getConfig(): ResolvedConfig; /** * Checks if a value structurally matches PluginManifest (duck-typing). * Rejects functions (factory plugins), null, and primitives. */ declare function isPluginManifest(value: unknown): value is PluginManifest; /** * Extract PluginManifest from a dynamically imported module. * Checks default export first, then named exports. */ declare function extractPluginManifest(mod: Record): PluginManifest | null; /** * Discover plugins from package.json dependencies. * Scans dependencies and devDependencies for packages matching `nexus-plugin-*`. * Dynamically imports each and extracts the PluginManifest. * * Errors are logged as warnings and skipped (non-fatal). */ declare function discoverPlugins(projectPath: string): Promise; /** * Checks if a value structurally matches ModuleManifest (duck-typing). * Required fields: name (string) and category (string). * Excludes PluginManifest (which has code + modules[]). */ declare function isModuleManifest(value: unknown): value is ModuleManifest; /** * Extract all ModuleManifest exports from a dynamically imported module. * A single index.ts can export multiple manifests (e.g. postsModule + commentsModule). */ declare function extractModuleManifests(mod: Record): ModuleManifest[]; /** * Discover modules from conventional directory structure. * Scans `src/modules/` (or `MODULES_DIR` env var) for subdirectories * containing `index.js` or `index.ts` that export ModuleManifest. * * Errors are logged as warnings and skipped (non-fatal). */ declare function discoverModules(projectPath: string): Promise; /** * Discover plugins and modules via conventions. * * Flow: * 1. Auto-discover plugins from package.json dependencies * 2. Auto-discover modules from src/modules/ directory * * Results are cached for the lifetime of the process. */ declare function loadNexusConfig(): Promise; /** * Batch Reporter - Helper for reporting progress in batch/long-running actions via SSE * @module core/sse/batch-reporter */ /** * Reporter interface for batch actions to emit structured progress events */ interface BatchReporter { /** Signal that the batch process has started */ start(message?: string): void; /** Report progress with percentage and optional step info */ progress(percent: number, message?: string, step?: number, totalSteps?: number): void; /** Append a log entry (sent incrementally to client) */ log(level: 'info' | 'warn' | 'error', message: string): void; /** Signal successful completion with optional result data */ complete(result?: unknown): void; /** Signal failure with error code and message */ fail(code: string, message: string): void; } /** * Create a BatchReporter that emits structured progress events via SSE. * All events use the named event 'batch' so the client can listen with addEventListener('batch', ...). * * @param sender - SSE sender from ctx.core.sse.stream() * @returns BatchReporter instance * * @example * ```typescript * // In an action handler: * const reporter = input._batchReporter as BatchReporter * reporter.start('Importing records...') * for (let i = 0; i < records.length; i++) { * await processRecord(records[i]) * reporter.progress(Math.round((i + 1) / records.length * 100), `Processed ${i + 1}/${records.length}`, i + 1, records.length) * } * reporter.complete({ imported: records.length }) * ``` */ declare function createBatchReporter(sender: SSESender): BatchReporter; /** * SSE helpers for Hono. * * Wraps `hono/streaming`'s `streamSSE` so controllers don't import Hono's * streaming utilities directly. Also re-exports `createBatchReporter` from the * existing SSE module (it's framework-agnostic — takes a `SSESender`). */ /** * Stream an SSE response from a Hono handler. * * @param c Hono context * @param handler Async function that receives an `SSESender` (same shape as * the legacy Express helper) and emits events. * @param options Stream options — retry interval and keep-alive. */ declare function streamSse(c: NexusContext, handler: (sender: SSESender) => Promise, options?: SSEOptions): Promise; interface SocketIOOptions { /** JWT secret for token verification. If not provided, uses AUTH_SECRET env var */ jwtSecret?: string; /** CORS origin(s). If not provided, defaults to CORS_ORIGIN env var or '*' */ corsOrigin?: string | string[] | boolean; /** * Checks whether a role set can subscribe to real-time events for an entity. * Implementation must encapsulate subject lookup + CASL ability build. * Returns false to deny (socket will NOT join the room). */ canSubscribeEntity?: (roleIds: string[], module: string, entity: string) => Promise; } /** * Initializes Socket.IO on the HTTP server * @param httpServer - HTTP server instance * @param options - Socket.IO initialization options (jwtSecret injected to avoid modules/ dependency) */ declare function initSocketIO(httpServer: Server$2, options?: SocketIOOptions): Server$1; /** * Gets the Socket.IO instance * @throws Error if Socket.IO has not been initialized */ declare function getIO(): Server$1; /** * Checks whether Socket.IO is initialized */ declare function isSocketIOInitialized(): boolean; /** * Gets IDs of connected users */ declare function getConnectedUsers(): string[]; /** * Checks whether a specific user is connected */ declare function isUserConnected(userId: string): boolean; /** * Gets the number of sockets connected for a user */ declare function getUserSocketCount(userId: string): number; /** * Closes Socket.IO (for test cleanup or shutdown) */ declare function closeSocketIO(): void; /** * Hono middleware that requires authentication AND a CASL ability grant. * - No ability → 401 Unauthorized * - Ability but cannot perform action → 403 Forbidden */ declare function requireAbility(action: Actions$1, subject: Subjects): hono.MiddlewareHandler; /** * Header name used to identify nexus-client requests. * Lightweight security layer for internal endpoints. */ declare const NEXUS_CLIENT_HEADER = "X-Nexus-Client"; /** * Requires the X-Nexus-Client header. Returns 404 (not 401/403) without it * to avoid revealing endpoint existence. */ declare const requireNexusClient: hono.MiddlewareHandler; /** * Creates a middleware that optionally requires the X-Nexus-Client header. */ declare function createNexusClientMiddleware(required?: boolean): hono.MiddlewareHandler; interface RateLimitOptions { /** Time window in milliseconds (default: 60000 = 1 minute) */ windowMs?: number; /** Max requests per window (default: 100) */ max?: number; /** Message when limit is reached */ message?: string; /** Custom key generator (default: IP-based) */ keyGenerator?: (c: NexusContext) => string; /** Don't count successful requests (2xx) toward the limit */ skipSuccessfulRequests?: boolean; } declare function createRateLimit(options?: RateLimitOptions): NexusMiddleware; /** * Validates JSON body against a Zod schema. Parsed value accessible via * `c.req.valid('json')` in the handler. */ declare function validateBody(schema: T): hono.MiddlewareHandler ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never) ? true : false) extends true ? { json?: ([T extends ZodSchema ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never] extends [any] ? T extends ZodSchema ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never : [Exclude ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never, undefined>] extends [never] ? {} : [Exclude ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never, undefined>] extends [object] ? undefined extends (T extends ZodSchema ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never) ? ((T extends ZodSchema ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never) & undefined) | (((Exclude ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never, (T extends ZodSchema ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never) & undefined> extends infer T_3 ? { [K_2 in keyof T_3]: ([Exclude] extends [string] ? [string & Exclude] extends [hono_utils_types.UnionToIntersection>] ? false : true : false) extends true ? T_3[K_2] : ([unknown] extends [T_3[K_2]] ? false : undefined extends T_3[K_2] ? true : false) extends true ? T_3[K_2] : Target extends "form" ? T$1 | T$1[] : Target extends "query" ? string | string[] : Target extends "param" ? string : Target extends "header" ? string : Target extends "cookie" ? string : unknown; } : never) extends infer T_2 ? { [K_1 in keyof T_2]: T_2[K_1]; } : never) extends infer T_1 ? { [K in keyof T_1]: T_1[K]; } : never) : (((T extends ZodSchema ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never) extends infer T_6 ? { [K_5 in keyof T_6]: ([Exclude] extends [string] ? [string & Exclude] extends [hono_utils_types.UnionToIntersection>] ? false : true : false) extends true ? T_6[K_5] : ([unknown] extends [T_6[K_5]] ? false : undefined extends T_6[K_5] ? true : false) extends true ? T_6[K_5] : Target extends "form" ? T$1 | T$1[] : Target extends "query" ? string | string[] : Target extends "param" ? string : Target extends "header" ? string : Target extends "cookie" ? string : unknown; } : never) extends infer T_5 ? { [K_4 in keyof T_5]: T_5[K_4]; } : never) extends infer T_4 ? { [K_3 in keyof T_4]: T_4[K_3]; } : never : {}) | undefined; } : { json: [T extends ZodSchema ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never] extends [any] ? T extends ZodSchema ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never : [Exclude ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never, undefined>] extends [never] ? {} : [Exclude ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never, undefined>] extends [object] ? undefined extends (T extends ZodSchema ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never) ? ((T extends ZodSchema ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never) & undefined) | (((Exclude ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never, (T extends ZodSchema ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never) & undefined> extends infer T_9 ? { [K_2 in keyof T_9]: ([Exclude] extends [string] ? [string & Exclude] extends [hono_utils_types.UnionToIntersection>] ? false : true : false) extends true ? T_9[K_2] : ([unknown] extends [T_9[K_2]] ? false : undefined extends T_9[K_2] ? true : false) extends true ? T_9[K_2] : Target extends "form" ? T$1 | T$1[] : Target extends "query" ? string | string[] : Target extends "param" ? string : Target extends "header" ? string : Target extends "cookie" ? string : unknown; } : never) extends infer T_8 ? { [K_1 in keyof T_8]: T_8[K_1]; } : never) extends infer T_7 ? { [K in keyof T_7]: T_7[K]; } : never) : (((T extends ZodSchema ? zod.input : T extends zod_v4_core.$ZodType> ? zod_v4.input : never) extends infer T_12 ? { [K_5 in keyof T_12]: ([Exclude] extends [string] ? [string & Exclude] extends [hono_utils_types.UnionToIntersection>] ? false : true : false) extends true ? T_12[K_5] : ([unknown] extends [T_12[K_5]] ? false : undefined extends T_12[K_5] ? true : false) extends true ? T_12[K_5] : Target extends "form" ? T$1 | T$1[] : Target extends "query" ? string | string[] : Target extends "param" ? string : Target extends "header" ? string : Target extends "cookie" ? string : unknown; } : never) extends infer T_11 ? { [K_4 in keyof T_11]: T_11[K_4]; } : never) extends infer T_10 ? { [K_3 in keyof T_10]: T_10[K_3]; } : never : {}; }; out: { json: T extends ZodSchema ? zod.output : T extends zod_v4_core.$ZodType> ? zod_v4.infer : never; }; }, never>; /** * Module source type */ type ModuleSource = 'core' | 'plugin' | 'standalone'; /** Options for registerPlugin */ interface RegisterPluginOptions { /** Disable endpoint mounting (services still available via init) */ disableEndpoints?: boolean; } /** * Options for registering a module */ interface RegisterModuleOptions { /** Module source ('core', 'plugin', 'standalone') */ source?: ModuleSource; /** Plugin name (only if source === 'plugin') */ pluginName?: string; /** If true, module routes won't be mounted (services still available via init) */ disableEndpoints?: boolean; /** Table name prefix for plugin modules */ tablePrefix?: string; /** Original (unprefixed) table names declared by this plugin */ pluginTables?: Set; } /** * Registers a module manifest in the engine store. * * @remarks * Registration is separate from initialization to support dependency ordering. * All modules must be registered before the server starts so the engine can * resolve dependencies and determine initialization order via topological sort. * * The engine validates uniqueness of table names and CASL subjects to prevent * conflicts between modules. Tree entities require at least one root node in seed. * * @param mod - Module manifest with name, definitions, and optional init/destroy * @param options - Source (core/plugin/standalone) and plugin name if applicable * * @example * ```typescript * registerModule(postsModule) * registerModule(commentsModule, { source: 'plugin', pluginName: 'cms' }) * ``` */ declare function registerModule(mod: ModuleManifest, options?: RegisterModuleOptions): void; /** * Registers all modules from a plugin manifest. * * @remarks * Plugins bundle multiple related modules under a single namespace. This enables: * - Shared branding (label, icon) inherited by modules without their own * - Unified category assignment for UI grouping * - Plugin-level lookup via getPlugin() for configuration access * * Use `disableEndpoints` when you need plugin services available via ctx.services * but don't want HTTP routes mounted (e.g., internal-only modules). * * @param plugin - Plugin manifest with modules array and shared metadata * @param options - Optional settings like disableEndpoints * * @example * ```typescript * registerPlugin(cmsPlugin) * registerPlugin(analyticsPlugin, { disableEndpoints: true }) * ``` */ declare function registerPlugin(plugin: PluginManifest, options?: RegisterPluginOptions): void; /** * Application manifest containing modules and metadata */ interface AppManifest { /** Package name */ name: string; /** Package version */ version: string; /** Registered modules */ modules: ModuleManifest[]; /** Registered plugins (only for user manifest) */ plugins?: PluginManifest[]; } /** * Gets all registered modules */ declare function getModules(): ModuleManifest[]; /** * Sorts modules by dependencies (topological sort). * Ensures a module is processed after its dependencies. */ declare function getOrderedModules(): ModuleManifest[]; /** * Gets a module by name */ declare function getModule(name: string): ModuleManifest | undefined; /** * Gets a plugin by name */ declare function getPlugin(name: string): PluginManifest | undefined; /** * Gets all registered plugins */ declare function getPlugins(): PluginManifest[]; /** * Gets all subjects registered in modules. * Includes 'all', which is always available. */ declare function getRegisteredSubjects(): string[]; /** * Validates that a subject exists in a registered module */ declare function isValidSubject(subject: string): boolean; /** * Get core modules (registered by nexus-backend) */ declare function getCoreModules(): ModuleManifest[]; /** * Get user modules (registered via plugins or standalone) */ declare function getUserModules(): ModuleManifest[]; /** * Get the core manifest (nexus-backend library) * Reads name/version from getLibPath()/package.json */ declare function getCoreManifest(): AppManifest; /** * Get the user manifest (user's project) * Reads name/version from getProjectPath()/package.json * Returns null if getLibPath() === getProjectPath() (development mode) */ declare function getUserManifest(): AppManifest | null; /** * Check if running as a library (user project exists) */ declare function hasUserApp(): boolean; /** * Checks if a module is registered by name */ declare function hasModule(name: string): boolean; /** * Checks if a plugin is registered by name */ declare function hasPlugin(name: string): boolean; /** * Core module loader. * * Loads all core modules in dependency order using topological sort. */ /** * Loads the backend core modules in dependency order. * Uses topological sort to ensure dependencies are registered first. * * Filters out modules that only apply to user projects (e.g. plugins module * is excluded during nexus-backend development where libPath === projectPath). * * @throws Error if circular dependencies are detected */ declare function loadCoreModules(): void; /** * Runtime types for entity services (framework-agnostic). * HTTP-layer types (EntityController, EntityHandler, EntityRuntime) live in * src/http/controllers/types.ts. */ /** * Entity service interface - base contract for all entity services. */ interface EntityService { findAll(query?: EntityQuery): Promise>; findById(id: string): Promise; create?(data: Partial): Promise; update?(id: string, data: Partial): Promise; delete?(id: string, options?: { actorId?: string; }): Promise; count?(filters?: Record): Promise; readonly definition: EntityDefinition; } /** * Entity Factory - Creates services from EntityDefinition. * HTTP routing lives in src/http/entity-factory.ts (Hono). */ /** * Create service based on entity type (sync - throws for tree/dag) */ declare function createEntityService = Record>(ctx: ModuleContext, definition: EntityDefinition, options?: CreateEntityServiceOptions): BaseEntityService$1; /** * Create services for all definitions in a module */ declare function createModuleServices(ctx: ModuleContext, definitions: EntityDefinition[]): Promise>; /** * Get the key used to store service in ctx.services */ declare function getServiceKey(definition: EntityDefinition): string; /** * Repository port interfaces (hexagonal architecture). * * Services receive a repository by injection. Tests mock these interfaces; * production code uses Knex implementations in `./knex/`. */ /** * Common filter type shared across repositories. */ type RepoFilters = Record; /** * Collection repository — CRUD over a table keyed by `id`. * Returns raw rows (not parsed); callers handle JSON parsing and hooks. */ interface ICollectionRepository> { findAll(query?: EntityQuery): Promise>; findById(id: string): Promise; create(data: Partial): Promise; update(id: string, data: Partial): Promise; delete(id: string): Promise; count(filters?: RepoFilters): Promise; bulkCreate(items: Partial[]): Promise; bulkUpdate(items: Array<{ id: string; data: Partial; }>): Promise; bulkDelete(ids: string[]): Promise; } /** * Single entity repository — KV-style; may or may not have scopeField. */ interface ISingleRepository> { get(scope?: string): Promise; upsert(data: Partial, scope?: string): Promise; } /** * Tree repository — hierarchy with single parent per node. */ interface ITreeRepository> extends ICollectionRepository { getTree(rootId?: string, maxDepth?: number): Promise; findRoots(): Promise; findChildren(parentId: string | null): Promise; getAncestors(id: string): Promise; getDescendants(id: string): Promise; move(id: string, newParentId: string | null): Promise; } /** * DAG repository — multi-parent via join table. */ interface IDagRepository> extends ITreeRepository { getParents(nodeId: string): Promise; setParents(nodeId: string, parentIds: string[]): Promise; addParent(nodeId: string, parentId: string): Promise; removeParent(nodeId: string, parentId: string): Promise; } /** * View repository — read-only. */ interface IViewRepository> { findAll(query?: EntityQuery): Promise>; findById(id: string): Promise; count(filters?: RepoFilters): Promise; } /** * Union type for factory return. */ type EntityRepository> = ICollectionRepository | ISingleRepository | ITreeRepository | IDagRepository | IViewRepository; /** * Collection Service - Full CRUD for collection entities * * Supports: * - Full CRUD operations (create, read, update, delete) * - Soft delete (optional) * - Timestamps (created_at, updated_at) * - Audit fields (created_by, updated_by) * - Pagination, sorting, search, filters */ declare class CollectionService = Record> extends BaseEntityService { readonly definition: CollectionEntityDefinition; constructor(ctx: ModuleContext, definition: CollectionEntityDefinition, hooks?: EntityServiceHooks, repo?: EntityRepository); /** * Get all entities with pagination */ findAll(query?: EntityQuery): Promise>; /** * Apply soft delete filter to all CRUD operations. * Migrated from applyTypeFilters(qb) to getTypeFilters() — repo handles WHERE deleted_at IS NULL via filter helper. */ protected getTypeFilters(): Record; /** * Post-process items: apply envDefaults overrides to matched records. */ protected afterFindAll(items: T[]): Promise; /** * Post-process single record after parse: apply envDefaults overrides. * Runs before the external afterFindById hook (via processAfterFindById in base). */ protected processAfterFindById(record: T | null): Promise; /** * Get entity by ID — delegates to baseFindById (repo-backed; soft-delete via getTypeFilters). */ findById(id: string): Promise; /** * Create new entity */ create(data: Partial): Promise; /** * Update entity */ update(id: string, data: Partial): Promise; /** * Delete entity (soft delete if enabled) */ delete(id: string, options?: { actorId?: string; }): Promise; /** * Restore soft-deleted entity. */ restore(id: string): Promise; /** * Find multiple entities by IDs */ findByIds(ids: string[]): Promise; /** * Check if entity exists */ exists(id: string): Promise; /** * Seed initial data from definition.seed. * Supports inline arrays and SeedConfig objects. * Idempotent: only inserts records whose ID doesn't exist yet. */ seed(): Promise; /** * Run retention cleanup based on definition (absorbed from EventService). * Deletes old records by age and/or caps total rows. */ runRetentionCleanup(): Promise; } /** * Single Service - Singleton entities stored in single_records * * Supports: * - Read single config * - Update config * - Defaults on first read * - Scoped mode (absorbed from ConfigService): multiple records keyed by scope * * Uses single_records table with key-value storage: * { key: 'site_config', value: { siteName: '...', logo: '...' } } * * Scoped mode (when scopeField is set): * { key: 'mail_config:default', value: { host: '...', port: 587 } } * { key: 'mail_config:tenant-123', value: { host: '...', port: 465 } } */ declare class SingleService = Record> extends BaseEntityService { readonly definition: SingleEntityDefinition; constructor(ctx: ModuleContext, definition: SingleEntityDefinition, hooks?: EntityServiceHooks, repo?: EntityRepository); /** * Build the KV repository for this single entity. * SingleService always stores in single_records (KV), so we override buildRepo * to provide a KnexSingleKvRepository instead of the default factory output. */ protected buildRepo(): EntityRepository; /** * Get table - overridden since single entities use single_records */ protected get table(): string; /** * Get the setting key */ private get key(); /** * Whether this entity operates in scoped mode (multiple records) */ private get isScoped(); /** * Build the full key for a scope value */ private scopedKey; /** * Get all - returns single item in paginated format (singleton mode) * or all scoped records (scoped mode) */ findAll(_query?: EntityQuery): Promise>; /** * Get all scoped records */ private findAllScoped; /** * Get the singleton value (singleton mode) * or get a scoped record by ID (scoped mode) */ findById(id: string): Promise; /** * Find a scoped record by its DB id */ private findByIdScoped; /** * Get the setting - alias for findById */ get(): Promise; /** * Get config by scope value */ findByScope(scopeValue: string): Promise; /** * Update or create config by scope */ upsertByScope(scopeValue: string, data: Partial): Promise; /** * Set a scoped config as the default */ setAsDefault(scopeValue: string): Promise; /** * Get the default scoped config */ getDefault(): Promise; /** * Update the singleton value (singleton mode) * or update a scoped record by ID (scoped mode) */ update(id: string, data: Partial): Promise; /** * Update a scoped record by its DB id */ private updateScoped; /** * Set the setting - alias for update */ set(data: Partial): Promise; /** * Reset to defaults */ reset(): Promise; /** * Seed defaults into the database if record doesn't exist. * This ensures endpoints return persisted data instead of in-memory defaults. * * @returns 1 if seeded, 0 if already exists or no defaults */ seed(): Promise; /** * Parse the value column — handles both string (text column) and * pre-parsed object (json/jsonb column where pg driver auto-deserializes). */ private parseValue; /** * Merge entity with defaults */ private mergeWithDefaults; /** * Applies envMapping overrides and appends _envLocked to the result. * No-op if envMapping is not defined or no env vars are set. */ private applyEnvMapping; } /** * View Service - Read-only views with custom queries * * Supports: * - Read all (paginated) * - Read by ID * - Custom query builder or SQL VIEW */ declare class ViewService = Record> extends BaseEntityService { readonly definition: ViewEntityDefinition; constructor(ctx: ModuleContext, definition: ViewEntityDefinition, hooks?: EntityServiceHooks, repo?: EntityRepository); /** * Get all items with pagination. * * When definition.query is a function (custom Knex builder), the query is * executed directly because KnexViewRepository only supports named views/tables. * * LIMITATION: Custom query-builder path bypasses the repo layer. This is * intentional — the repo interface only accepts a view/table name; supporting * arbitrary builders would require extending IViewRepository. */ findAll(query?: EntityQuery): Promise>; /** * Get item by ID. * * LIMITATION: Custom query-builder path bypasses the repo layer for the same * reason as findAll — the builder is not accessible via IViewRepository. */ findById(id: string): Promise; /** * findAll using a custom Knex query builder from definition.query. * Called only when definition.query is a function. */ private findAllWithQueryBuilder; /** * Create SQL VIEW from definition (for migrations) */ createView(): Promise; /** * Drop SQL VIEW */ dropView(): Promise; /** * Views are read-only */ create(_data: Partial): Promise; update(_id: string, _data: Partial): Promise; delete(_id: string): Promise; } declare class ComputedService = Record> extends BaseEntityService { readonly definition: ComputedEntityDefinition; private cache; constructor(ctx: ModuleContext, definition: ComputedEntityDefinition, hooks?: EntityServiceHooks, repo?: EntityRepository); /** * Register observer for live mode: when invalidation events fire, * recompute and push entity.updated to subscribed clients. */ private registerLiveObserver; /** * Get table - computed entities don't have a local table */ protected get table(): string; /** * Get cache key */ private getCacheKey; /** * Fetch data from source services and apply resolver (absorbed from VirtualService) */ private computeFromSources; /** * Execute compute function or source aggregation */ private compute; /** * Get all computed items with pagination */ findAll(query?: EntityQuery): Promise>; /** * Get computed item by ID */ findById(id: string): Promise; /** * Recompute with specific params */ recompute(params?: Record): Promise; /** * Clear all cache for this entity */ clearCache(): Promise; /** * Get cache statistics */ getCacheStats(): _gzl10_nexus_sdk.CacheStats; /** * Computed entities are read-only */ create(_data: Partial): Promise; update(_id: string, _data: Partial): Promise; delete(_id: string): Promise; } declare class ExternalService = Record> extends BaseEntityService { readonly definition: ExternalEntityDefinition; private adapter; private cache; constructor(ctx: ModuleContext, definition: ExternalEntityDefinition, hooks?: EntityServiceHooks, repo?: EntityRepository); /** * Get table - the resource identifier in the external system */ protected get table(): string; /** * Get cache key for a request */ private getCacheKey; /** * Ensure adapter is available */ private ensureAdapter; /** * Get all items with pagination */ findAll(query?: EntityQuery): Promise>; /** * Get item by ID */ findById(id: string): Promise; /** * Create via adapter */ create(data: Partial): Promise; /** * Update via adapter */ update(id: string, data: Partial): Promise; /** * Delete via adapter */ delete(id: string): Promise; /** * Clear all cache for this entity */ clearCache(): Promise; /** * Refresh data from external source (bypass cache) */ refresh(query?: EntityQuery): Promise>; /** * Get cache statistics */ getCacheStats(): _gzl10_nexus_sdk.CacheStats; } /** * Logger service interface */ interface LoggerService { fatal(msg: string, ...args: unknown[]): void; fatal(obj: object, msg?: string, ...args: unknown[]): void; error(msg: string, ...args: unknown[]): void; error(obj: object, msg?: string, ...args: unknown[]): void; warn(msg: string, ...args: unknown[]): void; warn(obj: object, msg?: string, ...args: unknown[]): void; info(msg: string, ...args: unknown[]): void; info(obj: object, msg?: string, ...args: unknown[]): void; debug(msg: string, ...args: unknown[]): void; debug(obj: object, msg?: string, ...args: unknown[]): void; trace(msg: string, ...args: unknown[]): void; trace(obj: object, msg?: string, ...args: unknown[]): void; child(bindings: pino.Bindings): pino.Logger; captureException(error: Error, context?: Record): void; captureMessage(msg: string, level?: 'info' | 'warning' | 'error'): void; setUser(user: { id: string; email?: string; }): void; isSentryEnabled(): boolean; } /** * Base Entity Service - Abstract class for all entity services */ /** * Abstract base class for entity services * * Provides common functionality: * - Access to ModuleContext (db, logger, etc.) * - Entity definition * - Default pagination * - Hook system for lifecycle events (internos + externos via createEntityService) */ declare abstract class BaseEntityService implements EntityService { protected readonly ctx: ModuleContext; readonly definition: EntityDefinition; protected readonly db: Knex; protected readonly logger: ModuleContext['core']['logger']; protected readonly generateId: () => string; protected readonly generateIdByType: (type?: 'ulid' | 'uuid' | 'nanoid' | 'cuid2' | 'auto' | 'custom') => string | undefined; protected readonly errors: ModuleContext['core']['errors']; /** Module name for entity event emission (stamped by registry) */ protected readonly moduleName: string; /** Stable entity identifier for real-time rooms and events (table, key, or label) */ protected readonly entityKey: string; /** Hooks externos inyectados via createEntityService */ protected readonly externalHooks?: EntityServiceHooks; /** Temporary storage for delete actor ID (used by audit event emission) */ protected _deleteActorId?: string; /** * Repository for CRUD operations. * If not provided in the constructor, built lazily via buildRepo() from ctx + definition. * Note: single/computed/external entity types may not have a valid repo; for those * the repo is constructed only if accessed and the type is supported by the factory. */ protected readonly repo: EntityRepository; constructor(ctx: ModuleContext, definition: EntityDefinition, hooks?: EntityServiceHooks, repo?: EntityRepository); /** * Build default repository from context + definition. * Subclasses can override to provide custom logic if needed. * Returns a stub repo for entity types not supported by the factory * (single, computed, external) so this.repo is always assignable. */ protected buildRepo(): EntityRepository; /** * Get the ID type configured for this entity. * @returns ID type from definition or 'ulid' as default */ protected getIdType(): 'ulid' | 'uuid' | 'nanoid' | 'cuid2' | 'auto' | 'custom' | 'pattern'; /** * Get the pattern configuration for this entity (if using pattern IDs). */ protected getPatternConfig(): { pattern: string; prefix?: string; suffix?: string; resetOn?: 'never' | 'year' | 'month' | 'day'; startAt?: number; } | undefined; /** * Resolve the ID for a new entity (async version). * - If user provides ID, use it (for all types) * - For 'auto': return undefined (DB generates) * - For 'custom' without user ID: throw error * - For 'pattern': generate using sequence * - For other types: generate ID * * @param providedId - ID provided by user (optional) * @returns Resolved ID or undefined for auto-increment */ protected resolveEntityId(providedId?: unknown): Promise; /** * Get table name for persistent entities */ protected get table(): string; protected get loggerService(): LoggerService | undefined; /** * Get all entities with pagination */ abstract findAll(query?: EntityQuery): Promise>; /** * Get entity by ID */ abstract findById(id: string): Promise; /** * Count entities matching optional filters. * Available for all persistent entity types. * Subclasses can override for soft delete or adapter support. */ count(filters?: Record): Promise; /** * Template method for findAll with customizable hooks. * Subclasses can override hooks instead of reimplementing the whole method. * * Hooks: * - applyTypeFilters(qb): Add type-specific filters (softDelete, TTL, etc.) * - getDefaultSort(): Return default sort config { field, order } * - afterFindAll(items): Post-process items before returning */ protected baseFindAll(query?: EntityQuery): Promise>; /** * Type-specific filters (soft-delete, TTL, retention, etc.) merged into all CRUD operations. * Subclasses override to inject filters that apply to findAll/findById/count/delete. * Default: no filters. * * Example (soft-delete): * protected override getTypeFilters() { return { deleted_at: null } } */ protected getTypeFilters(): Record; /** * @deprecated Use getTypeFilters() instead. Kept for backward compatibility but * no longer invoked by baseFindAll/baseFindById/count/baseDelete. * Subclasses that still override this should migrate to getTypeFilters(). */ protected applyTypeFilters(qb: Knex.QueryBuilder): Knex.QueryBuilder; /** * Get default sort configuration for this entity type. * Uses definition.defaultSort if defined, otherwise null. * Override in subclasses for type-specific defaults (e.g., events = created_at desc). */ protected getDefaultSort(): { field: string; order: 'asc' | 'desc'; } | null; /** * Apply sorting with type-specific defaults. */ protected applySortingWithDefaults(qb: Knex.QueryBuilder, query?: EntityQuery): Knex.QueryBuilder; /** * Post-process items after query. * Override in subclasses for cleanup tasks, etc. */ protected afterFindAll(items: T[]): Promise; /** * Standard findById implementation for DB-backed entities. * Subclasses can override findById to add custom behavior (e.g., soft delete filter, default merge). */ protected baseFindById(id: string): Promise; /** * In-memory findAll for services without DB tables (computed, virtual). * Applies filters, search, sort, and pagination to a raw array. */ protected inMemoryFindAll(items: unknown[], query?: EntityQuery): PaginatedResult; /** * Get current timestamp formatted for the database. * Avoids importing schema-helpers at module level (circular dependency). */ protected getNowTimestamp(): string; /** * Standard hard-delete implementation for DB-backed entities. */ protected baseDelete(id: string): Promise; /** * Process a record after findById. * Calls external afterFindById hook if defined. */ protected processAfterFindById(record: T | null): Promise; /** * Called before creating an entity. * Runs the external hook (if any) and then allows subclass override. * @returns Modified data */ protected beforeCreate(data: Partial): Promise>; /** Override in subclasses for custom beforeCreate logic */ protected internalBeforeCreate(data: Partial): Promise>; /** * Convert empty strings to null for foreign key fields. * Prevents FK constraint violations when selects are cleared. */ protected sanitizeForeignKeys(data: Partial): Partial; /** * Called after creating an entity */ protected afterCreate(entity: T): Promise; /** Override in subclasses for custom afterCreate logic */ protected internalAfterCreate(entity: T): Promise; /** * Called before updating an entity * @returns Modified data */ protected beforeUpdate(id: string, data: Partial): Promise>; /** Override in subclasses for custom beforeUpdate logic */ protected internalBeforeUpdate(_id: string, data: Partial): Promise>; /** * Called after updating an entity */ protected afterUpdate(entity: T): Promise; /** Override in subclasses for custom afterUpdate logic */ protected internalAfterUpdate(entity: T): Promise; /** * Called before deleting an entity */ protected beforeDelete(id: string): Promise; /** Override in subclasses for custom beforeDelete logic */ protected internalBeforeDelete(_id: string): Promise; /** * Called after deleting an entity */ protected afterDelete(id: string): Promise; /** Override in subclasses for custom afterDelete logic */ protected internalAfterDelete(id: string): Promise; /** * Emit entity change event via ctx.core.events. * * Always emits for cross-module listeners (taxonomy cleanup, attachment cleanup, etc.). * The event bridge routes these to Socket.IO rooms only when realtime mode is enabled. * Fire-and-forget: does not block the HTTP response. */ protected emitEntityEvent(action: 'created' | 'updated' | 'deleted', entity?: T, id?: string): void; /** * Emit audit.log event for entities with audit: true. * Fire-and-forget via event bus — audit module picks it up if loaded. */ protected emitAuditEvent(action: 'record.created' | 'record.updated' | 'record.deleted', entity?: T, id?: string, actorId?: string): void; /** * Build paginated result from items and count */ protected buildPaginatedResult(items: T[], total: number, page: number, limit: number): PaginatedResult; /** * Get pagination params with defaults */ protected getPagination(query?: ServerEntityQuery): { page: number; limit: number; offset: number; }; /** * Apply sorting to query builder */ protected applySorting(qb: Knex.QueryBuilder, query?: EntityQuery): Knex.QueryBuilder; /** * Apply search filter to query builder * Override in subclasses for entity-specific search */ protected applySearch(qb: Knex.QueryBuilder, search: string): Knex.QueryBuilder; /** * Apply filters to query builder * Supports operators: $eq, $ne, $gt, $gte, $lt, $lte, $contains, $startswith, $endswith, $in, $nin, $isnull * * @example * // Simple equality * { status: 'active' } * * // With operators * { name: { $contains: 'john' } } * { age: { $gte: 18, $lt: 65 } } * * // Array shorthand for $in * { status: ['active', 'pending'] } */ protected applyFilters(qb: Knex.QueryBuilder, filters: Record): Knex.QueryBuilder; /** * Apply filters in memory (for non-persistent entities) * Supports same operators as Knex: $eq, $ne, $gt, $gte, $lt, $lte, $contains, $startswith, $endswith, $in, $nin, $isnull */ protected applyInMemoryFilters(items: unknown[], filters: Record): unknown[]; /** * Get sensitive fields from CASL config. * Only available for EntityCaslConfig, not simplified action CASL. */ protected get sensitiveFields(): string[]; /** * Exclude sensitive fields from entity data. * Uses definition.casl.sensitiveFields if configured. */ protected excludeSensitiveFields>(data: D): Partial; /** * Exclude sensitive fields from array of entities. */ protected excludeSensitiveFieldsFromArray>(items: D[]): Partial[]; /** * Parse fields in an entity based on field definitions. * - Converts JSON strings to objects for fields with db.type = 'json' * - Converts numeric 0/1 to native booleans for fields with db.type = 'boolean' * * SQLite and MySQL store booleans as integers (0/1), so we need to convert them. */ protected parseJsonFields>(entity: E): E; /** * Parse fields in array of entities */ protected parseJsonFieldsFromArray>(items: E[]): E[]; /** * Serialize entity data for DB insertion/update. * - Converts objects/arrays to JSON strings for fields with db.type = 'json' * (all engines: pg driver does NOT auto-serialize plain objects — calls .toString() → [object Object]) * - Converts booleans to 0/1 for SQLite */ protected serializeForDb>(data: E): E; /** * Execute an action defined in this entity programmatically * * Allows invoking EntityActions from other services without HTTP context. * NOTE: Does NOT verify CASL permissions - caller is responsible for authorization. * * @param actionKey - Key of the action to execute * @param input - Input for the handler (without _record if recordId is provided) * @param recordId - ID of the record (optional, loads automatically) * @param options - Additional options * @returns Result from the action handler * * @example * // From another service * const storageService = ctx.services['storage_files'] as CollectionService * const result = await storageService.executeAction('thumbnail', { width: 200 }, fileId) */ executeAction(actionKey: string, input?: Record, recordId?: string, options?: { userId?: string; skipValidation?: boolean; }): Promise; } /** * Hono-flavored entity controller types. * * Mirrors the Express-era `EntityController`/`EntityHandler` shape but binds * to a Hono `NexusContext` and requires handlers to return a `Response`. * The canonical `EntityService` interface still lives in runtime/types.ts. */ /** Handler signature for Hono: every handler MUST return a Response. */ type EntityHandler = (c: NexusContext) => Promise; /** Entity controller interface — handlers are optional based on entity type capabilities. */ interface EntityController { list: EntityHandler; get: EntityHandler; create?: EntityHandler; update?: EntityHandler; delete?: EntityHandler; count?: EntityHandler; bulkCreate?: EntityHandler; bulkUpdate?: EntityHandler; bulkDelete?: EntityHandler; execute?: EntityHandler; recompute?: EntityHandler; } /** * Hono entity factory — parallel to runtime/entity-factory.ts. * * Service creation delegates to the existing runtime factory (unchanged in F2). * Only the controller + router swap to Hono-native implementations. */ interface EntityRuntime { service: BaseEntityService$1; controller: EntityController; router: HonoApp; } /** * Tree Service - Hierarchical data with parent-child relationships * * Extends CollectionService with: * - Automatic parent_id field injection * - Cycle detection in beforeCreate/beforeUpdate hooks * - Tree-specific operations (getTree, getAncestors, getDescendants, move) * * @incompatible RedisAdapter - requires foreign key support */ declare class TreeService = Record> extends CollectionService { /** Original tree entity definition (before conversion to collection) */ readonly treeDefinition: TreeEntityDefinition; constructor(ctx: ModuleContext, definition: TreeEntityDefinition, hooks?: EntityServiceHooks, repo?: EntityRepository); /** * Validate that the adapter is compatible with tree entities. * Some adapters don't support foreign keys required for tree structure. */ private validateAdapterCompatibility; /** * Get all tree nodes with optional filtering */ findAll(query?: EntityQuery): Promise>; /** * Get tree structure as nested hierarchy * * @param rootId - Optional root node ID. If provided, returns subtree from that node. * @param maxDepth - Optional maximum depth to include. * @returns Array of root-level TreeNode objects with nested children */ getTree(rootId?: string, maxDepth?: number): Promise[]>; /** * Build a nested tree structure from a flat BFS-ordered array. * * @param items - Flat list of nodes (all nodes that belong in the tree) * @param rootId - Optional root node ID to use as single root * @returns Array of root-level TreeNode objects with nested children */ private buildNestedTree; /** * Truncate tree nodes beyond the specified depth * * @param nodes - Tree nodes to truncate * @param maxDepth - Maximum depth (0 = only roots, 1 = roots + direct children, etc.) * @param currentDepth - Current depth level (used in recursion) */ private truncateAtDepth; /** * Get ancestors of a node (path from node to root) * * @param id - Node ID to get ancestors for * @returns Array of ancestor nodes, ordered from immediate parent to root */ getAncestors(id: string): Promise; /** * Get all descendants of a node (all children recursively) * * @param id - Node ID to get descendants for * @returns Array of all descendant nodes (breadth-first order) */ getDescendants(id: string): Promise; /** * Get direct children of a node * * @param parentId - Parent node ID, or null for root nodes * @returns Array of direct child nodes */ findChildren(parentId: string | null): Promise; /** * Get root nodes (nodes with no parent) * * @returns Array of root nodes */ findRoots(): Promise; /** * Move a node to a new parent * * @param id - Node ID to move * @param newParentId - New parent ID, or null to make it a root node * @returns Updated node * @throws ValidationError if move would create a cycle */ move(id: string, newParentId: string | null): Promise; /** * Get the depth of a node in the tree (root = 0) * * @param id - Node ID * @returns Depth level (0 for root nodes) */ getDepth(id: string): Promise; /** * Check if a node is an ancestor of another node * * @param ancestorId - Potential ancestor node ID * @param descendantId - Potential descendant node ID * @returns true if ancestorId is an ancestor of descendantId */ isAncestorOf(ancestorId: string, descendantId: string): Promise; /** * Validate that setting parent_id would not create a cycle. * * A cycle occurs when: * - Node becomes its own parent (self-reference) * - Node becomes parent of one of its ancestors (circular reference) * * @param nodeId - Node being modified * @param newParentId - Proposed new parent * @throws ValidationError if cycle would be created */ private validateNoCycle; /** * Validate parent exists before creating a node */ protected internalBeforeCreate(data: Partial): Promise>; /** * Validate parent change doesn't create cycle before updating */ protected internalBeforeUpdate(id: string, data: Partial): Promise>; /** * Seed initial tree data from definition. * Supports both flat format (with parent_id) and nested format (with children). * Inserts nodes that don't exist yet (matched by ID). * * @returns Number of nodes seeded */ seed(): Promise; } /** * DAG Service - Directed Acyclic Graph with multiple parents per node * * Extends CollectionService with: * - Junction table for N:N parent relationships ({table}_parents) * - Cycle detection for graphs (validates no circular references) * - Parent management operations (get/set/add/remove parents) * - Tree-like traversal adapted for DAG structure * * Delegation: graph traversal and parent mutations are delegated to * IDagRepository. The DagService preserves JSON-field parsing, * service-level validation (parent existence), event emission and seeding. * * Column reconciliation: legacy DagService used `{table}_parents` with * columns `node_id`/`parent_id`. The default KnexDagRepository convention * is `{table}_links` with `child_id`/`parent_id`. We override `buildRepo()` * to pass the legacy names so the existing DB schema keeps working. * * @incompatible RedisAdapter - requires foreign key support */ declare class DagService = Record> extends CollectionService { /** Original DAG entity definition (before conversion to collection) */ readonly dagDefinition: DagEntityDefinition; /** Name of the parents junction table */ readonly parentsTableName: string; constructor(ctx: ModuleContext, definition: DagEntityDefinition, hooks?: EntityServiceHooks, repo?: EntityRepository); /** * Validate that the adapter is compatible with DAG entities. * Some adapters don't support foreign keys required for parent relationships. */ private validateAdapterCompatibility; /** * Get all DAG nodes with optional filtering */ findAll(query?: EntityQuery): Promise>; /** * Get all parents of a node */ getParents(nodeId: string): Promise; /** * Set all parents for a node (replaces existing parents). * Validates parent existence at the service layer; cycle detection * happens inside the repo (and is rethrown as ValidationError). */ setParents(nodeId: string, parentIds: string[]): Promise; /** * Add a parent to a node (idempotent). */ addParent(nodeId: string, parentId: string): Promise; /** * Remove a parent from a node */ removeParent(nodeId: string, parentId: string): Promise; /** * Translate repo-level cycle errors into service ValidationError. */ private rethrowCycleAsValidation; /** * Get DAG as tree structure. * Note: shared nodes are duplicated under each parent in the resulting tree. * * @param rootId - Optional root node ID. If provided, returns subtree from that node. */ getTree(rootId?: string): Promise[]>; /** * Get all ancestors of a node (multiple paths to roots). */ getAncestors(id: string): Promise; /** * Get all descendants of a node (all children recursively). */ getDescendants(id: string): Promise; /** * Find direct children (nodes that have this as a parent) */ findChildren(parentId: string | null): Promise; /** * Get root nodes (nodes with no parents) */ findRoots(): Promise; /** * Get the depth of a node in the DAG (minimum path to any root = 0) */ getDepth(id: string): Promise; /** * Check if a node is an ancestor of another node */ isAncestorOf(ancestorId: string, descendantId: string): Promise; /** * Delete a node and its parent relationships (in either direction). * * IDagRepository does not expose a "remove all links touching N" primitive, * so we use removeParent for the inbound side and rely on getDescendants + * removeParent to clean the outbound side. */ delete(id: string): Promise; /** * Seed initial DAG data from definition. * Inserts nodes that don't exist yet (matched by ID). * Supports both inline arrays and URL-based SeedConfig. * * Seed items can include a `parent_ids` array for parent relationships. * IMPORTANT: Seed items should be ordered with parents before children * to ensure parent nodes exist when creating relationships. * * @returns Number of nodes seeded */ seed(): Promise; } /** * Users module types */ /** User type discriminator */ type UserType = 'human' | 'bot' | 'service'; /** * System user */ interface User { id: string; type: UserType; email: string | null; password: string | null; name: string; avatar?: string | null; metadata?: Record | null; consent_date?: string | null; consent_version?: string | null; marketing_opt_in: boolean; created_at: Date; updated_at: Date; created_by: string | null; updated_by: string | null; } /** * System role */ interface Role { id: string; name: string; description?: string | null; created_at: Date; updated_at: Date; created_by?: string | null; updated_by?: string | null; } /** User without password for API responses */ type UserWithoutPassword = Omit; /** User presence (Socket.IO) */ interface UserPresence { isOnline: boolean; socketCount: number; } /** User with roles included (multi-role) */ interface UserWithRoles extends UserWithoutPassword { /** Whether the user has a password set (false for OIDC-only users) */ has_password: boolean; roles: Role[]; role_ids: string[]; presence: UserPresence; } /** Role with user count */ interface RoleWithCounts extends Role { users_count: number; } /** Input for a single audit log entry */ interface AuditEntry { /** Origin: 'core:auth', 'plugin:importer', 'module:users', etc. */ source: string; /** Action performed: 'login', 'import', 'export', 'password_reset', etc. */ action: string; /** User ID of the actor (nullable for system events) */ actorId?: string; /** Email snapshot (survives user deletion) */ actorEmail?: string; /** Type of affected resource */ resourceType?: string; /** ID of affected resource */ resourceId?: string; /** Client IP address */ ip?: string; /** Client User-Agent */ userAgent?: string; /** Additional event-specific data */ metadata?: Record; } /** Query filters for audit log */ interface AuditQuery { source?: string; action?: string; actorId?: string; resourceType?: string; resourceId?: string; since?: Date; until?: Date; limit?: number; offset?: number; } /** Audit log row as stored in DB (snake_case — consistent with Knex) */ interface AuditLogRow { id: string; source: string; action: string; actor_id: string | null; actor_email: string | null; resource_type: string | null; resource_id: string | null; ip_address: string | null; user_agent: string | null; metadata: string | null; created_at: string; } /** AuditService public API */ interface AuditService { /** Log a single audit event (fire-and-forget) */ log(entry: AuditEntry): void; /** Log multiple audit events in a single batch insert */ logBatch(entries: AuditEntry[]): void; /** Query audit log entries */ query(filters: AuditQuery): Promise; } interface JwtPayload { userId: string; email: string; roleIds: string[]; roleNames: string[]; impersonatedBy?: string; } interface TokenPair { accessToken: string; refreshToken: string; } /** * Schedule statuses */ type ScheduleStatus = 'idle' | 'running' | 'paused' | 'error' | 'retrying'; /** * Retry backoff strategies */ type RetryBackoff = 'fixed' | 'exponential' | 'linear'; /** * Schedule persisted in the DB */ interface Schedule { id: string; name: string; description?: string | null; cron_expression: string; timezone: string; function_key: string; function_args?: Record | null; enabled: boolean; status: ScheduleStatus; last_run_at?: string | null; last_error?: string | null; run_count: number; max_retries: number; retry_count: number; retry_delay_ms: number; retry_backoff: RetryBackoff; created_at: string; updated_at: string; created_by?: string | null; updated_by?: string | null; } /** * Input for creating a schedule */ interface CreateScheduleInput { name: string; description?: string; cron_expression: string; timezone?: string; function_key: string; function_args?: Record; enabled?: boolean; max_retries?: number; retry_delay_ms?: number; retry_backoff?: RetryBackoff; } /** * Input for updating a schedule */ interface UpdateScheduleInput { name?: string; description?: string; cron_expression?: string; timezone?: string; function_key?: string; function_args?: Record; enabled?: boolean; max_retries?: number; retry_delay_ms?: number; retry_backoff?: RetryBackoff; } /** * Registered function for the function registry */ type ScheduleFunction = (args?: Record) => Promise; /** * Execution result */ interface ExecutionResult { success: boolean; data?: unknown; error?: string; duration_ms: number; } /** * Next run info */ interface NextRunInfo { id: string; name: string; next_run: string; cron_expression: string; } /** * Public API exposed by the schedules service to other modules and plugins */ interface SchedulesApi { registerFunction(key: string, fn: ScheduleFunction): void; unregisterFunction(key: string): void; create(data: CreateScheduleInput, userId?: string): Promise; update(id: string, data: UpdateScheduleInput, userId?: string): Promise; delete(id: string): Promise; pause(id: string): Promise; resume(id: string): Promise; trigger(id: string): Promise; getNextRuns(limit?: number): Promise; ensureSystemSchedule(data: Omit): Promise; } /** * Minimal user type for ability building. * Accepts any object with id (and optionally email). */ interface MinimalUser { id: string; email?: string; } /** * Builds CASL abilities from role names and entity definitions. * Permissions are static in code (entity.casl.permissions[roleName]). * * ADMIN role automatically receives 'manage:all' permission. * * @param user - Authenticated user (needs id for condition interpolation) * @param roleNames - User's role names (e.g., ['ADMIN', 'EDITOR']) */ declare function defineAbilityFor(user: MinimalUser, roleNames: string[]): Promise; declare function packRules(ability: AppAbility): RawRuleOf[]; declare function unpackRules(rules: RawRuleOf[]): AppAbility; declare const EventEmitter2: typeof pkg.EventEmitter2; interface DbEventPayload { table: string; action: 'created' | 'updated' | 'deleted'; data: unknown; timestamp: Date; } interface NexusEvents { 'server.starting': { port: number; host: string; }; 'server.started': { port: number; host: string; }; 'server.stopping': undefined; 'server.stopped': undefined; 'server.restarting': undefined; 'db.connected': { type: 'sqlite' | 'postgresql' | 'mysql'; }; 'db.disconnected': undefined; [key: `db.${string}.created`]: DbEventPayload; [key: `db.${string}.updated`]: DbEventPayload; [key: `db.${string}.deleted`]: DbEventPayload; 'auth.login': { userId: string; email: string; }; 'auth.logout': { userId: string; }; 'auth.refresh': { userId: string; }; 'auth.failed': { email: string; reason: string; }; 'socket.initialized': undefined; 'socket.user.connected': { userId: string; roleIds: string[]; socketId: string; }; 'socket.user.disconnected': { userId: string; socketId: string; }; 'notifications.sent': { id: string; target_type: string; target_value?: string; sent: number; }; 'entity.created': EntityChangePayload; 'entity.updated': EntityChangePayload; 'entity.deleted': EntityChangePayload; 'chat.message.created': { channelId: string; messageId: string; userId: string; body: string; }; 'chat.message.deleted': { channelId: string; messageId: string; userId: string; }; 'chat.channel.created': { channelId: string; type: 'dm' | 'group'; createdBy: string; }; 'chat.member.joined': { channelId: string; userId: string; role: string; }; 'chat.member.left': { channelId: string; userId: string; }; 'audit.log': { source: string; action: string; actorId?: string; actorEmail?: string; resourceType?: string; resourceId?: string; ip?: string; userAgent?: string; metadata?: Record; }; } type NexusEventName = keyof NexusEvents; type NexusEventPayload = NexusEvents[T]; declare class TypedEventEmitter extends EventEmitter2 { emitEvent(event: T, ...args: NexusEvents[T] extends undefined ? [] : [NexusEvents[T]]): boolean; onEvent(event: T, listener: NexusEvents[T] extends undefined ? () => void : (payload: NexusEvents[T]) => void): this; onceEvent(event: T, listener: NexusEvents[T] extends undefined ? () => void : (payload: NexusEvents[T]) => void): this; offEvent(event: T, listener: NexusEvents[T] extends undefined ? () => void : (payload: NexusEvents[T]) => void): this; } declare const nexusEvents: TypedEventEmitter; /** * Event Bridge - Declarative routing from nexusEvents to Socket.IO. * * Modules register BridgeRules to automatically forward internal events * to Socket.IO rooms. This replaces manual `io.to().emit()` calls * with a centralized, auditable routing layer. */ declare class EventBridge { private rules; private listeners; private debouncer; private initialized; /** * Register a bridge rule. * If the bridge is already initialized, the listener is attached immediately. */ registerRule(rule: BridgeRule): void; /** * Remove all rules for a specific nexusEvent. */ removeRule(nexusEvent: string): void; /** * Initialize the bridge — attach all registered rules to nexusEvents. * Should be called after Socket.IO is ready. */ init(): void; /** * Destroy — remove all listeners and clear state. */ destroy(): void; /** * Get all registered rules (for debugging/introspection). */ getRules(): readonly BridgeRule[]; private attachRule; private routeToSocket; } declare const eventBridge: EventBridge; /** * Room naming utilities for Socket.IO real-time entities. * * Delegates to SDK entityRoom() for building room names. * Provides parsing utilities used only by the backend. */ /** * Checks whether a room name is a tenant-scoped entity room. * Format: `entity:{tenantId}:{module}.{entity}` */ declare function isEntityRoom(room: string): boolean; /** * Parses tenantId, module and entity from a room name. * Returns null if room is not an entity room or is malformed. */ declare function parseEntityRoom(room: string): { tenantId: string; module: string; entity: string; } | null; /** * Root path of the nexus-backend library. * - Dev: /project/nexus-backend/ * - Lib: /user-project/node_modules/@gzl10/nexus-backend/ */ declare function getLibPath(): string; /** * Root path of the user project (process.cwd). * - Dev: matches getLibPath() * - Lib: /user-project/ */ declare function getProjectPath(): string; /** * Searches for .env by walking up from cwd (max 5 levels). * Useful for monorepos where .env is at the root. */ declare function findEnvFile(): string | null; /** * Proxy that delegates to the current logger. * Allows hot-swapping when the module initializes. */ declare const logger: pino.Logger; declare const createChildLogger: (context: string) => pino.Logger; /** * Query filter helpers. * * Shared logic for applying filters to Knex queries. * Used by both KnexAdapter and BaseService for DRY. */ /** * Apply filters to a Knex query builder. * * Supports: * - Simple equality: `{ name: 'John' }` * - Operators: `{ age: { $gte: 18 } }` * - Array shorthand for $in: `{ status: ['active', 'pending'] }` * - Null check: `{ deletedAt: null }` * * @example * let qb = db('users') * qb = applyFilters(qb, { status: 'active', age: { $gte: 18 } }) */ declare function applyFilters(qb: Knex.QueryBuilder, filters: Record): Knex.QueryBuilder; /** * @module @gzl10/nexus-backend * @description Express server with auto-generated CRUD, CASL authorization, and module system * * @exports start - Start the server with StartOptions * @exports stop - Graceful shutdown * @exports registerModule - Register a module manifest * @exports registerPlugin - Register a plugin with multiple modules * @exports BaseEntityService - Base class for entity services (extend for custom logic) * @exports CollectionService - Service for collection entities (full CRUD) * @exports TreeService - Service for hierarchical entities (CRUD + move) * @exports DagService - Service for multi-parent graph entities * @exports defineAbilityFor - CASL ability factory for permissions * @exports nexusEvents - Event emitter for server lifecycle and DB changes * @exports getIO - Get Socket.IO server instance for real-time features */ declare const version: string; export { type Actions$1 as Actions, type AppAbility, type AppManifest, type AuditEntry, type AuditLogRow, type AuditQuery, type AuditService, BaseEntityService, type BatchReporter, type CaslRulesFunction, CollectionService, ComputedService, SingleService as ConfigService, DagService, type DbEventPayload, type EntityController, type EntityHandler, type EntityRuntime, type EntityService, CollectionService as EventService, ExternalService, type HonoApp, type JwtPayload, NEXUS_CLIENT_HEADER, type NexusConfig, type NexusContext, type NexusEnv, type NexusEventName, type NexusEventPayload, type NexusEvents, type NexusMiddleware, type RateLimitOptions, CollectionService as ReferenceService, type RefreshToken, type ResolvedConfig, type Role, type RoleWithCounts, type SchedulesApi, type ServeSPAFunction, type ServeSPAOptions, SingleService, type SpaEntry, type StartOptions, type SubjectRegistry, type SubjectStrings, type Subjects, CollectionService as TempService, type TokenPair, TreeService, type User, type UserPresence, type UserWithRoles, type UserWithoutPassword, ViewService, ComputedService as VirtualService, applyFilters, closeSocketIO, createBatchReporter, createChildLogger, createEntityService, createModuleServices, createNexusClientMiddleware, createRateLimit, db, defineAbilityFor, destroyDb, discoverModules, discoverPlugins, eventBridge, extractModuleManifests, extractPluginManifest, findEnvFile, getConfig, getConnectedUsers, getCoreManifest, getCoreModules, getDatabaseType, getDb, getIO, getLibPath, getModule, getModules, getOrderedModules, getPlugin, getPlugins, getProjectPath, getRegisteredSubjects, getServiceKey, getUserManifest, getUserModules, getUserSocketCount, hasModule, hasPlugin, hasUserApp, initSocketIO, isEntityRoom, isModuleManifest, isPluginManifest, isRunning, isSocketIOInitialized, isUserConnected, isValidSubject, loadCoreModules, loadNexusConfig, logger, nexusEvents, packRules, parseEntityRoom, registerModule, registerPlugin, requireAbility, requireNexusClient, restart, start, stop, streamSse, unpackRules, validateBody as validate, version };