PHPackages                             wrd/sleepy - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. wrd/sleepy

ActiveLibrary[API Development](/categories/api)

wrd/sleepy
==========

A schema-first &amp; restful API toolkit for Laravel.

015[1 issues](https://github.com/kyletcooper/sleepy/issues)PHP

Since Feb 22Pushed 1y ago1 watchersCompare

[ Source](https://github.com/kyletcooper/sleepy)[ Packagist](https://packagist.org/packages/wrd/sleepy)[ RSS](/packages/wrd-sleepy/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

Sleepy
======

[](#sleepy)

A schema-first &amp; restful API toolkit for Laravel.

> ⚠️ This package is under active development.

**Contents**
[Installation](#installation)
[Setup](#setup)
[Core Concepts](#core-concepts)
[Schema](#schema)
[Attributes](#attributes)
[Embedding](#embedding)
[Filtering](#filtering)
[Sorting](#sorting)
[Pagination](#pagination)
[Authentication &amp; Authorization](#authentication--authorization)
[Discovery](#discovery)
[Exception Handling](#exception-handling)
[Commands](#commands)
[Configuration](#configuration)

Installation
============

[](#installation)

You can install Sleepy using Composer:

```
composer require wrd/sleepy

```

Setup
=====

[](#setup)

To get your Sleepy API started, create a new route file to include all of your API routes.

```
// route/api.php

use WRD\Sleepy\Support\Facades\API;

API::base('/api', function() {

  API::group( '/v1', function(){
    // This is optional, see authentication below.
    API::login();

    // Setup the collection & self routes for your model.
    API::model( Post::class );

  });
});
```

Then you can set up your Model to be API compatible using the `HasApiAll` trait.

The `attributes`, `filters` and `sorts` methods are all optional, Sleepy will provide sensible defaults.

> `HasApi` is a shorthand to include `HasApiModel`, `HasAttributes`, `HasFilters`, `HasSorts`, `HasEmbeds`, `HasLinks` and `HasPagination` all at once. You can choose to provide these individually if you'd prefer more control.

```
// App/Models/Post.php

use WRD\Sleepy\Fields\HasApi;
use WRD\Sleepy\Fields\Filters\Filters;
use WRD\Sleepy\Fields\Sorts\Sorts;
use WRD\Sleepy\Fields\Attributes\Attr;
//...

class Post extends Model
{
	use HasApi;

	// This defines the schema of your model.
	public static function attributes(): array {
		return [
			// This will automatically determine the model's key.
			'id' => Attr::key(),

			// This will be the class basename of the model.
			'type' => Attr::basename(),

			// This will pull from/write to the 'title' property from the model.
			'title' => Attr::string()->required(),

			// This will pull from/write to the 'body' property from the model.
			'body' => Attr::string()->required(),

			// You can prevent users from updating a field using ->readonly()
			// Here, 'url' is the format of the string.
			'thumbnail' => Attr::string( 'url' )->readonly(),
		];
  	}

	// Controls the ways consumers can filter your model's collection endpoint.
	public static function filters(): array{
		return [
			// Search allows you to do a fuzzy search across multiple columns.
			// Here, 'search' is the name of the query parameter. The names of the columns are passed to the filter.
			'search' => Filters::search('title,body'),
		];
  	}

	// Controls the ways consumers can sort your model's collection endpoint.
  	public static function sorts(): array{
		return [
			// Allow sorting by the title alphabetically.
			'title' => Sorts::alphabetical( "title" ),

			// Here, 'title' is the value that must be passed to the 'order_by' query parameter. The name of the database column is passed to the sort.
			'published' => Sorts::date( "created_at" ),
		];
	}
}
```

And you're done! You can now access your routes at  and . You can now search and sort your collection, create new posts and delete or update existing ones.

> ⚠️ Your model routes will automatically have authorization guards in place, acting on the assumption that you are using the Laravel policy system, so you'll need to be authenticated to see these endpoints.

Custom Routes
-------------

[](#custom-routes)

You're not required to attach all of your routes and endpoints to models, of course. You can create a custom route like so:

```
// routes/api.php

API::base('/api', function() {

	API::group( '/v1', function(){

		API::route( '/custom', function(){

			API::endpoint( 'GET' )
			->fields([
				'your_name' => Field::string()->default('world')
			])
			->describe('Says hello to you.')
			->action(function( ApiRequest $request ){
				return 'Hello ' . $request->fields()->get('world')
			})
			->responses( 200 )

		});

	});

});
```

Core Concepts
=============

[](#core-concepts)

Sleepy helps you quickly create restful APIs for your models using some simple shorthands, handling the routing, filtering, pagination and attribute reading/writing for you.

You can create routes independently of your models, but Sleepy shines best via it's `HasApi` trait, which will instantly prepare your model to have endpoints handling the `viewAny`, `create`, `show`, `update` and `delete` actions.

Schema-First
------------

[](#schema-first)

In Sleepy, almost everything you interact with is a sub-class of Schema - based on JSON Schema (with some differences, noted below). This allows you to document as you go and keeps your API consistent.

API Structure
-------------

[](#api-structure)

Sleepy APIs form a tree, starting with a Base (declared using `API::base()`)..

Each Base has one or more Groups. There can only be one API Base per application.

Each Group has one or more sub-groups or Routes. You can use groups to create a path that routes nested under (such as for versioning).

Each Route has one or more Endpoints.

Each Endpoint has a method (`GET`, `POST`, etc.) and a callback action.

You can add middleware to any node in the API tree by using the `middelware` method.

Schema
======

[](#schema)

Everything in Sleepy is driven by Schema. Our `Attribute`, `Filter`, `Sort` and generic `Field` classes all extend this schema base class. This allows us to build a flexible and consistent validation model and document as we go.

The Schema class uses a fluent approach to build up your schema. Below is documentation for each of the methods available.

Validation Methods
------------------

[](#validation-methods)

MethodParameterDescription`type`string|arraySet the list of allowed types.`nullable`-Add `null` to the list of allowed types.`enum`arrayEnsure the value is one of the provided.`const`mixedThe value must be exactly the provided.`required`-The value must be provided.`optional`-Sets required to false.### Strings

[](#strings)

MethodParameterDescription`default`mixedProvide a value for the request to fallback to if none is provided.`format`stringProvide the key of a format, registered to the WRD\\Sleepy\\Schema\\Format class. See [formats](#string-formats) below for allowed values.`pattern`stringA regex pattern to match against.`minLength`intThe minimum length of the value.`maxLength`intThe maximum length of the value.### Numbers / Integers

[](#numbers--integers)

MethodParameterDescription`min`int|floatThe minimum numeric value.`max`int|floatThe maximum numeric value.`multipleOf`int|floatIndicate that the value must be evenly divisble by this value.### Arrays

[](#arrays)

MethodParameterDescription`items`SchemaAll items in the array must match the sub-schema.`minItems`intThe minimum number of items in the array.`maxItems`intThe maximum number of items in the array.`uniqueItems`-Enforce that all items in the array are unique. Uses `array_unique` for comparisons.### Objects

[](#objects)

MethodParameterDescription`properties`array&lt;string, Schema&gt;Indicate the sub-schema for each object key.`additionalProperties`Schema|falseSet the schema of keys not provided by `properties`. Set to false to disallow additional properties.`minProperties`intThe minimum number of properties in the array.`maxProperties`intThe maximum number of properties in the array.Documentation Methods
---------------------

[](#documentation-methods)

Some schema methods do not affect validation and are only provided to document the schema for discovery.

MethodParameterDescription`title`stringProvide a descriptive title for the schema.`description`stringDescribe the field or attribute.`examples`arrayProvide example values that the consumer could use.`deprecated`-Mark the value as deprecated.`readonly`-Fields marked as readonly are not included in the request.`writeonly`-Attributes marked as writeonly are not included in the output.Schema Composition
------------------

[](#schema-composition)

You can use the `allOf`, `anyOf` &amp; `oneOf` to compose multiple schemas together. It's recommended you use `Schema::empty()->allOf( ... )` so that the base schema type is clean.

Validation Outside Schema
-------------------------

[](#validation-outside-schema)

You'll sometimes need to validate your fields in ways that JSON schema can't accomdate for. Thankfully, there's an escape hatch: `custom`.

You can provide one (or an array) of Laravel's core validation rules to `custom` and any error messages will be passed along.

Distinctions for JSON Schema
----------------------------

[](#distinctions-for-json-schema)

### Arrays

[](#arrays-1)

The following properties of arrays are not supported:

- `contains`
- `minContains` / `maxContains`
- `unevaluatedItems`
- prefixItems

### Objects

[](#objects-1)

The following properties of arrays are not supported:

- `patternProperties`
- `unevaluatedProperties`
- `propertyNames`

Also note: `required` is not implemented on objects but is an extension to the base schema class, meaning any schema object can declare itself as required.

### String Formats

[](#string-formats)

We support the following string formats:

- `date-time`
- `date`
- `time`
- `duration`
- `uri`
- `url`
- `ipv4`
- `ipv6`
- `email`

The following recommend formats are not supported by default:

- `idn-email`
- `hostname`
- `idn-hostname`
- `uuid`
- `uri-reference`
- `iri`
- `iri-reference`
- `uri-template`
- `json-pointer`
- `relative-json-pointer`
- `regex`

However, you can provide support for your own formats by using the static `WRD\Sleepy\Schema\Formats::registerFormat` method. This method takes the name of the format and a closure to validate the value. The closure must return strictly false to denote the provided value does not match the format, otherwise the value passes. Throwing an exception also fails the match.

Attributes
==========

[](#attributes)

Attributes allow you to define the schema of your model when it is outputted, as well as the fields available to be updated.

The `attributes` method should return an array of `WRD\Sleepy\Fields\Attributes`, where the key is the property on the Model class to pull the field from. See the example below for details.

```
// App/Models/Post.php

use WRD\Sleepy\Fields\HasApi;
use WRD\Sleepy\Fields\Attributes\Attr;
//...

class Post extends Model
{
	use HasApi;

	// This defines the schema of your model.
	public static function attributes(): array {
		return [
			// Key is ignored for 'key' and 'basename'
			'id' => Attr::key(),
			'type' => Attr::basename(),

			// 'title' & 'body' pull their values from the models database columns.
			'title' => Attr::string()->required(),
			'body' => Attr::string()->required(),

			// 'thumbnail' pulls from an Eloquent attribute.
			'thumbnail' => Attr::string( 'url' )->readonly(),

			// 'creator' writes to a Eloquent relationship.
			// This field is writeonly by default, see `Attr::belongsTo` in Core Attributes.
			'creator' => Attr::belongsTo( User::class, 'creator_id' ),
		];
	}
}
```

If you want an attribute to be outputted but not be written to, you can set it as `readonly`.

If you want an attribute to be writable to but not visible in the output, you can set it as `writeonly`.

Core Attributes
---------------

[](#core-attributes)

Core attributes are a set of pre-built attributes that hide away the complexity of building queries. You can create your own complex attributes using the `WRD\Sleepy\Fields\Attribute` class or use on the pre-build attributes from the `WRD\Sleepy\Fields\Attr` class.

`Attr` has delegates the default Schema static functions, allowing you to call `Attr::string` rather than needing to pull in the main `Attribute` class.

`Attr::key()`Gets the key of the model, using `Model::getKey()`.

`Attr::basename()`Gets the base name of the model class, formatted to lower case. For example `WRD\App\Post` would become `post`.

`Attr::belongsTo( string $ownerModel, ?string $ownerKey = null )`Allows you to change the owner ID of the model. These attributes are writeonly. If you want to surface the relationship in the output then you should use embedding.

Embedding
=========

[](#embedding)

Inspired by the HAL standard, you can use embedding to build requests the include information about related models.

```
// App/Models/Post.php

use WRD\Sleepy\Fields\HasApi;

class Post extends Model{
	use HasApi; // Or just HasEmbed.

	public static function embeds(): array{
		return [
			// Sleepy will access the model's 'creator' relationship and include a reference to the foriegn model.
			'creator' => User::embed(),
		];
	}
}
```

Links to the related models will be available under the `_links` key in the response. You can learn more about links in the [discovery section](#discovery).

To include the related model in the response, you can send the relationship name via the `_embed` query parameter. Multiple embeds can be provided, seperated by a comma. You can include nested relationships using dot notation.

For example,

```
POST https://example.com/api/v1/post/1?_embed=creator,creator.organisation
```

```
{
	"id": 9,
	"type": "post",
	"_links": {
		"self": {
			"href": "https://sitepuppet.test/api/v1/post/9"
		},
    "creator": {
      "href": "https://sitepuppet.test/api/v1/user/1",
      "embeddable": true
    },
	},
	"_embedded": {
      "creator": {
			"id": 1,
			"type": "user",
			"name": "Kyle Cooper",
			"email": "mail@example.com",
			"_links" {
				"self": {
					"href": "https://sitepuppet.test/api/v1/user/1"
				},
				"organisation": {
					"href": "https://sitepuppet.test/api/v1/organisation/3",
					"embeddable": true
				},
			},
			"_embedded": {
				"id": 3,
				"type": "organisation",
				"name": "WRD",
				"url": "https://wrd.agency",
				"_links": {
					"self": {
						"href": "https://sitepuppet.test/api/v1/organisation/3"
					},
				}
			}
		}
	}
}
```

> Note: Our schema for links extends the HAL specification by including `embeddable`, a boolean field which indicates that a link can requested to be embedded.

Changing the Default Names
--------------------------

[](#changing-the-default-names)

You can change the name of `_links` attribute by overriding the static `getEmbedLinksAttributeName` method on your model.

You can change the name of `_embedded` attribute by overriding the static `getEmbedsAttributeName` method on your model.

You can change the name of `_embedded` query parameter field by overriding the static `getEmbedFieldsName` method on your model.

Filtering
=========

[](#filtering)

Consumers can apply filters by sending the value of the filter to the query parameter matching the key of the filters array.

```
// App/Models/Post.php

use WRD\Sleepy\Fields\HasApi;
use WRD\Sleepy\Fields\Filters\Filters;
//...

class Post extends Model
{
	use HasApi; // Or just `HasFilters`.

	// Provides the allowed filters.
	public static function filters(): array{
		return [
			// The key is the name of the filter.
			// For example, the consumer would include '?search=hello' to search the title & body columns.
			'search' => Filters::search('title,body'),
		];
	}

	// ...
}
```

Operators
---------

[](#operators)

Filters can have support for multiple operators, allowing more flexible querying from consumers.

Consumers can either provide the value directly or using an operator name as an array key.

See `WRD\Sleepy\Fields\Filters\Operator` for the list of operator keys.

```
// Provide value directly.
GET https://example.com/api/v1/post?date=2025-02-01

// Provide an operator (greater than or equal to).
GET https://example.com/api/v1/post?date[gte]=2025-02-01
```

Core Filters
------------

[](#core-filters)

Core filters are a set of pre-built filters that hide away the complexity of building queries. You can create your own complex filters using the `WRD\Sleepy\Fields\Filter` class or use on the pre-build filters from the `WRD\Sleepy\Fields\Filters` class.

`Filters::text( string $column )`Matches a column exactly. *Allowed operators: Equals (eq), Not Equals (neq).*

`Filters::numeric( string $column )`Matches a column numerically. *Allowed operators: Equals (eq), Not Equals (neq), Greater (gt), Greater Equals (gte), Lesser (lt), Lesser Equals (lte).*

`Filters::date( string $column )`Matches a datetime column. *Allowed operators: Equals (eq), Not Equals (neq), Greater (gt), Greater Equals (gte), Lesser (lt), Lesser Equals (lte).*

`Filters::cases( string $column, array $cases )`Matches a column exactly, but only allowing specific values. *Allowed operators: Equals (eq), Not Equals (neq).*

`Filters::search( string $columns )`Fuzzy match a value. Multiple columns can be provided user commas to separate. *Allowed operators: Equals (eq).*

`Filters::belongsTo( string $model, ?string $foreignKey = null )`Show only models that belong to another. Consumers provide a string key for the owner model. *Allowed operators: Equals (eq).*

Sorting
=======

[](#sorting)

Consumers can provide the `order_by` and `order` keys to set the ordering method and direction, respectively.

The value of the `order_by` query parameter must be a key from the `sorts()` array. This defaults to the first key of the array.

The query value for `order` can be either `asc` (ascending) or `desc` (descending), defaulting to `asc`.

```
// App/Models/Post.php

use WRD\Sleepy\Fields\HasApi;
use WRD\Sleepy\Fields\Filters\Filters;
//...

class Post extends Model
{
	use HasApi; // Or just `HasSorts`.

	public static function sorts(): array{
		return [
			'title' => Sorts::alphabetical( "title" ),
			'published' => Sorts::date( "created_at" ),
		];
	}
}
```

Core Sorts
----------

[](#core-sorts)

Core sorts are a set of pre-built sorts that hide away the complexity of building queries. You can create your own complex sorts using the `WRD\Sleepy\Fields\Sort` class or use on the pre-build sorts from the `WRD\Sleepy\Fields\Sorts` class.

`Sorts::alphabetical( string $column )`Sort a column alphabetically.

`Sorts::numeric( string $column )`Sort a column numerically.

`Sorts::date( string $column )`Sort a by datetime.

`Sorts::cases( string $column, array $cases )`Sort aa column in a specified order. The results will be returning in the order specified in the `$cases` array. Useful for enums.

Pagination
==========

[](#pagination)

The collection endpoint for a model is automatically paginated. By default, 10 results are shown per page but this can be changed using the `per_page` query variable. This is capped to 99 items per page.

Paginated results push the models into the `items` field. Metadata about the pagination, such as the total number of pages, can be accessed via the `meta` field.

> ❓ Since `HasApi` includes `HasPagination`, your models collection endpoints will automatically be paginated. You can disable this by specifying the traits individually.

Authentication &amp; Authorization
==================================

[](#authentication--authorization)

Model Shorthand
---------------

[](#model-shorthand)

When using the `API::model()` shorthand, Sleepy will assume you have a standard Laravel policy for your model. It'll setup auth guards for `viewAny`, `create`, `view`, `update`, and `destroy`.

Custom Routes
-------------

[](#custom-routes-1)

For routes you setup on your own, you'll need to make sure you include an authorization function. **By default, custom routes are public.** You can use the `auth` fluent method to provide an callback function to authorize the user.

> If you provide an auth callback then Sleepy will automatically ensure that the user is authenticated before handling your callback.

```
API::endpoint( 'GET' )
	->action( function( ApiRequest $req ){
		// ...
	})
	->auth( function( ApiRequest $req, Model $model ) => {
		// The user has already been authenticated.
		// We just need to authorize them.
		return true;
	})
	->responses( 200, 401, 403 )
	// We should also make sure we document our responses.
	// Sleepy will return a HTTP 403 for unauthenticated responses and a 401 for unauthorized responses.
```

Login Shorthand
---------------

[](#login-shorthand)

Sleepy provides a `API::login` route out of the box as an easy starter method to authenticate requests. This provides a `GET` endpoint to check the authentication status, a `POST` endpoint to create an authenticated session and a `DELETE` endpoint to destory the session and log-out.

The login shorthand uses Laravel's default authentication system which is cookie based, making it stateful. If you want to use a basic stateless authentication system you could look into the [`auth.basic` middleware](https://laravel.com/docs/11.x/authentication#http-basic-authentication).

If you're looking for a more complex authentication system, such as API tokens, we'd recommend you reach for [Laravel Sanctum](https://laravel.com/docs/11.x/sanctum#main-content) to help with the authentication side of your project.

Discovery
=========

[](#discovery)

Sleepy uses it's schema-driven approach to automatically generate discovery endpoints for your API.

API Base / Group Discovery
--------------------------

[](#api-base--group-discovery)

A `GET` endpoint is automatically created at the URL of the API base and any groups you create that lists all of the available routes &amp; endpoints beneath that node.

Route / Endpoint Discovery
--------------------------

[](#route--endpoint-discovery)

Every route includes a `OPTIONS` endpoint which details all of the other endpoints available at that route. It lists the available methods &amp; fields for each route and, if available, the shared schema of the route's responses. For Model Routes, this will be the model's attribute schema.

Authenticated Discovery
-----------------------

[](#authenticated-discovery)

Routes are only included available for discovery depending on the authentication of the user.

If an endpoint is public, then it will always be available in discovery.

If an endpoint is not public (it has an authorization callback set), it will only be included if the user has authenticated themselves.

Routes &amp; groups are only available to unauthenticated users if they have at least one public endpoint (the private endpoints are still hidden).

Authorization callbacks are not called to validate if an endpoint can be included in discovery.

> ⚠️ Remember! Custom routes in Sleepy are public by default, and will appear in the discovery endpoints for unauthenticated users unless you add an authorization endpoint.

Linking
-------

[](#linking)

As show above, Sleepy will automatically populate the `_links` field of your model to include links to the API endpoints for related models.

Sleepy will also automatically provide links to the current model (`self`) and the collection of all models (`collection`).

You can control the names of the links field by overriding the static `getLinksAttributeName` method on your model for default links and the static `getEmbedLinksAttributeName` method for links to embedded models.

Exception Handling
==================

[](#exception-handling)

Sleepy will handle consistent JSON formatting of errors caused by unauthentication, unauthorization, 404s and field validation errors but it won't catch other exceptions.

Commands
========

[](#commands)

List
----

[](#list)

You can use the list command to print all of the API routes and their fields to the console.

```
php artisan sleepy:list

```

Markdown
--------

[](#markdown)

You can automatically generate markdown documentation from your API using the following command. This will be outputted to the specified directory, relative to the root of your project (in this case '/docs').

```
php artisan sleepy:markdown /docs

```

> ⚠️ **This is an experimental feature.**

Configuration
=============

[](#configuration)

You can publish the configuration file into your project using the following command:

```
php artisan vendor:publish --provider=WRD\Sleepy\Providers\SleepyServiceProvider

```

Currently Sleepy provides two configuration options:

NameTypeDescriptioninclude\_links\_in\_schemaboolDefaults to 'false'. Set to true to show the schema of links in the model schema.include\_embeds\_in\_schemaboolDefaults to false. Set to true to enable the schema of embeds in the provided in model schema.

###  Health Score

16

—

LowBetter than 5% of packages

Maintenance33

Infrequent updates — may be unmaintained

Popularity6

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity15

Early-stage or recently created project

 Bus Factor1

Top contributor holds 84% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/32ef50777a788e63fcc43cbdb552375205c6c7d50d6ec9b3a23079c0d9a993a1?d=identicon)[kyletcooper](/maintainers/kyletcooper)

---

Top Contributors

[![wrd-sodapixel](https://avatars.githubusercontent.com/u/161863489?v=4)](https://github.com/wrd-sodapixel "wrd-sodapixel (21 commits)")[![kyletcooper](https://avatars.githubusercontent.com/u/100929592?v=4)](https://github.com/kyletcooper "kyletcooper (4 commits)")

---

Tags

laravelrest-api

### Embed Badge

![Health badge](/badges/wrd-sleepy/health.svg)

```
[![Health](https://phpackages.com/badges/wrd-sleepy/health.svg)](https://phpackages.com/packages/wrd-sleepy)
```

###  Alternatives

[stripe/stripe-php

Stripe PHP Library

4.0k143.3M480](/packages/stripe-stripe-php)[twilio/sdk

A PHP wrapper for Twilio's API

1.6k92.9M272](/packages/twilio-sdk)[facebook/php-business-sdk

PHP SDK for Facebook Business

90821.9M34](/packages/facebook-php-business-sdk)[meilisearch/meilisearch-php

PHP wrapper for the Meilisearch API

74513.7M114](/packages/meilisearch-meilisearch-php)[google/gax

Google API Core for PHP

265103.1M454](/packages/google-gax)[google/common-protos

Google API Common Protos for PHP

173103.7M50](/packages/google-common-protos)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
