dynamoid-cdk-schema 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e17f72b5bacad48e8d0df3aac32d6d27332a6a14c8b3f1ed39d2ef3d1e5ca5de
4
- data.tar.gz: 92cdbec04b2b58ab80dba06bc809eb3ddffeec03a2107de46cad9f5a003eb37a
3
+ metadata.gz: 51132f9f8072871ab9915db6a75821e4c3f90582e356c2cb9f22f7a1022ae9e7
4
+ data.tar.gz: ba430e02974802775b082e672f4ec100a4f0a2ae4aceccfc547861665d7cb38f
5
5
  SHA512:
6
- metadata.gz: d54877e0eb240cfcb1727d179c7eff84382db1fd6809a3495826f84ecc236e2bd7ce6e71e682c9c18a5803dc68283b8695294fe732f60cb25a657d7a1fae76c4
7
- data.tar.gz: da0ac57c6fdedf438eec444575ae2cf2e88de1692d4fc15bc6fb69b5b3b71acd95ce222e1beec9d4d960560b307e5c7e7dab0c7d4fb39406773e7b441bb91927
6
+ metadata.gz: c717cbfe3c318a7b390d34653bc94cf05f6120a51c8ae5cb7ef7a7925606bcb2945d64518b83a88c8fe0bef5c68035dfa6e20353872c25957a0f5085cd6d0e07
7
+ data.tar.gz: b98d384b5febcebca7a81376407c0100c27bc37f6c80da2b9ce07f8625c436f6063cfe07df5a045795bb7229c228c1eea4979661a95c57c19d6ef06ba3754347
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2026-08-04
4
+
5
+ - `Dynamoid::CDK::Schema.dump(model)` / `.load(data)` / `.table_from(scope, id, data, **table_props)` — carry a
6
+ schema between processes as JSON-native data. For CDK apps that deliberately cannot load the application:
7
+ a separate infra bundle, a synth step without the app's gems, or models that need config the deploy machine
8
+ lacks. The app dumps its schema at build time, the CDK app reads the file at synth time.
9
+ - Dynamoid is no longer a runtime dependency, matching how `aws-cdk-lib` was already treated. Anyone introspecting
10
+ a model already has Dynamoid loaded; a CDK app building from dumped data should not have to install an ORM.
11
+ **Upgrading:** if you call `.describe` or `.table` and relied on this gem to pull Dynamoid in, declare it yourself.
12
+
3
13
  ## [0.1.0] - 2026-07-27
4
14
 
5
15
  - Initial release.
data/README.md CHANGED
@@ -74,6 +74,31 @@ is yours to pass through as keyword arguments — this gem is about the *schema*
74
74
  application config at load time (e.g. its table name from Rails config), make that available in the synth process — the
75
75
  CDK never uses the physical table name (it assigns one), so a placeholder is enough.
76
76
 
77
+ ## When the CDK app can't load your models
78
+
79
+ Sometimes it shouldn't have to: an infra bundle kept deliberately separate from the application, a synth step with none
80
+ of the app's gems, models that need configuration the deploy machine doesn't have. Dump the schema where the models
81
+ live, and read it where the tables are built:
82
+
83
+ ```ruby
84
+ # in the app, at build time — Dynamoid only
85
+ require "json"
86
+ File.write("build/tables.json", JSON.pretty_generate(Dynamoid::CDK::Schema.dump(Post)))
87
+ ```
88
+
89
+ ```ruby
90
+ # in the CDK app, at synth time — aws-cdk-lib only, no Dynamoid, no app
91
+ Dynamoid::CDK::Schema.table_from(
92
+ self, "Posts", JSON.parse(File.read("build/tables.json")), removal_policy: ...
93
+ )
94
+ ```
95
+
96
+ `dump` returns JSON-native data (strings, arrays, hashes, nil), and `load` turns it back into the same descriptor
97
+ `describe` would have produced. `table_from` accepts either.
98
+
99
+ This also makes the schema a build artifact you can diff, review or check into a deployment bundle — synthesis then
100
+ depends only on what was built, not on whether the app happens to load.
101
+
77
102
  ## Development
78
103
 
79
104
  After checking out the repo, run `bin/setup` to install dependencies, then `bundle exec rspec` to run the tests and
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dynamoid"
4
-
5
3
  module Dynamoid
6
4
  module CDK
7
5
  module Schema
@@ -13,6 +11,12 @@ module Dynamoid
13
11
  module_function
14
12
 
15
13
  def call(model)
