familia 2.0.0.pre6 → 2.0.0.pre7

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +57 -0
  3. data/.github/workflows/claude.yml +71 -0
  4. data/.gitignore +5 -1
  5. data/.rubocop.yml +3 -0
  6. data/CLAUDE.md +32 -13
  7. data/Gemfile +2 -2
  8. data/Gemfile.lock +2 -2
  9. data/docs/wiki/Feature-System-Guide.md +36 -5
  10. data/docs/wiki/Home.md +30 -20
  11. data/docs/wiki/Relationships-Guide.md +684 -0
  12. data/examples/bit_encoding_integration.rb +237 -0
  13. data/examples/redis_command_validation_example.rb +231 -0
  14. data/examples/relationships_basic.rb +273 -0
  15. data/lib/familia/connection.rb +3 -3
  16. data/lib/familia/data_type.rb +7 -4
  17. data/lib/familia/features/encrypted_fields/concealed_string.rb +21 -23
  18. data/lib/familia/features/encrypted_fields.rb +413 -4
  19. data/lib/familia/features/expiration.rb +319 -33
  20. data/lib/familia/features/quantization.rb +385 -44
  21. data/lib/familia/features/relationships/cascading.rb +438 -0
  22. data/lib/familia/features/relationships/indexing.rb +370 -0
  23. data/lib/familia/features/relationships/membership.rb +503 -0
  24. data/lib/familia/features/relationships/permission_management.rb +264 -0
  25. data/lib/familia/features/relationships/querying.rb +620 -0
  26. data/lib/familia/features/relationships/redis_operations.rb +274 -0
  27. data/lib/familia/features/relationships/score_encoding.rb +442 -0
  28. data/lib/familia/features/relationships/tracking.rb +379 -0
  29. data/lib/familia/features/relationships.rb +466 -0
  30. data/lib/familia/features/transient_fields.rb +192 -10
  31. data/lib/familia/features.rb +2 -1
  32. data/lib/familia/horreum/subclass/definition.rb +1 -1
  33. data/lib/familia/validation/command_recorder.rb +336 -0
  34. data/lib/familia/validation/expectations.rb +519 -0
  35. data/lib/familia/validation/test_helpers.rb +443 -0
  36. data/lib/familia/validation/validator.rb +412 -0
  37. data/lib/familia/validation.rb +140 -0
  38. data/lib/familia/version.rb +1 -1
  39. data/try/edge_cases/hash_symbolization_try.rb +1 -0
  40. data/try/edge_cases/reserved_keywords_try.rb +1 -0
  41. data/try/edge_cases/string_coercion_try.rb +2 -0
  42. data/try/encryption/encryption_core_try.rb +3 -1
  43. data/try/features/categorical_permissions_try.rb +515 -0
  44. data/try/features/encryption_fields/concealed_string_core_try.rb +3 -0
  45. data/try/features/encryption_fields/context_isolation_try.rb +1 -0
  46. data/try/features/relationships_edge_cases_try.rb +145 -0
  47. data/try/features/relationships_performance_minimal_try.rb +132 -0
  48. data/try/features/relationships_performance_simple_try.rb +155 -0
  49. data/try/features/relationships_performance_try.rb +420 -0
  50. data/try/features/relationships_performance_working_try.rb +144 -0
  51. data/try/features/relationships_try.rb +237 -0
  52. data/try/features/safe_dump_try.rb +3 -0
  53. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  54. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  55. data/try/helpers/test_helpers.rb +1 -1
  56. data/try/horreum/base_try.rb +14 -8
  57. data/try/horreum/enhanced_conflict_handling_try.rb +2 -0
  58. data/try/horreum/relations_try.rb +1 -1
  59. data/try/validation/atomic_operations_try.rb.disabled +320 -0
  60. data/try/validation/command_validation_try.rb.disabled +207 -0
  61. data/try/validation/performance_validation_try.rb.disabled +324 -0
  62. data/try/validation/real_world_scenarios_try.rb.disabled +390 -0
  63. metadata +32 -4
  64. data/docs/wiki/RelatableObjects-Guide.md +0 -563
  65. data/lib/familia/features/relatable_objects.rb +0 -125
  66. data/try/features/relatable_objects_try.rb +0 -220
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93cb8ea627c19daff3566c7373aa45ff04adb6d37a3346c3b3049712c18838ea
4
- data.tar.gz: 9d359e2067062a4c6afe91a76985362989a569611400f10f7155e505dd55e39b
3
+ metadata.gz: 7a93de51c8a35c420067d9e48895af37862866667af224a8e13f44647c4e4c76
4
+ data.tar.gz: bbdf64f77c182a0498fc757c393071b04b5da3594b4c7d5c12992fe058b44777
5
5
  SHA512:
