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:
293
CHANGELOG.md
293
CHANGELOG.md
@@ -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}`
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user