14
+ # Required here rather than at load: {Schema.table_from} builds tables
15
+ # from dumped data, and a CDK app doing only that has no reason to
16
+ # carry Dynamoid. Anyone introspecting a model already has it loaded —
17
+ # the model IS a Dynamoid class.
18
+ require "dynamoid"
19
+
16
20
  Descriptor.new(
17
21
  partition_key: attribute(model, model.hash_key),
18
22
  sort_key: (attribute(model, model.range_key) if model.range_key),
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dynamoid
4
+ module CDK
5
+ module Schema
6
+ # {Descriptor} to and from plain data, so the two halves of this gem can run
7
+ # in different processes.
8
+ #
9
+ # The usual arrangement loads the models at synth time and calls
10
+ # {Schema.table}. That is not always possible: a CDK app may deliberately
11
+ # not depend on the application it deploys — a separate infra bundle, a
12
+ # synth step with no database gems, an app whose models cannot load without
13
+ # config the deploy machine lacks. Then the app dumps its schema during
14
+ # build and the CDK app reads the result:
15
+ #
16
+ # # in the app, where Dynamoid and the models are loaded
17
+ # File.write("build/tables.json", JSON.dump(Schema.dump(Post)))
18
+ #
19
+ # # in the CDK app, which needs neither
20
+ # Schema.table_from(self, "Posts", JSON.parse(File.read("build/tables.json")))
21
+ #
22
+ # Keys are strings and values are strings, arrays, hashes or nil, so the
23
+ # result survives a JSON round trip unchanged.
24
+ module Serializer
25
+ module_function
26
+
27
+ def dump(descriptor)
28
+ {
29
+ "partition_key" => dump_attribute(descriptor.partition_key),
30
+ "sort_key" => dump_attribute(descriptor.sort_key),
31
+ "global_secondary_indexes" => descriptor.global_secondary_indexes.map { |i| dump_index(i) },
32
+ "local_secondary_indexes" => descriptor.local_secondary_indexes.map { |i| dump_index(i) }
33
+ }
34
+ end
35
+
36
+ def load(data)
37
+ data = stringify(data)
38
+
39
+ Descriptor.new(
40
+ partition_key: load_attribute(data["partition_key"]),
41
+ sort_key: load_attribute(data["sort_key"]),
42
+ global_secondary_indexes: Array(data["global_secondary_indexes"]).map { |i| load_index(i) },
43
+ local_secondary_indexes: Array(data["local_secondary_indexes"]).map { |i| load_index(i) }
44
+ )
45
+ end
46
+
47
+ def dump_attribute(attribute)
48
+ return nil if attribute.nil?
49
+
50
+ { "name" => attribute.name, "type" => attribute.type.to_s }
51
+ end
52
+
53
+ def load_attribute(data)
54
+ return nil if data.nil?
55
+
56
+ data = stringify(data)
57
+ Attribute.new(name: data.fetch("name"), type: data.fetch("type").to_sym)
58
+ end
59
+
60
+ def dump_index(index)
61
+ {
62
+ "name" => index.name,
63
+ "partition_key" => dump_attribute(index.partition_key),
64
+ "sort_key" => dump_attribute(index.sort_key),
65
+ "projection_type" => index.projection_type.to_s,
66
+ "non_key_attributes" => index.non_key_attributes
67
+ }
68
+ end
69
+
70
+ def load_index(data)
71
+ data = stringify(data)
72
+
73
+ Index.new(
74
+ name: data.fetch("name"),
75
+ partition_key: load_attribute(data["partition_key"]),
76
+ sort_key: load_attribute(data["sort_key"]),
77
+ projection_type: data.fetch("projection_type").to_sym,
78
+ non_key_attributes: data["non_key_attributes"]
79
+ )
80
+ end
81
+
82
+ # Accept symbol keys too: a descriptor that has not been through JSON is
83
+ # a reasonable thing to hand back, and failing on it would be a trap.
84
+ def stringify(data)
85
+ data.is_a?(Hash) ? data.transform_keys(&:to_s) : data
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -3,7 +3,7 @@
3
3
  module Dynamoid
4
4
  module CDK
5
5
  module Schema
6
- VERSION = "0.1.0"
6
+ VERSION = "0.2.0"
7
7
  end
8
8
  end
9
9
  end
@@ -12,6 +12,7 @@ end
12
12
 
13
13
  require_relative "schema/descriptor"
14
14
  require_relative "schema/introspector"
15
+ require_relative "schema/serializer"
15
16
  require_relative "schema/table_builder"
16
17
 
17
18
  module Dynamoid
@@ -38,6 +39,30 @@ module Dynamoid
38
39
  def table(scope, id, model, **table_props)
39
40
  TableBuilder.build(scope, id, describe(model), **table_props)
40
41
  end
42
+
43
+ # The model's table shape as plain data that survives JSON — for when the
44
+ # CDK app cannot load the models, and the schema has to travel as a file
45
+ # from build time to synth time. Needs only Dynamoid.
46
+ #
47
+ # File.write("build/tables.json", JSON.dump(Schema.dump(Post)))
48
+ def dump(model)
49
+ Serializer.dump(describe(model))
50
+ end
51
+
52
+ # The inverse: a {Descriptor} from whatever {dump} produced.
53
+ def load(data)
54
+ Serializer.load(data)
55
+ end
56
+
57
+ # Build a CDK +TableV2+ from dumped data (or a {Descriptor}) instead of
58
+ # from a model, so the CDK app needs neither Dynamoid nor the app itself.
59
+ # Table-level props pass through exactly as in {table}.
60
+ #
61
+ # Schema.table_from(self, "Posts", JSON.parse(File.read("build/tables.json")))
62
+ def table_from(scope, id, data, **table_props)
63
+ descriptor = data.is_a?(Descriptor) ? data : load(data)
64
+ TableBuilder.build(scope, id, descriptor, **table_props)
65
+ end
41
66
  end
42
67
  end
43
68
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamoid-cdk-schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Omar Qureshi
@@ -19,7 +19,7 @@ dependencies:
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
21
  version: '4'
22
- type: :runtime
22
+ type: :development
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
@@ -46,6 +46,7 @@ files:
46
46
  - lib/dynamoid/cdk/schema.rb
47
47
  - lib/dynamoid/cdk/schema/descriptor.rb
48
48
  - lib/dynamoid/cdk/schema/introspector.rb
49
+ - lib/dynamoid/cdk/schema/serializer.rb
49
50
  - lib/dynamoid/cdk/schema/table_builder.rb
50
51
  - lib/dynamoid/cdk/schema/version.rb
51
52
  - sig/dynamoid/cdk/schema.rbs