graphiti 1.8.1 → 1.8.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a623eae37b7a163ee35b336e1ac22c3b2d9c2550abf86f75688e20efca72327f
4
- data.tar.gz: 1e025562662054b45379e812252d45d4b66d48364856ce95d4cd0876a058774c
3
+ metadata.gz: 694b6036077d9ea14373be4b15e8e2d9cfab59253d79ce36d85303aa182a5482
4
+ data.tar.gz: 64afa2d52f244239c2b099f84500d18446ba04482e2b45447a8c8d9d93164be4
5
5
  SHA512:
6
- metadata.gz: 30044adbbca21d52a99f0a2f51374f0cf5449f2c6bed7badcbccd29d8668ac6edd8c45aa8a5bdcf40f992cc53d6f6b8055a91c1a2f73cf623508958603bff715
7
- data.tar.gz: 82797839f6d91cd86d6df735c87f9fd98490a21e5ff8a9e92ab2bc0e4b7c65a2c73f692b5c4b31a573a28582dea9e48b353a4cc417eb9b0ccba768f7f77b348c
6
+ metadata.gz: 31f0a5953dbbb9526c33d9710528cd1410cf120ce8b036d9d9cbcb3983b62cab77d0adaaf641b9dd3e342e6096eb85a3427353ea58396bdad76593bbcf392161
7
+ data.tar.gz: 692cb0d0042334e2e92847dc5aee81e2ecb090329b6763adf4d757da205ab61df88e24b3c54230d28285c1cae9007a9ca1c554769ed2d02452fba8afd623d2eb
@@ -2,7 +2,7 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [master]
5
+ branches: [main]
6
6
  pull_request: {}
7
7
 
8
8
  concurrency:
@@ -156,7 +156,7 @@ jobs:
156
156
  publish:
157
157
  name: Release
158
158
  runs-on: ubuntu-latest
159
- if: github.ref == 'refs/heads/master'
159
+ if: github.ref == 'refs/heads/main'
160
160
  needs: [test]
161
161
  steps:
162
162
  - name: Dispatch Release
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  graphiti changelog
2
2
 
3
+ ## [1.8.2](https://github.com/graphiti-api/graphiti/compare/v1.8.1...v1.8.2) (2025-05-20)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * 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))
9
+
3
10
  ## [1.8.1](https://github.com/graphiti-api/graphiti/compare/v1.8.0...v1.8.1) (2025-03-17)
4
11
 
5
12
  # [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
  [![CI](https://github.com/graphiti-api/graphiti/actions/workflows/ci.yml/badge.svg)](https://github.com/graphiti-api/graphiti/actions/workflows/ci.yml)
2
4
  [![Gem Version](https://badge.fury.io/rb/graphiti.svg)](https://badge.fury.io/rb/graphiti)
3
5
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
4
6
  [![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](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
- [Website](https://www.graphiti.dev/guides/)
9
+ [![discord](https://img.shields.io/badge/community-discord-8A2BE2?logo=discord)](https://discord.gg/wgqkMBsSRV)
10
+ [![guides](https://img.shields.io/badge/guides-https://www.graphiti.dev-F565A5)](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)*
@@ -140,7 +140,7 @@ module Graphiti
140
140
  end
141
141
  fiber_storage =
142
142
  if Fiber.current.respond_to?(:storage)
143
- Fiber.current&.storage&.keys&.each_with_object({}) do |key, memo|
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
- execution_context_changed = thread_id != Thread.current.object_id
153
- if execution_context_changed
154
- thread_storage&.keys&.each_with_object(Thread.current) do |key, thread_current|
155
- thread_current[key] = thread_storage[key]
156
- end
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
- result = Graphiti.broadcast(:global_thread_pool_task_run, self.class.global_thread_pool_stats) do
163
- yield(*args)
164
- end
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
- if execution_context_changed
167
- thread_storage&.keys&.each { |key| Thread.current[key] = nil }
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
- result
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
 
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "1.8.1"
2
+ VERSION = "1.8.2"
3
3
  end
data/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "release": {
23
23
  "branches": [
24
- "master",
24
+ "main",
25
25
  {
26
26
  "name": "beta",
27
27
  "prerelease": true
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.8.1
4
+ version: 1.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-03-17 00:00:00.000000000 Z
11
+ date: 2025-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-serializable