v0.1.0: consolidate intermediate tags into single fresh release

Wipes v0.1.0/v0.1.1 history.  fpc-cron hadn't shipped to any
external consumer yet; the rebuild against fpc-db v0.4.0 was
inline development noise.  This commit + tag is the clean public
starting point.

CRON_VERSION reset to 0.1.0.  CHANGELOG rewritten as a single
0.1.0 entry covering the full library surface as it stands now.
This commit is contained in:
2026-05-06 11:32:25 -07:00
parent a3278c755f
commit ca1243c396
2 changed files with 107 additions and 190 deletions

View File

@@ -10,218 +10,135 @@ Semver intent:
- **minor** — additive features
- **patch** — bug fixes, internal cleanups
## 0.1.1 — 2026-05-06
## 0.1.0 — 2026-05-06
Rebuild against fpc-db **v0.4.0** (namespace rename `db.*`
`database.*`). No behaviour change. 73/73 assertions still pass.
### Changed
- `uses` clauses across `cron.runner` / tests / examples /
docs swept from `db.{types,pool,dialect,schema}` to
`database.{types,pool,dialect,schema}`. See fpc-db v0.4.0
changelog for the full mapping and rationale.
### Action for consumers
Pin against fpc-cron v0.1.1 + fpc-db v0.4.0 together, OR stay
on fpc-cron v0.1.0 + fpc-db v0.3.0. Mixing across the boundary
won't compile.
## 0.1.0 — 2026-05-05
Initial release. Verbatim port of Fastway-Server's `TFWScheduler`
class from `fw_scheduler.pas` per `feedback_copy_dont_reinterpret.md`.
Method bodies copied line-by-line from canonical with only the
documented mechanical translations applied; the cron parser pair
(`ParseCronField` / `MatchesCron`) is byte-identical to canonical
after type-rename normalisation.
Initial public release. Cron + interval task runner extracted
from canonical Fastway `TFWScheduler` (`fw_scheduler.pas`,
1093 lines) per `feedback_copy_dont_reinterpret.md`. Cron
parser bodies are byte-identical to canonical after the
type-rename normalisation.
### Added
- `cron.runner.TCron` cron+interval task runner thread (port of
`TFWScheduler`, all 1093 lines of canonical `fw_scheduler.pas`).
- `cron.types` unit with `TCronTaskKind`, `TCronTask`,
`TRunTaskProc`, `TGetExtraTasksFunc`, `TSystemTaskProc`.
(Logger type is `log.types.TLogProc` from fpc-log — the
canonical ecosystem-wide logger shape.)
- `cron.events` unit with `TCronOnTaskStart` /
`TCronOnTaskComplete` / `TCronOnTaskRegistered` /
`TCronOnPluginOrphaned` / `TCronOnThreadStart` /
`TCronOnThreadStop` typed observer callbacks. Same
per-library typed-callback shape as fpc-binkp's `bp.events`
and fpc-comet's `cm.events`.
- `cron.runner.BuildSystemSchedulerSpec(ANowExpr)` and
`BuildSchedulerLogSpec` — public fpc-db `TDBTable` specs for
the two runner-owned tables. Mirrors canonical Fastway's
`fw_schema.pas` `BuildSystemScheduler` / `BuildSchedulerLog`.
- `cron.version` unit with `CRON_VERSION_MAJOR/MINOR/PATCH/STRING`.
- `fpc.cfg` — multi-target FPC config so consumers can build
with `-Fucfg=fpc.cfg`.
- `docs/API.md` — full callable reference.
- `docs/architecture.md` — layers + dependency graph + DB
schema + threading model + integration with sibling fpc-*
libraries.
- `docs/DEVELOPER_GUIDE.md` — consumer-oriented walkthrough
covering lifecycle, observer callbacks, logger plumbing,
cron grammar, and the bridging pattern to fpc-events.
- `cron.runner.TCron` — task runner thread, public surface:
- `Create(APool, ARunTask=nil, AGetExtraTasks=nil, ALogger=nil)`
— constructs in suspended state; declares schema, loads
tasks, runs `SyncPluginTasks`.
- `RegisterSystemTask(AName, AProc)` — register a callback
for a `system/<name>` task (replaces canonical's
hardcoded case-table).
- `RefreshTasks` / `RunTaskNow(ATaskID)`.
- `GetTasksJSON: TJSONArray` / `GetTaskJSON(ATaskID): TJSONObject`.
- `UpdateTask(ATaskID, AUpdates): Boolean` — apply enabled /
schedule_type / interval_seconds / cron_expr changes,
persists to DB.
- `Running: Boolean`.
- `class function ParseCronField(AField, AMin, AMax): TBits`
- `class function MatchesCron(ACronExpr, ATime): Boolean`
- Typed observer properties: `OnTaskStart` / `OnTaskComplete`
/ `OnTaskRegistered` / `OnPluginOrphaned` / `OnThreadStart`
/ `OnThreadStop`.
- `cron.types``TCronTaskKind`, `TCronTask`, `TRunTaskProc`,
`TGetExtraTasksFunc`, `TSystemTaskProc`.
- `cron.events` — typed observer callback types
(`TCronOnTaskStart`, ...) following the bp.events / cm.events
pattern.
- `cron.runner.BuildSystemSchedulerSpec(ANowExpr): TDBTable`
and `BuildSchedulerLogSpec: TDBTable``database.TDBTable`
specs mirroring canonical Fastway `fw_schema.pas`'s
`BuildSystemScheduler` / `BuildSchedulerLog`.
- `cron.version``CRON_VERSION` semver constants.
### Adjustments from canonical (the only behaviour-preserving
changes)
| Canonical | fpc-cron |
|----------------------------|--------------------------|
| `TFWScheduler` | `TCron` |
| `TFWSchedulerTask` | `TCronTask` |
| `TFWSchedulerTaskKind` | `TCronTaskKind` |
| `TRunPluginTaskProc` | `TRunTaskProc` |
| `TGetPluginTasksFunc` | `TGetExtraTasksFunc` |
| Canonical | fpc-cron |
|------------------------|------------------------|
| `TFWScheduler` | `TCron` |
| `TFWSchedulerTask` | `TCronTask` |
| `TFWSchedulerTaskKind` | `TCronTaskKind` |
| `TRunPluginTaskProc` | `TRunTaskProc` |
| `TGetPluginTasksFunc` | `TGetExtraTasksFunc` |
- **`uses` clause:** dropped `fw_consts`, `fw_log`, `fw_config`,
`fw_database`, `fw_plugin_api`, `fw_plugin_updates`,
`fw_plugin_host`, `dbapi_consts`, `dbapi_dialect`. Added
`log.types` (fpc-log), `database.dialect`, `database.schema`, `database.pool`
(fpc-db v0.3.0), and the local `cron.types` / `cron.events`.
fpc-cron does NOT depend on fpc-events — events are emitted
as typed observer callbacks per the per-library convention.
- **TDBPool dependency injection:** the canonical reaches a
global `DB` (Fastway's `TFWDatabase`). fpc-cron takes a
`TDBPool` parameter on `Create` instead. This is the ENTIRE
adjustment to LoadTasksFromDB / SyncPluginTasks /
UpdateTaskInDB / LogTaskRun — they still hit the database
directly, just through `FPool` instead of `DB`. Specifically:
- `Q := DB.NewQuery; Q.SQL.Text := X; Q.Open; ...; DB.FreeQuery(Q)`
becomes `PQ := FPool.QuerySQL(X); try ...; finally PQ.Free; end`.
- `Q.ExecSQL; DB.Transaction.Commit` becomes
`FPool.ExecSQL(SQL, Params)`.
- `DB.Dialect.InsertOrIgnoreStmt(...)` becomes
`FPool.InsertIgnore(table, cols, vals)`.
- `DB.Dialect.NowExpr` becomes `FPool.Dialect.NowExpr`.
- **Events: typed observer callbacks instead of EventBus.**
Canonical reaches the global `EventBus` and fires
`'_system' / 'scheduler.task_start'` (line 532) +
`'_system' / FW_EVENT_SCHEDULER_RESULT` (line 609).
fpc-cron exposes typed observer callbacks (`OnTaskStart` /
`OnTaskComplete` / `OnTaskRegistered` / `OnPluginOrphaned` /
`OnThreadStart` / `OnThreadStop`, assignable as properties on
`TCron`) — same per-library typed-callback pattern as
fpc-binkp's `bp.events` and fpc-comet's `cm.events`.
Consumers wanting ecosystem-wide pub/sub fan-out write a
small bridge class that forwards selected callbacks to a
fpc-events `TEventBus`.
- **Logging:** `fw_log.Log.Info/Warning/Error/Debug(...)` calls
routed through an optional `log.types.TLogProc` callback
via the internal `DoLog(level, msg)` shim, with
`Category='cron'`. Default `nil` = silent. Same logger shape
every other fpc-* library uses.
- **System-task dispatch:** canonical's hardcoded `case
ATaskName of` over Fastway-specific names (`log_cleanup`,
`db_vacuum`, `wal_checkpoint`, `config_watch`,
`plugin_update_check`, `old_news_cleanup`, etc.) becomes a
consumer-registered table. `RegisterSystemTask(name, proc)`
adds an entry; the dispatch shape (linear lookup, fallback to
`'unknown system task'` warning) is identical to canonical.
- **`DoWalCheckpointTruncate` lifted out.** Top-level procedure
in canonical (line 616) is SQLite + Fastway specific. Stays
in Fastway-Server. fpc-cron consumers register their own
checkpoint task via `RegisterSystemTask` if needed.
- **No global `Scheduler` singleton.** Canonical exports `var
Scheduler: TFWScheduler;` for app-wide access; fpc-cron
consumers create their own instance.
- **`ParseCronField` / `MatchesCron` widened to
`class function`.** Canonical has them as private instance
methods; their bodies never reference `Self`, so promoting
them to class functions lets pure-cron tests run without
spinning up a full `TCron` instance. Bodies are byte-identical
to canonical (verified by normalised diff).
- `uses` clause swaps: drops `fw_consts` / `fw_log` / `fw_config`
/ `fw_database` / `fw_plugin_api` / `fw_plugin_updates` /
`fw_plugin_host` / `dbapi_consts` / `dbapi_dialect`; adds
`log.types` (fpc-log), `database.dialect` / `database.schema`
/ `database.pool` (fpc-db v0.1.0), `cron.types` /
`cron.events`.
- Global `DB` replaced with `TDBPool` parameter on `Create`.
- `EventBus.Fire(...)` calls replaced with typed observer
callbacks (assignable as properties on `TCron`). Same
per-library typed-callback pattern as fpc-binkp's
`bp.events` and fpc-comet's `cm.events`.
- `fw_log.Log.X(...)` calls routed through optional
`log.types.TLogProc` callback with `Category='cron'`.
- `DoWalCheckpointTruncate` (SQLite + Fastway specific) and
the hardcoded `case ATaskName` system-task table lifted out
— consumers register their own system tasks via
`RegisterSystemTask`.
- No global `Scheduler` singleton — consumer creates own.
- `ParseCronField` / `MatchesCron` promoted from private
instance methods to `class function` for testability;
bodies byte-verbatim from canonical.
### Behaviours preserved verbatim (per the COPY rule)
Verified by normalised diff (type renames + log-call swaps
applied to canonical, then `diff -u` vs port — zero behavioural
diff after normalisation):
- `LoadTasksFromDB` / `SyncPluginTasks` / `UpdateTaskInDB` /
`LogTaskRun` hit the database directly. Looks like it should
be inverted into consumer callbacks; canonical doesn't, so we
don't.
`LogTaskRun` hit the database directly.
- 5-position cron parser (`*`, `*/N`, `a,b,c`, `a-b`, `a-b/N`,
single literals). Body byte-verbatim to canonical including
the `{ Check for step }` / `{ Every value with step }` /
`{ Check for range }` / `{ Single value }` inline comments.
- `MatchesCron` body byte-verbatim including the
`DecodeDateFully(ATime, Mn, Hr, Dom, Dow)` call (whose result
is overwritten by the next four calls — cosmetic, but kept
verbatim per COPY rule).
- Cron expression interpretation in *local* time, conversion to
UTC at storage time.
- `UTCNow = LocalTimeToUniversal(Now)` for all stored timestamps.
literal).
- Cron expressions in *local* time; UTC at storage time.
- `UTCNow = LocalTimeToUniversal(Now)` for every persisted
timestamp.
- Schedule-miss-on-long-task: next firing is delay AFTER
previous run *returned*.
- TThread + PRTLEvent + 1-second wake loop.
- Schedule-miss-on-long-task semantics: next firing is delay
AFTER previous run returned, not after it started.
- Pool-bound schema reconciliation: `system_scheduler` and
`scheduler_log` are declared in `Create`.
- Event names fired through the optional bus:
`scheduler.task_start` (literal, canonical line 532) and
`scheduler.task_result` (= `FW_EVENT_SCHEDULER_RESULT` =
`'scheduler.task_result'`, canonical line 609). Source plugin
name `'_system'`. All literal-inlined to match canonical.
- Suspended-Create + `.Start`.
- SyncPluginTasks orphan cleanup.
- Event names through bus bridge: `'scheduler.task_start'` and
`'scheduler.task_result'` (canonical literals).
- DB table names `system_scheduler` and `scheduler_log` kept
verbatim from canonical so existing `fw_scheduler.pas`
databases can be reused.
- `TCronTask` in-memory record shape (every field, every type).
- `GetTasksJSON` / `GetTaskJSON` / `UpdateTask` JSON shapes
(every field, every name).
verbatim (Fastway database is reusable as-is).
### Known issues inherited verbatim
- `MatchesCron` allocates five `TBits` instances before its
`try`/`finally`; if a later `ParseCronField` raises, earlier
`TBits` instances leak. Per `feedback_copy_dont_reinterpret.md`
this is preserved as-is. Real-world impact is negligible
(cron expressions reaching `MatchesCron` come from the DB and
are validated upstream); flagged here for future cleanup.
- `MatchesCron` allocates the five `TBits` field-bit-arrays
before its `try`/`finally`; if `ParseCronField` raises on
field 2..5, earlier `TBits` instances leak. Negligible
real-world impact.
- `MatchesCron` calls `DecodeDateFully(ATime, Mn, Hr, Dom, Dow)`
whose results are overwritten by the immediately-following
`MonthOf`/`DayOf`/`HourOf`/`MinuteOf`/`DayOfTheWeek` calls.
Pure cosmetic; canonical has it, so we have it.
### Notes for downstream consumers
- `Create` is constructed *suspended* (canonical
`inherited Create(True)`). Caller must call `C.Start` to
begin the wake loop.
- `Create` runs `Pool.DeclareTable` for both runner tables.
Two runners sharing a pool both declare; the second is a
no-op (DeclareTable is idempotent).
- `SyncPluginTasks` *frees* the `TJSONArray` returned by the
supplier callback. Consumers must not retain it.
- Typed observer callbacks fire synchronously on the runner
thread; heavy work in `OnTaskStart` / `OnTaskComplete` delays
subsequent dispatches.
whose results are immediately overwritten by the next four
calls. Cosmetic.
### Tests
- `tests/test_cron.pas` — 37 assertions covering ParseCronField
for `*` / `*/N` / `a,b,c` / `a-b` / `a-b/N` / single literals
/ out-of-range, and MatchesCron for every-minute / top-of-hour
/ daily-3am / hourly-:30 / weekday-9-17 / every-15m / Jan-1-
midnight / Sundays-at-midnight / malformed-expr.
- `tests/test_runner.pas` — 36 assertions covering Init,
NextRun-recalculation, RunTaskNow, SyncPluginTasks-registers,
SyncPluginTasks-removes-orphans, IntervalFires, SystemTask-
Fires, SystemTaskFailureRecorded, UpdateTask-disables,
RefreshTasks, typed observer callbacks (OnTaskStart +
OnTaskComplete) fire, cron task executes through the runner
(run_count + last_run + NextRun-recalc), and
two-runners-on-one-pool (idempotent DeclareTable).
73 assertions across 23 scenarios:
Total: **73 assertions across 23 scenarios.** All passing on
x86_64-linux against fpc-log + fpc-db v0.3.0.
- `tests/test_cron.pas` — 37 pure-cron assertions.
- `tests/test_runner.pas` — 36 SQLite-backed end-to-end:
Init, NextRun-recalc, RunTaskNow, SyncPluginTasks add +
orphan, IntervalFires, SystemTaskFires,
SystemTaskFailureRecorded, UpdateTask-disables,
RefreshTasks, typed observer callbacks fire, cron task
executes through runner, two runners on one pool
(idempotent DeclareTable).
### Examples
- `examples/interval_task.pas` — schedule a 5-second interval
task, run for ~12 seconds, log every firing.
- `examples/cron_task.pas` — exercise `MatchesCron` for a daily
3 AM schedule and persist a cron task in `system_scheduler`.
- `examples/interval_task.pas` — 5-second interval task.
- `examples/cron_task.pas` — daily 3 AM cron task.
### Build and test
```
bash build.sh # compile every src/*.pas
bash run_tests.sh # build + tests + examples
```
`fpc.cfg` provides multi-target FPC config.
### Dependencies
- fpc-log v0.1.0 — `log.types.TLogProc`
- fpc-db v0.1.0 — `database.{dialect,schema,pool}`

View File

@@ -11,8 +11,8 @@ interface
const
CRON_VERSION_MAJOR = 0;
CRON_VERSION_MINOR = 1;
CRON_VERSION_PATCH = 1;
CRON_VERSION_STRING = '0.1.1';
CRON_VERSION_PATCH = 0;
CRON_VERSION_STRING = '0.1.0';
implementation