lutaml-store 0.1.1

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 (110) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +27 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +10 -0
  6. data/.rubocop_todo.yml +450 -0
  7. data/CLAUDE.md +57 -0
  8. data/CODE_OF_CONDUCT.md +132 -0
  9. data/CORRECTED_HTTP_CACHE_IMPLEMENTATION.md +209 -0
  10. data/CORRECTED_HTTP_CACHE_PLAN.md +164 -0
  11. data/Gemfile +15 -0
  12. data/Gemfile.lock +220 -0
  13. data/README.adoc +1430 -0
  14. data/Rakefile +12 -0
  15. data/TODO.impl/0-lutaml-store-self-quality.md +112 -0
  16. data/TODO.impl/1-lutaml-hal-migration.md +60 -0
  17. data/TODO.impl/2-glossarist-migration.md +359 -0
  18. data/TODO.impl/3-lutaml-jsonschema-migration.md +273 -0
  19. data/bin/console +11 -0
  20. data/bin/setup +8 -0
  21. data/demo/Gemfile +15 -0
  22. data/demo/Gemfile.lock +61 -0
  23. data/demo/README.adoc +301 -0
  24. data/demo/data/vcards/co/contact_10_thompson.data +1 -0
  25. data/demo/data/vcards/co/contact_10_thompson.meta +1 -0
  26. data/demo/data/vcards/co/contact_1_doe.data +1 -0
  27. data/demo/data/vcards/co/contact_1_doe.meta +1 -0
  28. data/demo/data/vcards/co/contact_2_smith.data +1 -0
  29. data/demo/data/vcards/co/contact_2_smith.meta +1 -0
  30. data/demo/data/vcards/co/contact_3_johnson.data +1 -0
  31. data/demo/data/vcards/co/contact_3_johnson.meta +1 -0
  32. data/demo/data/vcards/co/contact_4_garcia.data +1 -0
  33. data/demo/data/vcards/co/contact_4_garcia.meta +1 -0
  34. data/demo/data/vcards/co/contact_5_wilson.data +1 -0
  35. data/demo/data/vcards/co/contact_5_wilson.meta +1 -0
  36. data/demo/data/vcards/co/contact_6_brown.data +1 -0
  37. data/demo/data/vcards/co/contact_6_brown.meta +1 -0
  38. data/demo/data/vcards/co/contact_7_davis.data +1 -0
  39. data/demo/data/vcards/co/contact_7_davis.meta +1 -0
  40. data/demo/data/vcards/co/contact_8_anderson.data +1 -0
  41. data/demo/data/vcards/co/contact_8_anderson.meta +1 -0
  42. data/demo/data/vcards/co/contact_9_taylor.data +1 -0
  43. data/demo/data/vcards/co/contact_9_taylor.meta +1 -0
  44. data/demo/data/vcards.db +0 -0
  45. data/demo/pottery_class_demo.rb +164 -0
  46. data/demo/vcard_models.rb +140 -0
  47. data/demo/vcard_store_demo.rb +526 -0
  48. data/lib/lutaml/store/adapter/base.rb +65 -0
  49. data/lib/lutaml/store/adapter/filesystem.rb +288 -0
  50. data/lib/lutaml/store/adapter/memory.rb +225 -0
  51. data/lib/lutaml/store/adapter/sqlite.rb +193 -0
  52. data/lib/lutaml/store/adapter.rb +12 -0
  53. data/lib/lutaml/store/attribute_updater.rb +198 -0
  54. data/lib/lutaml/store/basic_store.rb +190 -0
  55. data/lib/lutaml/store/cache.rb +108 -0
  56. data/lib/lutaml/store/cache_store.rb +282 -0
  57. data/lib/lutaml/store/composite_model_handler.rb +169 -0
  58. data/lib/lutaml/store/compression.rb +137 -0
  59. data/lib/lutaml/store/config.rb +178 -0
  60. data/lib/lutaml/store/database_store.rb +425 -0
  61. data/lib/lutaml/store/events.rb +92 -0
  62. data/lib/lutaml/store/format/base.rb +33 -0
  63. data/lib/lutaml/store/format/json.rb +25 -0
  64. data/lib/lutaml/store/format/jsonl.rb +37 -0
  65. data/lib/lutaml/store/format/marshal_format.rb +37 -0
  66. data/lib/lutaml/store/format/yaml.rb +29 -0
  67. data/lib/lutaml/store/format/yamls.rb +35 -0
  68. data/lib/lutaml/store/format.rb +33 -0
  69. data/lib/lutaml/store/http_cache.rb +279 -0
  70. data/lib/lutaml/store/http_cache_config.rb +53 -0
  71. data/lib/lutaml/store/http_cache_entry.rb +69 -0
  72. data/lib/lutaml/store/http_header_processor.rb +175 -0
  73. data/lib/lutaml/store/integrity.rb +102 -0
  74. data/lib/lutaml/store/model_registration.rb +75 -0
  75. data/lib/lutaml/store/model_registry.rb +123 -0
  76. data/lib/lutaml/store/model_serializer.rb +69 -0
  77. data/lib/lutaml/store/monitor.rb +192 -0
  78. data/lib/lutaml/store/storage_key.rb +40 -0
  79. data/lib/lutaml/store/version.rb +7 -0
  80. data/lib/lutaml/store.rb +41 -0
  81. data/lutaml-store.gemspec +35 -0
  82. data/plan.adoc +606 -0
  83. data/sig/lutaml/store.rbs +6 -0
  84. data/spec/lutaml/store/adapter_interface_spec.rb +89 -0
  85. data/spec/lutaml/store/anti_pattern_guard_spec.rb +35 -0
  86. data/spec/lutaml/store/anti_pattern_spec.rb +78 -0
  87. data/spec/lutaml/store/autoload_spec.rb +34 -0
  88. data/spec/lutaml/store/cache_store_spec.rb +271 -0
  89. data/spec/lutaml/store/compression_spec.rb +78 -0
  90. data/spec/lutaml/store/config_enhanced_spec.rb +158 -0
  91. data/spec/lutaml/store/corrected_http_cache_integration_spec.rb +336 -0
  92. data/spec/lutaml/store/custom_serializer_spec.rb +108 -0
  93. data/spec/lutaml/store/database_store_spec.rb +279 -0
  94. data/spec/lutaml/store/file_io_spec.rb +219 -0
  95. data/spec/lutaml/store/format_round_trip_spec.rb +110 -0
  96. data/spec/lutaml/store/format_spec.rb +70 -0
  97. data/spec/lutaml/store/http_cache_entry_spec.rb +203 -0
  98. data/spec/lutaml/store/http_cache_hal_integration_spec.rb +404 -0
  99. data/spec/lutaml/store/http_cache_spec.rb +422 -0
  100. data/spec/lutaml/store/http_header_processor_spec.rb +290 -0
  101. data/spec/lutaml/store/import_spec.rb +90 -0
  102. data/spec/lutaml/store/integrity_spec.rb +157 -0
  103. data/spec/lutaml/store/key_collision_serializer_spec.rb +98 -0
  104. data/spec/lutaml/store/load_save_spec.rb +107 -0
  105. data/spec/lutaml/store/lutaml_model_integration_spec.rb +291 -0
  106. data/spec/lutaml/store/model_serializer_spec.rb +140 -0
  107. data/spec/lutaml/store/store_spec.rb +182 -0
  108. data/spec/lutaml/store_spec.rb +21 -0
  109. data/spec/spec_helper.rb +16 -0
  110. metadata +166 -0
