yarunoka / laravel
Laravel integration for the Yarunoka schedule definition DSL
Fund package maintenance!
Requires
- php: ^8.4
- illuminate/container: ^13.0
- illuminate/contracts: ^13.0
- illuminate/database: ^13.0
- illuminate/support: ^13.0
- yarunoka/core: ^1.2
Requires (Dev)
- azuyalabs/yasumi: ^2.11
- bamarni/composer-bin-plugin: ^1.8
- orchestra/testbench: ^11.1
- phpunit/phpunit: ^13.0
Suggests
- azuyalabs/yasumi: Answer the yasumi-{Provider} resolver names with computed public holidays
This package is auto-updated.
Last update: 2026-08-10 17:19:00 UTC
README
Laravel integration for the Yarunoka schedule definition DSL.
What is this?
Yarunoka is a small JSON DSL — Yrnk — that states calendar rules like "payday is the 25th, moved up to the previous business day" as data, plus a pure engine that answers questions about them. The DSL and the engine live in yarunoka/core; the language-independent specification lives in the spec repository.
This package binds that engine into Laravel:
- A service provider builds the evaluation environment (timezone,
calendar, resolvers) from
config/yarunoka.phpand bindsYrnkEvaluator/YrnkParserinto the container — scoped per request, and yielding to any binding the application makes itself. - Eloquent casts store schedules in JSON columns with validation on
both paths: a schedule column comes back as a
Schedulewrapper with the firing decision (isDue), a schedules-part column asSchedules(a list ofSchedulecomposed with any), and a whole-document column as a bareYrnk. - Validation rules (
ValidYrnk,ValidYrnkSchedule,ValidYrnkSchedules) reject a bad request with the engine's own message on the validation error. - Container-made resolvers: a name in the config maps to a class the Laravel container instantiates on first use, so constructor injection works; binding one of the core's layer interfaces wins over the config.
Installation
composer require yarunoka/laravel
Requires PHP 8.4 or newer and Laravel 13. The service provider is registered by package auto-discovery. To publish the config:
php artisan vendor:publish --tag=yarunoka-config
Quick example
Name the wall-clock timezone and the calendar once, in the config:
// config/yarunoka.php return [ 'timezone' => 'Asia/Tokyo', 'calendar' => [ 'holidays' => 'yasumi-Japan', // resolved automatically when azuyalabs/yasumi is installed 'business_holidays' => ['2026-08-14'], 'business_days' => [], ], 'resolvers' => [], ];
The bindings are scoped — resolved once per request or queue job — and
the config is read at first use within a scope. Where one scope lives
long (tinker, a test), a config change made after that first use
waits for the next scope; app()->forgetScopedInstances() renews
within the current one, though it resets every scoped service, not
only Yarunoka's.
Cast a JSON column to one schedule by naming the wrapper in casts()
(Schedules::class casts a column of many the same way):
use Yarunoka\Laravel\Schedule; class Routine extends Model { protected function casts(): array { return ['schedule' => Schedule::class]; } }
A column can also hold a whole Yrnk document — its own timezone and
calendar per row. Yrnk is the core's class, so there is no wrapper to
name: a whole-document column names the cast class itself, in the
model's casts():
use Yarunoka\Laravel\Casts\AsYrnk; protected function casts(): array { return ['document' => AsYrnk::class]; }
Validate a request and store the schedule as it was spelled — an invalid schedule never reaches the database:
use Yarunoka\Laravel\Rules\ValidYrnkSchedule; $validated = $request->validate([ 'schedule' => ['required', new ValidYrnkSchedule()], ]); $routine = Routine::create(['schedule' => $validated['schedule']]);
Ask the firing question from a poller — was there a scheduled point since the last run?
if ($routine->schedule->isDue(now(), since: $routine->last_run_at)) { // fire, then advance last_run_at }
Documentation
- yarunoka/core — the DSL and the engine this package wraps, with guides on reading, writing, and evaluating documents
- The spec repository — the DSL specification