ObjectStackObjectStack

Plugin Lifecycle Advanced

Plugin Lifecycle Advanced protocol schemas

Advanced Plugin Lifecycle — host-driven library vocabularies

Declares the INPUT contracts of the host-driven lifecycle classes exported by @objectstack/corePluginHealthMonitor (reads PluginHealthCheck, emits PluginHealthStatus / PluginHealthReport) and HotReloadManager (reads HotReloadConfig, snapshots via PluginStateSnapshot). The kernel does not construct either class: a HOST composes them and passes these shapes directly (content/docs/protocol/kernel/lifecycle.mdx, the #11811 examples, is the supported usage).

This module deliberately declares NO authorable configuration surface — see the #11825 retirement record below.

Source: packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts

TypeScript Usage

import { HotReloadConfigSchema, PluginHealthCheckSchema, PluginHealthReportSchema, PluginHealthStatusSchema, PluginStateSnapshotSchema } from '@objectstack/spec/kernel';
import type { HotReloadConfig, PluginHealthCheck, PluginHealthReport, PluginHealthStatus, PluginStateSnapshot } from '@objectstack/spec/kernel';

// Validate data
const result = HotReloadConfigSchema.parse(data);

HotReloadConfig

Properties

PropertyTypeRequiredDescription
enabledbooleanoptional (default: false)
watchPatternsneveroptional[REMOVED] HotReloadConfig.watchPatterns was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove) — nothing ever read it. Its only two uses were log lines in HotReloadManager, and one of them announced 'File watching started' at INFO level while no watcher was ever constructed: startWatching held a placeholder, and watchHandles was read, deleted, iterated and cleared but never set. So an author could declare a glob and no file change could ever trigger a reload. Delete the key. File watching is the HOST's job in this host-driven library: run your own watcher, declare your globs wherever that watcher reads them, and call HotReloadManager.scheduleReload(pluginName, reloadFn) when one matches — the debounced integration point this class does implement, and which is unchanged.
debounceDelayintegeroptional (default: 1000)Wait time after change detection before reload
preserveStatebooleanoptional (default: true)Keep plugin state across reloads
stateStrategyEnum<'memory' | 'none'>optional (default: "memory")How to preserve state during reload
shutdownTimeoutintegeroptional (default: 30000)Maximum time to wait for graceful shutdown
beforeReloadstring[]optionalHook names to call before reload
afterReloadstring[]optionalHook names to call after reload

PluginHealthCheck

Properties

