typelizer 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +45 -1
- data/README.md +32 -680
- data/lib/tasks/generate.rake +50 -15
- data/lib/typelizer/config.rb +10 -3
- data/lib/typelizer/configuration.rb +4 -0
- data/lib/typelizer/dsl.rb +14 -5
- data/lib/typelizer/generator.rb +2 -2
- data/lib/typelizer/interface.rb +28 -21
- data/lib/typelizer/listen.rb +26 -7
- data/lib/typelizer/middleware.rb +52 -0
- data/lib/typelizer/model_plugins/active_record.rb +3 -0
- data/lib/typelizer/openapi.rb +11 -17
- data/lib/typelizer/property.rb +40 -27
- data/lib/typelizer/railtie.rb +12 -9
- data/lib/typelizer/route_config.rb +50 -0
- data/lib/typelizer/route_generator.rb +149 -0
- data/lib/typelizer/route_writer.rb +171 -0
- data/lib/typelizer/serializer_plugins/alba/trait_interface.rb +2 -1
- data/lib/typelizer/serializer_plugins/alba.rb +8 -14
- data/lib/typelizer/shape.rb +39 -0
- data/lib/typelizer/templates/enums.ts.erb +3 -0
- data/lib/typelizer/templates/index.ts.erb +2 -1
- data/lib/typelizer/templates/route_controller.erb +36 -0
- data/lib/typelizer/templates/route_index.erb +14 -0
- data/lib/typelizer/templates/route_runtime.js +97 -0
- data/lib/typelizer/templates/route_runtime.ts +109 -0
- data/lib/typelizer/type_inference.rb +15 -7
- data/lib/typelizer/type_parser.rb +28 -0
- data/lib/typelizer/version.rb +1 -1
- data/lib/typelizer/writer.rb +4 -4
- data/lib/typelizer.rb +16 -2
- metadata +13 -4
- /data/lib/typelizer/templates/{fingerprint.ts.erb → fingerprint.erb} +0 -0
data/README.md
CHANGED
|
@@ -2,727 +2,79 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/typelizer)
|
|
4
4
|
|
|
5
|
-
Typelizer generates TypeScript types
|
|
6
|
-
|
|
7
|
-
## Table of Contents
|
|
8
|
-
|
|
9
|
-
- [Features](#features)
|
|
10
|
-
- [Installation](#installation)
|
|
11
|
-
- [Usage](#usage)
|
|
12
|
-
- [Basic Setup](#basic-setup)
|
|
13
|
-
- [Manual Typing](#manual-typing)
|
|
14
|
-
- [Alba Traits](#alba-traits)
|
|
15
|
-
- [TypeScript Integration](#typescript-integration)
|
|
16
|
-
- [Manual Generation](#manual-generation)
|
|
17
|
-
- [Automatic Generation in Development](#automatic-generation-in-development)
|
|
18
|
-
- [Disabling Typelizer](#disabling-typelizer)
|
|
19
|
-
- [OpenAPI Schema Generation](#openapi-schema-generation)
|
|
20
|
-
- [Configuration](#configuration)
|
|
21
|
-
- [Global Configuration](#simple-configuration)
|
|
22
|
-
- [Writers (multiple outputs)](#defining-multiple-writers)
|
|
23
|
-
- [Per-Serializer Configuration](#per-serializer-configuration)
|
|
24
|
-
- [Credits](#credits)
|
|
25
|
-
- [License](#license)
|
|
26
|
-
|
|
27
|
-
<a href="https://evilmartians.com/?utm_source=typelizer&utm_campaign=project_page">
|
|
28
|
-
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Built by Evil Martians" width="236" height="54">
|
|
29
|
-
</a>
|
|
5
|
+
Typelizer generates TypeScript types, route helpers, and OpenAPI schemas from your Ruby on Rails application. It keeps your backend and frontend in sync without hand-maintaining types.
|
|
30
6
|
|
|
31
7
|
## Features
|
|
32
8
|
|
|
33
|
-
- Automatic TypeScript interface generation
|
|
34
|
-
-
|
|
35
|
-
- Supports
|
|
9
|
+
- Automatic TypeScript interface generation from serializers
|
|
10
|
+
- Type-safe route helpers from Rails routes
|
|
11
|
+
- Supports Alba, ActiveModel::Serializer, Oj::Serializer, Panko::Serializer
|
|
12
|
+
- OpenAPI 3.0/3.1 schema generation
|
|
13
|
+
- Multiple output writers with layered configuration
|
|
36
14
|
- File watching with automatic regeneration in development
|
|
37
|
-
- Multiple output writers: emit several variants (e.g., snake_case and camelCase) in parallel
|
|
38
15
|
|
|
39
|
-
##
|
|
16
|
+
## Quick Start
|
|
40
17
|
|
|
41
|
-
|
|
18
|
+
Add to your Gemfile:
|
|
42
19
|
|
|
43
20
|
```ruby
|
|
44
21
|
gem "typelizer"
|
|
45
22
|
```
|
|
46
23
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
### Basic Setup
|
|
50
|
-
|
|
51
|
-
Include the Typelizer DSL in your serializers:
|
|
24
|
+
Include the DSL in your serializers:
|
|
52
25
|
|
|
53
26
|
```ruby
|
|
54
27
|
class ApplicationResource
|
|
55
28
|
include Alba::Resource
|
|
56
29
|
include Typelizer::DSL
|
|
57
|
-
|
|
58
|
-
# For Alba, we recommend using the `helper` method instead of `include`.
|
|
59
|
-
# See the documentation: https://github.com/okuramasafumi/alba/blob/main/README.md#helper
|
|
60
|
-
# helper Typelizer::DSL
|
|
61
30
|
end
|
|
62
31
|
|
|
63
32
|
class PostResource < ApplicationResource
|
|
64
33
|
attributes :id, :title, :body
|
|
65
|
-
|
|
66
|
-
has_one :author, serializer: AuthorResource
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
class AuthorResource < ApplicationResource
|
|
70
|
-
# specify the model to infer types from (optional)
|
|
71
|
-
typelize_from User
|
|
72
|
-
|
|
73
|
-
attributes :id, :name
|
|
74
|
-
end
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Typelizer will automatically generate TypeScript interfaces based on your serializer definitions using information from your models.
|
|
78
|
-
|
|
79
|
-
### Manual Typing
|
|
80
|
-
|
|
81
|
-
You can manually specify TypeScript types in your serializers:
|
|
82
|
-
|
|
83
|
-
```ruby
|
|
84
|
-
class PostResource < ApplicationResource
|
|
85
|
-
attributes :id, :title, :body, :published_at
|
|
86
|
-
|
|
87
|
-
typelize "string"
|
|
88
|
-
attribute :author_name do |post|
|
|
89
|
-
post.author.name
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
typelize :string, nullable: true, comment: "Author's avatar URL"
|
|
93
|
-
attribute :avatar do
|
|
94
|
-
"https://example.com/avatar.png" if active?
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
`typelize` can be used with a Hash to specify multiple types at once.
|
|
100
|
-
|
|
101
|
-
```ruby
|
|
102
|
-
class PostResource < ApplicationResource
|
|
103
|
-
attributes :id, :title, :body, :published_at
|
|
104
|
-
|
|
105
|
-
attribute :author_name do |post|
|
|
106
|
-
post.author.name
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
typelize author_name: :string, published_at: :string
|
|
110
|
-
end
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
You can also use shortcut syntax for common type modifiers:
|
|
114
|
-
|
|
115
|
-
```ruby
|
|
116
|
-
class PostResource < ApplicationResource
|
|
117
|
-
typelize author_name: "string?" # optional string (name?: string)
|
|
118
|
-
typelize tag_ids: "number[]" # array of numbers (tag_ids: Array<number>)
|
|
119
|
-
typelize categories: "string?[]" # optional array of strings (categories?: Array<string>)
|
|
120
|
-
|
|
121
|
-
# Shortcuts can be combined with explicit options
|
|
122
|
-
typelize status: [:string?, nullable: true] # optional and nullable
|
|
123
|
-
|
|
124
|
-
# Also works with keyless typelize
|
|
125
|
-
typelize :string?
|
|
126
|
-
attribute :nickname do |user|
|
|
127
|
-
user.nickname
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
You can reference other serializers directly by passing the class. Typelizer resolves the class to its generated type name automatically:
|
|
133
|
-
|
|
134
|
-
```ruby
|
|
135
|
-
class PostResource < ApplicationResource
|
|
136
|
-
attributes :id, :title
|
|
137
|
-
|
|
138
|
-
# Reference another serializer — resolves to its generated TypeScript type
|
|
139
|
-
typelize reviewer: [AuthorResource, {optional: true, nullable: true}]
|
|
140
|
-
attribute :reviewer do |post|
|
|
141
|
-
post.reviewer
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# Self-reference works too
|
|
145
|
-
typelize previous_post: PostResource
|
|
146
|
-
attribute :previous_post do |post|
|
|
147
|
-
post.previous_post
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
Union types are supported for polymorphic associations. You can use serializer class references, which resolve to their generated type names:
|
|
153
|
-
|
|
154
|
-
```ruby
|
|
155
|
-
class PostResource < ApplicationResource
|
|
156
|
-
attributes :id, :title
|
|
157
|
-
|
|
158
|
-
# Union of two serializers — resolves to generated type names
|
|
159
|
-
typelize commentable: [UserResource, CommentResource]
|
|
160
|
-
attribute :commentable
|
|
161
|
-
|
|
162
|
-
# Nullable union — extracts null and marks as nullable
|
|
163
|
-
typelize approver: "AuthorResource | null"
|
|
164
|
-
attribute :approver
|
|
165
|
-
|
|
166
|
-
# Pipe-delimited string with serializer names
|
|
167
|
-
typelize target: "UserResource | CommentResource"
|
|
168
|
-
attribute :target
|
|
169
|
-
|
|
170
|
-
# Pipe-delimited string with namespaced serializer
|
|
171
|
-
typelize item: "Namespace::UserResource | CommentResource"
|
|
172
|
-
attribute :item
|
|
173
|
-
end
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
You can also use plain TypeScript type names for custom types that aren't backed by serializers:
|
|
177
|
-
|
|
178
|
-
```ruby
|
|
179
|
-
class PostResource < ApplicationResource
|
|
180
|
-
attributes :id, :title
|
|
181
|
-
|
|
182
|
-
# Plain type names — passed through as-is to TypeScript
|
|
183
|
-
typelize content: "TextBlock | ImageBlock"
|
|
184
|
-
attribute :content
|
|
185
|
-
|
|
186
|
-
# Works with arrays of symbols too
|
|
187
|
-
typelize sections: [:TextBlock, :ImageBlock]
|
|
188
|
-
attribute :sections
|
|
189
|
-
end
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
This generates:
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
type Post = {
|
|
196
|
-
id: number;
|
|
197
|
-
title: string;
|
|
198
|
-
content: TextBlock | ImageBlock;
|
|
199
|
-
sections: TextBlock | ImageBlock;
|
|
200
|
-
}
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
String arrays are treated as string literal unions — useful for enums and state machines:
|
|
204
|
-
|
|
205
|
-
```ruby
|
|
206
|
-
class PostResource < ApplicationResource
|
|
207
|
-
attributes :id, :title
|
|
208
|
-
|
|
209
|
-
# Array of strings — generates string literal union type
|
|
210
|
-
typelize status: ["draft", "published", "archived"]
|
|
211
|
-
attribute :status
|
|
212
|
-
|
|
213
|
-
# Works with Rails enums and state machines
|
|
214
|
-
typelize review_state: ReviewStateMachine.states.keys
|
|
215
|
-
attribute :review_state
|
|
216
|
-
end
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
This generates:
|
|
220
|
-
|
|
221
|
-
```typescript
|
|
222
|
-
type Post = {
|
|
223
|
-
id: number;
|
|
224
|
-
title: string;
|
|
225
|
-
status: 'draft' | 'published' | 'archived';
|
|
226
|
-
review_state: 'pending' | 'approved' | 'rejected';
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
> **Note:** In arrays, **strings** become string literal types (`'a'`), while **symbols** and **class constants** become type references (`A`). You can mix them: `[:number, "auto"]` produces `number | 'auto'`.
|
|
231
|
-
|
|
232
|
-
For more complex type definitions, use the full API:
|
|
233
|
-
|
|
234
|
-
```ruby
|
|
235
|
-
typelize attribute_name: [:string, :Date, optional: true, nullable: true, multi: true, enum: %w[foo bar], comment: "Attribute description", deprecated: "Use `another_attribute` instead"]
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Alba Traits
|
|
239
|
-
|
|
240
|
-
Typelizer supports [Alba traits](https://github.com/okuramasafumi/alba#traits), generating separate TypeScript types for each trait. When using `with_traits` in associations, Typelizer generates intersection types.
|
|
241
|
-
|
|
242
|
-
```ruby
|
|
243
|
-
class UserResource < ApplicationResource
|
|
244
|
-
attributes :id, :name
|
|
245
|
-
|
|
246
|
-
trait :detailed do
|
|
247
|
-
attributes :email, :created_at
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
trait :with_posts do
|
|
251
|
-
has_many :posts, resource: PostResource, with_traits: [:summary]
|
|
252
|
-
end
|
|
253
|
-
end
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
This generates:
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
// User.ts
|
|
260
|
-
export type User = {
|
|
261
|
-
id: number;
|
|
262
|
-
name: string;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
type UserDetailedTrait = {
|
|
266
|
-
email: string;
|
|
267
|
-
created_at: string;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
type UserWithPostsTrait = {
|
|
271
|
-
posts: Array<Post & PostSummaryTrait>;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export default User;
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
When using `with_traits` in associations, Typelizer generates intersection types combining the base type with trait types:
|
|
278
|
-
|
|
279
|
-
```ruby
|
|
280
|
-
class TeamResource < ApplicationResource
|
|
281
|
-
attributes :id, :name
|
|
282
|
-
has_one :lead, resource: UserResource, with_traits: [:detailed]
|
|
283
|
-
has_many :members, resource: UserResource, with_traits: [:detailed, :with_posts]
|
|
284
|
-
end
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
This generates:
|
|
288
|
-
|
|
289
|
-
```typescript
|
|
290
|
-
// Team.ts
|
|
291
|
-
import type { User, UserDetailedTrait, UserWithPostsTrait } from "@/types";
|
|
292
|
-
|
|
293
|
-
export type Team = {
|
|
294
|
-
id: number;
|
|
295
|
-
name: string;
|
|
296
|
-
lead: User & UserDetailedTrait;
|
|
297
|
-
members: Array<User & UserDetailedTrait & UserWithPostsTrait>;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export default Team;
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
The `typelize` method works inside traits for manual type specification:
|
|
304
|
-
|
|
305
|
-
```ruby
|
|
306
|
-
trait :with_stats do
|
|
307
|
-
typelize :number
|
|
308
|
-
attribute :posts_count do |user|
|
|
309
|
-
user.posts.count
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
typelize score: :number
|
|
313
|
-
attributes :score
|
|
314
|
-
end
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### TypeScript Integration
|
|
318
|
-
|
|
319
|
-
Typelizer generates TypeScript interfaces in the specified output directory:
|
|
320
|
-
|
|
321
|
-
```typescript
|
|
322
|
-
// app/javascript/types/serializers/Post.ts
|
|
323
|
-
export interface Post {
|
|
324
|
-
id: number;
|
|
325
|
-
title: string;
|
|
326
|
-
category?: "news" | "article" | "blog" | null;
|
|
327
|
-
body: string;
|
|
328
|
-
published_at: string | null;
|
|
329
|
-
author_name: string;
|
|
330
|
-
}
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
All generated interfaces are automatically imported in a single file:
|
|
334
|
-
|
|
335
|
-
```typescript
|
|
336
|
-
// app/javascript/types/serializers/index.ts
|
|
337
|
-
export * from "./post";
|
|
338
|
-
export * from "./author";
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
We recommend importing this file in a central location:
|
|
342
|
-
|
|
343
|
-
```typescript
|
|
344
|
-
// app/javascript/types/index.ts
|
|
345
|
-
import "@/types/serializers";
|
|
346
|
-
// Custom types can be added here
|
|
347
|
-
// ...
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
With such a setup, you can import all generated interfaces in your TypeScript files:
|
|
351
|
-
|
|
352
|
-
```typescript
|
|
353
|
-
import { Post } from "@/types";
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
This setup also allows you to use custom types in your serializers:
|
|
357
|
-
|
|
358
|
-
```ruby
|
|
359
|
-
class PostWithMetaResource < ApplicationResource
|
|
360
|
-
attributes :id, :title
|
|
361
|
-
typelize "PostMeta"
|
|
362
|
-
attribute :meta do |post|
|
|
363
|
-
{ likes: post.likes, comments: post.comments }
|
|
364
|
-
end
|
|
365
34
|
end
|
|
366
35
|
```
|
|
367
36
|
|
|
368
|
-
|
|
369
|
-
// app/javascript/types/serializers/PostWithMeta.ts
|
|
370
|
-
|
|
371
|
-
import { PostMeta } from "@/types";
|
|
372
|
-
|
|
373
|
-
export interface Post {
|
|
374
|
-
id: number;
|
|
375
|
-
title: string;
|
|
376
|
-
meta: PostMeta;
|
|
377
|
-
}
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
The `"@/types"` import path is configurable:
|
|
381
|
-
|
|
382
|
-
```ruby
|
|
383
|
-
Typelizer.configure do |config|
|
|
384
|
-
config.types_import_path = "@/types";
|
|
385
|
-
end
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
See the [Configuration](#configuration) section for more options.
|
|
389
|
-
|
|
390
|
-
### Manual Generation
|
|
391
|
-
|
|
392
|
-
To manually generate TypeScript interfaces use one of the following commands:
|
|
37
|
+
Generate TypeScript types:
|
|
393
38
|
|
|
394
39
|
```bash
|
|
395
|
-
# Generate new interfaces
|
|
396
40
|
rails typelizer:generate
|
|
397
|
-
|
|
398
|
-
# Clean output directory and regenerate all interfaces
|
|
399
|
-
rails typelizer:generate:refresh
|
|
400
|
-
````
|
|
401
|
-
|
|
402
|
-
### Automatic Generation in Development
|
|
403
|
-
|
|
404
|
-
When [Listen](https://github.com/guard/listen) is installed, Typelizer automatically watches for changes and regenerates interfaces in development mode. You can disable this behavior:
|
|
405
|
-
|
|
406
|
-
```ruby
|
|
407
|
-
Typelizer.listen = false
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### Disabling Typelizer
|
|
411
|
-
|
|
412
|
-
Sometimes we want to use Typelizer only with manual generation. To disable Typelizer during development, we can set `DISABLE_TYPELIZER` environment variable to `true`. This doesn't affect manual generation.
|
|
413
|
-
|
|
414
|
-
## OpenAPI Schema Generation
|
|
415
|
-
|
|
416
|
-
Typelizer can generate [OpenAPI](https://swagger.io/specification/) component schemas from your serializers. This is useful for documenting your API or integrating with tools like [rswag](https://github.com/rswag/rswag).
|
|
417
|
-
|
|
418
|
-
Get all schemas as a hash:
|
|
419
|
-
|
|
420
|
-
```ruby
|
|
421
|
-
Typelizer.openapi_schemas
|
|
422
|
-
# => {
|
|
423
|
-
# "Post" => {
|
|
424
|
-
# type: :object,
|
|
425
|
-
# properties: {
|
|
426
|
-
# id: { type: :integer },
|
|
427
|
-
# title: { type: :string },
|
|
428
|
-
# published_at: { type: :string, format: :"date-time", nullable: true }
|
|
429
|
-
# },
|
|
430
|
-
# required: [:id, :title]
|
|
431
|
-
# },
|
|
432
|
-
# "Author" => { ... }
|
|
433
|
-
# }
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
By default, schemas are generated for OpenAPI 3.0. Pass `openapi_version: "3.1"` for OpenAPI 3.1 output (e.g., `type: [:string, :null]` instead of `nullable: true`):
|
|
437
|
-
|
|
438
|
-
```ruby
|
|
439
|
-
Typelizer.openapi_schemas(openapi_version: "3.1")
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
Generate a schema for a single interface:
|
|
443
|
-
|
|
444
|
-
```ruby
|
|
445
|
-
interfaces = Typelizer.interfaces
|
|
446
|
-
post_interface = interfaces.find { |i| i.name == "Post" }
|
|
447
|
-
Typelizer::OpenAPI.schema_for(post_interface)
|
|
448
|
-
Typelizer::OpenAPI.schema_for(post_interface, openapi_version: "3.1")
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
Column types are mapped to OpenAPI types automatically:
|
|
452
|
-
|
|
453
|
-
| Column type | OpenAPI type | Format |
|
|
454
|
-
|---|---|---|
|
|
455
|
-
| `integer` | `integer` | |
|
|
456
|
-
| `bigint` | `integer` | `int64` |
|
|
457
|
-
| `float` | `number` | `float` |
|
|
458
|
-
| `decimal` | `number` | `double` |
|
|
459
|
-
| `boolean` | `boolean` | |
|
|
460
|
-
| `string`, `text`, `citext` | `string` | |
|
|
461
|
-
| `uuid` | `string` | `uuid` |
|
|
462
|
-
| `date` | `string` | `date` |
|
|
463
|
-
| `datetime` | `string` | `date-time` |
|
|
464
|
-
| `time` | `string` | `time` |
|
|
465
|
-
|
|
466
|
-
Enums, nullable fields, arrays, deprecated flags, and `$ref` associations are all handled automatically.
|
|
467
|
-
|
|
468
|
-
## Configuration
|
|
469
|
-
|
|
470
|
-
Typelizer provides several global configuration options:
|
|
471
|
-
|
|
472
|
-
```ruby
|
|
473
|
-
# Directories to search for serializers:
|
|
474
|
-
Typelizer.dirs = [Rails.root.join("app", "resources"), Rails.root.join("app", "serializers")]
|
|
475
|
-
# Reject specific classes from being typelized:
|
|
476
|
-
Typelizer.reject_class = ->(serializer:) { false }
|
|
477
|
-
# Logger for debugging:
|
|
478
|
-
Typelizer.logger = Logger.new($stdout, level: :info)
|
|
479
|
-
# Force enable or disable file watching with Listen:
|
|
480
|
-
Typelizer.listen = nil
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
### Configuration Layers
|
|
484
|
-
|
|
485
|
-
Typelizer uses a hierarchical system to resolve settings. Settings are applied in the following order of precedence, where higher numbers override lower ones:
|
|
486
|
-
|
|
487
|
-
1. **Per-Serializer Overrides**: Settings defined using `typelizer_config` directly within a serializer class. This layer has the highest priority.
|
|
488
|
-
2. **Writer-Specific Settings**: Settings defined within a `config.writer(:name) { ... }` block.
|
|
489
|
-
3. **Global Settings**: Application-wide settings defined by direct assignment (e.g., `config.comments = true`) within the `Typelizer.configure` block.
|
|
490
|
-
4. **Library Defaults**: The gem's built-in default values.
|
|
491
|
-
|
|
492
|
-
### Simple Configuration (Single Output)
|
|
493
|
-
|
|
494
|
-
For most apps, a single output is enough. All settings in an initializer apply to the `:default` writer and also act as a global baseline.
|
|
495
|
-
|
|
496
|
-
- Settings like `dirs` are considered **Global** and establish a baseline for all writers.
|
|
497
|
-
- Settings like `output_dir` or `comments` configure the implicit **`:default` writer**.
|
|
498
|
-
|
|
499
|
-
```ruby
|
|
500
|
-
# config/initializers/typelizer.rb
|
|
501
|
-
Typelizer.configure do |config|
|
|
502
|
-
# This is a GLOBAL SETTING. It applies to ALL writers.
|
|
503
|
-
config.dirs = [Rails.root.join("app/serializers")]
|
|
504
|
-
|
|
505
|
-
# This setting configures the :default writer and ALSO acts as a global setting.
|
|
506
|
-
config.output_dir = "app/javascript/types/generated"
|
|
507
|
-
config.comments = true
|
|
508
|
-
end
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
### Defining Multiple Writers
|
|
512
|
-
|
|
513
|
-
The multi-writer system allows for the generation of multiple, distinct TypeScript outputs. Each output is managed by a named writer with an isolated configuration.
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
#### Writer Inheritance Rules
|
|
517
|
-
|
|
518
|
-
- By default, a new writer inherits its base settings from the Global Settings.
|
|
519
|
-
- To inherit from another existing writer, use the `from:` option.
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
**A Note on the :default Writer and Inheritance**
|
|
523
|
-
- You usually do not need to declare `writer(:default)`. The implicit default writer automatically uses your global settings.
|
|
524
|
-
- Declare `writer(:default)` when you want to apply specific overrides to it that should not be inherited by other new writers. This provides a way to separate your application's global baseline from settings that are truly unique to the default output
|
|
525
|
-
|
|
526
|
-
#### Example of the distinction:
|
|
527
|
-
```ruby
|
|
528
|
-
Typelizer.configure do |config|
|
|
529
|
-
# === Global Setting ===
|
|
530
|
-
# `comments: true` applies to :default and will be inherited by :camel_case.
|
|
531
|
-
config.comments = true
|
|
532
|
-
|
|
533
|
-
# === Default-Writer-Only Setting ===
|
|
534
|
-
# `prefer_double_quotes: true` applies ONLY to the :default writer.
|
|
535
|
-
# It is NOT a global setting and will NOT be inherited by :camel_case.
|
|
536
|
-
config.writer(:default) do |c|
|
|
537
|
-
c.prefer_double_quotes = true
|
|
538
|
-
end
|
|
539
|
-
|
|
540
|
-
# === New Writer Definition ===
|
|
541
|
-
config.writer(:camel_case) do |c|
|
|
542
|
-
c.output_dir = "app/javascript/types/camel_case"
|
|
543
|
-
# This writer inherits `comments: true` from globals.
|
|
544
|
-
# It does NOT inherit `prefer_double_quotes: true` from the :default writer's block.
|
|
545
|
-
# Its `prefer_double_quotes` will be `false` (the library default).
|
|
546
|
-
end
|
|
547
|
-
end
|
|
548
41
|
```
|
|
549
42
|
|
|
550
|
-
|
|
551
|
-
You can define writers either inside the configure block or directly on the Typelizer module.
|
|
552
|
-
|
|
553
|
-
1. **Inside the configure block**
|
|
554
|
-
|
|
555
|
-
This is the approach for keeping all configuration centralized.
|
|
43
|
+
## Documentation
|
|
556
44
|
|
|
557
|
-
|
|
558
|
-
# config/initializers/typelizer.rb
|
|
559
|
-
Typelizer.configure do |config|
|
|
560
|
-
# ... global settings ...
|
|
45
|
+
**Full documentation: https://typelizer.dev**
|
|
561
46
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
47
|
+
- [Getting Started](https://typelizer.dev/getting-started)
|
|
48
|
+
- [Manual Typing](https://typelizer.dev/guides/manual-typing)
|
|
49
|
+
- [Route Helpers](https://typelizer.dev/guides/routes)
|
|
50
|
+
- [Configuration](https://typelizer.dev/reference/configuration)
|
|
51
|
+
- [Type Mapping](https://typelizer.dev/reference/type-mapping)
|
|
566
52
|
|
|
567
|
-
|
|
568
|
-
c.output_dir = "app/javascript/types/admin"
|
|
569
|
-
c.null_strategy = :optional
|
|
570
|
-
end
|
|
571
|
-
end
|
|
572
|
-
```
|
|
53
|
+
## Development
|
|
573
54
|
|
|
574
|
-
|
|
55
|
+
You need PostgreSQL running locally. Then:
|
|
575
56
|
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
end
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
#### Comprehensive Example
|
|
584
|
-
This example configures three distinct outputs, demonstrating all inheritance mechanisms.
|
|
585
|
-
|
|
586
|
-
```ruby
|
|
587
|
-
# config/initializers/typelizer.rb
|
|
588
|
-
Typelizer.configure do |config|
|
|
589
|
-
# === 1. Global Settings (Baseline for ALL writers) ===
|
|
590
|
-
config.comments = true
|
|
591
|
-
config.dirs = [Rails.root.join("app/serializers")]
|
|
592
|
-
|
|
593
|
-
# === 2. The :default writer (snake_case output) ===
|
|
594
|
-
config.writer(:default) do |c|
|
|
595
|
-
c.output_dir = "app/javascript/types/snake_case"
|
|
596
|
-
end
|
|
597
|
-
|
|
598
|
-
# === 3. A new :camel_case writer ===
|
|
599
|
-
# Inherits `comments: true` and `dirs` from the Global Settings.
|
|
600
|
-
config.writer(:camel_case) do |c|
|
|
601
|
-
c.output_dir = "app/javascript/types/camel_case"
|
|
602
|
-
c.properties_transformer = lambda do |properties|
|
|
603
|
-
properties.map { |prop| prop.with_overrides(name: prop.name.to_s.camelize(:lower)) }
|
|
604
|
-
end
|
|
605
|
-
end
|
|
606
|
-
|
|
607
|
-
# === 4. An "admin" writer that clones :camel_case ===
|
|
608
|
-
# Use `from:` to explicitly inherit another writer's complete configuration.
|
|
609
|
-
config.writer(:admin, from: :camel_case) do |c|
|
|
610
|
-
c.output_dir = "app/javascript/types/admin"
|
|
611
|
-
# This writer inherits the properties_transformer from :camel_case.
|
|
612
|
-
c.null_strategy = :optional
|
|
613
|
-
end
|
|
614
|
-
end
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
### Per-serializer configuration
|
|
618
|
-
|
|
619
|
-
Use `typelizer_config` within a serializer class to apply overrides with the highest possible priority.
|
|
620
|
-
These settings will supersede any conflicting settings from the active writer, global settings, or library defaults.
|
|
621
|
-
|
|
622
|
-
```ruby
|
|
623
|
-
class PostResource < ApplicationResource
|
|
624
|
-
typelizer_config do |c|
|
|
625
|
-
c.null_strategy = :nullable_and_optional
|
|
626
|
-
c.plugin_configs = { alba: { ts_mapper: { "UUID" => { type: :string } } } }
|
|
627
|
-
end
|
|
628
|
-
end
|
|
57
|
+
```bash
|
|
58
|
+
bundle install
|
|
59
|
+
cd spec/app && RAILS_ENV=test bundle exec rails db:create db:migrate && cd ../..
|
|
60
|
+
bundle exec rspec
|
|
629
61
|
```
|
|
630
62
|
|
|
631
|
-
|
|
63
|
+
The test suite uses a dummy Rails app in `spec/app/` with models, migrations, and serializers for all four supported frameworks (Alba, AMS, OjSerializers, Panko). Linting is done with StandardRB:
|
|
632
64
|
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
typelizer_config do |c|
|
|
636
|
-
c.output_dir = Rails.root.join("app/javascript/types/admin")
|
|
637
|
-
end
|
|
638
|
-
end
|
|
65
|
+
```bash
|
|
66
|
+
bundle exec standardrb
|
|
639
67
|
```
|
|
640
68
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
```ruby
|
|
644
|
-
Typelizer.configure do |config|
|
|
645
|
-
# Name to type mapping for serializer classes
|
|
646
|
-
config.serializer_name_mapper = ->(serializer) { ... }
|
|
647
|
-
|
|
648
|
-
# Custom file path mapping (decouples filename from type name)
|
|
649
|
-
# Receives the mapped name (output of serializer_name_mapper) and returns a file path.
|
|
650
|
-
# When nil (default), filename is derived from the type name.
|
|
651
|
-
# Example: ->(name) { name.gsub("::", "/") }
|
|
652
|
-
# Alba::UserSerializer → types/Alba/User.ts (type name stays AlbaUser)
|
|
653
|
-
config.filename_mapper = nil
|
|
654
|
-
|
|
655
|
-
# Maps serializers to their corresponding model classes
|
|
656
|
-
config.serializer_model_mapper = ->(serializer) { ... }
|
|
657
|
-
|
|
658
|
-
# Custom transformation for generated properties
|
|
659
|
-
config.properties_transformer = ->(properties) { ... }
|
|
660
|
-
|
|
661
|
-
# Strategy for ordering properties in generated TypeScript interfaces
|
|
662
|
-
# :none - preserve serializer definition order (default)
|
|
663
|
-
# :alphabetical - sort properties A-Z (case-insensitive)
|
|
664
|
-
# :id_first_alphabetical - place 'id' first, then sort remaining A-Z
|
|
665
|
-
# Proc - custom sorting function receiving array of Property objects
|
|
666
|
-
config.properties_sort_order = :none
|
|
667
|
-
|
|
668
|
-
# Strategy for ordering imports in generated TypeScript interfaces
|
|
669
|
-
# :none - preserve original order (default)
|
|
670
|
-
# :alphabetical - sort imports A-Z (case-insensitive)
|
|
671
|
-
# Proc - custom sorting function receiving array of import strings
|
|
672
|
-
config.imports_sort_order = :none
|
|
673
|
-
|
|
674
|
-
# Plugin for model type inference (default: ModelPlugins::Auto)
|
|
675
|
-
config.model_plugin = Typelizer::ModelPlugins::Auto
|
|
676
|
-
|
|
677
|
-
# Plugin for serializer parsing (default: SerializerPlugins::Auto)
|
|
678
|
-
config.serializer_plugin = Typelizer::SerializerPlugins::Auto
|
|
679
|
-
|
|
680
|
-
# Additional configurations for specific plugins
|
|
681
|
-
config.plugin_configs = { alba: { ts_mapper: {...} } }
|
|
682
|
-
|
|
683
|
-
# Custom DB to TypeScript type mapping
|
|
684
|
-
config.type_mapping = config.type_mapping.merge(jsonb: "Record<string, undefined>", ... )
|
|
685
|
-
|
|
686
|
-
# Strategy for handling null values (:nullable, :optional, or :nullable_and_optional)
|
|
687
|
-
config.null_strategy = :nullable
|
|
688
|
-
|
|
689
|
-
# Strategy for handling serializer inheritance (:none, :inheritance)
|
|
690
|
-
# :none - lists all attributes of the serializer in the type
|
|
691
|
-
# :inheritance - extends the type from the parent serializer
|
|
692
|
-
config.inheritance_strategy = :none
|
|
693
|
-
|
|
694
|
-
# Strategy for handling `has_one` and `belongs_to` associations nullability (:database, :active_record)
|
|
695
|
-
# :database - uses the database column nullability
|
|
696
|
-
# :active_record - uses the `required` / `optional` association options
|
|
697
|
-
config.associations_strategy = :database
|
|
698
|
-
|
|
699
|
-
# Directory where TypeScript interfaces will be generated
|
|
700
|
-
config.output_dir = Rails.root.join("app/javascript/types/serializers")
|
|
701
|
-
|
|
702
|
-
# Import path for generated types in TypeScript files
|
|
703
|
-
# (e.g., `import { MyType } from "@/types"`)
|
|
704
|
-
config.types_import_path = "@/types"
|
|
705
|
-
|
|
706
|
-
# List of type names that should be considered global in TypeScript
|
|
707
|
-
# (i.e. not prefixed with the import path)
|
|
708
|
-
config.types_global = %w[Array Date Record File FileList]
|
|
709
|
-
|
|
710
|
-
# Support TypeScript's Verbatim module syntax option (default: false)
|
|
711
|
-
# Will change imports and exports of types from default to support this syntax option
|
|
712
|
-
config.verbatim_module_syntax = false
|
|
713
|
-
|
|
714
|
-
# Use double quotes in generated TypeScript interfaces (default: false)
|
|
715
|
-
config.prefer_double_quotes = false
|
|
716
|
-
|
|
717
|
-
# Support comments in generated TypeScript interfaces (default: false)
|
|
718
|
-
# Will add comments to the generated interfaces
|
|
719
|
-
config.comments = false
|
|
720
|
-
end
|
|
721
|
-
```
|
|
69
|
+
`bundle exec rake` runs both the tests and the linter.
|
|
722
70
|
|
|
723
71
|
## Credits
|
|
724
72
|
|
|
725
|
-
Typelizer is inspired by [types_from_serializers](https://github.com/ElMassimo/types_from_serializers).
|
|
73
|
+
Typelizer is inspired by [types_from_serializers](https://github.com/ElMassimo/types_from_serializers), [js-routes](https://github.com/railsware/js-routes), and [Wayfinder](https://github.com/nicholasvansanten/wayfinder).
|
|
74
|
+
|
|
75
|
+
<a href="https://evilmartians.com/?utm_source=typelizer&utm_campaign=project_page">
|
|
76
|
+
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Built by Evil Martians" width="236" height="54">
|
|
77
|
+
</a>
|
|
726
78
|
|
|
727
79
|
## License
|
|
728
80
|
|