rails_audit_log-graphql 0.2.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c89d640d69c533ded27f477ed99b03275c34b4564c90e6eaccfe86a131f2447
4
- data.tar.gz: d34b732d907a2db6b9c6c0bfba266933aaf6f21be9c8770caa8c64b50a81c870
3
+ metadata.gz: fd228bcaff874e10bbd50927cb3bc44d22b5c860acf8dd444624eff80c7dea96
4
+ data.tar.gz: 29ebf0ccd63fd0827689d3cf0b5e5bd3b4c6194c12468aa347c0fadb1fe0c5f3
5
5
  SHA512:
6
- metadata.gz: faa1b363d2aaebc1b3f5573f0b6c757a5d6b4d1fe8b6a1af7d906bd9877f23cecf897b75640b9f61e24e622d06c4233825be5047bada0d92371f954749a2499f
7
- data.tar.gz: '08c32d0f02192b6db82d11e414ac04beb42a54ce61bb6dd8b025b2866f1569bc65b7b177ba308a4c0d241d7486029741e03abf082af38726fa2bbd30d6d2c6f4'
6
+ metadata.gz: 81930d3cedc11053354b5b86349d5483b326f43d774ebea49f97bc61e827c1ede95fa61c1b2671c611df266a9f7b4f4e497d472d47d86fc01b9e6863dd2b8df2
7
+ data.tar.gz: 158775764845a120940581a0e6c47be8ca3ba992c5195b4bb899c970d069f27dc94faf53c4dde7ee1e4752992d8de62adec09f3f20a68e8209499239e9589887
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2026-06-04
4
+
5
+ ### Added
6
+
7
+ - `AuditLogEntryCreated` subscription — `auditLogEntryCreated(itemType:, itemId:)` subscribes to new entries for a specific record; `auditLogEntryCreated(actorId:)` subscribes to all entries by a specific actor
8
+ - `AuditLogSubscriptionsMixin` — include into host app's `SubscriptionType` to add the `auditLogEntryCreated` subscription field
9
+ - `BaseSubscription` base class for all gem GraphQL subscription types
10
+ - `Broadcaster` — call `Broadcaster.new(schema: MySchema).start` in an initializer to relay `rails_audit_log.entry_created` `ActiveSupport::Notifications` events into GraphQL subscription triggers; supports `stop` to unsubscribe
11
+
12
+ ## [0.3.0] - 2026-06-03
13
+
14
+ ### Added
15
+
16
+ - `ActorType` (`AuditLogActor`) — new object type with `id` and `typeName` fields exposing the polymorphic actor reference; adds `actor: AuditLogActor` field on `AuditLogEntry`
17
+ - `AuditedResourceType` (`AuditedResource`) — new object type with `id` and `typeName` fields exposing the audited model reference; adds `auditedResource: AuditedResource!` field on `AuditLogEntry`
18
+ - `DiffType` (`AuditLogDiff`) — new object type with `attribute`, `from`, and `to` fields; adds `diff: [AuditLogDiff!]` field on `AuditLogEntry` parsed from `objectChanges`
19
+
3
20
  ## [0.2.0] - 2026-06-03
4
21
 
5
22
  ### Added
