Download the PHP package dskripchenko/laravel-delayed-process without Composer
On this page you can find all versions of the php package dskripchenko/laravel-delayed-process. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download dskripchenko/laravel-delayed-process
More information about dskripchenko/laravel-delayed-process
Files in dskripchenko/laravel-delayed-process
Package laravel-delayed-process
Short Description Delayed Process For Laravel.
License MIT
Homepage https://github.com/dskripchenko/laravel-delayed-process
Informations about the package laravel-delayed-process
Laravel Delayed Process
Language: 中文
Asynchronous execution of long-running operations in Laravel with UUID-based tracking, automatic retry, security allowlist, and transparent frontend interceptors for Axios, Fetch, and XHR.
Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- Architecture
- Process Lifecycle
- Project Structure
- Backend API
- Frontend Interceptors
- Configuration Reference
- Database Schema
- Security
- Cookbook
- License
Features
- Async Processing — offload heavy operations to a queue, return UUID immediately
- UUID Tracking — every process gets a UUIDv7 for status polling
- Automatic Retry — configurable max attempts with error capture on final failure
- Security Allowlist — only explicitly allowed entity classes can be executed
- Frontend Interceptors — transparent Axios, Fetch, and XHR interceptors that auto-poll until completion
- Batch Polling —
BatchPollerclass for polling multiple UUIDs in a single request - Loop Prevention —
X-Delayed-Process-Pollheader prevents interceptors from re-intercepting poll requests - Lifecycle Events —
ProcessCreated,ProcessStarted,ProcessCompleted,ProcessFailedevents for observability - Progress Tracking — 0-100% progress updates via
ProcessProgressInterface - Webhook Callbacks — HTTP POST notifications to
callback_urlon terminal status - TTL / Expiration — automatic process expiration via
expires_at+delayed:expirecommand - Cancellation — cancel processes in
new/waitstatus via builder - Per-entity Queue Config — configure queue, connection, and timeout per entity class
- Artisan Commands —
delayed:process,delayed:clear,delayed:unstuck,delayed:expire,delayed:migrate-v1(legacy migration) - Structured Logging — captures all
MessageLoggedevents during execution, configurable buffer limit - Atomic Claiming — race-condition-safe process claiming via atomic UPDATE
- PostgreSQL Optimized — partial indexes, JSONB columns, TIMESTAMPTZ; MySQL/MariaDB also supported
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.5 |
| Laravel | ^12.0 |
| Database | PostgreSQL (recommended) or MySQL/MariaDB |
Installation
Publish the configuration file:
Run the migration:
Register allowed entities in config/delayed-process.php:
Quick Start
1. Create a Handler
2. Trigger a Delayed Process (Backend)
3. Status Endpoint
4. Frontend — Axios Interceptor
Architecture
Lifecycle Overview
Component Overview
| Component | Class | Purpose |
|---|---|---|
| Model | DelayedProcess |
Eloquent model — stores process state, result, logs |
| Builder | DelayedProcessBuilder |
Custom Eloquent builder — whereNew(), whereStuck(), claimForExecution() |
| Factory | DelayedProcessFactory |
Creates process, validates entity, dispatches job |
| Runner | DelayedProcessRunner |
Executes process — claim, resolve, run, handle errors |
| Logger | DelayedProcessLogger |
Buffers log entries during execution, flushes to model |
| Job | DelayedProcessJob |
Laravel queue job — bridges queue to runner |
| Resource | DelayedProcessResource |
JSON response format for status endpoint |
| Resolver | CallableResolver |
Validates and resolves entity+method to callable |
| EntityConfigResolver | EntityConfigResolver |
Resolves per-entity queue/connection/timeout config |
| CallbackDispatcher | CallbackDispatcher |
Sends webhook POST on terminal status |
| Progress | DelayedProcessProgress |
Updates process progress (0-100%) |
Contracts
| Interface | Default Implementation |
|---|---|
ProcessFactoryInterface |
DelayedProcessFactory |
ProcessRunnerInterface |
DelayedProcessRunner |
ProcessLoggerInterface |
DelayedProcessLogger |
ProcessProgressInterface |
DelayedProcessProgress |
All bindings are registered in DelayedProcessServiceProvider. Override via Laravel's service container for custom implementations.
Events
| Event | Fired When | Properties |
|---|---|---|
ProcessCreated |
After Factory::make() saves process |
process |
ProcessStarted |
After Runner claims and starts execution | process |
ProcessCompleted |
After successful execution | process |
ProcessFailed |
After exception in execution | process, exception |
Process Lifecycle
Status Transitions
| Status | Value | Description |
|---|---|---|
| New | new |
Created, awaiting execution. Eligible for claiming. |
| Wait | wait |
Claimed by a worker, currently executing. Blocks re-entry. |
| Done | done |
Successfully completed. Result stored in data. Terminal. |
| Error | error |
All retry attempts exhausted. Error details in error_message / error_trace. Terminal. |
| Expired | expired |
TTL exceeded before completion. Marked by delayed:expire. Terminal. |
| Cancelled | cancelled |
Manually cancelled via Builder. Terminal. |
Retry Logic
- Worker atomically claims process:
UPDATE ... SET status='wait', try=try+1 WHERE status='new' - Handler executes
- On success:
status → done, result saved todata - On failure:
- If
try < attempts:status → new(eligible for retry) - If
try >= attempts:status → error, error details saved
- If
Project Structure
Backend API
Creating Processes
Use ProcessFactoryInterface (resolved via DI):
What happens inside make():
- Validates entity is in
allowed_entitiesconfig - Validates class and method exist
- Validates parameters are JSON-serializable
- Creates
DelayedProcessmodel in a DB transaction (auto-generates UUIDv7, setsstatus=new,expires_atfrom TTL config) - Configures job queue/connection/timeout from per-entity config
- Dispatches
DelayedProcessJobto the queue - Fires
ProcessCreatedevent - Returns the persisted model
Creating with Webhook Callback
When the process reaches a terminal status (done, error, expired, cancelled), an HTTP POST is sent to the callbackUrl with {uuid, status, data}.
Per-entity Queue Configuration
Status Endpoint Response
DelayedProcessResource returns:
Notes:
datais only included whenstatusis terminal (done,error,expired,cancelled)error_messageandis_error_truncatedare only included when an error existsprogress(0-100) reflects execution progressstarted_atandduration_mstrack execution timing
Artisan Commands
delayed:process — Synchronous Worker
Processes delayed tasks without requiring a queue worker. Useful for development or single-server deployments.
| Option | Default | Description |
|---|---|---|
--max-iterations |
0 (infinite) |
Stop after N processes. 0 = run forever. |
--sleep |
5 |
Seconds to sleep when no processes are found. |
delayed:clear — Cleanup Old Processes
Deletes terminal (done / error) processes older than a specified number of days.
| Option | Default | Description |
|---|---|---|
--days |
30 |
Delete processes older than N days. |
--chunk |
500 |
Batch delete size for memory efficiency. |
delayed:unstuck — Reset Stuck Processes
Resets processes stuck in wait status back to new so they can be retried.
| Option | Default | Description |
|---|---|---|
--timeout |
60 |
Consider processes stuck after N minutes in wait. |
--dry-run |
false |
List stuck processes without resetting them. |
delayed:expire — Expire TTL Processes
Marks processes whose expires_at has passed as expired.
| Option | Default | Description |
|---|---|---|
--dry-run |
false |
Show count without modifying. |
delayed:migrate-v1 — Legacy Migration
Upgrades the database schema from the legacy structure. Adds error_message / error_trace columns, converts columns to JSONB (PostgreSQL) or JSON (MySQL), creates partial/composite indexes, and adds CHECK constraint.
Frontend Interceptors
The resources/js/delayed-process/ module provides transparent interceptors that automatically detect delayed process responses and poll until completion.
How It Works
- Your API returns a response containing
{ payload: { delayed: { uuid: "..." } } } - The interceptor detects the UUID
- It starts polling the status endpoint:
GET {statusUrl}?uuid={uuid} - When
statusbecomesdone, the interceptor replaces the response payload with the result data - When
statusbecomeserror,expired, orcancelled, the interceptor throws aDelayedProcessError - Polling requests include
X-Delayed-Process-Poll: 1header to prevent infinite loops
File Structure
| File | Purpose |
|---|---|
index.ts |
Public exports |
types.ts |
TypeScript types, BatchPollerConfig, DelayedProcessError |
core/config.ts |
Default config, CSRF auto-detection, resolveConfig() |
core/poller.ts |
pollUntilDone() — core polling loop with timeout and abort |
core/batch-poller.ts |
BatchPoller — poll multiple UUIDs in a single request |
axios/interceptor.ts |
applyAxiosInterceptor() — Axios response interceptor |
fetch/patch.ts |
patchFetch() — monkey-patches window.fetch |
xhr/patch.ts |
patchXHR() — monkey-patches XMLHttpRequest (double-patch guard) |
Axios Interceptor
Fetch Patch
XHR Patch
DelayedProcessConfig
| Option | Type | Default | Description |
|---|---|---|---|
statusUrl |
string |
'/api/common/delayed-process/status' |
URL for polling process status |
pollingInterval |
number |
3000 |
Milliseconds between poll requests |
maxAttempts |
number |
100 |
Maximum number of poll attempts |
timeout |
number |
300000 |
Total timeout in milliseconds (5 min) |
headers |
Record<string, string> |
{} |
Extra headers for poll requests |
onPoll |
(uuid: string, attempt: number) => void |
undefined |
Callback invoked on each poll |
CSRF token from <meta name="csrf-token"> is automatically included in poll requests.
Batch Poller
For polling multiple processes at once (e.g., bulk operations):
DelayedProcessError
Thrown when a process completes with error, expired, or cancelled status, or polling times out.
Loop Prevention
All polling requests include the header X-Delayed-Process-Poll: 1. The interceptors check for this header and skip interception on poll requests, preventing infinite polling loops.
Configuration Reference
File: config/delayed-process.php
| Key | Type | Default | Description |
|---|---|---|---|
allowed_entities |
array |
[] |
FQCN allowlist — string values or Entity::class => [config] keyed arrays |
default_attempts |
int |
5 |
Maximum retry attempts before marking as error |
clear_after_days |
int |
30 |
delayed:clear deletes terminal processes older than this |
stuck_timeout_minutes |
int |
60 |
delayed:unstuck considers wait processes stuck after this |
log_sensitive_context |
bool |
false |
Include log context arrays in process logs |
log_buffer_limit |
int |
500 |
Max log entries in memory buffer per process (0 = unlimited) |
callback.enabled |
bool |
false |
Enable webhook POST on terminal status |
callback.timeout |
int |
10 |
Webhook HTTP timeout in seconds |
default_ttl_minutes |
int\|null |
null |
Default TTL for new processes (null = no expiration) |
job.timeout |
int |
300 |
Queue job timeout in seconds |
job.tries |
int |
1 |
Queue job retry attempts (separate from process attempts) |
job.backoff |
array |
[30, 60, 120] |
Queue job backoff delays in seconds |
command.sleep |
int |
5 |
delayed:process sleep when idle (seconds) |
command.max_iterations |
int |
0 |
delayed:process iteration limit (0 = infinite) |
command.throttle |
int |
100000 |
delayed:process throttle between iterations (microseconds) |
clear_chunk_size |
int |
500 |
delayed:clear batch delete size |
Database Schema
Table: delayed_processes
| Column | Type | Default | Description |
|---|---|---|---|
id |
bigint PK |
auto-increment | Primary key |
uuid |
string(36) UNIQUE |
auto (UUIDv7) | Unique process identifier |
entity |
string nullable |
NULL |
FQCN of handler class |
method |
string |
— | Handler method name |
parameters |
jsonb / json |
[] |
Serialized invocation arguments |
data |
jsonb / json |
[] |
Execution result payload |
logs |
jsonb / json |
[] |
Captured log entries |
status |
string |
'new' |
Process status (new, wait, done, error, expired, cancelled) |
attempts |
tinyint unsigned |
5 |
Maximum retry attempts |
try |
tinyint unsigned |
0 |
Current attempt number |
error_message |
string(1000) nullable |
NULL |
Last error message (truncated with indicator) |
error_trace |
text nullable |
NULL |
Last error stack trace |
started_at |
timestamptz nullable |
NULL |
Execution start time |
duration_ms |
bigint unsigned nullable |
NULL |
Execution duration in milliseconds |
callback_url |
string(2048) nullable |
NULL |
Webhook URL for terminal status notification |
progress |
tinyint unsigned |
0 |
Execution progress (0-100) |
expires_at |
timestamptz nullable |
NULL |
Process expiration time (TTL) |
created_at |
timestamptz |
NOW | Creation timestamp |
updated_at |
timestamptz |
NOW | Last update timestamp |
Indexes
PostgreSQL (partial indexes for optimal performance):
| Index | Condition |
|---|---|
(status, try) |
WHERE status = 'new' |
(created_at) |
WHERE status IN ('done', 'error', 'expired', 'cancelled') |
(updated_at) |
WHERE status = 'wait' |
(expires_at) |
WHERE status IN ('new', 'wait') AND expires_at IS NOT NULL |
MySQL / MariaDB (composite indexes):
| Index |
|---|
(status, try) |
(status, created_at) |
(status, updated_at) |
Constraints
CHECK (status IN ('new', 'wait', 'done', 'error', 'expired', 'cancelled'))on all databases
Security
Entity Allowlist
Only classes listed in config('delayed-process.allowed_entities') can be executed. Attempting to create a process with an unlisted class throws EntityNotAllowedException.
Callable Validation
Before execution, CallableResolver verifies:
- Entity class is in the allowlist
- Class exists (
class_exists()) - Method exists (
method_exists())
Instantiation uses app($entity) — full Laravel DI container support.
Log Privacy
Set log_sensitive_context to false (default) to strip context arrays from captured log entries. Only log level, timestamp, and message are stored.
CSRF Protection
The frontend poller automatically reads <meta name="csrf-token"> and includes it in poll request headers. Ensure your status endpoint is behind CSRF middleware or explicitly verify the token.
Cookbook
For recipes, patterns, and troubleshooting, see the Cookbook.
Available in: 中文
Frontend Integration Guide
For a detailed step-by-step guide on integrating interceptors into Vue.js 3 and React applications, see the Frontend Interceptors Guide.
Includes: composables/hooks, progress tracking, batch polling, error handling, SSR support, and testing.
License
MIT © Denis Skripchenko