6
- metadata.gz: e020a48703858e929eeb11188029893656ef84ce98113e4c448d41e354edb4fe77df420aa4bab33b7775f0719ad5a693fc672c8159cd0e963be7750daa12d5b9
7
- data.tar.gz: c3e66800df77ccd33ccd5a6027d031173642446210309e60bf83243f59585a0416ea99002063f0080828eb24d1aa1fa50f3013e7dbf408b2299f2fea617b2fa3
6
+ metadata.gz: 59a39a481db42739bbebabab9211645b2f7e9eb605742ab936c0b65dfc32973931b1b1f8a1c5d725fed5f5eb0ebdbb87c1c405f87108390ad3b6eb8f5ca89c5a
7
+ data.tar.gz: 4dc7dededb21bd7ee988a4d260e30eceb43f19be4c3f5d99f87bac5d632af6e517af5eab2633758e0c6b1552a2635bd0e024ace2210ee1ee83362aa811458b8c
@@ -0,0 +1,57 @@
1
+ name: Claude Code Review
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize]
6
+ # Optional: Only run on specific file changes
7
+ # paths:
8
+ # - "src/**/*.ts"
9
+ # - "src/**/*.tsx"
10
+ # - "src/**/*.js"
11
+ # - "src/**/*.jsx"
12
+
13
+ jobs:
14
+ claude-review:
15
+ # Optional: Filter by PR author
16
+ # if: |
17
+ # github.event.pull_request.user.login == 'external-contributor' ||
18
+ # github.event.pull_request.user.login == 'new-developer' ||
19
+ # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
20
+
21
+ runs-on: ubuntu-latest
22
+ permissions:
23
+ contents: read
24
+ pull-requests: read
25
+ issues: read
26
+ id-token: write
27
+
28
+ steps:
29
+ - name: Checkout repository
30
+ uses: actions/checkout@v4
31
+ with:
32
+ fetch-depth: 1
33
+
34
+ - name: Run Claude Code Review
35
+ id: claude-review
36
+ uses: anthropics/claude-code-action@beta
37
+ with:
38
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
39
+
40
+ # Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
41
+ use_sticky_comment: true
42
+
43
+ prompt: |
44
+ Please review this pull request and provide feedback on:
45
+ - Code quality and best practices
46
+ - Potential bugs or issues
47
+ - Performance considerations
48
+ - Security concerns
49
+ - Test coverage
50
+
51
+ Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
52
+
53
+ Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
54
+
55
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
56
+ # or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
57
+ claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
@@ -0,0 +1,71 @@
1
+ name: Claude Code
2
+
3
+ on:
4
+ issue_comment:
5
+ types: [created]
6
+ pull_request_review_comment:
7
+ types: [created]
8
+ issues:
9
+ types: [opened, assigned]
10
+ pull_request_review:
11
+ types: [submitted]
12
+
13
+ jobs:
14
+ claude:
15
+ if: |
16
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20
+ runs-on: ubuntu-latest
21
+ permissions:
22
+ contents: read
23
+ pull-requests: read
24
+ issues: read
25
+ id-token: write
26
+ actions: read # Required for Claude to read CI results on PRs
27
+ steps:
28
+ - name: Checkout repository
29
+ uses: actions/checkout@v4
30
+ with:
31
+ fetch-depth: 1
32
+
33
+ - name: Run Claude Code
34
+ id: claude
35
+ uses: anthropics/claude-code-action@v1
36
+ with:
37
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
38
+
39
+ # This is an optional setting that allows Claude to read CI results on PRs
40
+ additional_permissions: |
41
+ actions: read
42
+
43
+ # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
44
+ # model: "claude-opus-4-20250514"
45
+
46
+ # Optional: Customize the trigger phrase (default: @claude)
47
+ # trigger_phrase: "/claude"
48
+
49
+ # Optional: Trigger when specific user is assigned to an issue
50
+ # assignee_trigger: "claude-bot"
51
+
52
+ # Optional: Allow Claude to run specific commands
53
+ # allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
54
+
55
+ # Optional: Add custom instructions for Claude to customize its behavior for your project
56
+ # custom_instructions: |
57
+ # Follow our coding standards
58
+ # Ensure all new code has tests
59
+ # Use TypeScript for new files
60
+
61
+ # Optional: Custom environment variables for Claude
62
+ # claude_env: |
63
+ # NODE_ENV: test
64
+
65
+ # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
66
+ # prompt: 'Update the pull request description to include a summary of changes.'
67
+
68
+ # Optional: Add claude_args to customize behavior and configuration
69
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
70
+ # or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
71
+ # claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)'
data/.gitignore CHANGED
@@ -1,14 +1,19 @@
1
1
  .DS_Store
