Skip to content

Commit 6c67d38

Browse files
gsmlgGSMLG-BOT
andauthored
feat: Phoenix.SessionProcess v1.0.0 - Production Ready (#6)
Co-authored-by: Jonathan Gao <[email protected]>
1 parent 7e7a8da commit 6c67d38

39 files changed

+5745
-5104
lines changed

.credo.exs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
%{
2+
configs: [
3+
%{
4+
name: "default",
5+
files: %{
6+
included: [
7+
"lib/",
8+
"test/"
9+
],
10+
excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"]
11+
},
12+
strict: true,
13+
checks: %{
14+
enabled: [
15+
#
16+
## consistency checks
17+
#
18+
{Credo.Check.Consistency.ExceptionNames, []},
19+
{Credo.Check.Consistency.LineEndings, []},
20+
{Credo.Check.Consistency.ParameterPatternMatching, []},
21+
{Credo.Check.Consistency.SpaceAroundOperators, []},
22+
{Credo.Check.Consistency.SpaceInParentheses, []},
23+
{Credo.Check.Consistency.TabsOrSpaces, []},
24+
25+
#
26+
## design checks
27+
#
28+
{Credo.Check.Design.AliasUsage,
29+
[priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]},
30+
31+
#
32+
## readability checks
33+
#
34+
{Credo.Check.Readability.AliasOrder, []},
35+
{Credo.Check.Readability.FunctionNames, []},
36+
{Credo.Check.Readability.LargeNumbers, []},
37+
{Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},
38+
{Credo.Check.Readability.ModuleAttributeNames, []},
39+
{Credo.Check.Readability.ModuleDoc, []},
40+
{Credo.Check.Readability.ModuleNames, []},
41+
{Credo.Check.Readability.ParenthesesInCondition, []},
42+
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
43+
{Credo.Check.Readability.PipeIntoAnonymousFunctions, []},
44+
{Credo.Check.Readability.PredicateFunctionNames, []},
45+
{Credo.Check.Readability.PreferImplicitTry, []},
46+
{Credo.Check.Readability.RedundantBlankLines, []},
47+
{Credo.Check.Readability.Semicolons, []},
48+
{Credo.Check.Readability.SpaceAfterCommas, []},
49+
{Credo.Check.Readability.StringSigils, []},
50+
{Credo.Check.Readability.TrailingBlankLine, []},
51+
{Credo.Check.Readability.TrailingWhiteSpace, []},
52+
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
53+
{Credo.Check.Readability.VariableNames, []},
54+
{Credo.Check.Readability.WithSingleClause, []},
55+
56+
#
57+
## refactoring checks
58+
#
59+
{Credo.Check.Refactor.Apply, []},
60+
{Credo.Check.Refactor.CondStatements, []},
61+
{Credo.Check.Refactor.CyclomaticComplexity, []},
62+
{Credo.Check.Refactor.FilterCount, []},
63+
{Credo.Check.Refactor.FilterFilter, []},
64+
{Credo.Check.Refactor.FunctionArity, []},
65+
# Disable LongQuoteBlocks for macros that inject GenServer implementations
66+
# {Credo.Check.Refactor.LongQuoteBlocks, []},
67+
{Credo.Check.Refactor.MapJoin, []},
68+
{Credo.Check.Refactor.MapMap, []},
69+
{Credo.Check.Refactor.MatchInCondition, []},
70+
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
71+
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
72+
{Credo.Check.Refactor.Nesting, []},
73+
{Credo.Check.Refactor.RedundantWithClauseResult, []},
74+
{Credo.Check.Refactor.RejectFilter, []},
75+
{Credo.Check.Refactor.UnlessWithElse, []},
76+
{Credo.Check.Refactor.WithClauses, []},
77+
78+
#
79+
## warning checks
80+
#
81+
{Credo.Check.Warning.ApplicationConfigInModuleAttribute, []},
82+
{Credo.Check.Warning.BoolOperationOnSameValues, []},
83+
{Credo.Check.Warning.Dbg, []},
84+
{Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},
85+
{Credo.Check.Warning.IExPry, []},
86+
{Credo.Check.Warning.IoInspect, []},
87+
{Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []},
88+
{Credo.Check.Warning.OperationOnSameValues, []},
89+
{Credo.Check.Warning.OperationWithConstantResult, []},
90+
{Credo.Check.Warning.RaiseInsideRescue, []},
91+
{Credo.Check.Warning.SpecWithStruct, []},
92+
{Credo.Check.Warning.UnsafeExec, []},
93+
{Credo.Check.Warning.UnusedEnumOperation, []},
94+
{Credo.Check.Warning.UnusedFileOperation, []},
95+
{Credo.Check.Warning.UnusedKeywordOperation, []},
96+
{Credo.Check.Warning.UnusedListOperation, []},
97+
{Credo.Check.Warning.UnusedPathOperation, []},
98+
{Credo.Check.Warning.UnusedRegexOperation, []},
99+
{Credo.Check.Warning.UnusedStringOperation, []},
100+
{Credo.Check.Warning.UnusedTupleOperation, []},
101+
{Credo.Check.Warning.WrongTestFileExtension, []}
102+
],
103+
disabled: [
104+
# Disabled because macros like __using__(:process) inject GenServer implementations
105+
# which naturally result in long quote blocks that cannot be easily refactored
106+
{Credo.Check.Refactor.LongQuoteBlocks, []}
107+
]
108+
}
109+
}
110+
]
111+
}

.github/workflows/benchmark.yml

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: Benchmark
2+
3+
on:
4+
pull_request:
5+
branches: [ main, develop ]
6+
workflow_dispatch:
7+
inputs:
8+
benchmark:
9+
description: 'Benchmark to run'
10+
required: false
11+
default: 'all'
12+
type: choice
13+
options:
14+
- all
15+
- simple
16+
- session
17+
- dispatch
18+
19+
jobs:
20+
benchmark:
21+
name: Run Benchmarks
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 15
24+
25+
steps:
26+
- name: Checkout code
27+
uses: actions/checkout@v4
28+
29+
- name: Set up Elixir
30+
uses: erlef/setup-beam@v1
31+
with:
32+
elixir-version: '1.18'
33+
otp-version: '28'
34+
35+
- name: Restore dependencies cache
36+
uses: actions/cache@v4
37+
with:
38+
path: |
39+
deps
40+
_build
41+
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
42+
restore-keys: ${{ runner.os }}-mix-
43+
44+
- name: Install dependencies
45+
run: mix deps.get
46+
47+
- name: Compile
48+
run: mix compile
49+
50+
- name: Run Simple Benchmark
51+
if: github.event.inputs.benchmark == 'simple' || github.event.inputs.benchmark == 'all' || github.event_name == 'pull_request'
52+
run: |
53+
echo "::group::Simple Benchmark"
54+
mix run bench/simple_bench.exs
55+
echo "::endgroup::"
56+
57+
- name: Run Session Benchmark
58+
if: github.event.inputs.benchmark == 'session' || github.event.inputs.benchmark == 'all' || github.event_name == 'pull_request'
59+
run: |
60+
echo "::group::Session Benchmark"
61+
mix run bench/session_benchmark.exs
62+
echo "::endgroup::"
63+
64+
- name: Run Dispatch Benchmark
65+
if: github.event.inputs.benchmark == 'dispatch' || github.event.inputs.benchmark == 'all' || github.event_name == 'pull_request'
66+
run: |
67+
echo "::group::Dispatch Benchmark"
68+
mix run bench/dispatch_benchmark.exs
69+
echo "::endgroup::"
70+
71+
- name: Benchmark Summary
72+
if: always()
73+
run: |
74+
echo "## Benchmark Results" >> $GITHUB_STEP_SUMMARY
75+
echo "" >> $GITHUB_STEP_SUMMARY
76+
echo "✅ Benchmarks completed successfully!" >> $GITHUB_STEP_SUMMARY
77+
echo "" >> $GITHUB_STEP_SUMMARY
78+
echo "### Benchmarks Run:" >> $GITHUB_STEP_SUMMARY
79+
if [ "${{ github.event.inputs.benchmark }}" == "all" ] || [ "${{ github.event_name }}" == "pull_request" ]; then
80+
echo "- Simple Benchmark (quick performance check)" >> $GITHUB_STEP_SUMMARY
81+
echo "- Session Benchmark (comprehensive session lifecycle)" >> $GITHUB_STEP_SUMMARY
82+
echo "- Dispatch Benchmark (dispatch performance)" >> $GITHUB_STEP_SUMMARY
83+
elif [ "${{ github.event.inputs.benchmark }}" == "simple" ]; then
84+
echo "- Simple Benchmark (quick performance check)" >> $GITHUB_STEP_SUMMARY
85+
elif [ "${{ github.event.inputs.benchmark }}" == "session" ]; then
86+
echo "- Session Benchmark (comprehensive session lifecycle)" >> $GITHUB_STEP_SUMMARY
87+
elif [ "${{ github.event.inputs.benchmark }}" == "dispatch" ]; then
88+
echo "- Dispatch Benchmark (dispatch performance)" >> $GITHUB_STEP_SUMMARY
89+
fi
90+
echo "" >> $GITHUB_STEP_SUMMARY
91+
echo "### Performance Metrics" >> $GITHUB_STEP_SUMMARY
92+
echo "See detailed results in the benchmark steps above." >> $GITHUB_STEP_SUMMARY

CHANGELOG.md

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,179 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.0] - 2025-10-31
9+
10+
### Breaking Changes
11+
12+
- **Renamed `@prefix` to `@action_prefix` in reducer modules**
13+
- All reducer modules must now use `@action_prefix` instead of `@prefix`
14+
- The `@action_prefix` can be `nil` or `""` to create catch-all reducers that handle all actions
15+
- Migration: Simply rename `@prefix` to `@action_prefix` in your reducer modules
16+
- Example:
17+
```elixir
18+
# Before (v0.x)
19+
defmodule MyReducer do
20+
use Phoenix.SessionProcess, :reducer
21+
@name :my_reducer
22+
@prefix "my" # Old name
23+
end
24+
25+
# After (v1.0.0)
26+
defmodule MyReducer do
27+
use Phoenix.SessionProcess, :reducer
28+
@name :my_reducer
29+
@action_prefix "my" # New name
30+
end
31+
```
32+
33+
- **Changed `dispatch/3` and `dispatch_async/3` return values**
34+
- Both functions now return `:ok` instead of `{:ok, new_state}`
35+
- All dispatches are now async (fire-and-forget) by default
36+
- Use `get_state/1-2` to retrieve state after dispatch
37+
- Migration:
38+
```elixir
39+
# Before (v0.x)
40+
{:ok, new_state} = SessionProcess.dispatch(session_id, :increment)
41+
IO.inspect(new_state)
42+
43+
# After (v1.0.0)
44+
:ok = SessionProcess.dispatch(session_id, :increment)
45+
new_state = SessionProcess.get_state(session_id)
46+
IO.inspect(new_state)
47+
```
48+
49+
- **Removed deprecated `Phoenix.SessionProcess.Redux` module**
50+
- The old struct-based Redux API has been removed
51+
- Use the Redux Store API (built into SessionProcess) instead
52+
- See migration guide: `REDUX_TO_SESSIONPROCESS_MIGRATION.md`
53+
54+
- **Action prefix stripping in reducers**
55+
- Reducers with `@action_prefix` now receive action types with the prefix stripped
56+
- When a reducer has `@action_prefix "counter"`, `handle_action` receives `"increment"` instead of `"counter.increment"`
57+
- Dispatch calls still use full prefixed names (e.g., `dispatch(id, "counter.increment")`)
58+
- Catch-all reducers (prefix `nil` or `""`) receive unchanged action types
59+
- Migration:
60+
```elixir
61+
# Before (v0.x)
62+
defmodule CounterReducer do
63+
use Phoenix.SessionProcess, :reducer
64+
@name :counter
65+
@action_prefix "counter"
66+
67+
def handle_action(%Action{type: "counter.increment"}, state) do
68+
%{state | count: state.count + 1}
69+
end
70+
end
71+
72+
# After (v1.0.0)
73+
defmodule CounterReducer do
74+
use Phoenix.SessionProcess, :reducer
75+
@name :counter
76+
@action_prefix "counter"
77+
78+
def handle_action(%Action{type: "increment"}, state) do
79+
%{state | count: state.count + 1}
80+
end
81+
end
82+
83+
# Dispatch calls remain unchanged
84+
dispatch(session_id, "counter.increment")
85+
```
86+
87+
### Added
88+
89+
- **`dispatch_async/3` function for explicit async dispatch**
90+
- Same behavior as `dispatch/3` but with clearer naming for async operations
91+
- Makes code intent more explicit when dispatching async actions
92+
- Example: `:ok = SessionProcess.dispatch_async(session_id, :increment)`
93+
94+
### Changed
95+
96+
- **Improved action routing with `@action_prefix`**
97+
- More consistent naming aligns with action routing semantics
98+
- Catch-all reducers now explicitly use `nil` or `""` for `@action_prefix`
99+
- Better documentation and examples for action routing
100+
101+
### Migration Guide
102+
103+
1. **Rename `@prefix` to `@action_prefix` in all reducer modules**
104+
- Search your codebase for `@prefix` in reducer modules
105+
- Replace with `@action_prefix`
106+
- No logic changes required
107+
108+
2. **Update action type patterns in handle_action/handle_async**
109+
- Remove the prefix from action type patterns
110+
- Example: `"counter.increment"` becomes `"increment"`
111+
- Dispatch calls remain unchanged (still use full prefixed names)
112+
- Only affects reducers with non-nil/non-empty `@action_prefix`
113+
114+
3. **Update dispatch call sites to handle `:ok` return value**
115+
- Replace `{:ok, state} = dispatch(...)` with `:ok = dispatch(...)`
116+
- Add `get_state(session_id)` calls where you need the updated state
117+
- Consider: Do you actually need the state? Many dispatches are fire-and-forget
118+
119+
4. **Remove uses of deprecated Redux module**
120+
- If using `Phoenix.SessionProcess.Redux` struct-based API
121+
- Migrate to Redux Store API (SessionProcess IS the store)
122+
- See `REDUX_TO_SESSIONPROCESS_MIGRATION.md` for detailed migration
123+
124+
### Notes
125+
126+
- All changes are breaking but migrations are straightforward
127+
- Most codebases will only need to rename `@prefix` to `@action_prefix`
128+
- Dispatch return value change makes async nature more explicit
129+
- v0.9.x will be the last version supporting deprecated APIs
130+
131+
## [0.6.0] - 2025-10-29
132+
133+
### Added
134+
- **Redux Store API**: SessionProcess now IS the Redux store - no separate Redux struct needed
135+
- `Phoenix.SessionProcess.dispatch/3` - Dispatch actions synchronously or asynchronously
136+
- `Phoenix.SessionProcess.subscribe/4` - Subscribe to state changes with optional selectors
137+
- `Phoenix.SessionProcess.unsubscribe/2` - Remove subscriptions
138+
- `Phoenix.SessionProcess.register_reducer/3` - Register named reducers
139+
- `Phoenix.SessionProcess.register_selector/3` - Register named selectors
140+
- `Phoenix.SessionProcess.get_state/2` - Get state with optional selector
141+
- `Phoenix.SessionProcess.select/2` - Apply registered selector to current state
142+
- `user_init/1` callback for defining initial Redux state
143+
- **Enhanced LiveView Integration**: New helpers for Redux Store API
144+
- `Phoenix.SessionProcess.LiveView.mount_store/4` - Mount with direct SessionProcess subscriptions
145+
- `Phoenix.SessionProcess.LiveView.unmount_store/1` - Clean up subscriptions (optional, automatic cleanup via monitoring)
146+
- `Phoenix.SessionProcess.LiveView.dispatch_store/3` - Dispatch actions with sync/async options
147+
- **Selector-Based Subscriptions**: Only receive updates when selected state changes
148+
- Efficient fine-grained state updates
149+
- Memoized selector support
150+
- Automatic equality checking to prevent unnecessary notifications
151+
- **Process Monitoring**: Automatic subscription cleanup when LiveView processes terminate
152+
- **Comprehensive Documentation**: Migration guides and examples
153+
- `MIGRATION_GUIDE.md` - Quick migration guide with 2-step process
154+
- `REDUX_TO_SESSIONPROCESS_MIGRATION.md` - Detailed migration guide
155+
- `examples/liveview_redux_store_example.ex` - Complete working example (400+ lines)
156+
- Updated CLAUDE.md with comprehensive Redux Store API documentation
157+
158+
### Changed
159+
- **70% Less Boilerplate**: Simplified API eliminates manual Redux struct management
160+
- **Simpler Architecture**: SessionProcess handles Redux infrastructure internally
161+
- **Better Performance**: Selector-based updates reduce unnecessary state notifications
162+
- **Improved DX**: Clearer code intent with less nesting and fewer concepts
163+
164+
### Deprecated
165+
- `Phoenix.SessionProcess.Redux` module - Use Redux Store API instead
166+
- `Redux.init_state/2` - Use `user_init/1` callback
167+
- `Redux.dispatch/3` - Use `SessionProcess.dispatch/3`
168+
- `Redux.subscribe/3` - Use `SessionProcess.subscribe/4`
169+
- `Redux.get_state/1` - Use `SessionProcess.get_state/2`
170+
- `Phoenix.SessionProcess.LiveView` old API - Use new Redux Store API
171+
- `mount_session/4` - Use `mount_store/4`
172+
- `unmount_session/1` - Use `unmount_store/1`
173+
- **Migration Timeline**: Deprecated APIs will be removed in v1.0.0 (supported through v0.9.x)
174+
175+
### Migration
176+
- All old code continues to work with deprecation warnings
177+
- See `MIGRATION_GUIDE.md` for quick 2-step migration
178+
- See `REDUX_TO_SESSIONPROCESS_MIGRATION.md` for detailed examples
179+
- No breaking changes - 100% backward compatible
180+
8181
## [0.4.0] - 2024-10-24
9182

10183
### Added
@@ -88,6 +261,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88261
- Rate limiting and session limit enforcement
89262
- Telemetry integration for monitoring
90263

264+
[0.6.0]: https://github.com/gsmlg-dev/phoenix_session_process/compare/v0.4.0...v0.6.0
91265
[0.4.0]: https://github.com/gsmlg-dev/phoenix_session_process/compare/v0.3.1...v0.4.0
92266
[0.3.1]: https://github.com/gsmlg-dev/phoenix_session_process/compare/v0.3.0...v0.3.1
93267
[0.3.0]: https://github.com/gsmlg-dev/phoenix_session_process/compare/v0.2.0...v0.3.0

0 commit comments

Comments
 (0)