michael4d45 / effect-schema-generator
Generate TypeScript interfaces and Effect schemas from PHP Spatie Data classes
Installs: 57
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/michael4d45/effect-schema-generator
Requires
- php: ^8.3
- laravel/framework: ^11.0|^12.0
- laravel/surveyor: ^0.1
- nikic/php-parser: ^5.4
- phpdocumentor/reflection-docblock: ^5.3
- spatie/laravel-data: ^4.0
Requires (Dev)
- carthage-software/mago: ^1.2
- michael4d45/coverage-treemap: ^1.0.0
- orchestra/testbench: ^10.0
- pestphp/pest: ^4.0
README
Generate TypeScript interfaces and Effect schemas from PHP Spatie Data classes. This package bridges your PHP domain models to your TypeScript frontend contracts by generating AST-based representations and transforming them into valid TypeScript code.
Installation
You can install the package via composer:
composer require michael4d45/effect-schema-generator
You can publish the config file with:
php artisan vendor:publish --tag="effect-schema-config"
Usage
Define your Spatie Data classes as usual:
namespace App\Data; use Spatie\LaravelData\Data; class UserData extends Data { public function __construct( public int $id, public string $name, public string $email, ) {} }
Then run the generator command:
php artisan effect-schema:transform
This will generate TypeScript interfaces and Effect schemas in your configured output directory (defaults to resources/ts/schemas).
Commands
php artisan effect-schema:transform # Generate schemas php artisan effect-schema:transform --dry-run # Preview output
Features
- AST-based generation: Robust transformation from PHP to TypeScript.
- Spatie Data support: Deep integration with
spatie/laravel-data. - Effect Schema: Generates not just interfaces, but runtime-validatable Effect schemas.
- Configurable Transformers: Easily extend the generator with custom type mappings.
- PHPDoc Parsing: Uses PHPDoc annotations to refine or override types.
Generated File Structure
The generator creates TypeScript files that mirror your PHP namespace structure. For example:
PHP Namespaces: Generated TypeScript:
├── App\Data\ ├── resources/ts/schemas/
│ ├── UserData.php │ └── App/Data.ts
│ └── Models\ │ └── App/Data/Models.ts
│ └── GameSessionData │
└── App\Enums\ └── App/Enums.ts
└── Role.php
Each TypeScript file contains:
- TypeScript interfaces (for the decoded types)
- Encoded interfaces (for the serialized types)
- Effect Schema definitions (for runtime validation)
Example Output
Given this PHP Data class:
namespace App\Data; use Spatie\LaravelData\Data; use Carbon\Carbon; class UserData extends Data { public function __construct( public int $id, public string $name, public string $email, public bool $is_admin, public ?Carbon $email_verified_at, ) {} }
The generator produces this TypeScript file (App/Data.ts):
import { Schema as S } from "effect"; export interface UserData { readonly id: number; readonly name: string; readonly email: string; readonly is_admin: boolean; readonly email_verified_at: Date | null; } export interface UserDataEncoded { readonly id: number; readonly name: string; readonly email: string; readonly is_admin: boolean; readonly email_verified_at: string | null; } export const UserDataSchema: S.Schema<UserData, UserDataEncoded> = S.Struct({ id: S.Number, name: S.String, email: S.String, is_admin: S.Boolean, email_verified_at: S.NullOr(S.DateFromString), });
Complex Types
The generator handles complex types, relationships, and collections:
namespace App\Data; use Spatie\LaravelData\Data; use Illuminate\Support\Collection; class ApiResponseData extends Data { /** * @param Collection<int, UserData> $users */ public function __construct( public Collection $users, public ?UserData $currentUser, ) {} }
Generates:
import { Schema as S } from "effect"; import { UserData, UserDataEncoded, UserDataSchema } from "./User"; export interface ApiResponseData { readonly users: readonly UserData[]; readonly currentUser: UserData | null; } export interface ApiResponseDataEncoded { readonly users: readonly UserDataEncoded[]; readonly currentUser: UserDataEncoded | null; } export const ApiResponseDataSchema = S.Struct({ users: S.Array( S.suspend((): S.Schema<UserData, UserDataEncoded> => UserDataSchema) ), currentUser: S.NullOr( S.suspend((): S.Schema<UserData, UserDataEncoded> => UserDataSchema) ), });
Enums
PHP enums are converted to TypeScript type unions with corresponding schemas:
namespace App\Enums; enum Role: string { case HOST = 'host'; case PLAYER = 'player'; case SPECTATOR = 'spectator'; }
Generates:
import { Schema as S } from "effect"; export type Role = "host" | "player" | "spectator"; export const RoleSchema = S.Union( S.Literal("host"), S.Literal("player"), S.Literal("spectator") );
Configuration
The published config supports discoverers for data classes and enums.
return [ // Discoverers for Spatie Data classes 'data_discoverers' => [ [ 'class' => EffectSchemaGenerator\Discovery\SpatieDataClassDiscoverer::class, 'paths' => [app_path('Data')], ], ], // Discoverers for native PHP enums 'enum_discoverers' => [ [ 'class' => EffectSchemaGenerator\Discovery\NativeEnumDiscoverer::class, 'paths' => [app_path('Enums')], ], ], 'transformers' => [ // ... ], 'phpdoc_overrides_types' => true, 'output' => [ 'directory' => resource_path('js/schemas'), 'file_extension' => '.ts', 'clear_output_directory_before_write' => true, ], ];
Key options:
data_discoverers: list of discoverer definitions (class+paths) used to find Spatie Data classes.enum_discoverers: list of discoverer definitions (class+paths) used to find PHP enums.transformers: custom type transformation and writer plugins.output.directory: destination for generated TypeScript files.output.file_extension: generated file extension (default.ts).output.clear_output_directory_before_write: whentrue, delete current output directory contents before writing new files.
To add a custom discoverer plugin, implement either
EffectSchemaGenerator\Discovery\DataClassDiscoverer or
EffectSchemaGenerator\Discovery\EnumDiscoverer, then register it in the
corresponding discoverer list.
Testing
composer test
License
The MIT License (MIT). Please see License File for more information.