2
2
  .bundle
3
3
  .byebug*
4
+ .ruby-lsp
4
5
  .prompts
5
6
  .history
6
7
  .devcontainer
7
8
  .vscode
9
+ .serena
10
+ .claude
11
+ .yardoc
8
12
  .*.json
9
13
  *.env
10
14
  *.log
11
15
  *.md
16
+ .mcp.json
12
17
  !README.md
13
18
  !CLAUDE.md
14
19
  *.txt
@@ -21,7 +26,6 @@ tmp
21
26
  vendor
22
27
  *.gem
23
28
  doc/
24
- .yardoc
25
29
  !docs/overview.md
26
30
  !docs/connection_pooling.md
27
31
  !docs/wiki/**/*.md
data/.rubocop.yml CHANGED
@@ -80,3 +80,6 @@ Naming/AsciiIdentifiers:
80
80
 
81
81
  Style/FrozenStringLiteralComment:
82
82
  Enabled: false
83
+
84
+ Naming/MemoizedInstanceVariableName:
85
+ Enabled: false
data/CLAUDE.md CHANGED
@@ -6,19 +6,38 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
6
6
 
7
7
  ### Testing
8
8
 
9
- Tryouts framework rules:
10
- 1) **Structure**: 3 sections - setup (optional), testcases, teardown (optional)
11
- 2) **Test cases**: Use `##` for descriptions, Ruby code, then `#=>` expectations
12
- 3) **Variables**: Instance variables (`@var`) persist across all sections
13
- 4) **Expectations**: `#=>` (value), `#==>` (boolean), `#=:>` (type), `#=!>` (exception)
14
- 5) **Comments**: Use single `#` prefix, DO NOT label sections
15
- 6) **Philosophy**: Plain realistic code, avoid mocks/test DSL
16
- 7) **Result**: Last expression in each test case is the result
17
-
18
- - **Run tests**: `bundle exec try` (uses tryouts testing framework)
19
- - **Run specific test file, verbose**: `bundle exec try -v try/specific_test_try.rb`
20
- - **Debug mode**: `FAMILIA_DEBUG=1 bundle exec try -D`
21
- - **Trace mode**: `FAMILIA_TRACE=1 bundle exec try -D` (detailed Redis operation logging)
9
+ **Tryouts framework rules:**
10
+ 1. **Structure**: Each file has 3 sections - setup (optional), testcases, teardown (optional); each testcase has 3 required parts: description, code, expectations.
11
+ 2. **Test cases**: Use `##` line prefix for test descriptions, Ruby code, then `#=>` expectations
12
+ 3. **Variables**: Instance variables (`@var`) persist across sections; local variables do not.
13
+ 4. **Expectations**: Multiple expectation types available (`#=>`, `#==>`, `#=:>`, `#=!>`, etc.); each testcase can have multiple expectations.
14
+ 5. **Comments**: Use single `#` prefix, but DO NOT label file sections
15
+ 6. **Philosophy**: Plain realistic code, avoid mocks/test DSL
16
+ 7. **Result**: Last expression in each test case is the result
17
+
18
+ **Running tests:**
19
+ - **Basic**: `bundle exec try` (auto-discovers `*_try.rb` and `*.try.rb` files)
20
+ - **All options**: `bundle exec try --help` (complete CLI reference with agent-specific notes)
21
+
22
+ **Agent-optimized workflow:**
23
+ - **Default agent mode**: `bundle exec try --agent` (structured, token-efficient output for LLMs)
24
+ - **Focus modes**: `bundle exec try --agent --agent-focus summary` (options: `summary|first-failure|critical`)
25
+ - `summary`: Overview of test results only
26
+ - `first-failure`: Stop at first failure with details
27
+ - `critical`: Only show critical issues and summary
28
+
29
+ **Framework integration:**
30
+ - **RSpec**: `bundle exec try --rspec` (generates RSpec-compatible output)
31
+ - **Minitest**: `bundle exec try --minitest` (generates Minitest-compatible output)
32
+
33
+ **Debugging options:**
34
+ - **Stack traces**: `bundle exec try -s` (stack traces without debug logging)
35
+ - **Debug mode**: `bundle exec try -D` (additional logging including stack traces)
36
+ - **Verbose failures**: `bundle exec try -vf` (detailed failure output)
37
+ - **Fresh context**: `bundle exec try --fresh-context` (isolate test cases)
38
+
39
+ *Note: Use `--agent` mode for optimal token efficiency when analyzing test results programmatically.*
40
+
22
41
 
