tana 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tana
4
+ # The primary interface for making API requests.
5
+ class Client
6
+ include Import[:configuration]
7
+
8
+ def initialize(endpoint: Endpoints::Add::Action.new, **)
9
+ @endpoint = endpoint
10
+ super(**)
11
+ yield configuration if block_given?
12
+ end
13
+
14
+ def add(...) = endpoint.call(...)
15
+
16
+ private
17
+
18
+ attr_reader :endpoint
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tana
4
+ module Configuration
5
+ # Defines the content of the client configuration for API requests.
6
+ Content = Struct.new :accept, :token, :url
7
+ end
8
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "refinements/string"
4
+
5
+ module Tana
6
+ module Configuration
7
+ # Handles loading of configuration with environment defaults.
8
+ class Loader
9
+ using Refinements::String
10
+
11
+ def initialize model = Content, environment: ENV
12
+ @model = model
13
+ @environment = environment
14
+ end
15
+
16
+ def call
17
+ model[
18
+ accept: environment.fetch("TANA_API_ACCEPT", "application/json"),
19
+ token: environment["TANA_API_TOKEN"],
20
+ url: environment.fetch("TANA_API_URL", "https://europe-west1-tagr-prod.cloudfunctions.net")
21
+ ]
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :model, :environment
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-container"
4
+ require "http"
5
+
6
+ module Tana
7
+ # Defines application dependencies.
8
+ module Container
9
+ extend Dry::Container::Mixin
10
+
11
+ register(:configuration, memoize: true) { Configuration::Loader.new.call }
12
+ register(:http) { HTTP }
13
+ register(:client, memoize: true) { API::Client.new }
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "transactable"
4
+
5
+ module Tana
6
+ module Endpoints
7
+ module Add
8
+ # Handles a repository create action.
9
+ class Action
10
+ include Import[:client]
11
+ include Transactable
12
+
13
+ # rubocop:todo Metrics/ParameterLists
14
+ def initialize(
15
+ request: Requests::Add.new,
16
+ response: Responses::Root,
17
+ model: Models::Root,
18
+ **
19
+ )
20
+ @request = request
21
+ @response = response
22
+ @model = model
23
+ super(**)
24
+ end
25
+ # rubocop:enable Metrics/ParameterLists
26
+
27
+ def call(body, **)
28
+ pipe body,
29
+ validate(request),
30
+ insert("addToNodeV2", at: 0),
31
+ insert(**),
32
+ to(client, :post),
33
+ try(:parse, catch: JSON::ParserError),
34
+ validate(response),
35
+ to(model, :for)
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :request, :response, :model
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "infusible"
4
+
5
+ module Tana
6
+ Import = Infusible.with Container
7
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "core"
4
+
5
+ module Tana
6
+ module Models
7
+ # A generic node.
8
+ Node = Data.define :id, :name, :description, :type, :children do
9
+ def self.for(**attributes) = new(**attributes.transform_keys!(nodeId: :id))
10
+
11
+ def initialize id:, type:, name: nil, description: nil, children: Core::EMPTY_ARRAY
12
+ updated_children = children.map { |child| self.class.for(**child) }
13
+ super id:, name:, type:, description:, children: updated_children
14
+ end
15
+
16
+ alias_method :to_hash, :to_h
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "core"
4
+
5
+ module Tana
6
+ module Models
7
+ # A root node.
8
+ Root = Data.define :children do
9
+ def self.for(node: Node, **attributes)
10
+ attributes.fetch(:children, Core::EMPTY_ARRAY)
11
+ .map { |child| node.for(**child) }
12
+ .then { |children| new children: }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/validation"
4
+ require "refinements/array"
5
+
6
+ module Tana
7
+ module Requests
8
+ # Validates an API add request.
9
+ class Add < Dry::Validation::Contract
10
+ using Refinements::Array
11
+
12
+ TARGETS = ["INBOX", "SCHEMA", /\A[0-9a-z\-_]{12}\Z/i].freeze
13
+
14
+ def initialize(targets: TARGETS, **)
15
+ @targets = targets
16
+ super(**)
17
+ end
18
+
19
+ json do
20
+ optional(:targetNodeId).filled :string
21
+ required(:nodes).value(:array, min_size?: 1).each { hash LevelA }
22
+ end
23
+
24
+ rule :targetNodeId do
25
+ next unless key? && targets.none? { |target| value.match? target }
26
+
27
+ key.failure "is invalid. Use: #{targets.to_usage :or}"
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :targets
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Requests
7
+ # Validates the first level of an API request.
8
+ LevelA = Dry::Schema.JSON parent: Node.schema do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelB }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Requests
7
+ # Validates the second level of an API request.
8
+ LevelB = Dry::Schema.JSON parent: Node.schema do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelC }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Requests
7
+ # Validates the third level of an API request.
8
+ LevelC = Dry::Schema.JSON parent: Node.schema do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelD }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Requests
7
+ # Validates the fourth level of an API request.
8
+ LevelD = Dry::Schema.JSON parent: Node.schema do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelE }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Requests
7
+ # Validates the fifth level of an API request.
8
+ LevelE = Dry::Schema.JSON parent: Node.schema do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelF }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Requests
7
+ # Validates the sixth level of an API request.
8
+ LevelF = Dry::Schema.JSON parent: Node.schema do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelG }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Requests
7
+ # Validates the seventh level of an API request.
8
+ LevelG = Dry::Schema.JSON parent: Node.schema do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelH }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Requests
7
+ # Validates the eight level of an API request.
8
+ LevelH = Dry::Schema.JSON parent: Node.schema do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelI }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Requests
7
+ # Validates the ninth level of an API request.
8
+ LevelI = Dry::Schema.JSON parent: Node.schema do
9
+ optional(:children).value(:array, min_size?: 1).each { hash Node.schema }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/validation"
4
+ require "refinements/array"
5
+
6
+ module Tana
7
+ module Requests
8
+ # Validates a generic node within an API request.
9
+ class Node < Dry::Validation::Contract
10
+ TYPES = %w[boolean date field file plain reference url].freeze
11
+
12
+ using Refinements::Array
13
+
14
+ def initialize(types: TYPES, **)
15
+ @types = types
16
+ super(**)
17
+ end
18
+
19
+ json do
20
+ optional(:attributeId).filled :string
21
+ optional(:contentType).filled :string
22
+ optional(:dataType).filled :string
23
+ optional(:description).filled :string
24
+ optional(:file).filled :string
25
+ optional(:filename).filled :string
26
+ optional(:id).filled :string
27
+ optional(:name).filled :string
28
+ optional(:supertags).array(:hash) { required(:id).filled :string }
29
+ optional(:type).filled :string
30
+ optional(:value).filled :bool
31
+ end
32
+
33
+ rule :type do
34
+ key.failure "is invalid. Use: #{types.to_usage :or}" if key? && !types.include?(value)
35
+ end
36
+
37
+ rule :dataType do
38
+ key.failure "is invalid. Use: #{types.to_usage :or}" if key? && !types.include?(value)
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :types
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Responses
7
+ # Validates the first level of an API response.
8
+ LevelA = Dry::Schema.JSON parent: Node do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelB }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Responses
7
+ # Validates the second level of an API response.
8
+ LevelB = Dry::Schema.JSON parent: Node do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelC }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Responses
7
+ # Validates the third level of an API response.
8
+ LevelC = Dry::Schema.JSON parent: Node do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelD }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Responses
7
+ # Validates the fourth level of an API response.
8
+ LevelD = Dry::Schema.JSON parent: Node do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelE }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Responses
7
+ # Validates the fifth level of an API response.
8
+ LevelE = Dry::Schema.JSON parent: Node do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelF }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Responses
7
+ # Validates the sixth level of an API response.
8
+ LevelF = Dry::Schema.JSON parent: Node do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelG }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Responses
7
+ # Validates the seventh level of an API response.
8
+ LevelG = Dry::Schema.JSON parent: Node do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelH }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Responses
7
+ # Validates the eighth level of an API response.
8
+ LevelH = Dry::Schema.JSON parent: Node do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelI }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Responses
7
+ # Validates the ninth level of an API response.
8
+ LevelI = Dry::Schema.JSON parent: Node do
9
+ optional(:children).value(:array, min_size?: 1).each { hash Node }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Responses
7
+ # Validates a generic node within an API response.
8
+ Node = Dry::Schema.JSON do
9
+ optional(:description).filled :string
10
+ optional(:name).filled :string
11
+ required(:nodeId).filled :string
12
+ required(:type).filled :string
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+
5
+ module Tana
6
+ module Responses
7
+ # Validates the root node of an API response.
8
+ Root = Dry::Schema.JSON do
9
+ optional(:children).value(:array, min_size?: 1).each { hash LevelA }
10
+ end
11
+ end
12
+ end
data/lib/tana.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/monads"
4
+ require "dry/validation"
5
+ require "zeitwerk"
6
+
7
+ Dry::Schema.load_extensions :monads
8
+ Dry::Validation.load_extensions :monads
9
+
10
+ Zeitwerk::Loader.new.then do |loader|
11
+ loader.inflector.inflect "api" => "API"
12
+ loader.tag = File.basename __FILE__, ".rb"
13
+ loader.push_dir __dir__
14
+ loader.setup
15
+ end
16
+
17
+ # Main namespace.
18
+ module Tana
19
+ def self.loader registry = Zeitwerk::Registry
20
+ @loader ||= registry.loaders.find { |loader| loader.tag == File.basename(__FILE__, ".rb") }
21
+ end
22
+
23
+ def self.new(...) = Client.new(...)
24
+ end
data/tana.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "tana"
5
+ spec.version = "0.0.0"
6
+ spec.authors = ["Brooke Kuhlmann"]
7
+ spec.email = ["brooke@alchemists.io"]
8
+ spec.homepage = "https://alchemists.io/projects/tana"
9
+ spec.summary = "A monadic API client for the Tana Personal Knowledge Management system."
10
+ spec.license = "Hippocratic-2.1"
11
+
12
+ spec.metadata = {
13
+ "bug_tracker_uri" => "https://github.com/bkuhlmann/tana/issues",
14
+ "changelog_uri" => "https://alchemists.io/projects/tana/versions",
15
+ "documentation_uri" => "https://alchemists.io/projects/tana",
16
+ "funding_uri" => "https://github.com/sponsors/bkuhlmann",
17
+ "label" => "Tana",
18
+ "rubygems_mfa_required" => "true",
19
+ "source_code_uri" => "https://github.com/bkuhlmann/tana"
20
+ }
21
+
22
+ spec.signing_key = Gem.default_key_path
23
+ spec.cert_chain = [Gem.default_cert_path]
24
+
25
+ spec.required_ruby_version = "~> 3.3"
26
+ spec.add_dependency "core", "~> 1.0"
27
+ spec.add_dependency "dry-container", "~> 0.11"
28
+ spec.add_dependency "dry-monads", "~> 1.6"
29
+ spec.add_dependency "dry-validation", "~> 1.10"
30
+ spec.add_dependency "http", "~> 5.1"
31
+ spec.add_dependency "infusible", "~> 3.0"
32
+ spec.add_dependency "refinements", "~> 12.0"
33
+ spec.add_dependency "transactable", "~> 0.10"
34
+ spec.add_dependency "zeitwerk", "~> 2.6"
35
+
36
+ spec.extra_rdoc_files = Dir["README*", "LICENSE*"]
37
+ spec.files = Dir["*.gemspec", "lib/**/*"]
38
+ end
data.tar.gz.sig ADDED
Binary file