PropertyTypeRequiredDescription
intervalintegeroptional (default: 30000)How often to perform health checks (default: 30s)
timeoutintegeroptional (default: 5000)Maximum time to wait for health check response
failureThresholdintegeroptional (default: 3)Consecutive failures needed to mark unhealthy
successThresholdintegeroptional (default: 1)Consecutive successes needed to mark healthy
checkMethodstringoptionalMethod name to call for health check
autoRestartneveroptional[REMOVED] PluginHealthCheck.autoRestart was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove) — it never restarted a plugin.A PluginHealthMonitor never restarted anything. attemptRestart called plugin.destroy() and stopped there — the in-source comment said "Call destroy and init to restart", but init appeared in health-monitor.ts ONLY inside that comment. What a plugin actually got was: destroy, a log line reading 'Plugin restarted', status recovering, and periodic health checks continuing against the destroyed instance — which the default check ({ name: 'plugin-loaded', status: 'passed' }, used whenever no checkMethod resolves) passes forever, so the terminal report on a destroyed, never-re-initialised plugin was healthy. Delete the key. Restarting a plugin is the HOST's job in this host-driven library, and the monitor could not do it even in principle: Plugin.init(ctx) needs a PluginContext, which only the kernel constructs and which it exposes to nobody (ObjectKernel.context is private; KernelBase.createContext is protected). Poll getHealthStatus(pluginName) / getHealthReport(pluginName) and act on unhealthy / failed at the level that owns the plugin's lifetime — recreate the kernel, or let your supervisor restart the process. The monitor reports; it does not act.
maxRestartAttemptsneveroptional[REMOVED] PluginHealthCheck.maxRestartAttempts was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove) — it capped a restart that never happened.A PluginHealthMonitor never restarted anything. attemptRestart called plugin.destroy() and stopped there — the in-source comment said "Call destroy and init to restart", but init appeared in health-monitor.ts ONLY inside that comment. What a plugin actually got was: destroy, a log line reading 'Plugin restarted', status recovering, and periodic health checks continuing against the destroyed instance — which the default check ({ name: 'plugin-loaded', status: 'passed' }, used whenever no checkMethod resolves) passes forever, so the terminal report on a destroyed, never-re-initialised plugin was healthy. The cap counted destroy calls, so raising it only scheduled further "restarts" of a plugin that was never brought back up. Delete the key. Restarting a plugin is the HOST's job in this host-driven library, and the monitor could not do it even in principle: Plugin.init(ctx) needs a PluginContext, which only the kernel constructs and which it exposes to nobody (ObjectKernel.context is private; KernelBase.createContext is protected). Poll getHealthStatus(pluginName) / getHealthReport(pluginName) and act on unhealthy / failed at the level that owns the plugin's lifetime — recreate the kernel, or let your supervisor restart the process. The monitor reports; it does not act.
restartBackoffneveroptional[REMOVED] PluginHealthCheck.restartBackoff was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove) — it delayed a restart that never happened.A PluginHealthMonitor never restarted anything. attemptRestart called plugin.destroy() and stopped there — the in-source comment said "Call destroy and init to restart", but init appeared in health-monitor.ts ONLY inside that comment. What a plugin actually got was: destroy, a log line reading 'Plugin restarted', status recovering, and periodic health checks continuing against the destroyed instance — which the default check ({ name: 'plugin-loaded', status: 'passed' }, used whenever no checkMethod resolves) passes forever, so the terminal report on a destroyed, never-re-initialised plugin was healthy. The chosen strategy only moved when the destroy landed. Delete the key. Restarting a plugin is the HOST's job in this host-driven library, and the monitor could not do it even in principle: Plugin.init(ctx) needs a PluginContext, which only the kernel constructs and which it exposes to nobody (ObjectKernel.context is private; KernelBase.createContext is protected). Poll getHealthStatus(pluginName) / getHealthReport(pluginName) and act on unhealthy / failed at the level that owns the plugin's lifetime — recreate the kernel, or let your supervisor restart the process. The monitor reports; it does not act.

PluginHealthReport

Properties

PropertyTypeRequiredDescription
statusEnum<'healthy' | 'degraded' | 'unhealthy' | 'failed' | 'recovering' | 'unknown'>Current health status of the plugin
timestampstring
messagestringoptional
metrics{ uptime?: number; memoryUsage?: number; cpuUsage?: number; activeConnections?: number; … }optional
checks{ name: string; status: Enum<'passed' | 'failed' | 'warning'>; message?: string; data?: Record<string, any> }[]optional
dependencies{ pluginId: string; status: Enum<'healthy' | 'degraded' | 'unhealthy' | 'failed' | 'recovering' | 'unknown'>; message?: string }[]optional

Nested Shape: PluginHealthReport.metrics

PropertyTypeRequiredDescription
uptimenumberoptionalPlugin uptime in milliseconds
memoryUsagenumberoptionalMemory usage in bytes
cpuUsagenumberoptionalCPU usage percentage
activeConnectionsnumberoptionalNumber of active connections
errorRatenumberoptionalError rate (errors per minute)
responseTimenumberoptionalAverage response time in ms

Nested Shape: PluginHealthReport.checks[number]

PropertyTypeRequiredDescription
namestringCheck name
statusEnum<'passed' | 'failed' | 'warning'>
messagestringoptional
dataRecord<string, any>optional

Nested Shape: PluginHealthReport.dependencies[number]

PropertyTypeRequiredDescription
pluginIdstring
statusEnum<'healthy' | 'degraded' | 'unhealthy' | 'failed' | 'recovering' | 'unknown'>Current health status of the plugin
messagestringoptional

PluginHealthStatus

Current health status of the plugin

Allowed Values

  • healthy
  • degraded
  • unhealthy
  • failed
  • recovering
  • unknown

PluginStateSnapshot

Properties

PropertyTypeRequiredDescription
pluginIdstring
versionstring
timestampstring
stateRecord<string, any>
metadata{ checksum?: string; compressed: boolean; encryption?: string }optional

Nested Shape: PluginStateSnapshot.metadata

PropertyTypeRequiredDescription
checksumstringoptionalState checksum for verification
compressedbooleanoptional (default: false)
encryptionstringoptionalEncryption algorithm if encrypted

On this page