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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +27 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +450 -0
- data/CLAUDE.md +57 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CORRECTED_HTTP_CACHE_IMPLEMENTATION.md +209 -0
- data/CORRECTED_HTTP_CACHE_PLAN.md +164 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +220 -0
- data/README.adoc +1430 -0
- data/Rakefile +12 -0
- data/TODO.impl/0-lutaml-store-self-quality.md +112 -0
- data/TODO.impl/1-lutaml-hal-migration.md +60 -0
- data/TODO.impl/2-glossarist-migration.md +359 -0
- data/TODO.impl/3-lutaml-jsonschema-migration.md +273 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/demo/Gemfile +15 -0
- data/demo/Gemfile.lock +61 -0
- data/demo/README.adoc +301 -0
- data/demo/data/vcards/co/contact_10_thompson.data +1 -0
- data/demo/data/vcards/co/contact_10_thompson.meta +1 -0
- data/demo/data/vcards/co/contact_1_doe.data +1 -0
- data/demo/data/vcards/co/contact_1_doe.meta +1 -0
- data/demo/data/vcards/co/contact_2_smith.data +1 -0
- data/demo/data/vcards/co/contact_2_smith.meta +1 -0
- data/demo/data/vcards/co/contact_3_johnson.data +1 -0
- data/demo/data/vcards/co/contact_3_johnson.meta +1 -0
- data/demo/data/vcards/co/contact_4_garcia.data +1 -0
- data/demo/data/vcards/co/contact_4_garcia.meta +1 -0
- data/demo/data/vcards/co/contact_5_wilson.data +1 -0
- data/demo/data/vcards/co/contact_5_wilson.meta +1 -0
- data/demo/data/vcards/co/contact_6_brown.data +1 -0
- data/demo/data/vcards/co/contact_6_brown.meta +1 -0
- data/demo/data/vcards/co/contact_7_davis.data +1 -0
- data/demo/data/vcards/co/contact_7_davis.meta +1 -0
- data/demo/data/vcards/co/contact_8_anderson.data +1 -0
- data/demo/data/vcards/co/contact_8_anderson.meta +1 -0
- data/demo/data/vcards/co/contact_9_taylor.data +1 -0
- data/demo/data/vcards/co/contact_9_taylor.meta +1 -0
- data/demo/data/vcards.db +0 -0
- data/demo/pottery_class_demo.rb +164 -0
- data/demo/vcard_models.rb +140 -0
- data/demo/vcard_store_demo.rb +526 -0
- data/lib/lutaml/store/adapter/base.rb +65 -0
- data/lib/lutaml/store/adapter/filesystem.rb +288 -0
- data/lib/lutaml/store/adapter/memory.rb +225 -0
- data/lib/lutaml/store/adapter/sqlite.rb +193 -0
- data/lib/lutaml/store/adapter.rb +12 -0
- data/lib/lutaml/store/attribute_updater.rb +198 -0
- data/lib/lutaml/store/basic_store.rb +190 -0
- data/lib/lutaml/store/cache.rb +108 -0
- data/lib/lutaml/store/cache_store.rb +282 -0
- data/lib/lutaml/store/composite_model_handler.rb +169 -0
- data/lib/lutaml/store/compression.rb +137 -0
- data/lib/lutaml/store/config.rb +178 -0
- data/lib/lutaml/store/database_store.rb +425 -0
- data/lib/lutaml/store/events.rb +92 -0
- data/lib/lutaml/store/format/base.rb +33 -0
- data/lib/lutaml/store/format/json.rb +25 -0
- data/lib/lutaml/store/format/jsonl.rb +37 -0
- data/lib/lutaml/store/format/marshal_format.rb +37 -0
- data/lib/lutaml/store/format/yaml.rb +29 -0
- data/lib/lutaml/store/format/yamls.rb +35 -0
- data/lib/lutaml/store/format.rb +33 -0
- data/lib/lutaml/store/http_cache.rb +279 -0
- data/lib/lutaml/store/http_cache_config.rb +53 -0
- data/lib/lutaml/store/http_cache_entry.rb +69 -0
- data/lib/lutaml/store/http_header_processor.rb +175 -0
- data/lib/lutaml/store/integrity.rb +102 -0
- data/lib/lutaml/store/model_registration.rb +75 -0
- data/lib/lutaml/store/model_registry.rb +123 -0
- data/lib/lutaml/store/model_serializer.rb +69 -0
- data/lib/lutaml/store/monitor.rb +192 -0
- data/lib/lutaml/store/storage_key.rb +40 -0
- data/lib/lutaml/store/version.rb +7 -0
- data/lib/lutaml/store.rb +41 -0
- data/lutaml-store.gemspec +35 -0
- data/plan.adoc +606 -0
- data/sig/lutaml/store.rbs +6 -0
- data/spec/lutaml/store/adapter_interface_spec.rb +89 -0
- data/spec/lutaml/store/anti_pattern_guard_spec.rb +35 -0
- data/spec/lutaml/store/anti_pattern_spec.rb +78 -0
- data/spec/lutaml/store/autoload_spec.rb +34 -0
- data/spec/lutaml/store/cache_store_spec.rb +271 -0
- data/spec/lutaml/store/compression_spec.rb +78 -0
- data/spec/lutaml/store/config_enhanced_spec.rb +158 -0
- data/spec/lutaml/store/corrected_http_cache_integration_spec.rb +336 -0
- data/spec/lutaml/store/custom_serializer_spec.rb +108 -0
- data/spec/lutaml/store/database_store_spec.rb +279 -0
- data/spec/lutaml/store/file_io_spec.rb +219 -0
- data/spec/lutaml/store/format_round_trip_spec.rb +110 -0
- data/spec/lutaml/store/format_spec.rb +70 -0
- data/spec/lutaml/store/http_cache_entry_spec.rb +203 -0
- data/spec/lutaml/store/http_cache_hal_integration_spec.rb +404 -0
- data/spec/lutaml/store/http_cache_spec.rb +422 -0
- data/spec/lutaml/store/http_header_processor_spec.rb +290 -0
- data/spec/lutaml/store/import_spec.rb +90 -0
- data/spec/lutaml/store/integrity_spec.rb +157 -0
- data/spec/lutaml/store/key_collision_serializer_spec.rb +98 -0
- data/spec/lutaml/store/load_save_spec.rb +107 -0
- data/spec/lutaml/store/lutaml_model_integration_spec.rb +291 -0
- data/spec/lutaml/store/model_serializer_spec.rb +140 -0
- data/spec/lutaml/store/store_spec.rb +182 -0
- data/spec/lutaml/store_spec.rb +21 -0
- data/spec/spec_helper.rb +16 -0
- 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
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"}}
|