graphiti 1.8.1 → 1.9.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/.github/workflows/ci.yml +16 -2
- data/CHANGELOG.md +14 -0
- data/README.md +218 -12
- data/gemfiles/rails_8_1_graphiti_rails.gemfile +19 -0
- data/lib/graphiti/configuration.rb +1 -1
- data/lib/graphiti/scope.rb +35 -16
- data/lib/graphiti/version.rb +1 -1
- data/package.json +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1153a7480fa50066880ae8fd1b5a4f99a24e69ae3ebffb40ae114dbfcffc9910
|
|
4
|
+
data.tar.gz: 0f3836b75fb3db4a88393d1ec7877340dedea40e9c59350a8e30f1f4ebce10f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e62b853539691e1649da7ff41441f75e5726af05ee88de6d0721b909926733162c07eeb82763dfc0bbc19ea953026ebd5ab2acf03e11313a7d584894dc89263
|
|
7
|
+
data.tar.gz: 046642e0a06b2340054d501ace62e56c42cb1ab66c3f1939c05fc59b9b14107109276848e2204eb4b6b18378ab3c4ba18ca45cd8025e54036e413f7079943068
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -2,7 +2,7 @@ name: CI
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
branches: [
|
|
5
|
+
branches: [main]
|
|
6
6
|
pull_request: {}
|
|
7
7
|
|
|
8
8
|
concurrency:
|
|
@@ -45,6 +45,7 @@ jobs:
|
|
|
45
45
|
- gemfiles/rails_7_1_graphiti_rails.gemfile
|
|
46
46
|
- gemfiles/rails_7_2_graphiti_rails.gemfile
|
|
47
47
|
- gemfiles/rails_8_0_graphiti_rails.gemfile
|
|
48
|
+
- gemfiles/rails_8_1_graphiti_rails.gemfile
|
|
48
49
|
appraisal:
|
|
49
50
|
- true
|
|
50
51
|
- false
|
|
@@ -58,6 +59,9 @@ jobs:
|
|
|
58
59
|
- ruby: ruby-head
|
|
59
60
|
gemfile: gemfiles/rails_8_0_graphiti_rails.gemfile
|
|
60
61
|
appraisal: true
|
|
62
|
+
- ruby: ruby-head
|
|
63
|
+
gemfile: gemfiles/rails_8_1_graphiti_rails.gemfile
|
|
64
|
+
appraisal: true
|
|
61
65
|
- ruby: ruby-head
|
|
62
66
|
gemfile: Gemfile
|
|
63
67
|
appraisal: false
|
|
@@ -83,6 +87,16 @@ jobs:
|
|
|
83
87
|
appraisal: false
|
|
84
88
|
- gemfile: gemfiles/rails_8_0_graphiti_rails.gemfile
|
|
85
89
|
appraisal: false
|
|
90
|
+
- gemfile: gemfiles/rails_8_1_graphiti_rails.gemfile
|
|
91
|
+
appraisal: false
|
|
92
|
+
|
|
93
|
+
# Rails 8.1 needs ruby 3.2 +
|
|
94
|
+
- gemfile: gemfiles/rails_8_1_graphiti_rails.gemfile
|
|
95
|
+
ruby: 2.7
|
|
96
|
+
- gemfile: gemfiles/rails_8_1_graphiti_rails.gemfile
|
|
97
|
+
ruby: 3.0
|
|
98
|
+
- gemfile: gemfiles/rails_8_1_graphiti_rails.gemfile
|
|
99
|
+
ruby: 3.1
|
|
86
100
|
|
|
87
101
|
# Rails 8 needs ruby 3.2 +
|
|
88
102
|
- gemfile: gemfiles/rails_8_0_graphiti_rails.gemfile
|
|
@@ -156,7 +170,7 @@ jobs:
|
|
|
156
170
|
publish:
|
|
157
171
|
name: Release
|
|
158
172
|
runs-on: ubuntu-latest
|
|
159
|
-
if: github.ref == 'refs/heads/
|
|
173
|
+
if: github.ref == 'refs/heads/main'
|
|
160
174
|
needs: [test]
|
|
161
175
|
steps:
|
|
162
176
|
- name: Dispatch Release
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
graphiti changelog
|
|
2
2
|
|
|
3
|
+
# [1.9.0](https://github.com/graphiti-api/graphiti/compare/v1.8.2...v1.9.0) (2026-01-03)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* Support rails 8.1 ([#500](https://github.com/graphiti-api/graphiti/issues/500)) ([34d0cf0](https://github.com/graphiti-api/graphiti/commit/34d0cf03b6b4d887d10d660f0ae08bfa6833345f))
|
|
9
|
+
|
|
10
|
+
## [1.8.2](https://github.com/graphiti-api/graphiti/compare/v1.8.1...v1.8.2) (2025-05-20)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* prevent context loss by always setting thread and fiber locals ([#497](https://github.com/graphiti-api/graphiti/issues/497)) ([5f45f76](https://github.com/graphiti-api/graphiti/commit/5f45f76f590a8a15e9ae3d47d0673c483da11e66))
|
|
16
|
+
|
|
3
17
|
## [1.8.1](https://github.com/graphiti-api/graphiti/compare/v1.8.0...v1.8.1) (2025-03-17)
|
|
4
18
|
|
|
5
19
|
# [1.8.0](https://github.com/graphiti-api/graphiti/compare/v1.7.9...v1.8.0) (2025-03-17)
|
data/README.md
CHANGED
|
@@ -1,22 +1,228 @@
|
|
|
1
|
+
#### Graphiti
|
|
2
|
+
|
|
1
3
|
[](https://github.com/graphiti-api/graphiti/actions/workflows/ci.yml)
|
|
2
4
|
[](https://badge.fury.io/rb/graphiti)
|
|
3
5
|
[](https://github.com/testdouble/standard)
|
|
4
6
|
[](https://github.com/semantic-release/semantic-release)
|
|
5
7
|
|
|
6
|
-
<p align="center">
|
|
7
|
-
<a href="https://www.graphiti.dev/guides">
|
|
8
|
-
<img " src="https://user-images.githubusercontent.com/55264/54884141-c10ada00-4e43-11e9-866b-e3c01e33a7c7.png" />
|
|
9
|
-
</a>
|
|
10
|
-
</p>
|
|
11
|
-
|
|
12
|
-
Stylish Graph APIs.
|
|
13
8
|
|
|
14
|
-
[
|
|
9
|
+
[](https://discord.gg/wgqkMBsSRV)
|
|
10
|
+
[](https://www.graphiti.dev)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
<img align="right" src="https://user-images.githubusercontent.com/55264/54884141-c10ada00-4e43-11e9-866b-e3c01e33a7c7.png" alt="Graphiti logo" width="150px" />
|
|
15
|
+
Graphiti is a resource-oriented framework that sits on top of your models (usually ActiveRecord) and exposes them via a JSON:API-compliant interface. It abstracts common concerns like serialization, filtering, sorting, pagination, and sideloading relationships, so you can build powerful APIs with minimal boilerplate. By defining resources instead of controllers and serializers, Graphiti helps you keep your API logic organized, consistent, and easy to maintain.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
#### Examples
|
|
19
|
+
Here's an example resource from the [example app](https://github.com/graphiti-api/employee_directory/) just to give you a taste of the possibilities.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
class EmployeeResource < ApplicationResource
|
|
24
|
+
attribute :first_name, :string
|
|
25
|
+
attribute :last_name, :string
|
|
26
|
+
attribute :age, :integer
|
|
27
|
+
attribute :created_at, :datetime, writable: false
|
|
28
|
+
attribute :updated_at, :datetime, writable: false
|
|
29
|
+
attribute :title, :string, only: [:filterable, :sortable]
|
|
30
|
+
|
|
31
|
+
has_many :positions
|
|
32
|
+
has_many :tasks
|
|
33
|
+
many_to_many :teams
|
|
34
|
+
polymorphic_has_many :notes, as: :notable
|
|
35
|
+
has_one :current_position, resource: PositionResource do
|
|
36
|
+
params do |hash|
|
|
37
|
+
hash[:filter][:current] = true
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
filter :title, only: [:eq] do
|
|
42
|
+
eq do |scope, value|
|
|
43
|
+
scope.joins(:current_position).merge(Position.where(title: value))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
sort :title do |scope, value|
|
|
48
|
+
scope.joins(:current_position).merge(Position.order(title: value))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
sort :department_name, :string do |scope, value|
|
|
52
|
+
scope.joins(current_position: :department)
|
|
53
|
+
.merge(Department.order(name: value))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A pretty boilerplate controller that just interfaces with the resource
|
|
59
|
+
```ruby
|
|
60
|
+
class EmployeesController < ApplicationController
|
|
61
|
+
def index
|
|
62
|
+
employees = EmployeeResource.all(params)
|
|
63
|
+
respond_with(employees)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def show
|
|
67
|
+
employee = EmployeeResource.find(params)
|
|
68
|
+
respond_with(employee)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def create
|
|
72
|
+
employee = EmployeeResource.build(params)
|
|
73
|
+
|
|
74
|
+
if employee.save
|
|
75
|
+
render jsonapi: employee, status: 201
|
|
76
|
+
else
|
|
77
|
+
render jsonapi_errors: employee
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def update
|
|
82
|
+
employee = EmployeeResource.find(params)
|
|
83
|
+
|
|
84
|
+
if employee.update_attributes
|
|
85
|
+
render jsonapi: employee
|
|
86
|
+
else
|
|
87
|
+
render jsonapi_errors: employee
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def destroy
|
|
92
|
+
employee = EmployeeResource.find(params)
|
|
93
|
+
|
|
94
|
+
if employee.destroy
|
|
95
|
+
render jsonapi: { meta: {} }, status: 200
|
|
96
|
+
else
|
|
97
|
+
render jsonapi_errors: employee
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
</details>
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
Now you can query your endpoints simply and powerfully, like:
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
Request:
|
|
111
|
+
```http://localhost:3000/api/v1/employees?filter[title][eq]=Future Government Administrator&filter[age][lt]=40```
|
|
112
|
+
|
|
113
|
+
<details>
|
|
114
|
+
<summary>JSON-API response</summary>
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"data": [
|
|
119
|
+
{
|
|
120
|
+
"id": "1",
|
|
121
|
+
"type": "employees",
|
|
122
|
+
"attributes": {
|
|
123
|
+
"first_name": "Quinn",
|
|
124
|
+
"last_name": "Homenick",
|
|
125
|
+
"age": 36,
|
|
126
|
+
"created_at": "2025-03-21T23:04:40+00:00",
|
|
127
|
+
"updated_at": "2025-03-21T23:04:40+00:00"
|
|
128
|
+
},
|
|
129
|
+
"relationships": {
|
|
130
|
+
"positions": {
|
|
131
|
+
"links": {
|
|
132
|
+
"related": "/api/v1/positions?filter[employee_id]=1"
|
|
133
|
+
},
|
|
134
|
+
"data": [
|
|
135
|
+
{
|
|
136
|
+
"type": "positions",
|
|
137
|
+
"id": "1"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"type": "positions",
|
|
141
|
+
"id": "2"
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
"tasks": {
|
|
146
|
+
"links": {
|
|
147
|
+
"related": "/api/v1/tasks?filter[employee_id]=1"
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"teams": {
|
|
151
|
+
"links": {
|
|
152
|
+
"related": "/api/v1/teams?filter[employee_id]=1"
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"notes": {
|
|
156
|
+
"links": {
|
|
157
|
+
"related": "/api/v1/notes?filter[notable_id]=1&filter[notable_type][eql]=Employee"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"current_position": {
|
|
161
|
+
"links": {
|
|
162
|
+
"related": "/api/v1/positions?filter[current]=true&filter[employee_id]=1"
|
|
163
|
+
},
|
|
164
|
+
"data": {
|
|
165
|
+
"type": "positions",
|
|
166
|
+
"id": "1"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
],
|
|
172
|
+
"included": [
|
|
173
|
+
{
|
|
174
|
+
"id": "1",
|
|
175
|
+
"type": "positions",
|
|
176
|
+
"attributes": {
|
|
177
|
+
"title": "Future Government Administrator",
|
|
178
|
+
"active": true
|
|
179
|
+
},
|
|
180
|
+
"relationships": {
|
|
181
|
+
"employee": {
|
|
182
|
+
"links": {
|
|
183
|
+
"related": "/api/v1/employees/1"
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"department": {
|
|
187
|
+
"links": {
|
|
188
|
+
"related": "/api/v1/departments/3"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"id": "2",
|
|
195
|
+
"type": "positions",
|
|
196
|
+
"attributes": {
|
|
197
|
+
"title": "Manufacturing Specialist",
|
|
198
|
+
"active": false
|
|
199
|
+
},
|
|
200
|
+
"relationships": {
|
|
201
|
+
"employee": {
|
|
202
|
+
"links": {
|
|
203
|
+
"related": "/api/v1/employees/1"
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"department": {
|
|
207
|
+
"links": {
|
|
208
|
+
"related": "/api/v1/departments/2"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
"meta": {}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
</details>
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
[Graphiti Guides](https://www.graphiti.dev/guides/)
|
|
223
|
+
|
|
224
|
+
[Join the Discord](https://discord.gg/wgqkMBsSRV)
|
|
15
225
|
|
|
16
|
-
[Join the Slack Channel](https://join.slack.com/t/graphiti-api/shared_invite/enQtMjkyMTA3MDgxNTQzLTU5MDI4MDllNTEzOTE1Nzk0ZGJlNTcxZDYzMGY2ZTczMDY2OWZhM2RmNTU0YWNiOWZhZDhkMmU4MzQ5NzIyNWM)
|
|
17
226
|
|
|
18
|
-
Direct Contact: richmolj@gmail.com
|
|
19
227
|
|
|
20
|
-
Supports Rails >= 4.1
|
|
21
228
|
|
|
22
|
-
*Looking for JSONAPI-Suite? You're in the right place. Graphiti is the 1.0 version of JSONAPI Suite. For the deprecated Suite gem, go [here](https://github.com/jsonapi-suite/jsonapi_suite_deprecated)*
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "rails", "~> 8.1"
|
|
6
|
+
gem "rspec-rails"
|
|
7
|
+
gem "sqlite3", "~> 2.1"
|
|
8
|
+
gem "database_cleaner"
|
|
9
|
+
gem "graphiti-rails", "~> 0.4.0"
|
|
10
|
+
|
|
11
|
+
group :test do
|
|
12
|
+
gem "pry"
|
|
13
|
+
gem "pry-byebug", platform: [:mri]
|
|
14
|
+
gem "appraisal"
|
|
15
|
+
gem "guard"
|
|
16
|
+
gem "guard-rspec"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
gemspec path: "../"
|
|
@@ -115,7 +115,7 @@ module Graphiti
|
|
|
115
115
|
private
|
|
116
116
|
|
|
117
117
|
def infer_uri_decoder
|
|
118
|
-
if defined?(::ActionDispatch::Journey::Router::Utils)
|
|
118
|
+
if defined?(::ActionDispatch::Journey::Router::Utils) && ::ActionDispatch::Journey::Router::Utils.respond_to?(:unescape_uri)
|
|
119
119
|
# available in all supported versions of Rails.
|
|
120
120
|
# This method should be preferred for comparing URI path segments
|
|
121
121
|
# to params, as it is the exact decoder used in the Rails router.
|
data/lib/graphiti/scope.rb
CHANGED
|
@@ -140,7 +140,7 @@ module Graphiti
|
|
|
140
140
|
end
|
|
141
141
|
fiber_storage =
|
|
142
142
|
if Fiber.current.respond_to?(:storage)
|
|
143
|
-
Fiber.current
|
|
143
|
+
Fiber.current.storage&.keys&.each_with_object({}) do |key, memo|
|
|
144
144
|
memo[key] = Fiber[key]
|
|
145
145
|
end
|
|
146
146
|
end
|
|
@@ -149,27 +149,46 @@ module Graphiti
|
|
|
149
149
|
self.class.global_thread_pool_executor, Thread.current.object_id, thread_storage, fiber_storage, *args
|
|
150
150
|
) do |thread_id, thread_storage, fiber_storage, *args|
|
|
151
151
|
wrap_in_rails_executor do
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
fiber_storage&.keys&.each_with_object(Fiber) do |key, fiber_current|
|
|
158
|
-
fiber_current[key] = fiber_storage[key]
|
|
152
|
+
with_thread_locals(thread_storage) do
|
|
153
|
+
with_fiber_locals(fiber_storage) do
|
|
154
|
+
Graphiti.broadcast(:global_thread_pool_task_run, self.class.global_thread_pool_stats) do
|
|
155
|
+
yield(*args)
|
|
156
|
+
end
|
|
159
157
|
end
|
|
160
158
|
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
161
162
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
def with_thread_locals(thread_locals)
|
|
164
|
+
new_thread_locals = []
|
|
165
|
+
thread_locals.each do |key, value|
|
|
166
|
+
if !Thread.current[key]
|
|
167
|
+
new_thread_locals << key
|
|
168
|
+
end
|
|
169
|
+
Thread.current[key] = value
|
|
170
|
+
end
|
|
171
|
+
yield
|
|
172
|
+
ensure
|
|
173
|
+
new_thread_locals.each do |key|
|
|
174
|
+
Thread.current[key] = nil
|
|
175
|
+
end
|
|
176
|
+
end
|
|
165
177
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
fiber_storage&.keys&.each { |key| Fiber[key] = nil }
|
|
169
|
-
end
|
|
178
|
+
def with_fiber_locals(fiber_locals)
|
|
179
|
+
return yield unless fiber_locals
|
|
170
180
|
|
|
171
|
-
|
|
181
|
+
new_fiber_locals = []
|
|
182
|
+
fiber_locals.each do |key, value|
|
|
183
|
+
if !Fiber[key]
|
|
184
|
+
new_fiber_locals << key
|
|
172
185
|
end
|
|
186
|
+
Fiber[key] = value
|
|
187
|
+
end
|
|
188
|
+
yield
|
|
189
|
+
ensure
|
|
190
|
+
new_fiber_locals&.each do |key|
|
|
191
|
+
Fiber[key] = nil
|
|
173
192
|
end
|
|
174
193
|
end
|
|
175
194
|
|
data/lib/graphiti/version.rb
CHANGED
data/package.json
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graphiti
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lee Richmond
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jsonapi-serializable
|
|
@@ -264,6 +264,7 @@ files:
|
|
|
264
264
|
- gemfiles/rails_7_2_graphiti_rails.gemfile
|
|
265
265
|
- gemfiles/rails_7_graphiti_rails.gemfile
|
|
266
266
|
- gemfiles/rails_8_0_graphiti_rails.gemfile
|
|
267
|
+
- gemfiles/rails_8_1_graphiti_rails.gemfile
|
|
267
268
|
- graphiti.gemspec
|
|
268
269
|
- lib/graphiti.rb
|
|
269
270
|
- lib/graphiti/adapters/abstract.rb
|