openui-rails 0.3.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 +4 -4
- data/CHANGELOG.md +19 -1
- data/CONTRIBUTING.md +1 -1
- data/DEVELOPMENT.md +1 -1
- data/README.md +6 -6
- data/examples/support_desk/README.md +5 -3
- data/examples/support_desk/config/initializers/openui.rb +1 -0
- data/lib/generators/openui/idempotency/idempotency_generator.rb +19 -0
- data/lib/generators/openui/idempotency/templates/create_openui_action_claims.rb +16 -0
- data/lib/generators/openui/install/templates/config/initializers/openui.rb.tt +4 -0
- data/lib/openui/rails/actions/idempotency/active_record_store.rb +73 -0
- data/lib/openui/rails/actions/idempotency/memory_store.rb +2 -2
- data/lib/openui/rails/actions.rb +3 -0
- data/lib/openui/rails/testing/idempotency_store_contract.rb +79 -0
- data/lib/openui/rails/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1fd5862a42a9eaf7c669e6e94fa38d8aaac1784e4f7efe5340e9cec48756902a
|
|
4
|
+
data.tar.gz: f7ada4e9693c3de0c0d13b05a7cc7692a44b402b291721d430038a68c6047f4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0dc08e62c9508a13e11fa3d4995717d6b88be85458d505da3c363bccbfa1b57a1017bc59d72d667ed3d8043eb68f1005cd18b77bc87bae3ac9df0d2fe4683017
|
|
7
|
+
data.tar.gz: 524eb2239e400a15e6e0dee14d98b5d71e0a6f94866e4bfafda48917ea17f55a18a9eda9126cb13d7a7e4e149bd3ecfaa4f755ebac32f4ba2ca99d9b18db1d01
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,23 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.0] - 2026-10-20
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Optional `Actions::Idempotency::ActiveRecordStore` with atomic database claims,
|
|
14
|
+
ownership fencing, JSON result persistence, and explicit expiry cleanup.
|
|
15
|
+
SQLite is the verified database; memory remains the default.
|
|
16
|
+
- `openui:idempotency` generates the opt-in table and its unique key and expiry
|
|
17
|
+
indexes. Installing the engine alone creates no shared-store table.
|
|
18
|
+
- Reusable Minitest store-conformance checks, independent-process SQLite tests,
|
|
19
|
+
and a fresh-host migration and restart check against the built gem.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- The support-desk reference uses the shared store while retaining its business
|
|
24
|
+
uniqueness constraint. A shared claim does not guarantee exactly-once effects.
|
|
25
|
+
|
|
9
26
|
## [0.3.0] - 2026-10-19
|
|
10
27
|
|
|
11
28
|
### Added
|
|
@@ -116,7 +133,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
116
133
|
- Gem-scoped RubyGems Trusted Publishing through GitHub OIDC, with a protected
|
|
117
134
|
release environment and verified artifact handoff.
|
|
118
135
|
|
|
119
|
-
[Unreleased]: https://github.com/yorzi/openui-rails/compare/v0.
|
|
136
|
+
[Unreleased]: https://github.com/yorzi/openui-rails/compare/v0.4.0...main
|
|
137
|
+
[0.4.0]: https://github.com/yorzi/openui-rails/releases/tag/v0.4.0
|
|
120
138
|
[0.3.0]: https://github.com/yorzi/openui-rails/releases/tag/v0.3.0
|
|
121
139
|
[0.2.0]: https://github.com/yorzi/openui-rails/releases/tag/v0.2.0
|
|
122
140
|
[0.1.0]: https://github.com/yorzi/openui-rails/releases/tag/v0.1.0
|
data/CONTRIBUTING.md
CHANGED
|
@@ -25,7 +25,7 @@ bundle exec bin/rails test # unit + integration tests
|
|
|
25
25
|
bundle exec bin/rails test:system # browser (system) tests, needs Chrome
|
|
26
26
|
bundle exec rubocop # style (rubocop-rails-omakase)
|
|
27
27
|
bundle exec gem build openui-rails.gemspec
|
|
28
|
-
bundle exec ruby bin/smoke-package openui-rails-0.
|
|
28
|
+
bundle exec ruby bin/smoke-package openui-rails-0.4.0.gem
|
|
29
29
|
bundle exec bundler-audit check --update
|
|
30
30
|
bundle exec ruby -Itest test/compatibility/ruby_llm_smoke.rb
|
|
31
31
|
```
|
data/DEVELOPMENT.md
CHANGED
|
@@ -17,7 +17,7 @@ authorizes a live call. Follow this loop for each feature:
|
|
|
17
17
|
```sh
|
|
18
18
|
bin/check-reference
|
|
19
19
|
bin/check-reference --runtime tmp/real-runtime
|
|
20
|
-
bin/check-reference --gem tmp/package/openui-rails-0.
|
|
20
|
+
bin/check-reference --gem tmp/package/openui-rails-0.4.0.gem --runtime tmp/real-runtime
|
|
21
21
|
# Focus one browser scenario:
|
|
22
22
|
bin/check-reference --runtime tmp/real-runtime --name test_a_lost_turbo_envelope_recovers_from_the_scoped_snapshot
|
|
23
23
|
```
|
data/README.md
CHANGED
|
@@ -22,8 +22,8 @@ or renderer.
|
|
|
22
22
|
- One allowlisted action endpoint with authorization, validation, and replay protection.
|
|
23
23
|
- An optional RubyLLM streaming adapter.
|
|
24
24
|
|
|
25
|
-
Your app supplies scoped record lookup, stream subscriptions, snapshot
|
|
26
|
-
|
|
25
|
+
Your app supplies scoped record lookup, stream subscriptions, and snapshot
|
|
26
|
+
recovery. Multi-process apps can opt into the SQLite shared idempotency store.
|
|
27
27
|
|
|
28
28
|
## Get started
|
|
29
29
|
|
|
@@ -32,7 +32,7 @@ The runtime-fetch task also needs npm. RubyLLM is optional.
|
|
|
32
32
|
|
|
33
33
|
```ruby
|
|
34
34
|
# Gemfile
|
|
35
|
-
gem "openui-rails", "~> 0.
|
|
35
|
+
gem "openui-rails", "~> 0.4.0"
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
```sh
|
|
@@ -65,15 +65,15 @@ The documentation site contains the full guides and API reference:
|
|
|
65
65
|
- [RubyLLM](https://openui-rails.wangyaodi.com/ruby-llm/) and [production setup](https://openui-rails.wangyaodi.com/production/)
|
|
66
66
|
- [Upgrading](https://openui-rails.wangyaodi.com/upgrading/) and [API reference](https://openui-rails.wangyaodi.com/api-reference/)
|
|
67
67
|
|
|
68
|
-
Version `0.
|
|
69
|
-
See [What's New](https://openui-rails.wangyaodi.com/whats-new-in-0-
|
|
68
|
+
Version `0.4.0` adds optional shared replay storage for Rails workers.
|
|
69
|
+
See [What's New](https://openui-rails.wangyaodi.com/whats-new-in-0-4/) for the upgrade.
|
|
70
70
|
The API may change during `0.x`; see the
|
|
71
71
|
[changelog](CHANGELOG.md) and the proposed [path to 1.0](ROADMAP.md).
|
|
72
72
|
The site's stable channel follows the latest release; [/next/](https://openui-rails.wangyaodi.com/next/)
|
|
73
73
|
follows `main`.
|
|
74
74
|
|
|
75
75
|
For repository work, use [Contributing](CONTRIBUTING.md),
|
|
76
|
-
[Releasing](RELEASING.md), and the [0.
|
|
76
|
+
[Releasing](RELEASING.md), and the [0.3.0 release record](RELEASE_0_3.md).
|
|
77
77
|
Report vulnerabilities through the [security policy](SECURITY.md).
|
|
78
78
|
|
|
79
79
|
## License
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
This opt-in reference belongs to the host application, outside the engine's
|
|
4
4
|
public API. It uses synthetic accounts, recorded generation, the real pinned
|
|
5
|
-
OpenUI runtime, SQLite, importmap, and HTTP Turbo Streams.
|
|
5
|
+
OpenUI runtime, SQLite, importmap, and HTTP Turbo Streams. It opts into the
|
|
6
|
+
Active Record idempotency store and retains a unique business receipt for closing
|
|
7
|
+
a ticket. Shared claims do not replace that database constraint.
|
|
6
8
|
|
|
7
9
|
From the repository:
|
|
8
10
|
|
|
9
11
|
```sh
|
|
10
12
|
mkdir -p tmp/package
|
|
11
|
-
bundle exec gem build openui-rails.gemspec --output tmp/package/openui-rails-0.
|
|
12
|
-
bin/check-reference --gem tmp/package/openui-rails-0.
|
|
13
|
+
bundle exec gem build openui-rails.gemspec --output tmp/package/openui-rails-0.4.0.gem
|
|
14
|
+
bin/check-reference --gem tmp/package/openui-rails-0.4.0.gem --runtime tmp/real-runtime
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
Fetch the runtime first. See the [complete guide](https://openui-rails.wangyaodi.com/reference-workflow/)
|
|
@@ -8,6 +8,7 @@ OpenUI::Rails.configure do |config|
|
|
|
8
8
|
config.surface_resolver = ->(id, actor:, **) {
|
|
9
9
|
SupportDesk::Conversation.find_by(id: id, user_id: actor.id, tenant_id: actor.tenant_id) if actor
|
|
10
10
|
}
|
|
11
|
+
config.idempotency_store = OpenUI::Rails::Actions::Idempotency::ActiveRecordStore.new
|
|
11
12
|
config.idempotency_scope = ->(invocation) { [ invocation.actor.tenant_id, invocation.actor.id ] }
|
|
12
13
|
config.authorize_action = ->(invocation) {
|
|
13
14
|
actor, conversation = invocation.actor, invocation.surface
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/active_record"
|
|
4
|
+
|
|
5
|
+
module OpenUI
|
|
6
|
+
# Generate the opt-in shared action-claim table without changing configuration.
|
|
7
|
+
class IdempotencyGenerator < ::ActiveRecord::Generators::Base
|
|
8
|
+
namespace "openui:idempotency"
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
desc "Create the optional OpenUI shared idempotency store migration."
|
|
11
|
+
# This generator has no model-name argument.
|
|
12
|
+
argument :name, type: :string, default: "openui_action_claims", hide: true
|
|
13
|
+
|
|
14
|
+
# Write the optional, reversible host migration.
|
|
15
|
+
def create_store_migration
|
|
16
|
+
migration_template "create_openui_action_claims.rb", "db/migrate/create_openui_action_claims.rb"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateOpenUIActionClaims < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :openui_action_claims do |t|
|
|
6
|
+
t.string :key, null: false
|
|
7
|
+
t.string :digest
|
|
8
|
+
t.string :claim_token, null: false
|
|
9
|
+
t.boolean :completed, null: false, default: false
|
|
10
|
+
t.text :result_json
|
|
11
|
+
t.datetime :expires_at, null: false, precision: 6
|
|
12
|
+
end
|
|
13
|
+
add_index :openui_action_claims, :key, unique: true
|
|
14
|
+
add_index :openui_action_claims, :expires_at
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -14,6 +14,10 @@ OpenUI::Rails.configure do |config|
|
|
|
14
14
|
config.runtime_assets = :self_hosted
|
|
15
15
|
# config.runtime_integrity = "sha384-..."
|
|
16
16
|
|
|
17
|
+
# Optional shared SQLite claims: first run openui:idempotency and db:migrate.
|
|
18
|
+
# See the Shared Idempotency Storage guide for JSON results and crash limits.
|
|
19
|
+
# config.idempotency_store = OpenUI::Rails::Actions::Idempotency::ActiveRecordStore.new
|
|
20
|
+
|
|
17
21
|
# Authorize every action/tool call. This fails closed: with no authorizer an
|
|
18
22
|
# action is denied. Return truthy to permit.
|
|
19
23
|
# config.authorize_action = ->(invocation) do
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record"
|
|
4
|
+
require "json"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
|
|
7
|
+
module OpenUI
|
|
8
|
+
module Rails
|
|
9
|
+
module Actions
|
|
10
|
+
module Idempotency
|
|
11
|
+
# Optional shared action store. Verified with SQLite on Rails 8.0/8.1.
|
|
12
|
+
# Install its table with `rails generate openui:idempotency` and db:migrate.
|
|
13
|
+
# JSON values replay across workers; object keys become strings. TTL runs
|
|
14
|
+
# from claim creation, not completion. Never assumes exactly-once effects.
|
|
15
|
+
class ActiveRecordStore
|
|
16
|
+
class Entry < ::ActiveRecord::Base # :nodoc:
|
|
17
|
+
self.table_name = "openui_action_claims"
|
|
18
|
+
end
|
|
19
|
+
private_constant :Entry
|
|
20
|
+
|
|
21
|
+
# @param model [Class] dedicated Active Record model, optionally using another database
|
|
22
|
+
# @param clock [#call] shared wall clock returning UTC Time values
|
|
23
|
+
def initialize(model: Entry, clock: -> { Time.now.utc })
|
|
24
|
+
@model = model
|
|
25
|
+
@clock = clock
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Atomically insert or replace an expired claim. Returns the same
|
|
29
|
+
# [status, result, stored_digest, token] contract as MemoryStore.
|
|
30
|
+
# A changed digest is returned to the dispatcher, which rejects it.
|
|
31
|
+
def claim(key, ttl:, digest: nil)
|
|
32
|
+
raise ConfigurationError, "idempotency ttl must be positive" unless ttl.is_a?(Numeric) && ttl.finite? && ttl > 0
|
|
33
|
+
|
|
34
|
+
loop do
|
|
35
|
+
now = @clock.call
|
|
36
|
+
attributes = { key: key, digest: digest, claim_token: SecureRandom.hex(16),
|
|
37
|
+
completed: false, result_json: nil, expires_at: now + ttl }
|
|
38
|
+
inserted = @model.insert_all([ attributes ], unique_by: :key, returning: %w[id])
|
|
39
|
+
return [ :claimed, nil, digest, attributes[:claim_token] ] if inserted.any?
|
|
40
|
+
|
|
41
|
+
replaced = @model.where(key: key).where("expires_at <= ?", now).update_all(attributes.except(:key))
|
|
42
|
+
return [ :claimed, nil, digest, attributes[:claim_token] ] if replaced == 1
|
|
43
|
+
|
|
44
|
+
# Cleanup can delete the expired row between SQL statements.
|
|
45
|
+
entry = @model.find_by(key: key)
|
|
46
|
+
next unless entry
|
|
47
|
+
|
|
48
|
+
value = entry.completed ? JSON.parse(entry.result_json) : nil
|
|
49
|
+
return [ entry.completed ? :replay : :pending, value, entry.digest, entry.claim_token ]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Record only while this token owns a live claim. JSON encoding rejects
|
|
54
|
+
# unsupported Ruby objects before writing; it never invokes Marshal.
|
|
55
|
+
def record(key, result, claim_token:)
|
|
56
|
+
json = JSON.generate(JsonValue.copy(result))
|
|
57
|
+
@model.where(key: key, claim_token: claim_token).where("expires_at > ?", @clock.call)
|
|
58
|
+
.update_all(result_json: json, completed: true)
|
|
59
|
+
nil
|
|
60
|
+
rescue ArgumentError, JSON::GeneratorError => error
|
|
61
|
+
raise ConfigurationError, "idempotency result must be a JSON value: #{error.message}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Delete expired claims, returning the number removed. Call from a host
|
|
65
|
+
# maintenance task; expiry of the requested key never needs this sweep.
|
|
66
|
+
def delete_expired
|
|
67
|
+
@model.where("expires_at <= ?", @clock.call).delete_all
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -7,8 +7,8 @@ module OpenUI
|
|
|
7
7
|
module Actions
|
|
8
8
|
module Idempotency
|
|
9
9
|
# Default, single-process idempotency store. Hosts that run multiple
|
|
10
|
-
# processes
|
|
11
|
-
#
|
|
10
|
+
# processes can opt into ActiveRecordStore on SQLite
|
|
11
|
+
# or inject another shared adapter implementing the same contract.
|
|
12
12
|
#
|
|
13
13
|
# Contract:
|
|
14
14
|
# claim(key, ttl:, digest:) -> [status, result, digest, claim_token]
|
data/lib/openui/rails/actions.rb
CHANGED
|
@@ -13,6 +13,9 @@ module OpenUI
|
|
|
13
13
|
# a host authorizer, stale-surface protection and idempotency, shared by
|
|
14
14
|
# both component actions and browser tool calls.
|
|
15
15
|
module Actions
|
|
16
|
+
module Idempotency
|
|
17
|
+
autoload :ActiveRecordStore, "openui/rails/actions/idempotency/active_record_store"
|
|
18
|
+
end
|
|
16
19
|
end
|
|
17
20
|
end
|
|
18
21
|
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenUI
|
|
4
|
+
module Rails
|
|
5
|
+
module Testing
|
|
6
|
+
# Reusable Minitest contract for idempotency adapters. Include this module
|
|
7
|
+
# in a test case that defines build_store and advance_store_clock(seconds).
|
|
8
|
+
# Each test needs an empty store and a controllable shared clock.
|
|
9
|
+
module IdempotencyStoreContract
|
|
10
|
+
def test_claim_distinguishes_pending_from_completed
|
|
11
|
+
store = build_store
|
|
12
|
+
status, value, digest, token = store.claim("key", ttl: 60, digest: "original")
|
|
13
|
+
assert_equal :claimed, status
|
|
14
|
+
assert_nil value
|
|
15
|
+
assert_equal "original", digest
|
|
16
|
+
refute_empty token
|
|
17
|
+
assert_equal [ :pending, nil, "original", token ], store.claim("key", ttl: 60, digest: "changed")
|
|
18
|
+
store.record("key", "saved", claim_token: token)
|
|
19
|
+
assert_equal [ :replay, "saved", "original", token ], store.claim("key", ttl: 60, digest: "changed")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_completed_nil_and_false_replay
|
|
23
|
+
store = build_store
|
|
24
|
+
[ nil, false ].each_with_index do |value, index|
|
|
25
|
+
key = "nullable-#{index}"
|
|
26
|
+
token = store.claim(key, ttl: 60, digest: "d").last
|
|
27
|
+
store.record(key, value, claim_token: token)
|
|
28
|
+
assert_equal [ :replay, value, "d", token ], store.claim(key, ttl: 60, digest: "d")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_json_results_round_trip
|
|
33
|
+
store = build_store
|
|
34
|
+
value = { "list" => [ 1, 2.5, true, false, nil, "你好" ], "nested" => { "ok" => true } }
|
|
35
|
+
token = store.claim("key", ttl: 60, digest: "d").last
|
|
36
|
+
store.record("key", value, claim_token: token)
|
|
37
|
+
assert_equal value, store.claim("key", ttl: 60, digest: "d")[1]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_expired_owner_cannot_record
|
|
41
|
+
store = build_store
|
|
42
|
+
token = store.claim("key", ttl: 10, digest: "old").last
|
|
43
|
+
advance_store_clock(10)
|
|
44
|
+
store.record("key", "late", claim_token: token)
|
|
45
|
+
status, value, _, replacement = store.claim("key", ttl: 60, digest: "new")
|
|
46
|
+
assert_equal :claimed, status
|
|
47
|
+
assert_nil value
|
|
48
|
+
refute_equal token, replacement
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_replaced_owner_cannot_overwrite
|
|
52
|
+
store = build_store
|
|
53
|
+
old = store.claim("key", ttl: 10, digest: "old").last
|
|
54
|
+
advance_store_clock(11)
|
|
55
|
+
token = store.claim("key", ttl: 60, digest: "new").last
|
|
56
|
+
store.record("key", "new result", claim_token: token)
|
|
57
|
+
store.record("key", "old result", claim_token: old)
|
|
58
|
+
assert_equal [ :replay, "new result", "new", token ], store.claim("key", ttl: 60, digest: "new")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_wrong_token_and_unknown_key_do_not_complete_a_claim
|
|
62
|
+
store = build_store
|
|
63
|
+
token = store.claim("key", ttl: 60, digest: "d").last
|
|
64
|
+
store.record("missing", "value", claim_token: token)
|
|
65
|
+
store.record("key", "value", claim_token: "wrong")
|
|
66
|
+
assert_equal :pending, store.claim("key", ttl: 60, digest: "d").first
|
|
67
|
+
assert_equal :claimed, store.claim("missing", ttl: 60, digest: "d").first
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_ttl_must_be_positive_and_finite
|
|
71
|
+
store = build_store
|
|
72
|
+
[ 0, -1, Float::INFINITY, Float::NAN, "60", nil ].each do |ttl|
|
|
73
|
+
assert_raises(OpenUI::Rails::ConfigurationError) { store.claim("key", ttl: ttl, digest: "d") }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/openui/rails/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openui-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andy Wang
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -80,12 +80,15 @@ files:
|
|
|
80
80
|
- examples/support_desk/config/routes.rb
|
|
81
81
|
- examples/support_desk/db/migrate/20261019000000_create_support_desk.rb
|
|
82
82
|
- examples/support_desk/install.rb
|
|
83
|
+
- lib/generators/openui/idempotency/idempotency_generator.rb
|
|
84
|
+
- lib/generators/openui/idempotency/templates/create_openui_action_claims.rb
|
|
83
85
|
- lib/generators/openui/install/install_generator.rb
|
|
84
86
|
- lib/generators/openui/install/templates/config/initializers/openui.rb.tt
|
|
85
87
|
- lib/generators/openui/install/templates/config/openui/README.md
|
|
86
88
|
- lib/openui/rails.rb
|
|
87
89
|
- lib/openui/rails/actions.rb
|
|
88
90
|
- lib/openui/rails/actions/dispatcher.rb
|
|
91
|
+
- lib/openui/rails/actions/idempotency/active_record_store.rb
|
|
89
92
|
- lib/openui/rails/actions/idempotency/memory_store.rb
|
|
90
93
|
- lib/openui/rails/actions/invocation.rb
|
|
91
94
|
- lib/openui/rails/actions/registry.rb
|
|
@@ -116,6 +119,7 @@ files:
|
|
|
116
119
|
- lib/openui/rails/stream/transports/memory.rb
|
|
117
120
|
- lib/openui/rails/stream/transports/turbo_streams.rb
|
|
118
121
|
- lib/openui/rails/surface.rb
|
|
122
|
+
- lib/openui/rails/testing/idempotency_store_contract.rb
|
|
119
123
|
- lib/openui/rails/version.rb
|
|
120
124
|
- lib/openui/rails/view_helper.rb
|
|
121
125
|
- lib/tasks/openui.rake
|