23
42
  ### Development Setup
24
43
  - **Install dependencies**: `bundle install`
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ group :test do
9
9
  gem 'tryouts', path: '../tryouts'
10
10
  gem 'uri-valkey', path: '..//uri-valkey/gems', glob: 'uri-valkey.gemspec'
11
11
  else
12
- gem 'tryouts', '~> 3.3.2', require: false
12
+ gem 'tryouts', '~> 3.5.1', require: false
13
13
  end
14
14
  gem 'concurrent-ruby', '~> 1.3.5', require: false
15
15
  gem 'ruby-prof'
@@ -19,13 +19,13 @@ end
19
19
  group :development, :test do
20
20
  # byebug only works with MRI
21
21
  gem 'byebug', '~> 11.0', require: false if RUBY_ENGINE == 'ruby'
22
+ gem 'irb', '~> 1.15.2', require: false
22
23
  gem 'kramdown', require: false # Required for YARD markdown processing
23
24
  gem 'pry-byebug', '~> 3.10.1', require: false if RUBY_ENGINE == 'ruby'
24
25
  gem 'rubocop', require: false
25
26
  gem 'rubocop-performance', require: false
26
27
  gem 'rubocop-thread_safety', require: false
27
28
  gem 'yard', '~> 0.9', require: false
28
- gem 'irb', '~> 1.15.2', require: false
29
29
  end
30
30
 
31
31
  group :optional do
data/Gemfile.lock CHANGED
@@ -113,7 +113,7 @@ GEM
113
113
  ruby-progressbar (1.13.0)
114
114
  stackprof (0.2.27)
115
115
  stringio (3.1.7)
116
- tryouts (3.3.2)
116
+ tryouts (3.5.1)
117
117
  concurrent-ruby (~> 1.0)
118
118
  irb
119
119
  minitest (~> 5.0)
@@ -148,7 +148,7 @@ DEPENDENCIES
148
148
  rubocop-thread_safety
149
149
  ruby-prof
150
150
  stackprof
151
- tryouts (~> 3.3.2)
151
+ tryouts (~> 3.5.1)
152
152
  yard (~> 0.9)
153
153
 
154
154
  BUNDLED WITH
@@ -104,6 +104,42 @@ client.auth_token.expose { |token| make_api_call(token) }
104
104
  client.auth_token.clear! # Explicit cleanup
105
105
  ```
106
106
 
107
+ #### Relationships
108
+ ```ruby
109
+ class Customer < Familia::Horreum
110
+ feature :relationships
111
+
112
+ identifier_field :custid
113
+ field :custid, :name, :email
114
+
115
+ # Define relationship collections
116
+ tracked_in :active_users, type: :sorted_set
117
+ indexed_by :email_lookup, field: :email
118
+ set :domains
119
+ end
120
+
121
+ class Domain < Familia::Horreum
122
+ feature :relationships
123
+
124
+ identifier_field :domain_id
125
+ field :domain_id, :name
126
+
127
+ # Declare membership in customer collections
128
+ member_of Customer, :domains, type: :set
129
+ end
130
+
131
+ # Usage
132
+ customer = Customer.new(custid: "cust123", name: "Acme Corp")
133
+ domain = Domain.new(domain_id: "dom456", name: "acme.com")
134
+
135
+ # Establish bidirectional relationships
136
+ domain.add_to_customer_domains(customer.custid)
137
+ customer.domains.add(domain.identifier)
138
+
139
+ # Query relationships
140
+ domain.in_customer_domains?(customer.custid) # => true
141
+ ```
142
+
107
143
  #### Quantization
108
144
  ```ruby
109
145
  class Metric < Familia::Horreum
@@ -487,10 +523,6 @@ end
487
523
  class Model < Familia::Horreum
488
524
  feature :expiration
489
525
 
490
- def has_ttl_support?
491
- self.class.features_enabled.include?(:expiration)
492
- end
493
-
494
526
  def available_features
