kanji-web 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/Rakefile +1 -0
- data/bin/kanji +6 -0
- data/lib/kanji.rb +5 -0
- data/lib/kanji/application.rb +37 -0
- data/lib/kanji/cli.rb +36 -0
- data/lib/kanji/cli/generate.rb +13 -0
- data/lib/kanji/container.rb +12 -0
- data/lib/kanji/errors.rb +10 -0
- data/lib/kanji/generate.rb +48 -0
- data/lib/kanji/generators/abstract_generator.rb +50 -0
- data/lib/kanji/generators/project.rb +155 -0
- data/lib/kanji/generators/type.rb +110 -0
- data/lib/kanji/graph.rb +1 -0
- data/lib/kanji/graph/coerce_type.rb +66 -0
- data/lib/kanji/graph/container.rb +18 -0
- data/lib/kanji/graph/helpers.rb +27 -0
- data/lib/kanji/graph/import.rb +8 -0
- data/lib/kanji/graph/query.rb +16 -0
- data/lib/kanji/graph/register_mutation.rb +43 -0
- data/lib/kanji/graph/register_object.rb +40 -0
- data/lib/kanji/graph/schema.rb +17 -0
- data/lib/kanji/import.rb +5 -0
- data/lib/kanji/instance_define.rb +11 -0
- data/lib/kanji/logger.rb +10 -0
- data/lib/kanji/repository.rb +53 -0
- data/lib/kanji/templates/.gitignore +11 -0
- data/lib/kanji/templates/.keep +0 -0
- data/lib/kanji/templates/.rspec +2 -0
- data/lib/kanji/templates/Gemfile +14 -0
- data/lib/kanji/templates/README.md.tt +12 -0
- data/lib/kanji/templates/Rakefile.tt +113 -0
- data/lib/kanji/templates/app/mutation_type.rb.tt +14 -0
- data/lib/kanji/templates/app/query_type.rb.tt +13 -0
- data/lib/kanji/templates/app/repositories/repo.rb.tt +7 -0
- data/lib/kanji/templates/app/schema.rb.tt +21 -0
- data/lib/kanji/templates/app/types.rb.tt +5 -0
- data/lib/kanji/templates/app/types/type.rb.tt +26 -0
- data/lib/kanji/templates/app/views/graphiql.html.erb +24 -0
- data/lib/kanji/templates/bin/console.tt +7 -0
- data/lib/kanji/templates/bin/setup +7 -0
- data/lib/kanji/templates/config.ru.tt +2 -0
- data/lib/kanji/templates/db/sample_data.rb +1 -0
- data/lib/kanji/templates/db/seed.rb +1 -0
- data/lib/kanji/templates/migration.rb.tt +10 -0
- data/lib/kanji/templates/spec/db_spec_helper.rb.tt +23 -0
- data/lib/kanji/templates/spec/factories/example.rb +9 -0
- data/lib/kanji/templates/spec/spec_helper.rb +61 -0
- data/lib/kanji/templates/spec/support/db/factory.rb +8 -0
- data/lib/kanji/templates/spec/support/db/helpers.rb.tt +13 -0
- data/lib/kanji/templates/system/boot.rb.tt +13 -0
- data/lib/kanji/templates/system/boot/graph.rb.tt +11 -0
- data/lib/kanji/templates/system/boot/monitor.rb.tt +9 -0
- data/lib/kanji/templates/system/boot/repos.rb.tt +23 -0
- data/lib/kanji/templates/system/boot/rom.rb.tt +37 -0
- data/lib/kanji/templates/system/boot/settings.rb.tt +10 -0
- data/lib/kanji/templates/system/project/application.rb.tt +35 -0
- data/lib/kanji/templates/system/project/container.rb.tt +13 -0
- data/lib/kanji/templates/system/project/import.rb.tt +5 -0
- data/lib/kanji/templates/system/project/settings.rb.tt +8 -0
- data/lib/kanji/type.rb +62 -0
- data/lib/kanji/type/argument.rb +12 -0
- data/lib/kanji/type/attribute.rb +14 -0
- data/lib/kanji/type/attribute_definer.rb +33 -0
- data/lib/kanji/type/class_interface.rb +156 -0
- data/lib/kanji/type/mutation.rb +13 -0
- data/lib/kanji/type/mutation_definer.rb +43 -0
- data/lib/kanji/types.rb +11 -0
- data/lib/kanji/types/callable.rb +30 -0
- data/lib/kanji/types/type_interface.rb +43 -0
- data/lib/kanji/version.rb +3 -0
- metadata +538 -0
data/lib/kanji/graph.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "kanji/graph/container"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require "graphql"
|
|
2
|
+
|
|
3
|
+
module Kanji
|
|
4
|
+
class Graph
|
|
5
|
+
class CoerceType
|
|
6
|
+
def self.call(type)
|
|
7
|
+
if array_type?(type)
|
|
8
|
+
member_type = get_member_type(type)
|
|
9
|
+
graphql_type = get_graphql_type(member_type)
|
|
10
|
+
graphql_type.to_list_type
|
|
11
|
+
else
|
|
12
|
+
get_graphql_type(type)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
TYPE_MAP = {
|
|
19
|
+
"String" => "String",
|
|
20
|
+
"Integer" => "Int",
|
|
21
|
+
"Float" => "Float",
|
|
22
|
+
"FalseClass" => "Boolean",
|
|
23
|
+
"TrueClass | FalseClass" => "Boolean",
|
|
24
|
+
"FalseClass | TrueClass" => "Boolean"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def self.get_graphql_type(type)
|
|
28
|
+
return type if type.is_a?(GraphQL::ObjectType)
|
|
29
|
+
|
|
30
|
+
type_string = TYPE_MAP[get_primitive_type(type)]
|
|
31
|
+
|
|
32
|
+
if type.optional?
|
|
33
|
+
GraphQL::Define::TypeDefiner.instance.send(type_string)
|
|
34
|
+
else
|
|
35
|
+
!GraphQL::Define::TypeDefiner.instance.send(type_string)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.get_primitive_type(type)
|
|
40
|
+
type.optional? ?
|
|
41
|
+
type.right.primitive.to_s :
|
|
42
|
+
type.primitive.to_s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.get_member_type(type)
|
|
46
|
+
member_type = type.options[:member] || type.type.options[:member]
|
|
47
|
+
|
|
48
|
+
if has_ancestor?(member_type, Kanji::Type)
|
|
49
|
+
member_type[:graphql_type]
|
|
50
|
+
else
|
|
51
|
+
member_type
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.array_type?(type)
|
|
56
|
+
return type.primitive == Array if defined?(type.primitive)
|
|
57
|
+
defined?(type.type) && type.type.primitive == Array
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.has_ancestor?(type, ancestor)
|
|
61
|
+
return false unless type.respond_to?(:ancestors)
|
|
62
|
+
type.ancestors.include?(ancestor)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "dry-container"
|
|
2
|
+
require "kanji/graph/register_object"
|
|
3
|
+
require "kanji/graph/register_mutation"
|
|
4
|
+
require "kanji/graph/query"
|
|
5
|
+
require "kanji/graph/schema"
|
|
6
|
+
|
|
7
|
+
module Kanji
|
|
8
|
+
class Graph
|
|
9
|
+
class Container
|
|
10
|
+
extend Dry::Container::Mixin
|
|
11
|
+
|
|
12
|
+
register :register_object, -> (params) { RegisterObject.new(params) }
|
|
13
|
+
register :register_mutation, -> (params) { RegisterMutation.new(params) }
|
|
14
|
+
register :query, Kanji::Graph::Query
|
|
15
|
+
register :schema, Kanji::Graph::Schema
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "dry-container"
|
|
2
|
+
require "dry-auto_inject"
|
|
3
|
+
require "kanji/graph/coerce_type"
|
|
4
|
+
require "graphql"
|
|
5
|
+
|
|
6
|
+
module Graph
|
|
7
|
+
class Helpers
|
|
8
|
+
extend Dry::Container::Mixin
|
|
9
|
+
|
|
10
|
+
Import = Dry::AutoInject(Graph::Helpers)
|
|
11
|
+
|
|
12
|
+
register :coerce_type, -> { Graph::CoerceType.new }
|
|
13
|
+
|
|
14
|
+
register :explain do
|
|
15
|
+
->(schema) { schema.execute GraphQL::Introspection::INTROSPECTION_QUERY }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
register :generate do
|
|
19
|
+
lambda do |schema, destination|
|
|
20
|
+
result = JSON.pretty_generate(explain(schema))
|
|
21
|
+
unless File.exist?(destination) && File.read(destination) == result
|
|
22
|
+
File.write(destination, result)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require "graphql"
|
|
2
|
+
|
|
3
|
+
module Kanji
|
|
4
|
+
class Graph
|
|
5
|
+
class Query
|
|
6
|
+
def self.call(schema:, query:, variables: {}, context: {})
|
|
7
|
+
GraphQL::Query.new(
|
|
8
|
+
schema.call,
|
|
9
|
+
query,
|
|
10
|
+
variables: variables,
|
|
11
|
+
context: context
|
|
12
|
+
).result
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require "graphql"
|
|
2
|
+
require "dry-initializer"
|
|
3
|
+
require "kanji/types"
|
|
4
|
+
require "kanji/graph/coerce_type"
|
|
5
|
+
|
|
6
|
+
module Kanji
|
|
7
|
+
class Graph
|
|
8
|
+
class RegisterMutation
|
|
9
|
+
extend Dry::Initializer
|
|
10
|
+
|
|
11
|
+
option :return_type, Types::Class
|
|
12
|
+
option :attributes, Types::Strict::Array.member(Type::Attribute)
|
|
13
|
+
option :name, Types::Strict::String
|
|
14
|
+
option :description, Types::Strict::String, optional: true
|
|
15
|
+
option :resolve, Types::Callable
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
return_type = self.return_type
|
|
19
|
+
attributes = self.attributes
|
|
20
|
+
name = self.name
|
|
21
|
+
description = self.description
|
|
22
|
+
resolve_proc = self.resolve
|
|
23
|
+
coercer = Graph::CoerceType
|
|
24
|
+
|
|
25
|
+
GraphQL::Field.define do
|
|
26
|
+
type -> { return_type }
|
|
27
|
+
name name
|
|
28
|
+
description description
|
|
29
|
+
|
|
30
|
+
attributes.each do |attribute|
|
|
31
|
+
argument(
|
|
32
|
+
attribute.name,
|
|
33
|
+
coercer.(attribute.type),
|
|
34
|
+
attribute.description
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
resolve resolve_proc
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require "graphql"
|
|
2
|
+
require "dry-initializer"
|
|
3
|
+
require "kanji/types"
|
|
4
|
+
require "kanji/type/attribute"
|
|
5
|
+
require "kanji/graph/coerce_type"
|
|
6
|
+
|
|
7
|
+
module Kanji
|
|
8
|
+
class Graph
|
|
9
|
+
class RegisterObject
|
|
10
|
+
extend Dry::Initializer
|
|
11
|
+
|
|
12
|
+
option :attributes, Types::Strict::Array.member(Type::Attribute)
|
|
13
|
+
option :name, Types::Strict::String
|
|
14
|
+
option :description, Types::Strict::String, optional: true
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
name = "#{self.name}Type"
|
|
18
|
+
attributes = self.attributes
|
|
19
|
+
description = self.description
|
|
20
|
+
coercer = Graph::CoerceType
|
|
21
|
+
|
|
22
|
+
GraphQL::ObjectType.define do
|
|
23
|
+
name name
|
|
24
|
+
description description
|
|
25
|
+
|
|
26
|
+
attributes.each do |attribute|
|
|
27
|
+
field attribute.name do
|
|
28
|
+
type -> { coercer.(attribute.type) }
|
|
29
|
+
description attribute.description
|
|
30
|
+
|
|
31
|
+
if attribute.resolve
|
|
32
|
+
resolve attribute.resolve
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require "graphql"
|
|
2
|
+
|
|
3
|
+
module Kanji
|
|
4
|
+
class Graph
|
|
5
|
+
class Schema
|
|
6
|
+
def call(query_type, mutation_type)
|
|
7
|
+
GraphQL::Schema.define do
|
|
8
|
+
query -> { query_type }
|
|
9
|
+
mutation -> { mutation_type }
|
|
10
|
+
|
|
11
|
+
max_depth 10
|
|
12
|
+
max_complexity 200
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/kanji/import.rb
ADDED
data/lib/kanji/logger.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require "rom-repository"
|
|
2
|
+
require "dry/core/inflector"
|
|
3
|
+
|
|
4
|
+
module Kanji
|
|
5
|
+
class Repository < ROM::Repository::Root
|
|
6
|
+
def klass
|
|
7
|
+
@_klass ||= self.class
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def relation_name
|
|
11
|
+
@_relation_name ||= begin
|
|
12
|
+
name = Dry::Core::Inflector.demodulize(klass.name)
|
|
13
|
+
Dry::Core::Inflector.underscore(name)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def type
|
|
18
|
+
@_type ||= begin
|
|
19
|
+
name = Dry::Core::Inflector.demodulize(klass.name)
|
|
20
|
+
singular_name = Dry::Core::Inflector.singularize(name)
|
|
21
|
+
Dry::Core::Inflector.constantize("Types::#{singular_name}")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def relation
|
|
26
|
+
@_relation ||= relations[relation_name].map_to(type)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def value_object
|
|
30
|
+
@_value_object ||= type.resolve(:value_object)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def all
|
|
34
|
+
relation.to_a
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create(params)
|
|
38
|
+
pk = relation.insert(params)
|
|
39
|
+
relation.where(id: pk).one
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def update(params)
|
|
43
|
+
relation.where(id: params["id"]).update(params)
|
|
44
|
+
relation.where(id: params["id"]).one
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def destroy(id)
|
|
48
|
+
item = relation.where(id: id).one
|
|
49
|
+
relation.where(id: id).delete
|
|
50
|
+
item
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# <%= config[:camel_cased_app_name] %>
|
|
2
|
+
|
|
3
|
+
Welcome! You’ve generated an app using Kanji.
|
|
4
|
+
|
|
5
|
+
## First steps
|
|
6
|
+
|
|
7
|
+
1. Run `bundle`
|
|
8
|
+
1. Review `.env` & `.env.test` (and make a copy to e.g. `.example.env` if you want example settings checked in)
|
|
9
|
+
1. Create a `blog_development` database
|
|
10
|
+
1. Add your own steps to `bin/setup`
|
|
11
|
+
1. Run the app with `bundle exec shotgun -p 3000 -o 0.0.0.0 config.ru`
|
|
12
|
+
1. Initialize git with `git init` and make your initial commit
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
require "bundler/setup"
|
|
2
|
+
require "pry-byebug" unless ENV["RACK_ENV"] == "production"
|
|
3
|
+
require "rom/sql/rake_task"
|
|
4
|
+
require "shellwords"
|
|
5
|
+
require_relative "system/<%= config[:underscored_project_name] %>/container"
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
require "rspec/core/rake_task"
|
|
9
|
+
RSpec::Core::RakeTask.new :spec
|
|
10
|
+
task default: [:spec]
|
|
11
|
+
rescue LoadError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def db
|
|
15
|
+
<%= config[:camel_cased_app_name] %>::Container["persistence.db"]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def settings
|
|
19
|
+
<%= config[:camel_cased_app_name] %>::Container["settings"]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def database_uri
|
|
23
|
+
require "uri"
|
|
24
|
+
URI.parse(settings.database_url)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def postgres_env_vars(uri)
|
|
28
|
+
{}.tap do |vars|
|
|
29
|
+
vars["PGHOST"] = uri.host
|
|
30
|
+
vars["PGPORT"] = uri.port if uri.port
|
|
31
|
+
vars["PGUSER"] = uri.user if uri.user
|
|
32
|
+
vars["PGPASSWORD"] = uri.password if uri.password
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
namespace :db do
|
|
37
|
+
task :setup do
|
|
38
|
+
<%= config[:camel_cased_app_name] %>::Container.start :rom
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
task :settings do
|
|
42
|
+
<%= config[:camel_cased_app_name] %>::Container.start :settings
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc "Print current database schema version"
|
|
46
|
+
task version: :setup do
|
|
47
|
+
version =
|
|
48
|
+
if db.tables.include?(:schema_migrations)
|
|
49
|
+
db[:schema_migrations].order(:filename).last[:filename]
|
|
50
|
+
else
|
|
51
|
+
"not available"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
puts "Current schema version: #{version}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
desc "Create database"
|
|
58
|
+
task create: :settings do
|
|
59
|
+
if system("which createdb", out: File::NULL)
|
|
60
|
+
uri = database_uri
|
|
61
|
+
system(postgres_env_vars(uri), "createdb #{Shellwords.escape(uri.path[1..-1])}")
|
|
62
|
+
else
|
|
63
|
+
puts "You must have Postgres installed to create a database"
|
|
64
|
+
exit 1
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
desc "Drop database"
|
|
69
|
+
task drop: :settings do
|
|
70
|
+
if system("which dropdb", out: File::NULL)
|
|
71
|
+
uri = database_uri
|
|
72
|
+
system(postgres_env_vars(uri), "dropdb #{Shellwords.escape(uri.path[1..-1])}")
|
|
73
|
+
else
|
|
74
|
+
puts "You must have Postgres installed to drop a database"
|
|
75
|
+
exit 1
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
desc "Migrate database up to latest migration available"
|
|
80
|
+
task :migrate do
|
|
81
|
+
# Enhance the migration task provided by ROM
|
|
82
|
+
|
|
83
|
+
# Once it finishes, dump the db structure
|
|
84
|
+
Rake::Task["db:structure:dump"].execute
|
|
85
|
+
|
|
86
|
+
# And print the current migration version
|
|
87
|
+
Rake::Task["db:version"].execute
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
namespace :structure do
|
|
91
|
+
desc "Dump database structure to db/structure.sql"
|
|
92
|
+
task :dump do
|
|
93
|
+
if system("which pg_dump", out: File::NULL)
|
|
94
|
+
uri = database_uri
|
|
95
|
+
system(postgres_env_vars(uri), "pg_dump -s -x -O #{Shellwords.escape(uri.path[1..-1])}", out: "db/structure.sql")
|
|
96
|
+
else
|
|
97
|
+
puts "You must have pg_dump installed to dump the database structure"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
desc "Load seed data into the database"
|
|
103
|
+
task :seed do
|
|
104
|
+
seed_data = File.join("db", "seed.rb")
|
|
105
|
+
load(seed_data) if File.exist?(seed_data)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
desc "Load a small, representative set of data so that the application can start in a useful state (for development)."
|
|
109
|
+
task :sample_data do
|
|
110
|
+
sample_data = File.join("db", "sample_data.rb")
|
|
111
|
+
load(sample_data) if File.exist?(sample_data)
|
|
112
|
+
end
|
|
113
|
+
end
|