@@ -0,0 +1,273 @@
1
+ # TODO.impl/3-lutaml-jsonschema-migration.md
2
+
3
+ # Migration Plan: lutaml-jsonschema → lutaml-store
4
+
5
+ ## Current state analysis
6
+
7
+ ### How lutaml-jsonschema stores LutaML model objects
8
+
9
+ lutaml-jsonschema is a JSON Schema parser/generator/SPA builder. It has the
10
+ simplest storage profile of the three repos — it does **no persistent storage
11
+ at all**. Its "storage" is purely in-memory:
12
+
13
+ 1. **`SchemaSet`** — holds `@schemas` (Hash of name → `Schema`), `@file_paths`
14
+ (Hash), `@source_jsons` (Hash). Loaded from JSON files on disk.
15
+
16
+ 2. **`ReferenceResolver`** — navigates `Schema` object graphs to resolve `$ref`
17
+ pointers. No storage, pure computation.
18
+
19
+ 3. **`Configuration`** — loads from YAML file. Simple config object, not a
20
+ storage concern.
21
+
22
+ 4. **`Combiner`** — merges multiple schemas. Pure computation.
23
+
24
+ 5. **SPA generation** — `Spa::SpaBuilder`, `Spa::Generator` produce HTML/Vue
25
+ output. Write-only, no storage.
26
+
27
+ ### Architecture (current)
28
+
29
+ ```
30
+ Lutaml::Jsonschema
31
+ ├── SchemaSet (in-memory schema collection)
32
+ │ ├── @schemas (Hash name → Schema)
33
+ │ ├── load_from_files → File.read → Schema.from_json
34
+ │ ├── load_from_directory → Dir.glob → load_from_files
35
+ │ ├── resolve_ref → ReferenceResolver
36
+ │ └── validate! → walks schema tree collecting errors
37
+
38
+ ├── Schema (Lutaml::Model::Serializable)
39
+ │ └── Recursive JSON Schema model with composition, conditionals, etc.
40
+
41
+ ├── PropertyEntry (Lutaml::Model::Serializable)
42
+ │ └── name + Schema pair
43
+
44
+ ├── Link (Lutaml::Model::Serializable)
45
+ │ └── JSON Hyper-Schema link
46
+
47
+ ├── ReferenceResolver — navigates Schema graphs
48
+ ├── Combiner — merges schemas
49
+ ├── Configuration — YAML config loading
50
+ └── SPA generator — static site generation
51
+ ```
52
+
53
+ ### Key observation
54
+
55
+ lutaml-jsonschema is **not a storage application**. It's a **parsing and
56
+ transformation pipeline**. The storage it does (loading JSON files into memory)
57
+ is trivial — `File.read` → `Schema.from_json`.
58
+
59
+ However, there are storage-adjacent concerns where lutaml-store could help:
60
+
61
+ 1. **Schema caching** — If the same schemas are loaded repeatedly (e.g., in a
62
+ dev server), caching parsed `Schema` objects avoids re-parsing.
63
+
64
+ 2. **Reference resolution caching** — `$ref` resolution is recursive and could
65
+ benefit from memoization via a store.
66
+
67
+ 3. **SPA incremental builds** — The SPA generator could use lutaml-store to
68
+ cache intermediate build artifacts.
69
+
70
+ ### Problems identified
71
+
72
+ | Category | Issue | Location |
73
+ |---|---|---|
74
+ | **No caching** | `SchemaSet.load_from_files` re-parses every time | `schema_set.rb:14-21` |
75
+ | **In-memory state** | `@source_jsons` and `@file_paths` are informal caches stored alongside `@schemas` | `schema_set.rb:36-38` |
76
+ | **No lazy loading** | All schemas loaded eagerly; no on-demand loading | `schema_set.rb` |
77
+ | **Duplicated index logic** | `ReferenceResolver`, `SchemaSet#find_schema_by_filename`, and `SchemaSet#resolve_file_ref` all do similar lookup work | Multiple |
78
+
79
+ ## Migration strategy
80
+
81
+ ### Phase 1: Use lutaml-store as a schema cache
82
+
83
+ The primary value of lutaml-store for this repo is **caching parsed schemas**
84
+ to avoid re-reading and re-parsing JSON files.
85
+
86
+ ```ruby
87
+ module Lutaml
88
+ module Jsonschema
89
+ class SchemaStore
90
+ def initialize(cache_config: {})
91
+ @store = Lutaml::Store.new(
92
+ adapter: cache_config[:adapter] || :memory,
93
+ models: [
94
+ { model: Schema, key: :dollar_id }
95
+ ],
96
+ cache: {
97
+ enabled: true,
98
+ ttl: cache_config[:ttl] || 3600,
99
+ max_size: cache_config[:max_size] || 100
100
+ }
101
+ )
102
+ end
103
+
104
+ def load_schema(path, name: nil)
105
+ name ||= File.basename(path, ".*")
106
+ cached = @store.fetch(model: Schema, dollar_id: name)
107
+ return cached if cached
108
+
109
+ data = File.read(path)
110
+ schema = Schema.from_json(data)
111
+ @store.save(schema)
112
+ schema
113
+ end
114
+
115
+ def fetch(name)
116
+ @store.fetch(model: Schema, dollar_id: name)
117
+ end
118
+
119
+ def preload_directory(dir)
120
+ Dir.glob(File.join(dir, "*.json")).each do |path|
121
+ load_schema(path)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ ```
128
+
129
+ ### Phase 2: Integrate `SchemaStore` into `SchemaSet`
130
+
131
+ `SchemaSet` currently loads all schemas eagerly. With `SchemaStore`:
132
+
133
+ ```ruby
134
+ class SchemaSet
135
+ def initialize(base_dir: nil, schema_store: nil)
136
+ @schemas = {}
137
+ @base_dir = base_dir
138
+ @store = schema_store || SchemaStore.new
139
+ @resolver = ReferenceResolver.new(@schemas)
140
+ end
141
+
142
+ def self.load_from_files(*paths, base_dir: nil)
143
+ set = new(base_dir: base_dir)
144
+ paths.each do |path|
145
+ name = File.basename(path, ".*")
146
+ schema = set.instance_variable_get(:@store).load_schema(path, name: name)
147
+ set.add(name, schema, path)
148
+ end
149
+ set
150
+ end
151
+ end
152
+ ```
153
+
154
+ Schemas loaded through `SchemaStore` are automatically cached. Subsequent loads
155
+ hit the cache instead of re-parsing JSON.
156
+
157
+ ### Phase 3: Cache reference resolution results
158
+
159
+ `ReferenceResolver` does recursive graph navigation. Add memoization via
160
+ lutaml-store:
161
+
162
+ ```ruby
163
+ class ReferenceResolver
164
+ def initialize(schemas = {}, resolution_cache: nil)
165
+ @schemas = schemas
166
+ @cache = resolution_cache # Lutaml::Store instance or nil
167
+ end
168
+
169
+ def resolve(ref, root_schema = nil)
170
+ cache_key = "#{root_schema&.dollar_id}:#{ref}"
171
+ cached = @cache&.get(cache_key)
172
+ return cached if cached
173
+
174
+ result = perform_resolve(ref, root_schema)
175
+ @cache&.set(cache_key, result)
176
+ result
177
+ end
178
+ end
179
+ ```
180
+
181
+ ### Phase 4: SPA incremental build cache
182
+
183
+ The SPA generator can use lutaml-store to cache generated artifacts:
184
+
185
+ ```ruby
186
+ class Spa::Generator
187
+ def initialize(schema_set, output_dir, cache_adapter: :filesystem)
188
+ @schema_set = schema_set
189
+ @output_dir = output_dir
190
+ @artifact_cache = Lutaml::Store.new(
191
+ adapter: { type: cache_adapter, path: File.join(output_dir, ".cache") }
192
+ )
193
+ end
194
+ end
195
+ ```
196
+
197
+ This enables incremental SPA builds — only regenerate schemas that changed.
198
+
199
+ ### Phase 5: Remove `@source_jsons` and `@file_paths`
200
+
201
+ These informal caches in `SchemaSet` become unnecessary once `SchemaStore`
202
+ handles caching. The raw JSON can be re-fetched from the store if needed, or
203
+ the schema's `to_json` can regenerate it.
204
+
205
+ ## Completed
206
+
207
+ ### Phase 1: SchemaStore with schema caching (done)
208
+
209
+ Created `Lutaml::Jsonschema::SchemaStore` backed by `Lutaml::Store` for schema
210
+ caching. Schemas loaded from JSON files are automatically cached — subsequent
211
+ loads hit the store instead of re-parsing.
212
+
213
+ Also fixed a composite model handler edge case: nested registered model
214
+ instances with nil keys are now skipped (they're part of the parent model,
215
+ not independently stored).
216
+
217
+ | File | Change |
218
+ |---|---|
219
+ | `lutaml-store/lib/lutaml/store/model_registry.rb` | `find_composite_models` skips nested models with nil keys |
220
+ | `lutaml-jsonschema/lib/lutaml/jsonschema/schema_store.rb` | Schema caching via `Lutaml::Store` |
221
+ | `lutaml-jsonschema/lib/lutaml/jsonschema.rb` | Require for schema_store |
222
+ | `lutaml-jsonschema/lutaml-jsonschema.gemspec` | Added `lutaml-store ~> 0.1.0` dependency |
223
+ | `lutaml-jsonschema/Gemfile` | Added lutaml-store path gem |
224
+ | `lutaml-jsonschema/spec/lutaml/jsonschema/schema_store_spec.rb` | 6 specs for SchemaStore CRUD + caching |
225
+ | `lutaml-jsonschema/spec/spec_helper.rb` | Added `fixture_path` helper |
226
+
227
+ ### Test results
228
+
229
+ - **lutaml-store:** 19 core specs pass
230
+ - **lutaml-jsonschema:** 167 examples, 0 failures (no regressions)
231
+ - **lutaml-hal:** 210 examples, 0 failures
232
+ - **glossarist:** 1154 examples, 0 failures
233
+
234
+ ## Remaining (future work)
235
+
236
+ | File | Purpose |
237
+ |---|---|
238
+ | `lib/lutaml/jsonschema/schema_store.rb` | Schema caching via `Lutaml::Store` |
239
+
240
+ ## Files to modify
241
+
242
+ | File | Change |
243
+ |---|---|
244
+ | `schema_set.rb` | Accept optional `SchemaStore`; delegate loading to it; remove `@source_jsons`/`@file_paths` |
245
+ | `reference_resolver.rb` | Accept optional resolution cache |
246
+ | `spa/generator.rb` | Use `Lutaml::Store` for artifact caching |
247
+
248
+ ## Spec coverage needed
249
+
250
+ 1. **SchemaStore** — load, fetch, cache hit, cache miss, preload_directory
251
+ 2. **SchemaSet with SchemaStore** — schemas loaded from cache on second call
252
+ 3. **Reference resolution caching** — resolved refs cached, cache invalidated on schema change
253
+ 4. **SPA incremental builds** — only changed schemas regenerated
254
+
255
+ ## Risk assessment
256
+
257
+ - **Low risk overall** — this repo has the simplest integration. No persistent
258
+ storage is being replaced, only caching is being added.
259
+ - **No backward compatibility risk** — `SchemaStore` is additive; existing
260
+ `SchemaSet` API remains the same.
261
+ - **Cleanest migration path** — other repos can reference this as the simplest
262
+ example of lutaml-store integration.
263
+
264
+ ## Priority
265
+
266
+ This is the **lowest priority** migration because:
267
+ 1. No broken encapsulation to fix (no `send`, `instance_variable_get/set`, `respond_to?`)
268
+ 2. No storage layer to replace — only caching to add
269
+ 3. The repo is clean and well-structured already
270
+
271
+ Recommended order: Fix lutaml-store self-quality first, then migrate
272
+ lutaml-hal (most urgent encapsulation issues), then glossarist (most complex),
273
+ then lutaml-jsonschema (polish/caching).
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "lutaml/store"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require "irb"
11
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/demo/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Core dependencies
6
+ gem "lutaml-store", path: ".."
7
+
8
+ # Demo enhancement gems
9
+ gem "paint", "~> 2.3" # For colored terminal output
10
+ gem "sqlite3", "~> 1.4" # For SQLite database support
11
+ gem "table_tennis", "~> 0.0.6" # For table formatting
12
+
13
+ # Development dependencies
14
+ gem "bundler", "~> 2.0"
15
+ gem "rake", "~> 13.0"
data/demo/Gemfile.lock ADDED
@@ -0,0 +1,61 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ lutaml-store (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ csv (3.3.5)
10
+ ffi (1.17.2)
11
+ ffi (1.17.2-aarch64-linux-gnu)
12
+ ffi (1.17.2-aarch64-linux-musl)
13
+ ffi (1.17.2-arm-linux-gnu)
14
+ ffi (1.17.2-arm-linux-musl)
15
+ ffi (1.17.2-arm64-darwin)
16
+ ffi (1.17.2-x86-linux-gnu)
17
+ ffi (1.17.2-x86-linux-musl)
18
+ ffi (1.17.2-x86_64-darwin)
19
+ ffi (1.17.2-x86_64-linux-gnu)
20
+ ffi (1.17.2-x86_64-linux-musl)
21
+ memo_wise (1.13.0)
22
+ paint (2.3.0)
23
+ rake (13.3.0)
24
+ sqlite3 (1.7.3-aarch64-linux)
25
+ sqlite3 (1.7.3-arm-linux)
26
+ sqlite3 (1.7.3-arm64-darwin)
27
+ sqlite3 (1.7.3-x86-linux)
28
+ sqlite3 (1.7.3-x86_64-darwin)
29
+ sqlite3 (1.7.3-x86_64-linux)
30
+ table_tennis (0.0.6)
31
+ csv (~> 3.3)
32
+ ffi (~> 1.17)
33
+ memo_wise (~> 1.11)
34
+ paint (~> 2.3)
35
+ unicode-display_width (~> 3.1)
36
+ unicode-display_width (3.1.4)
37
+ unicode-emoji (~> 4.0, >= 4.0.4)
38
+ unicode-emoji (4.0.4)
39
+
40
+ PLATFORMS
41
+ aarch64-linux-gnu
42
+ aarch64-linux-musl
43
+ arm-linux-gnu
44
+ arm-linux-musl
45
+ arm64-darwin
46
+ x86-linux-gnu
47
+ x86-linux-musl
48
+ x86_64-darwin
49
+ x86_64-linux-gnu
50
+ x86_64-linux-musl
51
+
52
+ DEPENDENCIES
53
+ bundler (~> 2.0)
54
+ lutaml-store!
55
+ paint (~> 2.3)
56
+ rake (~> 13.0)
57
+ sqlite3 (~> 1.4)
58
+ table_tennis (~> 0.0.6)
59
+
60
+ BUNDLED WITH
61
+ 2.6.9
data/demo/README.adoc ADDED
@@ -0,0 +1,301 @@
1
+ = VCard Store Demo
2
+ :toc:
3
+ :toclevels: 3
4
+
5
+ == Overview
6
+
7
+ This demo showcases the capabilities of `lutaml-store` using vCard (Virtual Contact File) models. It demonstrates how to store, retrieve, and manage contact information using different storage backends and advanced features like caching, compression, and data integrity.
8
+
9
+ == Features Demonstrated
10
+
11
+ === Storage Backends
12
+ * **Memory Store** - Fast in-memory storage for temporary data
13
+ * **Filesystem Store** - File-based persistence with organized directory structure
14
+ * **SQLite Store** - Database storage for structured queries and relationships
15
+ * **Cache Store** - High-performance caching with TTL and LRU eviction
16
+
17
+ === Advanced Features
18
+ * **Data Integrity** - SHA256 checksums to ensure data hasn't been corrupted
19
+ * **Compression** - GZIP compression to reduce storage space
20
+ * **Search Capabilities** - Find contacts by organization, email domain, birth year
21
+ * **Multiple Key Strategies** - UID-based, email-based, and organized naming schemes
22
+
23
+ == Demo Structure
24
+
25
+ ----
26
+ demo/
27
+ ├── README.adoc # This documentation
28
+ ├── vcard_models.rb # VCard model definitions
29
+ ├── vcard_store_demo.rb # Main demo script
30
+ └── data/ # Generated during demo
31
+ ├── vcards/ # Filesystem store files
32
+ └── vcards.db # SQLite database
33
+ ----
34
+
35
+ == VCard Models
36
+
37
+ The demo uses simplified vCard models based on the vCard 4.0 specification:
38
+
39
+ === Core Models
40
+
41
+ ==== `Vcard`
42
+ Main contact record containing:
43
+ * `version` - vCard format version (4.0)
44
+ * `fn` - Formatted name (display name)
45
+ * `n` - Structured name components
46
+ * `tel` - Phone numbers (collection)
47
+ * `email` - Email addresses (collection)
48
+ * `org` - Organization
49
+ * `bday` - Birthday
50
+ * `uid` - Unique identifier
51
+
52
+ ==== `VcardName`
53
+ Structured name with components:
54
+ * `family` - Last name
55
+ * `given` - First name
56
+ * `additional` - Middle names
57
+ * `prefix` - Title (Dr., Mr., etc.)
58
+ * `suffix` - Suffix (Jr., PhD, etc.)
59
+
60
+ ==== `VcardTel`
61
+ Phone number with metadata:
62
+ * `value` - Phone number
63
+ * `type` - Phone type (work, mobile, home)
64
+ * `pref` - Preference ranking
65
+
66
+ ==== `VcardBday`
67
+ Birthday with flexible date formats:
68
+ * `value` - Date in ISO format or text
69
+
70
+ == Sample Data
71
+
72
+ The demo creates 10 diverse contact records representing different professions and use cases:
73
+
74
+ 1. **John Doe** - Tech Corp employee with multiple emails
75
+ 2. **Jane Smith** - Design Studio creative
76
+ 3. **Dr. Robert Johnson** - Medical professional
77
+ 4. **Maria Garcia** - Startup innovator
78
+ 5. **David Wilson** - Independent consultant
79
+ 6. **Sarah Brown** - University researcher
80
+ 7. **Michael Davis** - Creative agency worker
81
+ 8. **Lisa Anderson** - Financial services
82
+ 9. **James Taylor** - Music producer
83
+ 10. **Emma Thompson** - Legal professional
84
+
85
+ == Running the Demo
86
+
87
+ === Prerequisites
88
+
89
+ Ensure you have the lutaml-store gem and its dependencies installed:
90
+
91
+ [source,bash]
92
+ ----
93
+ bundle install
94
+ ----
95
+
96
+ === Execute the Demo
97
+
98
+ [source,bash]
99
+ ----
100
+ cd demo
101
+ ruby vcard_store_demo.rb
102
+ ----
103
+
104
+ === Expected Output
105
+
106
+ The demo will show:
107
+
108
+ 1. **Initialization** - Setting up different store types
109
+ 2. **Sample Data Creation** - Generating 10 vCard records
110
+ 3. **Memory Store Demo** - Fast in-memory operations
111
+ 4. **Filesystem Store Demo** - File persistence with organized naming
112
+ 5. **SQLite Store Demo** - Database storage with email-based keys
113
+ 6. **Cache Store Demo** - High-performance caching with TTL
114
+ 7. **Search Capabilities** - Finding contacts by various criteria
115
+ 8. **Data Integrity** - Demonstrating checksums and compression
116
+
117
+ == Storage Strategies
118
+
119
+ === Memory Store
120
+ * **Use Case**: Temporary data, fast access, testing
121
+ * **Key Strategy**: UUID-based for unique identification
122
+ * **Persistence**: None (data lost on restart)
123
+
124
+ === Filesystem Store
125
+ * **Use Case**: Simple persistence, human-readable storage
126
+ * **Key Strategy**: Organized naming (`contact_1_doe`)
127
+ * **Persistence**: Individual JSON files in directory structure
128
+
129
+ === SQLite Store
130
+ * **Use Case**: Structured queries, relationships, ACID transactions
131
+ * **Key Strategy**: Email username for easy lookup
132
+ * **Persistence**: Single database file with full SQL capabilities
133
+
134
+ === Cache Store
135
+ * **Use Case**: Performance optimization, temporary storage
136
+ * **Key Strategy**: UUID with TTL-based expiration
137
+ * **Features**: LRU eviction, automatic cleanup, cache statistics
138
+
139
+ == Search Examples
140
+
141
+ The demo includes several search patterns:
142
+
143
+ === By Organization
144
+ [source,ruby]
145
+ ----
146
+ # Find all contacts from "Tech Corp"
147
+ find_by_organization("Tech Corp")
148
+ ----
149
+
150
+ === By Email Domain
151
+ [source,ruby]
152
+ ----
153
+ # Find all contacts with "example.com" emails
154
+ find_by_email_domain("example.com")
155
+ ----
156
+
157
+ === By Birth Year
158
+ [source,ruby]
159
+ ----
160
+ # Find all contacts born in 1985
161
+ find_by_birth_year(1985)
162
+ ----
163
+
164
+ == Data Integrity Features
165
+
166
+ === Checksums
167
+ The demo shows how to enable SHA256 checksums:
168
+
169
+ [source,ruby]
170
+ ----
171
+ store = Lutaml::Store::Store.new(
172
+ adapter: { type: :memory },
173
+ integrity: { enabled: true, algorithm: :sha256 }
174
+ )
175
+ ----
176
+
177
+ === Compression
178
+ GZIP compression reduces storage space:
179
+
180
+ [source,ruby]
181
+ ----
182
+ store = Lutaml::Store::Store.new(
183
+ adapter: { type: :memory },
184
+ compression: { enabled: true, algorithm: :gzip }
185
+ )
186
+ ----
187
+
188
+ == Performance Considerations
189
+
190
+ === Memory Store
191
+ * **Pros**: Fastest access, no I/O overhead
192
+ * **Cons**: Limited by RAM, no persistence
193
+ * **Best For**: Caching, temporary data, testing
194
+
195
+ === Filesystem Store
196
+ * **Pros**: Simple, human-readable, version control friendly
197
+ * **Cons**: Slower for large datasets, limited query capabilities
198
+ * **Best For**: Configuration, small datasets, debugging
199
+
200
+ === SQLite Store
201
+ * **Pros**: ACID transactions, complex queries, excellent performance
202
+ * **Cons**: Single writer limitation, file locking
203
+ * **Best For**: Structured data, relationships, production use
204
+
205
+ === Cache Store
206
+ * **Pros**: Automatic expiration, LRU eviction, high performance
207
+ * **Cons**: Temporary storage, memory limited
208
+ * **Best For**: Frequently accessed data, API responses
209
+
210
+ == Extending the Demo
211
+
212
+ === Adding New Fields
213
+ To add new vCard fields:
214
+
215
+ 1. Update the `Vcard` model with new attributes
216
+ 2. Modify the `create_vcard` method to accept new parameters
217
+ 3. Update sample data generation
218
+ 4. Add search methods for new fields
219
+
220
+ === Custom Storage Backends
221
+ Create custom adapters by:
222
+
223
+ 1. Inheriting from `Lutaml::Store::Adapter::Base`
224
+ 2. Implementing required methods (`save`, `load`, `delete`, etc.)
225
+ 3. Registering the adapter with the store
226
+
227
+ === Advanced Queries
228
+ Implement complex search patterns:
229
+
230
+ [source,ruby]
231
+ ----
232
+ # Find contacts by age range
233
+ def find_by_age_range(min_age, max_age)
234
+ # Implementation using birth date calculations
235
+ end
236
+
237
+ # Find contacts with multiple emails
238
+ def find_multi_email_contacts
239
+ # Implementation checking email array length
240
+ end
241
+ ----
242
+
243
+ == Real-World Applications
244
+
245
+ This demo pattern can be adapted for:
246
+
247
+ * **Contact Management Systems** - CRM applications
248
+ * **User Profile Storage** - Web applications
249
+ * **Configuration Management** - Application settings
250
+ * **Document Storage** - Content management
251
+ * **API Response Caching** - Performance optimization
252
+ * **Session Storage** - Web session management
253
+
254
+ == Troubleshooting
255
+
256
+ === Common Issues
257
+
258
+ ==== Permission Errors
259
+ Ensure write permissions for the `demo/data/` directory:
260
+ [source,bash]
261
+ ----
262
+ chmod 755 demo/data
263
+ ----
264
+
265
+ ==== SQLite Lock Issues
266
+ If SQLite database is locked:
267
+ [source,bash]
268
+ ----
269
+ rm demo/data/vcards.db
270
+ ----
271
+
272
+ ==== Memory Issues
273
+ For large datasets, consider:
274
+ * Using filesystem or SQLite stores instead of memory
275
+ * Implementing pagination for search results
276
+ * Adding cleanup routines for cache stores
277
+
278
+ === Debug Mode
279
+ Enable verbose output by modifying the demo script:
280
+ [source,ruby]
281
+ ----
282
+ # Add at the top of vcard_store_demo.rb
283
+ ENV['DEBUG'] = 'true'
284
+ ----
285
+
286
+ == Next Steps
287
+
288
+ After running this demo, consider:
289
+
290
+ 1. **Exploring the Generated Data** - Examine files in `demo/data/`
291
+ 2. **Modifying Sample Data** - Add your own contact records
292
+ 3. **Implementing New Features** - Add search, sorting, or filtering
293
+ 4. **Performance Testing** - Benchmark different storage backends
294
+ 5. **Integration** - Use lutaml-store in your own applications
295
+
296
+ == Resources
297
+
298
+ * link:../README.md[Lutaml::Store Documentation]
299
+ * link:../lib/lutaml/store/[Source Code]
300
+ * link:../spec/[Test Suite]
301
+ * https://tools.ietf.org/html/rfc6350[vCard 4.0 Specification]
@@ -0,0 +1 @@
1
+ {:version=>"4.0", :fn=>"Emma Thompson", :n=>{:family=>"Thompson", :given=>"Emma", :additional=>nil, :prefix=>nil, :suffix=>nil}, :tel=>[{:value=>"+1-555-0110", :type=>"work", :pref=>nil}], :email=>["emma.thompson@law.com"], :org=>"Thompson & Associates", :bday=>{:value=>"1977-04-07"}, :uid=>"2361d65a-8810-49f9-bc3b-eeff2c229b5c"}
@@ -0,0 +1 @@
1
+ {"integrity":{"checksum":"c40eb05833b277bb6ab06bf4ae41b9ae82b13f17f607d3567da6bbf090b39b50","algorithm":"sha256","size":331,"created_at":"2025-07-11T00:01:58Z","version":"1.0"}}
@@ -0,0 +1 @@
1
+ {:version=>"4.0", :fn=>"John Doe", :n=>{:family=>"Doe", :given=>"John", :additional=>nil, :prefix=>nil, :suffix=>nil}, :tel=>[{:value=>"+1-555-0101", :type=>"work", :pref=>nil}], :email=>["john.doe@example.com", "j.doe@work.com"], :org=>"Tech Corp", :bday=>{:value=>"1985-03-15"}, :uid=>"a62ceddc-eac7-4a93-9a45-b356acbc3509"}
@@ -0,0 +1 @@
1
+ {"integrity":{"checksum":"80e64e58e91a6362bf3b3dadbec3614863f9139f7b98a2156e1700574e6e6fe0","algorithm":"sha256","size":326,"created_at":"2025-07-11T00:01:58Z","version":"1.0"}}
@@ -0,0 +1 @@
1
+ {:version=>"4.0", :fn=>"Jane Smith", :n=>{:family=>"Smith", :given=>"Jane", :additional=>nil, :prefix=>nil, :suffix=>nil}, :tel=>[{:value=>"+1-555-0102", :type=>"mobile", :pref=>nil}], :email=>["jane.smith@example.com"], :org=>"Design Studio", :bday=>{:value=>"1990-07-22"}, :uid=>"dc604681-c04c-4faa-9112-f0b8ece1e80d"}
@@ -0,0 +1 @@
1
+ {"integrity":{"checksum":"ff43bcd52684c7f7c29fda288ba23bf4c48b44cc4972d2d70eb02436765b34e8","algorithm":"sha256","size":320,"created_at":"2025-07-11T00:01:58Z","version":"1.0"}}
@@ -0,0 +1 @@
1
+ {:version=>"4.0", :fn=>"Dr. Robert Johnson", :n=>{:family=>"Johnson", :given=>"Robert", :additional=>nil, :prefix=>"Dr.", :suffix=>nil}, :tel=>[{:value=>"+1-555-0103", :type=>"work", :pref=>nil}], :email=>["r.johnson@hospital.com", "robert@personal.com"], :org=>"City Hospital", :bday=>{:value=>"1975-11-08"}, :uid=>"b63bdabe-bff7-4198-94d7-6730e2a52cd4"}
@@ -0,0 +1 @@
1
+ {"integrity":{"checksum":"20f9f5f2ac7306f4dc77947adf411afff60309918e4fb32525b54aa37dda130e","algorithm":"sha256","size":355,"created_at":"2025-07-11T00:01:58Z","version":"1.0"}}