data/README.md CHANGED
@@ -13,10 +13,18 @@ A [graphql-ruby](https://graphql-ruby.org) API layer for the [`rails_audit_log`]
13
13
  - [Installation](#installation)
14
14
  - [Usage](#usage)
15
15
  - [AuditLogEntryType](#auditlogentrytype)
16
+ - [AuditLogActor](#auditlogactor)
17
+ - [AuditedResource](#auditedresource)
18
+ - [AuditLogDiff](#auditlogdiff)
16
19
  - [AuditLogEntriesQueryMixin](#auditlogentriesquerymixin)
17
20
  - [auditLogEntry](#auditlogentryid-id-auditlogentry)
18
21
  - [auditLogEntries](#auditlogentries-auditlogentry)
22
+ - [auditLogEntriesConnection](#auditlogentriesconnection-auditlogentryconnection)
19
23
  - [Authentication](#authentication)
24
+ - [Subscriptions](#subscriptions)
25
+ - [AuditLogSubscriptionsMixin](#auditlogsubscriptionsmixin)
26
+ - [auditLogEntryCreated](#auditlogentrycreated)
27
+ - [Broadcaster](#broadcaster)
20
28
  - [Development](#development)
21
29
  - [Contributing](#contributing)
22
30
  - [License](#license)
@@ -61,6 +69,57 @@ This injects `include RailsAuditLog::Graphql::Queries::AuditLogEntriesQueryMixin
61
69
  | `actorType` | `String` | yes |
62
70
  | `actorId` | `ID` | yes |
63
71
  | `tenantId` | `String` | yes |
72
+ | `actor` | `AuditLogActor` | yes |
73
+ | `auditedResource` | `AuditedResource` | no |
74
+ | `diff` | `[AuditLogDiff!]` | yes |
75
+
76
+ [↑ Back to top](#table-of-contents)
77
+
78
+ ### AuditLogActor
79
+
80
+ `RailsAuditLog::Graphql::Types::ActorType` — a polymorphic reference to the actor who performed the audited action. Returned by the `actor` field on `AuditLogEntry`. `null` when no actor was recorded.
81
+
82
+ | GraphQL field | Type | Nullable |
83
+ |---|---|---|
84
+ | `id` | `ID` | no |
85
+ | `typeName` | `String` | no |
86
+
87
+ [↑ Back to top](#table-of-contents)
88
+
89
+ ### AuditedResource
90
+
91
+ `RailsAuditLog::Graphql::Types::AuditedResourceType` — a reference to the model record that was changed. Returned by the `auditedResource` field on `AuditLogEntry`. Always present.
92
+
93
+ | GraphQL field | Type | Nullable |
94
+ |---|---|---|
95
+ | `id` | `ID` | no |
96
+ | `typeName` | `String` | no |
97
+
98
+ [↑ Back to top](#table-of-contents)
99
+
100
+ ### AuditLogDiff
101
+
102
+ `RailsAuditLog::Graphql::Types::DiffType` — a single attribute change parsed from `objectChanges`. Returned as a list by the `diff` field on `AuditLogEntry`. `null` when `objectChanges` is not recorded (e.g. destroy events).
103
+
104
+ | GraphQL field | Type | Nullable | Description |
105
+ |---|---|---|---|
106
+ | `attribute` | `String` | no | Name of the changed attribute |
107
+ | `from` | `JSON` | yes | Value before the change |
108
+ | `to` | `JSON` | yes | Value after the change |
109
+
110
+ **Example:**
111
+
112
+ ```graphql
113
+ {
114
+ auditLogEntries(event: "update") {
115
+ diff {
116
+ attribute
117
+ from
118
+ to
119
+ }
120
+ }
121
+ }
122
+ ```
64
123
 
65
124
  [↑ Back to top](#table-of-contents)
66
125
 
@@ -89,10 +148,14 @@ List entries with optional filters and offset pagination.
89
148
  | `itemType` | `String` | — | Filter by audited model class name |
90
149
  | `itemId` | `ID` | — | Filter by audited record ID |
91
150
  | `actorId` | `ID` | — | Filter by actor ID |
151
+ | `since` | `ISO8601DateTime` | — | Return entries created at or after this time |
152
+ | `until` | `ISO8601DateTime` | — | Return entries created at or before this time |
153
+ | `touching` | `String` | — | Filter to entries that changed a specific attribute |
154
+ | `orderBy` | `AuditLogEntrySortInput` | `CREATED_AT DESC` | Sort field and direction |
92
155
  | `page` | `Int` | `1` | Page number (1-based) |
93
156
  | `perPage` | `Int` | `25` | Results per page |
94
157
 
95
- Results are ordered by `created_at DESC`.
158
+ Results default to `created_at DESC` ordering.
96
159
 
97
160
  #### `auditLogEntriesConnection(...): AuditLogEntryConnection!`
98
161
 
@@ -158,6 +221,84 @@ If no authenticate block is set, all queries are permitted.
158
221
 
159
222
  [↑ Back to top](#table-of-contents)
160
223
 
224
+ ### Subscriptions
225
+
226
+ Requires Action Cable in the host application.
227
+
228
+ #### AuditLogSubscriptionsMixin
229
+
230
+ Include `RailsAuditLog::Graphql::Subscriptions::AuditLogSubscriptionsMixin` into your app's `SubscriptionType` to add the `auditLogEntryCreated` field. Your schema must also use `GraphQL::Subscriptions::ActionCableSubscriptions`.
231
+
232
+ ```ruby
233
+ # app/graphql/types/subscription_type.rb
234
+ class Types::SubscriptionType < Types::BaseObject
235
+ include RailsAuditLog::Graphql::Subscriptions::AuditLogSubscriptionsMixin
236
+ end
237
+
238
+ # app/graphql/my_schema.rb
239
+ class MySchema < GraphQL::Schema
240
+ query Types::QueryType
241
+ subscription Types::SubscriptionType
242
+ use GraphQL::Subscriptions::ActionCableSubscriptions
243
+ end
244
+ ```
245
+
246
+ #### `auditLogEntryCreated`
247
+
248
+ Fires when a new `RailsAuditLog::AuditLogEntry` is created. Accepts one of two argument combinations:
249
+
250
+ | Argument | Type | Description |
251
+ |---|---|---|
252
+ | `itemType` | `String` | Model class name to scope the subscription to |
253
+ | `itemId` | `ID` | Record ID to scope the subscription to |
254
+ | `actorId` | `ID` | Actor ID — subscribe to all entries by a specific actor |
255
+
256
+ Subscribe to all changes on a specific record:
257
+
258
+ ```graphql
259
+ subscription {
260
+ auditLogEntryCreated(itemType: "Post", itemId: "42") {
261
+ id
262
+ event
263
+ diff { attribute from to }
264
+ }
265
+ }
266
+ ```
267
+
268
+ Subscribe to all entries by a specific actor:
269
+
270
+ ```graphql
271
+ subscription {
272
+ auditLogEntryCreated(actorId: "7") {
273
+ id
274
+ event
275
+ itemType
276
+ itemId
277
+ }
278
+ }
279
+ ```
280
+
281
+ #### Broadcaster
282
+
283
+ `RailsAuditLog::Graphql::Subscriptions::Broadcaster` bridges `ActiveSupport::Notifications` (fired by `RailsAuditLog::Streaming::NotificationsAdapter` or `ActiveJobAdapter`) to GraphQL subscription triggers. Start it in an initializer:
284
+
285
+ ```ruby
286
+ # config/initializers/rails_audit_log_graphql.rb
287
+ RailsAuditLog.configure do |c|
288
+ c.streaming_adapter = RailsAuditLog::Streaming::NotificationsAdapter.new
289
+ end
290
+
291
+ Rails.application.config.after_initialize do
292
+ RailsAuditLog::Graphql::Subscriptions::Broadcaster.new(schema: MySchema).start
293
+ end
294
+ ```
295
+
296
+ For each entry, the broadcaster triggers:
297
+ - `auditLogEntryCreated(itemType:, itemId:)` — notifies record-specific subscribers
298
+ - `auditLogEntryCreated(actorId:)` — notifies actor-specific subscribers (when an actor is present)
299
+
300
+ [↑ Back to top](#table-of-contents)
301
+
161
302
  ## Development
162
303
 
163
304
  ```bash
data/ROADMAP.md CHANGED
@@ -4,24 +4,6 @@ This gem adds a GraphQL API layer on top of [`rails_audit_log`](https://github.c
4
4
 
5
5
  ---
6
6
 
7
- ## 0.3.0 — Actor & Resource Resolver Types
8
-
9
- - **`ActorType`** — resolve the polymorphic `actor` to the concrete type in the host app's schema (requires a configurable type resolver proc)
10
- - **`AuditedResourceType`** — resolve `item_type`/`item_id` to the concrete audited model type
11
- - **`diffType`** — structured `{ from, to }` diff type instead of raw `objectChanges` JSON
12
-
13
- ---
14
-
15
- ## 0.4.0 — Subscriptions
16
-
17
- Requires Action Cable in the host application.
18
-
19
- - **`auditLogEntryCreated(itemType:, itemId:)`** — subscribe to new entries for a specific record
20
- - **`auditLogEntryCreated(actorId:)`** — subscribe to all entries by a specific actor
21
- - Hooks into `RailsAuditLog::Streaming::NotificationsAdapter` to trigger broadcasts
22
-
23
- ---
24
-
25
7
  ## 0.5.0 — Multi-tenancy & Advanced Filtering
26
8
 
27
9
  - **Tenant scoping** — automatically scope queries via `RailsAuditLog.current_tenant` when configured
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuditLog
4
+ module Graphql
5
+ module Subscriptions
6
+ class AuditLogEntryCreated < Types::BaseSubscription
7
+ description "Fires when a new audit log entry is created."
8
+
9
+ argument :item_type, String, required: false,
10
+ description: "Filter to entries for a specific model class name."
11
+ argument :item_id, GraphQL::Types::ID, required: false,
12
+ description: "Filter to entries for a specific record ID."
13
+ argument :actor_id, GraphQL::Types::ID, required: false,
14
+ description: "Filter to entries by a specific actor ID."
15
+
16
+ type Types::AuditLogEntryType, null: false
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuditLog
4
+ module Graphql
5
+ module Subscriptions
6
+ module AuditLogSubscriptionsMixin
7
+ def self.included(base)
8
+ base.field(
9
+ :audit_log_entry_created,
10
+ subscription: AuditLogEntryCreated,
11
+ description: "Fires when a new audit log entry is created."
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuditLog
4
+ module Graphql
5
+ module Subscriptions
6
+ class Broadcaster
7
+ EVENT = "rails_audit_log.entry_created"
8
+
9
+ def initialize(schema:)
10
+ @schema = schema
11
+ @subscriber = nil
12
+ end
13
+
14
+ def start
15
+ @subscriber = ActiveSupport::Notifications.subscribe(EVENT) do |*, payload|
16
+ broadcast(payload[:entry])
17
+ end
18
+ end
19
+
20
+ def stop
21
+ ActiveSupport::Notifications.unsubscribe(@subscriber) if @subscriber
22
+ @subscriber = nil
23
+ end
24
+
25
+ def broadcast(entry)
26
+ @schema.subscriptions.trigger(
27
+ "audit_log_entry_created",
28
+ {item_type: entry.item_type, item_id: entry.item_id.to_s},
29
+ entry
30
+ )
31
+
32
+ return unless entry.actor_id.present?
33
+
34
+ @schema.subscriptions.trigger(
35
+ "audit_log_entry_created",
36
+ {actor_id: entry.actor_id.to_s},
37
+ entry
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuditLog
4
+ module Graphql
5
+ module Types
6
+ class ActorType < BaseObject
7
+ graphql_name "AuditLogActor"
8
+ description "A polymorphic reference to the actor who performed the audited action."
9
+
10
+ field :id, GraphQL::Types::ID, null: false, description: "The actor's ID."
11
+ field :type_name, String, null: false, description: "The actor's model class name (e.g. \"User\")."
12
+
13
+ def id
14
+ object[:id]
15
+ end
16
+
17
+ def type_name
18
+ object[:type_name]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -20,6 +20,27 @@ module RailsAuditLog
20
20
  field :actor_id, GraphQL::Types::ID, null: true
21
21
  field :tenant_id, String, null: true
22
22
  field :created_at, GraphQL::Types::ISO8601DateTime, null: false
23
+ field :actor, Types::ActorType, null: true,
24
+ description: "The actor who performed this action, as a polymorphic reference."
25
+ field :audited_resource, Types::AuditedResourceType, null: false,
26
+ description: "The model type and ID of the record that was changed."
27
+ field :diff, [Types::DiffType, null: false], null: true,
28
+ description: "Structured per-attribute diffs parsed from objectChanges. Nil when no changes are recorded."
29
+
30
+ def actor
31
+ return nil if object.actor_id.nil? || object.actor_type.nil?
32
+ {id: object.actor_id, type_name: object.actor_type}
33
+ end
34
+
35
+ def audited_resource
36
+ {id: object.item_id, type_name: object.item_type}
37
+ end
38
+
39
+ def diff
40
+ changes = object.object_changes
41
+ return nil if changes.nil?
42
+ changes.map { |attr, (from_val, to_val)| {attribute: attr, from: from_val, to: to_val} }
43
+ end
23
44
  end
24
45
  end
25
46
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuditLog
4
+ module Graphql
5
+ module Types
6
+ class AuditedResourceType < BaseObject
7
+ graphql_name "AuditedResource"
8
+ description "A reference to the model record that was changed."
9
+
10
+ field :id, GraphQL::Types::ID, null: false, description: "The audited record's ID."
11
+ field :type_name, String, null: false, description: "The audited model class name (e.g. \"Post\")."
12
+
13
+ def id
14
+ object[:id]
15
+ end
16
+
17
+ def type_name
18
+ object[:type_name]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuditLog
4
+ module Graphql
5
+ module Types
6
+ class BaseSubscription < GraphQL::Schema::Subscription
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuditLog
4
+ module Graphql
5
+ module Types
6
+ class DiffType < BaseObject
7
+ graphql_name "AuditLogDiff"
8
+ description "A single attribute change with before and after values."
9
+
10
+ field :attribute, String, null: false, description: "The name of the changed attribute."
11
+ field :from, GraphQL::Types::JSON, null: true, description: "Value before the change."
12
+ field :to, GraphQL::Types::JSON, null: true, description: "Value after the change."
13
+
14
+ def attribute
15
+ object[:attribute]
16
+ end
17
+
18
+ def from
19
+ object[:from]
20
+ end
21
+
22
+ def to
23
+ object[:to]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RailsAuditLog
4
4
  module Graphql
5
- VERSION = "0.2.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
@@ -3,11 +3,18 @@
3
3
  require "graphql"
4
4
  require_relative "graphql/version"
5
5
  require_relative "graphql/types/base_object"
6
+ require_relative "graphql/types/base_subscription"
7
+ require_relative "graphql/types/diff_type"
8
+ require_relative "graphql/types/actor_type"
9
+ require_relative "graphql/types/audited_resource_type"
6
10
  require_relative "graphql/types/audit_log_entry_type"
7
11
  require_relative "graphql/types/sort_direction_enum"
8
12
  require_relative "graphql/types/audit_log_entry_sort_field_enum"
9
13
  require_relative "graphql/input_objects/audit_log_entry_sort_input"
10
14
  require_relative "graphql/queries/audit_log_entries_query_mixin"
15
+ require_relative "graphql/subscriptions/audit_log_entry_created"
16
+ require_relative "graphql/subscriptions/audit_log_subscriptions_mixin"
17
+ require_relative "graphql/subscriptions/broadcaster"
11
18
 
12
19
  module RailsAuditLog
13
20
  module Graphql
@@ -6,7 +6,29 @@ module RailsAuditLog
6
6
  class BaseObject < GraphQL::Schema::Object
7
7
  end
8
8
 
9
+ class BaseSubscription < GraphQL::Schema::Subscription
10
+ end
11
+
12
+ class DiffType < BaseObject
13
+ def attribute: () -> String
14
+ def from: () -> untyped
15
+ def to: () -> untyped
16
+ end
17
+
18
+ class ActorType < BaseObject
19
+ def id: () -> String
20
+ def type_name: () -> String
21
+ end
22
+
23
+ class AuditedResourceType < BaseObject
24
+ def id: () -> String
25
+ def type_name: () -> String
26
+ end
27
+
9
28
  class AuditLogEntryType < BaseObject
29
+ def actor: () -> Hash[Symbol, untyped]?
30
+ def audited_resource: () -> Hash[Symbol, untyped]
31
+ def diff: () -> Array[Hash[Symbol, untyped]]?
10
32
  end
11
33
 
12
34
  class SortDirectionEnum < GraphQL::Schema::Enum
@@ -32,6 +54,24 @@ module RailsAuditLog
32
54
  end
33
55
  end
34
56
 
57
+ module Subscriptions
58
+ class AuditLogEntryCreated < Types::BaseSubscription
59
+ def subscribe: (?item_type: String?, ?item_id: String?, ?actor_id: String?) -> :no_response
60
+ def update: (?item_type: String?, ?item_id: String?, ?actor_id: String?) -> untyped
61
+ end
62
+
63
+ module AuditLogSubscriptionsMixin
64
+ def self.included: (untyped base) -> void
65
+ end
66
+
67
+ class Broadcaster
68
+ def initialize: (schema: untyped) -> void
69
+ def start: () -> void
70
+ def stop: () -> void
71
+ def broadcast: (untyped entry) -> void
72
+ end
73
+ end
74
+
35
75
  module Queries
36
76
  module AuditLogEntriesQueryMixin
37
77
  def self.included: (untyped base) -> void
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_audit_log-graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -86,9 +86,16 @@ files:
86
86
  - lib/rails_audit_log/graphql/input_objects/audit_log_entry_sort_input.rb
87
87
  - lib/rails_audit_log/graphql/queries/audit_log_entries_query_mixin.rb
88
88
  - lib/rails_audit_log/graphql/release_tooling.rb
89
+ - lib/rails_audit_log/graphql/subscriptions/audit_log_entry_created.rb
90
+ - lib/rails_audit_log/graphql/subscriptions/audit_log_subscriptions_mixin.rb
91
+ - lib/rails_audit_log/graphql/subscriptions/broadcaster.rb
92
+ - lib/rails_audit_log/graphql/types/actor_type.rb
89
93
  - lib/rails_audit_log/graphql/types/audit_log_entry_sort_field_enum.rb
90
94
  - lib/rails_audit_log/graphql/types/audit_log_entry_type.rb
95
+ - lib/rails_audit_log/graphql/types/audited_resource_type.rb
91
96
  - lib/rails_audit_log/graphql/types/base_object.rb
97
+ - lib/rails_audit_log/graphql/types/base_subscription.rb
98
+ - lib/rails_audit_log/graphql/types/diff_type.rb
92
99
  - lib/rails_audit_log/graphql/types/sort_direction_enum.rb
93
100
  - lib/rails_audit_log/graphql/version.rb
94
101
  - sig/rails_audit_log/graphql.rbs