Problem
Each interface package with drivers currently maintains three independently-hardcoded lists of its drivers:
NoDriverException::DRIVER_PACKAGES const — read at runtime when no driver is bound
- Each driver's
composer.json conflict block — read by Composer at install time
marko/skeleton's suggest block — read by Composer during create-project
Adopting a new driver requires editing all three locations across multiple files (e.g., adding marko/view-blade needs ~5 file edits). This is error-prone, and existing lists already drift from each other.
Solution
Introduce a curated known-drivers.php file per interface package as the single source of truth:
// packages/database/known-drivers.php
<?php
declare(strict_types=1);
return [
'marko/database-pgsql' => 'PostgreSQL driver (recommended for new projects — strong JSON, FTS, pgvector support)',
'marko/database-mysql' => 'MySQL/MariaDB driver',
];
NoDriverException reads from this file at runtime. CI tests mechanically enforce sync between this file, each driver's composer.json conflict block, and skeleton's suggest block. Adopting a new driver becomes ~3 edits with a CI test catching any missed step.
Scope
All-or-nothing rollout to every interface package with ≥1 driver:
- Multi-driver: cache, errors, filesystem, inertia, mail, media, pubsub, queue, session, view
- Single-driver: authentication, encryption, http, log, notification, translation, page-cache
- Pilot in
marko/database to prove the pattern; roll out to the rest in parallel.
Additional improvements
NoDriverException output includes derived docs URLs (https://marko.build/docs/packages/{basename}/) for each driver, opening in a new tab when rendered via marko/errors-advanced
marko/errors-advanced gains URL auto-linkification (currently strips URLs to plain text)
marko/errors-advanced starts rendering context and suggestion fields (currently only renders message)
marko/view test suite cleaned up to have zero dependency on marko/view-latte (move IntegrationTest to view-latte where it belongs)
marko/page-cache's outlier noBinding() factory renamed to noDriverInstalled() for consistency
- Skeleton's
suggest block expanded to comprehensively list all drivers + optional add-ons (database-readwrite, page-cache-entity) with recommended-first ordering per interface
- Sensible recommended defaults: Twig (view), pgsql (database), file (cache/session/log), simple (errors prod), gd (media), sync (queue dev), redis (pubsub), smtp (mail prod), react (inertia)
Mechanical rule
A package is a driver iff its module.php bindings array contains the interface's defining contract. Add-ons like marko/database-readwrite (empty bindings, conditional boot-callback override) and marko/page-cache-entity (observer-only bridge) are NOT enrolled in known-drivers.php — they appear in skeleton suggest only.
For v1, known-drivers.php is the curated source. Future enhancement (deferred): marker interfaces or extra.marko.driver_for declarations for mechanical enforcement.
Out of scope (separate concerns)
- Wiring
NoDriverException to actually be thrown when no driver is bound (pre-existing gap across all 18 packages — exceptions are defined but unused)
admin/NoDriverException cleanup (admin sub-modules aren't drivers; that exception is vestigial)
- Marker interfaces for mechanical driver enforcement
Implementation
Plan written and ready: .claude/plans/known-drivers-registry/ on branch feature/known-drivers-registry. 25 tasks, ~5 parallel batches. Devil's-advocate review applied.
This is WIP — depends on PR #88 (view-twig) being merged first so task 017 can be expanded to cover both view drivers.
Problem
Each interface package with drivers currently maintains three independently-hardcoded lists of its drivers:
NoDriverException::DRIVER_PACKAGESconst — read at runtime when no driver is boundcomposer.jsonconflictblock — read by Composer at install timemarko/skeleton'ssuggestblock — read by Composer duringcreate-projectAdopting a new driver requires editing all three locations across multiple files (e.g., adding
marko/view-bladeneeds ~5 file edits). This is error-prone, and existing lists already drift from each other.Solution
Introduce a curated
known-drivers.phpfile per interface package as the single source of truth:NoDriverExceptionreads from this file at runtime. CI tests mechanically enforce sync between this file, each driver's composer.jsonconflictblock, and skeleton'ssuggestblock. Adopting a new driver becomes ~3 edits with a CI test catching any missed step.Scope
All-or-nothing rollout to every interface package with ≥1 driver:
marko/databaseto prove the pattern; roll out to the rest in parallel.Additional improvements
NoDriverExceptionoutput includes derived docs URLs (https://marko.build/docs/packages/{basename}/) for each driver, opening in a new tab when rendered viamarko/errors-advancedmarko/errors-advancedgains URL auto-linkification (currently strips URLs to plain text)marko/errors-advancedstarts renderingcontextandsuggestionfields (currently only rendersmessage)marko/viewtest suite cleaned up to have zero dependency onmarko/view-latte(moveIntegrationTestto view-latte where it belongs)marko/page-cache's outliernoBinding()factory renamed tonoDriverInstalled()for consistencysuggestblock expanded to comprehensively list all drivers + optional add-ons (database-readwrite,page-cache-entity) with recommended-first ordering per interfaceMechanical rule
A package is a driver iff its
module.phpbindingsarray contains the interface's defining contract. Add-ons likemarko/database-readwrite(empty bindings, conditional boot-callback override) andmarko/page-cache-entity(observer-only bridge) are NOT enrolled in known-drivers.php — they appear in skeleton suggest only.For v1,
known-drivers.phpis the curated source. Future enhancement (deferred): marker interfaces orextra.marko.driver_fordeclarations for mechanical enforcement.Out of scope (separate concerns)
NoDriverExceptionto actually be thrown when no driver is bound (pre-existing gap across all 18 packages — exceptions are defined but unused)admin/NoDriverExceptioncleanup (admin sub-modules aren't drivers; that exception is vestigial)Implementation
Plan written and ready:
.claude/plans/known-drivers-registry/on branchfeature/known-drivers-registry. 25 tasks, ~5 parallel batches. Devil's-advocate review applied.This is WIP — depends on PR #88 (view-twig) being merged first so task 017 can be expanded to cover both view drivers.