Files
fpc-events/docs/API.md
Ken Johnson 128ddcb4df v0.1.0: thread-safe pub/sub event bus
Verbatim port of Fastway-Server's TFWEventBus from fw_plugin_host.pas
per feedback_copy_dont_reinterpret.md.  Adjustments limited to:

  - Type renames (TFW* -> T*).
  - uses clause: drop fw_log; add log.types from fpc-log so the
    optional Logger property uses the canonical ecosystem-wide
    TLogProc shape, matching every other fpc-* library.
  - Per-handler exception logging now calls Logger with
    Level=llError, Category='events', and includes the source
    plugin (ASourcePlugin parameter) in the message text so the
    canonical signature stays meaningful.

Behaviours preserved verbatim: APluginName bulk-Unsubscribe key,
wildcard '*' subscriber, OnBroadcast external-listener tap,
snapshot-iterate-outside-lock pattern, per-handler exception
isolation, TCriticalSection.

docs/DEVELOPER_GUIDE.md added covering threading, payload
ownership, recursive Fire, OnBroadcast, logger plumbing, and
the relationship between fpc-events (ecosystem-wide pub/sub)
and per-library typed observer callbacks (bp.events / cm.events
pattern).

Tests: 44 assertions across 14 scenarios pass on x86_64-linux.
Pre-tag -vh audit on src/ev.bus.pas reports zero hints/warnings.
2026-05-05 18:13:10 -07:00

198 lines
5.0 KiB
Markdown

# fpc-events — API reference
Every callable the library exposes, with parameters, return values,
and runnable examples. See [`architecture.md`](architecture.md)
for the big picture and [`DEVELOPER_GUIDE.md`](DEVELOPER_GUIDE.md)
for the consumer-oriented walkthrough.
## Contents
- [Quick start](#quick-start)
- [Types (`events.bus`)](#types)
- [TEventBus](#teventbus)
- [Subscribe](#teventbussubscribe)
- [Unsubscribe](#teventbusunsubscribe)
- [UnsubscribeCallback](#teventbusunsubscribecallback)
- [Fire](#teventbusfire)
- [GetSubscriptionCount](#teventbusgetsubscriptioncount)
- [OnBroadcast (property)](#teventbusonbroadcast)
- [Logger (property)](#teventbuslogger)
- [Version constants](#version-constants)
---
## Quick start
```pascal
uses
Classes, SysUtils, fpjson,
log.types,
events.bus;
var
Bus: TEventBus;
Data: TJSONObject;
begin
Bus := TEventBus.Create;
try
Bus.Subscribe('demo', 'user.login', @MyHandler);
Data := TJSONObject.Create;
try
Data.Add('username', 'alice');
Bus.Fire('auth', 'user.login', Data);
finally
Data.Free;
end;
finally
Bus.Free;
end;
end.
```
## Types
```pascal
type
TEventCallback = procedure(const AEventType: string;
AData: TJSONObject) of object;
TEventBroadcast = procedure(const AEventType: string;
AData: TJSONObject) of object;
TEventSubscription = record
PluginName: string;
EventType: string;
Callback: TEventCallback;
end;
```
The optional `Logger` property is typed as `log.types.TLogProc`
(from fpc-log) — the canonical ecosystem-wide logger shape.
## TEventBus
Constructed with the parameterless `Create` constructor:
```pascal
Bus := TEventBus.Create;
try
...
finally
Bus.Free;
end;
```
Thread-safe via a single `TCriticalSection`. The `Free` call
releases the subscription array and the lock; outstanding
subscribers' method pointers are released but their *targets*
(the consumer's class instances) are never freed by the bus.
### `TEventBus.Subscribe`
```pascal
procedure Subscribe(const APluginName, AEventType: string;
ACallback: TEventCallback);
```
Register `ACallback` for events whose type equals `AEventType`,
or for *every* event when `AEventType = '*'`. `APluginName` is
a free-form group key used by [`Unsubscribe`](#teventbusunsubscribe);
pass `''` if your consumer doesn't need bulk removal.
`Subscribe` does NOT deduplicate — registering the same callback
twice produces two entries.
### `TEventBus.Unsubscribe`
```pascal
procedure Unsubscribe(const APluginName: string);
```
Remove every subscription whose `PluginName` matches
`APluginName` (case-insensitive). Use this when a logical
subsystem owns N subscriptions and is shutting down.
### `TEventBus.UnsubscribeCallback`
```pascal
procedure UnsubscribeCallback(ACallback: TEventCallback);
```
Remove every subscription whose method pointer (Code+Data)
matches `ACallback`. Use this for per-handler removal.
### `TEventBus.Fire`
```pascal
procedure Fire(const ASourcePlugin, AEventType: string;
AData: TJSONObject);
```
Deliver `(AEventType, AData)` to every subscriber whose
`EventType` equals `AEventType` *or* `'*'`, then invoke
`OnBroadcast` once if assigned. `ASourcePlugin` is metadata —
included in handler-error log messages but not used for
delivery routing.
`AData` ownership stays with the caller. Subscribers may read
fields and copy values out, but must not call `AData.Free`.
If a subscriber callback raises, the exception is caught,
forwarded to the `Logger` (with `Level=llError`,
`Category='events'`), and delivery continues to the next
subscriber. Same for `OnBroadcast`.
### `TEventBus.GetSubscriptionCount`
```pascal
function GetSubscriptionCount: Integer;
```
Number of registered subscriptions. Acquires the lock; safe to
call from any thread.
### `TEventBus.OnBroadcast`
```pascal
property OnBroadcast: TEventBroadcast read FOnBroadcast write FOnBroadcast;
```
Single nullable callback fired *after* subscriber delivery on
every `Fire` call. Originally used by Fastway-Server to
forward every event to web-admin WebSocket clients.
A `nil` `OnBroadcast` (the default) means no broadcast.
### `TEventBus.Logger`
```pascal
property Logger: TLogProc read FLogger write FLogger;
```
Optional logger for handler-exception messages. `TLogProc` is
defined in `log.types` (fpc-log) — the same logger shape used
by every other fpc-* library.
When a subscriber callback or the `OnBroadcast` tap raises:
- `Logger(llError, 'events', 'Handler error in <plugin> for <type> (from <src>): <msg>')`
- (`OnBroadcast` errors omit the plugin field.)
If `Logger` is `nil` (the default), exceptions are silently
swallowed.
## Version constants
In `events.version`:
```pascal
const
EVENTS_VERSION_MAJOR = 0;
EVENTS_VERSION_MINOR = 1;
EVENTS_VERSION_PATCH = 0;
EVENTS_VERSION_STRING = '0.1.0';
```
Bumped together with the git tag. Pin downstream consumers by
tag, not commit hash.