495
527
  self.class.features_enabled
496
528
  end
@@ -498,7 +530,6 @@ end
498
530
 
499
531
  model = Model.new
500
532
  model.available_features # => [:expiration]
501
- model.has_ttl_support? # => true
502
533
  ```
503
534
 
504
535
  ## Testing Features
data/docs/wiki/Home.md CHANGED
@@ -10,15 +10,15 @@ Welcome to the comprehensive documentation for Familia v2.0. This wiki covers al
10
10
  2. **[Transient Fields Guide](Transient-Fields-Guide.md)** - Non-persistent secure data handling with RedactedString
11
11
  3. **[Security Model](Security-Model.md)** - Cryptographic design and Ruby memory limitations
12
12
 
13
- ### 🏗️ Architecture & System Design
13
+ ### 🏗️ Architecture & System Design
14
14
 
15
15
  4. **[Feature System Guide](Feature-System-Guide.md)** - Modular architecture with dependencies and conflict resolution _(new!)_
16
16
  5. **[Connection Pooling Guide](Connection-Pooling-Guide.md)** - Provider pattern for efficient Redis/Valkey pooling _(new!)_
17
- 6. **[RelatableObjects Guide](RelatableObjects-Guide.md)** - Object relationships and ownership system _(new!)_
17
+ 6. **[Relationships Guide](Relationships-Guide.md)** - Object relationships and membership system _(new!)_
18
18
 
19
19
  ### 🛠️ Implementation & Usage
20
20
 
21
- 7. **[Implementation Guide](Implementation-Guide.md)** - Configuration, providers, and advanced usage
21
+ 7. **[Implementation Guide](Implementation-Guide.md)** - Configuration, providers, and advanced usage
22
22
  8. **[API Reference](API-Reference.md)** - Complete class and method documentation
23
23
 
24
24
  ### 🚀 Operations (As Needed)
@@ -52,7 +52,7 @@ class Customer < Familia::Horreum
52
52
  feature :safe_dump # API-safe serialization
53
53
  feature :expiration # TTL support
54
54
  feature :encrypted_fields # Secure storage
55
-
55
+
56
56
  field :name, :email
57
57
  encrypted_field :api_key
58
58
  default_expiration 24.hours
@@ -66,36 +66,46 @@ end
66
66
  Familia.connection_provider = lambda do |uri|
67
67
  parsed = URI.parse(uri)
68
68
  pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
69
-
69
+
70
70
  @pools[pool_key] ||= ConnectionPool.new(size: 10) do
71
71
  Redis.new(host: parsed.host, port: parsed.port, db: parsed.db || 0)
72
72
  end
73
-
73
+
74
74
  @pools[pool_key].with { |conn| conn }
75
75
  end
76
76
  ```
77
77
 
78
- ### Object Relationships (RelatableObjects)
78
+ ### Object Relationships
79
79
  ```ruby
80
80
  class Customer < Familia::Horreum
81
- feature :relatable_object
82
- self.logical_database = 0
83
- field :name, :email
81
+ feature :relationships
82
+ identifier_field :custid
83
+ field :custid, :name, :email
84
+
85
+ # Customer collections
86
+ set :domains
84
87
  end
85
88
 
86
- class Domain < Familia::Horreum
87
- feature :relatable_object
88
- self.logical_database = 0
89
- field :name, :dns_zone
89
+ class Domain < Familia::Horreum
90
+ feature :relationships
91
+ identifier_field :domain_id
92
+ field :domain_id, :name, :dns_zone
93
+
94
+ # Declare membership in customer collections
95
+ member_of Customer, :domains, type: :set
90
96
  end
91
97
 
92
- # Create objects with automatic ID generation
93
- customer = Customer.new(name: "Acme Corp")
94
- domain = Domain.new(name: "acme.com")
98
+ # Create objects and establish relationships
99
+ customer = Customer.new(custid: "cust123", name: "Acme Corp")
100
+ domain = Domain.new(domain_id: "dom456", name: "acme.com")
101
+
102
+ # Establish bidirectional relationship
103
+ domain.add_to_customer_domains(customer.custid)
104
+ customer.domains.add(domain.identifier)
95
105
 
96
- # Establish ownership
97
- Customer.owners.set(domain.objid, customer.objid)
98
- domain.owner?(customer) # => true
106
+ # Query relationships
107
+ domain.in_customer_domains?(customer.custid) # => true
108
+ customer.domains.member?(domain.identifier) # => true
99
109
  ```
100
110
 
101
111