Shared State and Requirements
Use shared state when plugins need to expose runtime objects to each other without adding another client.* property.
import { createPlugin, createSharedKey } from 'seyfert';
class LedgerService {
readBalance(userId: string) {
return 100;
}
close() {}
}
// curried call carries the value type on the key
export const ledgerKey = createSharedKey<LedgerService>()('ledger');
export const ledgerPlugin = createPlugin({
name: 'ledger',
register(api) {
// share a lazily-built service under the key
api.shared.set(ledgerKey, () => new LedgerService(), {
// dispose runs when this plugin tears down
dispose: (ledger) => ledger.close(),
});
},
});The curried createSharedKey<LedgerService>()('ledger') carries the value type on the key. Shared values resolve lazily on first access:
// get returns undefined when missing
const ledger = client.shared.get(ledgerKey);
// unwrap throws when missing
const requiredLedger = client.shared.unwrap(ledgerKey);get(...) returns undefined when a value is missing. unwrap(...) throws when it is missing. client.shared.has(key) checks without building it. The optional { dispose } runs when the owning plugin tears down.
Typed Shared Names
For app-wide shared names, augment RegisteredPluginShared.
import type { RegisteredPluginShared } from 'seyfert';
declare module 'seyfert' {
// map an app-wide shared name to its value type
interface RegisteredPluginShared {
ledger: LedgerService;
}
}Now createSharedKey('ledger') is typed, and string-based access works too:
client.shared.get('ledger'); // LedgerService | undefined
client.shared.unwrap('ledger'); // LedgerServiceShared names are claimed once. If two plugins register the same name, Seyfert throws an attributed conflict — unless the later one passes { override: true }.
Requirements
Use imports when your plugin brings another plugin with it. Use requires when your plugin expects another plugin — or a shared capability — to already be present.
const storage = storagePlugin();
export const economy = createPlugin({
name: 'economy',
// bring storage along and order economy after it
imports: [storage],
requires: [
// must be present
'plugin:storage',
// tolerated if absent
{ req: 'plugin:redis', optional: true },
// present within a semver range
{ req: 'plugin:storage', range: '^2.0.0' },
// a shared capability must exist
{ capability: ledgerKey },
],
register(api) {
// feature-check an optional dependency at runtime
if (api.has('plugin:redis')) {
api.diagnostics.warn('Redis support enabled.', { code: 'redis-enabled' });
}
},
});Rules:
importsaffects ordering and dedupes the same plugin instance.requiresvalidates that a requirement is present.- a missing required requirement throws during plugin resolution.
- a missing optional requirement creates a diagnostics warning.
- requirement shapes:
plugin:<name>,{ req, range }for a semver range, or{ capability: sharedKey }for a shared value.
requires is not a priority system. If your plugin must run after another plugin and you own that relationship, put the dependency in imports.
Diagnostics Warnings
Use api.diagnostics.warn(...) for degraded but usable states.
register(api) {
// record a warning for a degraded-but-usable state
api.diagnostics.warn('Optional package "redis" was not found.', {
code: 'missing-optional-peer',
});
}Warnings appear in client.plugins.diagnostics with plugin name, index, phase, code, and message.