Skip to content

Commit 21263ec

Browse files
chore(main): release 0.6.0 (#22)
🤖 I have created a release *beep* *boop* --- ## [0.6.0](v0.5.0...v0.6.0) (2026-03-07) ### Features * add null option and rewrite README ([#23](#23)) ([f673f6e](f673f6e)) ### Bug Fixes * typos ([cf89ffd](cf89ffd)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Phil Chen <06fahchen@gmail.com>
1 parent f673f6e commit 21263ec

3 files changed

Lines changed: 80 additions & 138 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
## [0.6.0](https://github.com/elixir-typed-structor/typed_structor/compare/v0.5.0...v0.6.0) (2026-03-07)
4+
5+
6+
### Features
7+
8+
* add null option and rewrite README ([#23](https://github.com/elixir-typed-structor/typed_structor/issues/23)) ([f673f6e](https://github.com/elixir-typed-structor/typed_structor/commit/f673f6eae9302b5e2468b00b1afe53c917729286))
9+
10+
11+
### Bug Fixes
12+
13+
* typos ([cf89ffd](https://github.com/elixir-typed-structor/typed_structor/commit/cf89ffd9ff05e2254e3f748f05a25e9ce5d52555))
14+
315
## [0.5.0](https://github.com/elixir-typed-structor/typed_structor/compare/v0.4.2...v0.5.0) (2024-09-27)
416

517

README.md

Lines changed: 67 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22

33
[![Build Status](https://github.com/elixir-typed-structor/typed_structor/actions/workflows/elixir.yml/badge.svg)](https://github.com/elixir-typed-structor/typed_structor/actions/workflows/elixir.yml)
44
[![Hex.pm](https://img.shields.io/hexpm/v/typed_structor)](https://hex.pm/packages/typed_structor)
5-
[![Document](https://img.shields.io/badge/document-gray)](https://hexdocs.pm/typed_structor)
5+
[![HexDocs](https://img.shields.io/badge/HexDocs-gray)](https://hexdocs.pm/typed_structor)
66
[![Plugin guides](https://img.shields.io/badge/plugin_guides-indianred?label=%F0%9F%94%A5&labelColor=snow)](https://hexdocs.pm/typed_structor/introduction.html)
77

8-
Define your structs and types in one place. TypedStructor generates `defstruct`, type specs, and `@enforce_keys` while keeping your code clean and explicit.
8+
TypedStructor eliminates the boilerplate of defining Elixir structs, type specs, and enforced keys separately. Define them once, keep them in sync automatically.
99

10-
## Why TypedStructor?
11-
12-
**Without TypedStructor**, you write everything twice:
10+
**Before** -- three declarations that must stay in sync manually:
1311

1412
```elixir
1513
defmodule User do
@@ -24,7 +22,7 @@ defmodule User do
2422
end
2523
```
2624

27-
**With TypedStructor**, you write it once:
25+
**After** -- a single source of truth:
2826

2927
```elixir
3028
defmodule User do
@@ -38,7 +36,16 @@ defmodule User do
3836
end
3937
```
4038

41-
Same result, half the boilerplate. Your struct definition and type spec stay in sync automatically.
39+
## Feature Highlights
40+
41+
- **Single definition** -- struct, type spec, and `@enforce_keys` generated from one block
42+
- **Nullable by default** -- unenforced fields without defaults automatically include `| nil`
43+
- **Fine-grained null control** -- override nullability per-field or per-block with the `:null` option
44+
- **Opaque and custom types** -- generate `@opaque`, `@typep`, or rename the type from `t()`
45+
- **Type parameters** -- define generic/parametric types
46+
- **Multiple definers** -- supports structs, exceptions, and Erlang records
47+
- **Plugin system** -- extend behavior at compile time with composable plugins
48+
- **Nested modules** -- define structs in submodules with the `:module` option
4249

4350
<!-- MODULEDOC -->
4451

@@ -49,7 +56,7 @@ Add `:typed_structor` to your dependencies in `mix.exs`:
4956
```elixir
5057
def deps do
5158
[
52-
{:typed_structor, "~> 0.5"}
59+
{:typed_structor, "~> 0.6"}
5360
]
5461
end
5562
```
@@ -74,59 +81,16 @@ defmodule User do
7481
use TypedStructor
7582
7683
typed_structor do
77-
field :id, pos_integer()
78-
field :name, String.t()
84+
field :id, pos_integer(), enforce: true # Required, never nil
85+
field :name, String.t() # Optional, nullable
86+
field :role, String.t(), default: "user" # Has default, not nullable
7987
end
8088
end
8189
```
8290
83-
This generates a struct and type where fields are nullable by default (`pos_integer() | nil`).
84-
85-
### Enforcing Required Fields
86-
87-
Make fields non-nullable by enforcing them:
88-
89-
```elixir
90-
typed_structor do
91-
field :id, pos_integer(), enforce: true # Required, never nil
92-
field :name, String.t() # Optional, can be nil
93-
end
94-
```
95-
96-
### Providing Defaults
97-
98-
Fields with defaults don't need to be nullable since they always have a value:
99-
100-
```elixir
101-
typed_structor do
102-
field :id, pos_integer(), enforce: true
103-
field :name, String.t(), default: "Unknown" # String.t() (not nullable)
104-
field :age, non_neg_integer() # non_neg_integer() | nil
105-
end
106-
```
107-
108-
### Controlling Nullability
109-
110-
You can explicitly control whether fields accept `nil`:
111-
112-
```elixir
113-
typed_structor do
114-
field :id, integer(), null: false # Never nil
115-
field :name, String.t(), null: true # Always nullable
116-
end
117-
```
118-
119-
Or set defaults for all fields in a block:
91+
### Nullability Rules
12092

121-
```elixir
122-
typed_structor null: false do
123-
field :id, integer() # Not nullable by default
124-
field :email, String.t() # Not nullable by default
125-
field :phone, String.t(), null: true # Override for this field
126-
end
127-
```
128-
129-
The interaction between `enforce`, `default`, and `null` follows this logic:
93+
The interaction between `:enforce`, `:default`, and `:null` determines whether a field's type includes `nil`:
13094

13195
| `:default` | `:enforce` | `:null` | Type includes `nil`? |
13296
|------------|------------|---------|----------------------|
@@ -135,52 +99,37 @@ The interaction between `enforce`, `default`, and `null` follows this logic:
13599
| `set` | - | - | no |
136100
| - | `true` | - | no |
137101

138-
This is particularly useful when modeling database records where some fields can be `nil`:
102+
You can set `:null` at the block level to change the default for all fields:
139103

140104
```elixir
141-
defmodule DatabaseRecord do
142-
use TypedStructor
143-
144-
typed_structor do
145-
# These fields can be nil when loaded from DB
146-
field :name, String.t()
147-
field :description, String.t()
148-
149-
# These fields cannot be nil (e.g., primary keys, timestamps)
150-
field :id, integer(), null: false
151-
field :inserted_at, DateTime.t(), null: false
152-
field :updated_at, DateTime.t(), null: false
153-
end
105+
typed_structor null: false do
106+
field :id, integer() # Not nullable
107+
field :email, String.t() # Not nullable
108+
field :phone, String.t(), null: true # Override: nullable
154109
end
155110
```
156111

157112
## Options
158113

159-
TypedStructor provides several options to customize your type definitions:
160-
161114
### Opaque Types
162115

163116
Use `type_kind: :opaque` to hide implementation details:
164117

165118
```elixir
166119
typed_structor type_kind: :opaque do
167-
field :id, pos_integer()
168120
field :secret, String.t()
169121
end
170-
171122
# Generates: @opaque t() :: %__MODULE__{...}
172123
```
173124

174125
### Custom Type Names
175126

176-
Override the default `t()` type name with `type_name`:
127+
Override the default `t()` type name:
177128

178129
```elixir
179130
typed_structor type_name: :user_data do
180131
field :id, pos_integer()
181-
field :name, String.t()
182132
end
183-
184133
# Generates: @type user_data() :: %__MODULE__{...}
185134
```
186135

@@ -196,13 +145,10 @@ typed_structor do
196145
field :value, value_type
197146
field :error, error_type
198147
end
199-
200148
# Generates: @type t(value_type, error_type) :: %__MODULE__{...}
201149
```
202150

203-
## Common Patterns
204-
205-
### Nested Structs
151+
### Nested Modules
206152

207153
Define structs in submodules:
208154

@@ -215,41 +161,45 @@ defmodule User do
215161
field :bio, String.t()
216162
end
217163
end
218-
219164
# Creates User.Profile with its own struct and type
220165
```
221166

222-
### Integration with Other Tools
167+
## Plugins
223168

224-
Skip struct generation to use with Ecto or other schema tools:
169+
Extend TypedStructor's behavior with plugins that run at compile time:
225170

226171
```elixir
227-
defmodule User do
228-
use TypedStructor
172+
typed_structor do
173+
plugin Guides.Plugins.Accessible
229174

230-
typed_structor module: Profile, define_struct: false do
231-
@derive {Jason.Encoder, only: [:email]}
232-
field :email, String.t(), enforce: true
175+
field :id, pos_integer()
176+
field :name, String.t()
177+
end
178+
```
233179

234-
use Ecto.Schema
235-
@primary_key false
180+
See the [Plugin Guides](https://hexdocs.pm/typed_structor/introduction.html) for examples and instructions on writing your own.
236181

237-
schema "users" do
238-
Ecto.Schema.field(:email, :string)
239-
end
182+
## Documentation
240183

241-
import Ecto.Changeset
184+
Add `@typedoc` inside the block, and `@moduledoc` at the module level as usual:
242185

243-
def changeset(%__MODULE__{} = user, attrs) do
244-
user
245-
|> cast(attrs, [:email])
246-
|> validate_required([:email])
247-
end
186+
```elixir
187+
defmodule User do
188+
@moduledoc "User account data"
189+
use TypedStructor
190+
191+
typed_structor do
192+
@typedoc "A user with authentication details"
193+
194+
field :id, pos_integer()
195+
field :name, String.t()
248196
end
249197
end
250198
```
251199

252-
## Advanced Features
200+
<!-- MODULEDOC -->
201+
202+
## Advanced Usage
253203

254204
### Exceptions
255205

@@ -273,7 +223,7 @@ end
273223

274224
### Records
275225

276-
Create Erlang-compatible records for interoperability:
226+
Create Erlang-compatible records:
277227

278228
```elixir
279229
defmodule UserRecord do
@@ -286,53 +236,33 @@ defmodule UserRecord do
286236
end
287237
```
288238

289-
## Plugins
239+
### Integration with Other Libraries
290240

291-
Extend TypedStructor's behavior with plugins. They run during compilation to add functionality:
241+
Use `define_struct: false` to skip struct generation when another library defines the struct:
292242

293243
```elixir
294244
defmodule User do
295245
use TypedStructor
296246

297-
typed_structor do
298-
plugin Guides.Plugins.Accessible # Adds Access behavior
299-
300-
field :id, pos_integer()
301-
field :name, String.t()
302-
end
303-
end
304-
305-
user = %User{id: 1, name: "Phil"}
306-
get_in(user, [:name]) # => "Phil"
307-
```
308-
309-
> #### Plugin Guides {: .tip}
310-
>
311-
> Check out the [Plugin Guides](guides/plugins/introduction.md) to learn how to create your own plugins.
312-
> All examples include copy-paste-ready code.
313-
314-
## Documentation
315-
316-
Add `@moduledoc` at the module level, and `@typedoc` inside the block:
317-
318-
```elixir
319-
defmodule User do
320-
@moduledoc "User management structures"
321-
use TypedStructor
247+
typed_structor define_struct: false do
248+
field :email, String.t(), enforce: true
322249

323-
typed_structor do
324-
@typedoc "A user with authentication details"
250+
use Ecto.Schema
251+
@primary_key false
325252

326-
field :id, pos_integer()
327-
field :name, String.t()
253+
schema "users" do
254+
Ecto.Schema.field(:email, :string)
255+
end
328256
end
329257
end
330258
```
331259

332-
<!-- MODULEDOC -->
260+
This generates only the type spec while letting the other library handle the struct definition.
261+
262+
For full Ecto integration with typed fields, see [EctoTypedSchema](https://github.com/elixir-typed-structor/ecto_typed_schema) -- a companion library built on TypedStructor.
333263

334264
## Learn More
335265

336-
- **API Reference**: Check `TypedStructor.typed_structor/2` and `TypedStructor.field/3` for all options
337-
- **Plugin System**: See `TypedStructor.Plugin` for creating custom plugins
338-
- **Guides**: Visit [hexdocs.pm/typed_structor](https://hexdocs.pm/typed_structor) for detailed guides
266+
- [HexDocs](https://hexdocs.pm/typed_structor) -- full API reference and guides
267+
- [Plugin Guides](https://hexdocs.pm/typed_structor/introduction.html) -- build and use plugins
268+
- [Changelog](https://hexdocs.pm/typed_structor/changelog.html) -- release history

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule TypedStructor.MixProject do
22
use Mix.Project
33

4-
@version "0.5.0"
4+
@version "0.6.0"
55
@source_url "https://github.com/elixir-typed-structor/typed_structor"
66

77
def project do

0 commit comments

Comments
 (0)