sequel-notion 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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +207 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +470 -0
  5. data/lib/sequel/adapters/notion.rb +159 -0
  6. data/lib/sequel/notion/dataset.rb +167 -0
  7. data/lib/sequel/notion/dataset_aggregates.rb +82 -0
  8. data/lib/sequel/notion/dataset_compounds.rb +89 -0
  9. data/lib/sequel/notion/dataset_computed.rb +124 -0
  10. data/lib/sequel/notion/dataset_grouping.rb +145 -0
  11. data/lib/sequel/notion/dataset_having.rb +99 -0
  12. data/lib/sequel/notion/dataset_joins.rb +124 -0
  13. data/lib/sequel/notion/dataset_pages.rb +145 -0
  14. data/lib/sequel/notion/dataset_selection.rb +51 -0
  15. data/lib/sequel/notion/dataset_truncation.rb +39 -0
  16. data/lib/sequel/notion/discovery.rb +76 -0
  17. data/lib/sequel/notion/errors.rb +9 -0
  18. data/lib/sequel/notion/filter_comparison.rb +123 -0
  19. data/lib/sequel/notion/filter_compiler.rb +121 -0
  20. data/lib/sequel/notion/filter_constants.rb +37 -0
  21. data/lib/sequel/notion/filter_helpers.rb +162 -0
  22. data/lib/sequel/notion/filter_like.rb +143 -0
  23. data/lib/sequel/notion/filter_like_tokenizer.rb +42 -0
  24. data/lib/sequel/notion/filter_negation.rb +76 -0
  25. data/lib/sequel/notion/filter_nested.rb +96 -0
  26. data/lib/sequel/notion/filter_nulls.rb +53 -0
  27. data/lib/sequel/notion/filter_predicates.rb +160 -0
  28. data/lib/sequel/notion/filter_shape.rb +75 -0
  29. data/lib/sequel/notion/filter_tables.rb +111 -0
  30. data/lib/sequel/notion/group_accumulator.rb +55 -0
  31. data/lib/sequel/notion/join_output.rb +58 -0
  32. data/lib/sequel/notion/join_where.rb +111 -0
  33. data/lib/sequel/notion/model_support.rb +52 -0
  34. data/lib/sequel/notion/notion_file.rb +228 -0
  35. data/lib/sequel/notion/page_api.rb +49 -0
  36. data/lib/sequel/notion/registry.rb +150 -0
  37. data/lib/sequel/notion/request_budget.rb +64 -0
  38. data/lib/sequel/notion/schema.rb +74 -0
  39. data/lib/sequel/notion/schema_lookup.rb +82 -0
  40. data/lib/sequel/notion/sort_compiler.rb +63 -0
  41. data/lib/sequel/notion/type_map.rb +125 -0
  42. data/lib/sequel/notion/type_map_builders.rb +125 -0
  43. data/lib/sequel/notion/type_map_dates.rb +67 -0
  44. data/lib/sequel/notion/type_map_extractors.rb +89 -0
  45. data/lib/sequel/notion/version.rb +7 -0
  46. data/lib/sequel/notion.rb +5 -0
  47. metadata +132 -0
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sequel/notion/filter_compiler"
4
+ require "sequel/notion/type_map"
5
+
6
+ module Sequel
7
+ module Notion
8
+ module SortCompiler
9
+ module_function
10
+
11
+ def compile(order_clauses)
12
+ Array(order_clauses).map { |clause| compile_one(clause) }
13
+ end
14
+
15
+ def compile_one(clause)
16
+ case clause
17
+ when Sequel::LiteralString then raise literal_error(clause)
18
+ when Sequel::SQL::OrderedExpression then compile_ordered(clause)
19
+ when Symbol, String, Sequel::SQL::Identifier,
20
+ Sequel::SQL::QualifiedIdentifier
21
+ compile_ascending(clause)
22
+ else
23
+ raise Sequel::Error,
24
+ "Unsupported order expression: #{clause.class}"
25
+ end
26
+ end
27
+
28
+ # Sequel.lit is a String, but SQL, not a property name
29
+ def literal_error(clause)
30
+ Sequel::Error.new("Notion takes no SQL in an order: #{clause}")
31
+ end
32
+
33
+ # Notion sorts empty values last in either direction, so only
34
+ # NULLS LAST (or no NULLS clause) can be honoured
35
+ def compile_ordered(clause)
36
+ if clause.nulls == :first
37
+ raise Sequel::Error,
38
+ "Notion sorts empty values last: #{clause.inspect}"
39
+ end
40
+
41
+ direction = clause.descending ? "descending" : "ascending"
42
+ { "property" => sort_property(clause.expression),
43
+ "direction" => direction }
44
+ end
45
+
46
+ def compile_ascending(clause)
47
+ { "property" => sort_property(clause),
48
+ "direction" => "ascending" }
49
+ end
50
+
51
+ # Notion sorts by a property or a timestamp, never by the
52
+ # page's own id or in_trash, and rejects them with a 400
53
+ def sort_property(expr)
54
+ raise literal_error(expr) if expr.is_a?(Sequel::LiteralString)
55
+
56
+ name = FilterCompiler.property_name(expr)
57
+ return name unless TypeMap::PAGE_KEYS.include?(name)
58
+
59
+ raise Sequel::Error, "Notion cannot sort by #{name}"
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "notion_file"
4
+ require_relative "type_map_builders"
5
+ require_relative "type_map_dates"
6
+ require_relative "type_map_extractors"
7
+
8
+ module Sequel
9
+ module Notion
10
+ module TypeMap
11
+ module_function
12
+
13
+ # Notion property types that the Notion API never accepts on write.
14
+ READ_ONLY_TYPES = %w[
15
+ formula rollup created_time last_edited_time
16
+ created_by last_edited_by unique_id button verification
17
+ ].freeze
18
+
19
+ # Keys every row carries for the page itself; a property of
20
+ # the same name would overwrite them
21
+ PAGE_KEYS = %w[id in_trash].freeze
22
+
23
+ # Writable Notion type => the builder for its value
24
+ BUILDERS = {
25
+ "title" => :build_rich_text,
26
+ "rich_text" => :build_rich_text,
27
+ "number" => :build_number,
28
+ "select" => :build_select,
29
+ "status" => :build_select,
30
+ "multi_select" => :build_multi_select,
31
+ "date" => :build_date,
32
+ "checkbox" => :build_checkbox,
33
+ "url" => :build_string,
34
+ "email" => :build_string,
35
+ "phone_number" => :build_string,
36
+ "relation" => :build_relation,
37
+ "people" => :build_people,
38
+ "files" => :build_files
39
+ }.freeze
40
+
41
+ # Notion page → flat Ruby hash
42
+ def page_to_row(page)
43
+ row = { id: page["id"], in_trash: page["in_trash"] }
44
+ props = page["properties"] || {}
45
+ check_property_names!(props.keys)
46
+
47
+ props.each do |name, prop|
48
+ row[name.to_sym] = extract_value(prop)
49
+ end
50
+
51
+ row
52
+ end
53
+
54
+ # Flat Ruby hash → Notion properties payload
55
+ #
56
+ # +prop_types+ maps property NAME (String) to Notion TYPE
57
+ # (String), e.g. {"Name" => "title", "Status" => "status"}.
58
+ # +hash+ keys may be Symbols or Strings.
59
+ def row_to_properties(hash, prop_types)
60
+ hash.each_with_object({}) do |(key, value), props|
61
+ name = key.to_s
62
+ type = prop_types[name]
63
+ ensure_known_property(name, type, prop_types)
64
+ props[name] = build_named_property(name, value, type)
65
+ end
66
+ end
67
+
68
+ def check_property_names!(names)
69
+ clash = names & PAGE_KEYS
70
+ return if clash.empty?
71
+
72
+ raise Sequel::Error,
73
+ "property #{clash.first.inspect} clashes with the " \
74
+ "page's own column; rename it in Notion"
75
+ end
76
+
77
+ def ensure_known_property(name, type, prop_types)
78
+ return if type
79
+
80
+ raise Sequel::Error,
81
+ "unknown property #{name.inspect}; " \
82
+ "available: #{prop_types.keys.sort.inspect}"
83
+ end
84
+
85
+ # build_property, wrapped so a failure names the property.
86
+ def build_named_property(name, value, type)
87
+ build_property(value, type)
88
+ rescue Sequel::Error => e
89
+ raise Sequel::Error,
90
+ "property #{name.inspect} (#{type.inspect}): " \
91
+ "#{e.message}"
92
+ end
93
+
94
+ # ----------------------------------------------------------
95
+ # Notion → Ruby
96
+ # ----------------------------------------------------------
97
+
98
+ def extract_value(prop)
99
+ type = prop["type"]
100
+ extractor = EXTRACTORS[type]
101
+ return send(extractor, prop) if extractor
102
+
103
+ prop[type]
104
+ end
105
+
106
+ # ----------------------------------------------------------
107
+ # Ruby → Notion
108
+ # ----------------------------------------------------------
109
+
110
+ # Build a single Notion property payload for +value+ given its
111
+ # Notion +type+. Returns {TYPE => value}.
112
+ def build_property(value, type)
113
+ builder = BUILDERS[type]
114
+ return { type => send(builder, value) } if builder
115
+
116
+ if READ_ONLY_TYPES.include?(type)
117
+ raise Sequel::Error,
118
+ "property type #{type.inspect} is read-only"
119
+ end
120
+
121
+ raise Sequel::Error, "unsupported property type #{type.inspect}"
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "notion_file"
4
+
5
+ # Ruby → Notion builders for Sequel::Notion::TypeMap, split out here to
6
+ # keep type_map.rb's module short. See BUILDERS in type_map.rb for the
7
+ # Notion type => builder mapping that dispatches to these.
8
+
9
+ module Sequel
10
+ module Notion
11
+ module TypeMap
12
+ # Float() also accepts hex ("0x1A"), binary ("0b11") and
13
+ # underscore-grouped ("1_000") strings; a Notion number
14
+ # property is plain decimal text only.
15
+ DECIMAL = /\A\s*[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?\s*\z/
16
+
17
+ module_function
18
+
19
+ def build_string(value) = value&.to_s
20
+
21
+ # title / rich_text: to_s, split into text objects of at most
22
+ # 2000 characters each, as Notion counts them (UTF-16 units)
23
+ def build_rich_text(value)
24
+ text_runs(value.to_s).map do |run|
25
+ { "type" => "text", "text" => { "content" => run } }
26
+ end
27
+ end
28
+
29
+ # A character outside the BMP counts two and is never split
30
+ def text_runs(str)
31
+ units = 0
32
+ str.each_char.slice_before do |c|
33
+ n = c.ord > 0xFFFF ? 2 : 1
34
+ (units += n) > 2000 && (units = n)
35
+ end.map(&:join)
36
+ end
37
+
38
+ def build_number(value)
39
+ case value
40
+ when nil then nil
41
+ when Integer then value
42
+ when Float then finite_number(value)
43
+ when Numeric then finite_number(value.to_f)
44
+ when String then parse_number(value)
45
+ else raise Sequel::Error, "invalid number: #{value.inspect}"
46
+ end
47
+ rescue RangeError # a Complex has no Float
48
+ raise Sequel::Error, "invalid number: #{value.inspect}"
49
+ end
50
+
51
+ # JSON has no NaN or Infinity
52
+ def finite_number(value)
53
+ return value if value.finite?
54
+
55
+ raise Sequel::Error, "invalid number: #{value.inspect}"
56
+ end
57
+
58
+ # Float overflows "1e400" to Infinity instead of raising; it
59
+ # also accepts hex/binary/underscored strings DECIMAL refuses
60
+ def parse_number(value)
61
+ raise Sequel::Error, "invalid number: #{value.inspect}" unless
62
+ value.match?(DECIMAL)
63
+
64
+ finite_number(Float(value))
65
+ rescue ArgumentError
66
+ raise Sequel::Error, "invalid number: #{value.inspect}"
67
+ end
68
+
69
+ def build_select(value)
70
+ return nil if value.nil?
71
+
72
+ { "name" => value.to_s }
73
+ end
74
+
75
+ def build_multi_select(value)
76
+ list(value).map { { "name" => it.to_s } }
77
+ end
78
+
79
+ def build_checkbox(value)
80
+ case value
81
+ when true, false
82
+ value
83
+ when nil
84
+ false
85
+ else
86
+ raise Sequel::Error,
87
+ "invalid checkbox value: #{value.inspect}"
88
+ end
89
+ end
90
+
91
+ # One id or an Array of them; nil is none
92
+ def build_relation(value) = list(value).map { { "id" => it } }
93
+
94
+ def build_people(value)
95
+ list(value).map { { "object" => "user", "id" => it } }
96
+ end
97
+
98
+ # One item or an Array of them; nil is none. Array() would
99
+ # turn a Hash into its pairs.
100
+ def list(value)
101
+ if value.is_a?(Hash)
102
+ raise Sequel::Error, "a list of names or ids takes no " \
103
+ "Hash: #{value.inspect}"
104
+ end
105
+
106
+ Array(value)
107
+ end
108
+
109
+ def build_files(value)
110
+ return [] if value.nil?
111
+
112
+ items = value.is_a?(Array) ? value : [value]
113
+ items.map { |item| build_file(item) }
114
+ end
115
+
116
+ def build_file(item)
117
+ case item
118
+ when File then item.to_notion
119
+ when String then File.external(item).to_notion
120
+ else raise Sequel::Error, "invalid file value: #{item.inspect}"
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ # Ruby → Notion date builders for Sequel::Notion::TypeMap, split out of
6
+ # type_map_builders.rb to keep that module short.
7
+
8
+ module Sequel
9
+ module Notion
10
+ module TypeMap
11
+ module_function
12
+
13
+ def build_date(value)
14
+ case value
15
+ when nil then nil
16
+ when String then { "start" => value }
17
+ when Range then build_date_range(value)
18
+ when Date, Time, DateTime then { "start" => value.iso8601 }
19
+ when Hash then build_date_hash(value)
20
+ else raise Sequel::Error, "invalid date value: #{value.inspect}"
21
+ end
22
+ end
23
+
24
+ # Notion's date needs a start; an endless range leaves the end open
25
+ def build_date_range(value)
26
+ if value.begin.nil?
27
+ raise Sequel::Error,
28
+ "a date range needs a start: #{value.inspect}"
29
+ end
30
+
31
+ { "start" => date_component(value.begin),
32
+ "end" => date_component(inclusive_end(value)) }
33
+ end
34
+
35
+ # Notion's end is inclusive: an exclusive range ends the day
36
+ # before, which only a Date end can say
37
+ def inclusive_end(range)
38
+ last = range.end
39
+ return last if last.nil? || !range.exclude_end?
40
+ return last.prev_day if last.instance_of?(Date)
41
+
42
+ raise Sequel::Error,
43
+ "an exclusive date range must end on a Date: " \
44
+ "#{range.inspect}"
45
+ end
46
+
47
+ # Like a Range, a Hash needs a start; nil is how a date is cleared
48
+ def build_date_hash(value)
49
+ start_v = value.key?(:start) ? value[:start] : value["start"]
50
+ end_v = value.key?(:end) ? value[:end] : value["end"]
51
+
52
+ if start_v.nil?
53
+ raise Sequel::Error,
54
+ "a date needs a start: #{value.inspect}"
55
+ end
56
+
57
+ result = { "start" => date_component(start_v) }
58
+ result["end"] = date_component(end_v) unless end_v.nil?
59
+ result
60
+ end
61
+
62
+ def date_component(value)
63
+ value.respond_to?(:iso8601) ? value.iso8601 : value
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "notion_file"
4
+
5
+ # Notion → Ruby extractors for Sequel::Notion::TypeMap, split out here to
6
+ # keep type_map.rb's module short. Types not listed in EXTRACTORS fall
7
+ # back to extract_value's default: prop[prop["type"]].
8
+
9
+ module Sequel
10
+ module Notion
11
+ module TypeMap
12
+ module_function
13
+
14
+ # Notion type => the extractor for its property hash. Only
15
+ # types whose value needs more than a plain prop[type] lookup
16
+ # are listed; see extract_value in type_map.rb for the rest.
17
+ EXTRACTORS = {
18
+ "title" => :extract_rich_text,
19
+ "rich_text" => :extract_rich_text,
20
+ "select" => :extract_select,
21
+ "status" => :extract_select,
22
+ "multi_select" => :extract_multi_select,
23
+ "date" => :extract_date,
24
+ "people" => :extract_people,
25
+ "relation" => :extract_relation,
26
+ "formula" => :extract_formula,
27
+ "files" => :extract_files,
28
+ "unique_id" => :extract_unique_id,
29
+ "rollup" => :extract_rollup
30
+ }.freeze
31
+
32
+ def extract_rich_text(prop)
33
+ prop[prop["type"]]&.map { it["plain_text"] }&.join
34
+ end
35
+
36
+ def extract_select(prop)
37
+ prop.dig(prop["type"], "name")
38
+ end
39
+
40
+ def extract_multi_select(prop)
41
+ prop["multi_select"]&.map { it["name"] }
42
+ end
43
+
44
+ # The start, or a Range of Strings when there is an end: the
45
+ # Range a write takes
46
+ def extract_date(prop)
47
+ date = prop["date"] || {}
48
+ return date["start"] unless date["end"]
49
+
50
+ date["start"]..date["end"]
51
+ end
52
+
53
+ # Its value: a number or date, or an Array of the rolled-up
54
+ # values, each read as its own type
55
+ def extract_rollup(prop)
56
+ inner = prop["rollup"]
57
+ return if inner.nil?
58
+ return inner["array"].map { extract_value(it) } if
59
+ inner["type"] == "array"
60
+
61
+ extract_value(inner)
62
+ end
63
+
64
+ # As Notion displays it: "TK-62", or "62" with no prefix
65
+ def extract_unique_id(prop)
66
+ id = prop["unique_id"] || {}
67
+ return if id["number"].nil?
68
+
69
+ [id["prefix"], id["number"]].compact.join("-")
70
+ end
71
+
72
+ def extract_people(prop)
73
+ prop["people"]&.map { it["id"] }
74
+ end
75
+
76
+ def extract_relation(prop)
77
+ prop["relation"]&.map { it["id"] }
78
+ end
79
+
80
+ def extract_formula(prop)
81
+ extract_value(prop["formula"])
82
+ end
83
+
84
+ def extract_files(prop)
85
+ File.from_notion_property(prop)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequel
4
+ module Notion
5
+ VERSION = "0.2.0"
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Entry point for `require "sequel/notion"` and Bundler's auto-require;
4
+ # Sequel itself loads the adapter on `Sequel.connect(adapter: :notion)`.
5
+ require "sequel/adapters/notion"
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel-notion
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Stéphane D'Alu
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: faraday
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday-retry
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: sequel
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '5.0'
54
+ description: A Sequel database adapter that maps Notion databases and data sources
55
+ to Sequel's Dataset interface. Supports multi-source databases, filtering, sorting,
56
+ pagination, and CRUD operations via the Notion API (2026-03-11).
57
+ email:
58
+ - sdalu@sdalu.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - CHANGELOG.md
64
+ - LICENSE.txt
65
+ - README.md
66
+ - lib/sequel/adapters/notion.rb
67
+ - lib/sequel/notion.rb
68
+ - lib/sequel/notion/dataset.rb
69
+ - lib/sequel/notion/dataset_aggregates.rb
70
+ - lib/sequel/notion/dataset_compounds.rb
71
+ - lib/sequel/notion/dataset_computed.rb
72
+ - lib/sequel/notion/dataset_grouping.rb
73
+ - lib/sequel/notion/dataset_having.rb
74
+ - lib/sequel/notion/dataset_joins.rb
75
+ - lib/sequel/notion/dataset_pages.rb
76
+ - lib/sequel/notion/dataset_selection.rb
77
+ - lib/sequel/notion/dataset_truncation.rb
78
+ - lib/sequel/notion/discovery.rb
79
+ - lib/sequel/notion/errors.rb
80
+ - lib/sequel/notion/filter_comparison.rb
81
+ - lib/sequel/notion/filter_compiler.rb
82
+ - lib/sequel/notion/filter_constants.rb
83
+ - lib/sequel/notion/filter_helpers.rb
84
+ - lib/sequel/notion/filter_like.rb
85
+ - lib/sequel/notion/filter_like_tokenizer.rb
86
+ - lib/sequel/notion/filter_negation.rb
87
+ - lib/sequel/notion/filter_nested.rb
88
+ - lib/sequel/notion/filter_nulls.rb
89
+ - lib/sequel/notion/filter_predicates.rb
90
+ - lib/sequel/notion/filter_shape.rb
91
+ - lib/sequel/notion/filter_tables.rb
92
+ - lib/sequel/notion/group_accumulator.rb
93
+ - lib/sequel/notion/join_output.rb
94
+ - lib/sequel/notion/join_where.rb
95
+ - lib/sequel/notion/model_support.rb
96
+ - lib/sequel/notion/notion_file.rb
97
+ - lib/sequel/notion/page_api.rb
98
+ - lib/sequel/notion/registry.rb
99
+ - lib/sequel/notion/request_budget.rb
100
+ - lib/sequel/notion/schema.rb
101
+ - lib/sequel/notion/schema_lookup.rb
102
+ - lib/sequel/notion/sort_compiler.rb
103
+ - lib/sequel/notion/type_map.rb
104
+ - lib/sequel/notion/type_map_builders.rb
105
+ - lib/sequel/notion/type_map_dates.rb
106
+ - lib/sequel/notion/type_map_extractors.rb
107
+ - lib/sequel/notion/version.rb
108
+ homepage: https://github.com/sdalu/sequel-notion
109
+ licenses:
110
+ - MIT
111
+ metadata:
112
+ homepage_uri: https://github.com/sdalu/sequel-notion
113
+ changelog_uri: https://github.com/sdalu/sequel-notion/blob/main/CHANGELOG.md
114
+ rubygems_mfa_required: 'true'
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '3.4'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubygems_version: 4.0.16
130
+ specification_version: 4
131
+ summary: Sequel adapter for Notion data sources
132
+ test_files: []