erupt-sdk 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/README.md +273 -0
- data/lib/erupt/defaults.json +1577 -0
- data/lib/erupt/http_server.rb +115 -0
- data/lib/erupt/model.rb +499 -0
- data/lib/erupt/node.rb +555 -0
- data/lib/erupt/repository.rb +113 -0
- data/lib/erupt/schema.json +2137 -0
- data/lib/erupt/schema.rb +68 -0
- data/lib/erupt/signature.rb +30 -0
- data/lib/erupt/sql.rb +226 -0
- data/lib/erupt/types.rb +300 -0
- data/lib/erupt/version.rb +5 -0
- data/lib/erupt-sdk.rb +4 -0
- data/lib/erupt.rb +12 -0
- data/sig/erupt/types.rbs +408 -0
- metadata +56 -0
data/lib/erupt/schema.rb
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Erupt
|
|
6
|
+
# Checks annotation JSON against erupt-annotations.schema.json (the same schema types.rb is generated from),
|
|
7
|
+
# so a misspelt attribute or an unknown enum value fails when the model is declared instead of silently
|
|
8
|
+
# doing nothing in the browser.
|
|
9
|
+
module Schema
|
|
10
|
+
DEFS = JSON.parse(File.read(File.join(__dir__, "schema.json")))["$defs"]
|
|
11
|
+
|
|
12
|
+
PRIMITIVES = {
|
|
13
|
+
"string" => ->(v) { v.is_a?(String) },
|
|
14
|
+
"integer" => ->(v) { v.is_a?(Integer) },
|
|
15
|
+
"number" => ->(v) { v.is_a?(Numeric) },
|
|
16
|
+
"boolean" => ->(v) { v == true || v == false }
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
def enum_values(name) = DEFS.fetch(name).fetch("enum")
|
|
22
|
+
|
|
23
|
+
# The declared defaults of an annotation, as Java fills an attribute left out.
|
|
24
|
+
def defaults(name) = DEFS.fetch(name)["properties"].each_with_object({}) { |(k, v), h| h[k] = v["default"] if v.key?("default") }
|
|
25
|
+
|
|
26
|
+
# Raise ArgumentError unless +value+ fits definition +name+. +partial+ (an override) lets every attribute
|
|
27
|
+
# be omitted; otherwise the attributes the Java side always emits must be present.
|
|
28
|
+
def check(name, value, path = name, partial: true)
|
|
29
|
+
walk({ "$ref" => "#/$defs/#{name}" }, value, path, partial)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def walk(schema, value, path, partial)
|
|
33
|
+
return walk_ref(schema["$ref"].split("/").last, value, path, partial) if schema["$ref"]
|
|
34
|
+
|
|
35
|
+
kind = schema["type"]
|
|
36
|
+
case kind
|
|
37
|
+
when "array"
|
|
38
|
+
raise ArgumentError, "#{path}: expected an array" unless value.is_a?(Array)
|
|
39
|
+
|
|
40
|
+
value.each_with_index { |item, i| walk(schema["items"], item, "#{path}[#{i}]", partial) }
|
|
41
|
+
when "object"
|
|
42
|
+
raise ArgumentError, "#{path}: expected an object" unless value.is_a?(Hash)
|
|
43
|
+
|
|
44
|
+
value.each { |k, item| walk(schema["additionalProperties"], item, "#{path}.#{k}", partial) } if schema["additionalProperties"]
|
|
45
|
+
else
|
|
46
|
+
raise ArgumentError, "#{path}: expected #{kind}, got #{JSON.generate(value)}" unless PRIMITIVES.fetch(kind).call(value)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def walk_ref(name, value, path, partial)
|
|
51
|
+
d = DEFS.fetch(name)
|
|
52
|
+
if d["enum"]
|
|
53
|
+
return if d["enum"].include?(value)
|
|
54
|
+
|
|
55
|
+
raise ArgumentError, "#{path}: '#{value}' is not a #{name}, expected one of #{d["enum"].join(", ")}"
|
|
56
|
+
end
|
|
57
|
+
raise ArgumentError, "#{path}: expected an object (#{name})" unless value.is_a?(Hash)
|
|
58
|
+
|
|
59
|
+
value.each do |key, item|
|
|
60
|
+
raise ArgumentError, "#{path}: #{name} has no attribute '#{key}'" unless d["properties"].key?(key)
|
|
61
|
+
|
|
62
|
+
walk(d["properties"][key], item, "#{path}.#{key}", partial)
|
|
63
|
+
end
|
|
64
|
+
missing = partial ? [] : (d["required"] || []) - value.keys
|
|
65
|
+
raise ArgumentError, "#{path}: missing #{missing.join(", ")}" unless missing.empty?
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
|
|
5
|
+
module Erupt
|
|
6
|
+
# Erupt-Signature (erupt-cloud/PROTOCOL.md §4.3): an HMAC-SHA256 the server puts on every forward, keyed by
|
|
7
|
+
# the node's access token, so the token need not travel and the signed headers cannot be forged.
|
|
8
|
+
module Signature
|
|
9
|
+
WINDOW_SECONDS = 300
|
|
10
|
+
UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# +body+ is the raw bytes, or nil for a multipart upload the server re-encodes (not covered).
|
|
15
|
+
def sign(access_token, method, path_and_query, timestamp, enforced, user, body)
|
|
16
|
+
canonical = [method.upcase, path_and_query, timestamp.to_s, enforced.to_s, sha256(user.to_s),
|
|
17
|
+
body.nil? ? UNSIGNED_PAYLOAD : sha256(body)].join("\n")
|
|
18
|
+
OpenSSL::HMAC.hexdigest("SHA256", access_token, canonical)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def verify(access_token, method, path_and_query, timestamp, enforced, user, body, signature, now)
|
|
22
|
+
ts = Integer(timestamp.to_s.strip, 10, exception: false)
|
|
23
|
+
return false if ts.nil? || (now - ts).abs > WINDOW_SECONDS
|
|
24
|
+
|
|
25
|
+
OpenSSL.secure_compare(sign(access_token, method, path_and_query, ts, enforced, user, body), signature.to_s.strip)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def sha256(data) = OpenSSL::Digest::SHA256.hexdigest(data)
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/erupt/sql.rb
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Erupt
|
|
4
|
+
# What SqlRepository needs from a database: +call(sql, params)+ runs one statement with bound values and
|
|
5
|
+
# returns the rows of a SELECT as hashes keyed by column name; +dialect+ (:sqlite, :postgres, :mysql) picks
|
|
6
|
+
# identifier quotes, placeholders and LIKE. The SDK depends on no driver: the adapters below only call the
|
|
7
|
+
# driver object they are handed, and any other library takes a block:
|
|
8
|
+
#
|
|
9
|
+
# Erupt::SqlExecutor.new(:postgres) do |sql, params|
|
|
10
|
+
# ActiveRecord::Base.connection_pool.with_connection { |c| c.raw_connection.exec_params(sql, params).to_a }
|
|
11
|
+
# end
|
|
12
|
+
class SqlExecutor
|
|
13
|
+
DIALECTS = %i[sqlite postgres mysql].freeze
|
|
14
|
+
|
|
15
|
+
attr_reader :dialect
|
|
16
|
+
|
|
17
|
+
def initialize(dialect, &run)
|
|
18
|
+
@dialect = dialect.to_sym
|
|
19
|
+
raise ArgumentError, "dialect must be one of #{DIALECTS.join(", ")}" unless DIALECTS.include?(@dialect)
|
|
20
|
+
|
|
21
|
+
@run = run or raise ArgumentError, "a block running (sql, params) is required"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call(sql, params) = @run.call(sql, params)
|
|
25
|
+
|
|
26
|
+
# pg gem: a PG::Connection, or a ConnectionPool of them.
|
|
27
|
+
def self.pg(db) = adapter(:postgres, db) { |c, sql, params| c.exec_params(sql, params).to_a }
|
|
28
|
+
|
|
29
|
+
# mysql2 gem: a Mysql2::Client, or a ConnectionPool of them; statements are prepared, so values are bound.
|
|
30
|
+
def self.mysql2(db)
|
|
31
|
+
adapter(:mysql, db) do |c, sql, params|
|
|
32
|
+
statement = c.prepare(sql)
|
|
33
|
+
begin
|
|
34
|
+
statement.execute(*params)&.to_a || []
|
|
35
|
+
ensure
|
|
36
|
+
statement.close
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# sqlite3 gem: a SQLite3::Database, or a ConnectionPool of them.
|
|
42
|
+
def self.sqlite3(db)
|
|
43
|
+
adapter(:sqlite, db) do |c, sql, params|
|
|
44
|
+
statement = c.prepare(sql)
|
|
45
|
+
begin
|
|
46
|
+
statement.execute(*params).map { |row| row.is_a?(Hash) ? row : statement.columns.zip(row).to_h }
|
|
47
|
+
ensure
|
|
48
|
+
statement.close
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A pool (anything with +with+, like the connection_pool gem) lends a connection per statement; a single
|
|
54
|
+
# connection is used by one thread at a time.
|
|
55
|
+
def self.adapter(dialect, db, &run)
|
|
56
|
+
lock = Mutex.new
|
|
57
|
+
new(dialect) do |sql, params|
|
|
58
|
+
db.respond_to?(:with) ? db.with { |c| run.call(c, sql, params) } : lock.synchronize { run.call(db, sql, params) }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
private_class_method :adapter
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# A table on PostgreSQL, MySQL or SQLite through a SqlExecutor. Column names are the model's field names
|
|
65
|
+
# (quoted, so case sensitive on PostgreSQL); every value is a bound parameter; LIKE escapes with
|
|
66
|
+
# <tt>ESCAPE '!'</tt>, which every database reads the same way. Without a sort, rows come back by primary key
|
|
67
|
+
# ascending. +auto_create+ creates a missing SQLite table: create production tables yourself.
|
|
68
|
+
class SqlRepository < Repository
|
|
69
|
+
QUOTES = { sqlite: %w[" "], postgres: %w[" "], mysql: %w[` `] }.freeze
|
|
70
|
+
OPS = { "EQ" => "=", "NEQ" => "<>", "GT" => ">", "GTE" => ">=", "LT" => "<", "LTE" => "<=" }.freeze
|
|
71
|
+
SQLITE_TYPES = { EditType::NUMBER => "NUMERIC", EditType::BOOLEAN => "INTEGER", EditType::DATE => "DATE",
|
|
72
|
+
DATE_TIME => "TIMESTAMP", EditType::REFERENCE_TABLE => "INTEGER",
|
|
73
|
+
EditType::REFERENCE_TREE => "INTEGER" }.freeze
|
|
74
|
+
|
|
75
|
+
# Collects bound values and writes the dialect's placeholder for each.
|
|
76
|
+
class Params
|
|
77
|
+
attr_reader :values
|
|
78
|
+
|
|
79
|
+
def initialize(dialect)
|
|
80
|
+
@dialect, @values = dialect, []
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def <<(value)
|
|
84
|
+
@values << bindable(value)
|
|
85
|
+
@dialect == :postgres ? "$#{@values.size}" : "?"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def bindable(value)
|
|
91
|
+
case value
|
|
92
|
+
when true, false then @dialect == :postgres ? value : (value ? 1 : 0)
|
|
93
|
+
when Time, DateTime then value.strftime("%Y-%m-%d %H:%M:%S.%L")
|
|
94
|
+
when Date then value.strftime("%Y-%m-%d")
|
|
95
|
+
else value
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def initialize(executor, table, auto_create: false)
|
|
101
|
+
super()
|
|
102
|
+
raise ArgumentError, "illegal table name '#{table}'" unless IDENTIFIER.match?(table.to_s)
|
|
103
|
+
raise ArgumentError, "auto_create creates SQLite tables only" if auto_create && executor.dialect != :sqlite
|
|
104
|
+
|
|
105
|
+
@executor, @table, @auto_create = executor, table.to_s, auto_create
|
|
106
|
+
@dialect = executor.dialect
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def bind(model)
|
|
110
|
+
super
|
|
111
|
+
@columns = model.fields.keys.to_h { |name| [name.downcase, name] }
|
|
112
|
+
return unless @auto_create
|
|
113
|
+
|
|
114
|
+
cols = model.fields.each_value.map do |f|
|
|
115
|
+
f.pk? ? "#{q(f.name)} INTEGER PRIMARY KEY AUTOINCREMENT" : "#{q(f.name)} #{SQLITE_TYPES.fetch(f.type, "TEXT")}"
|
|
116
|
+
end
|
|
117
|
+
@executor.call("CREATE TABLE IF NOT EXISTS #{q(@table)} (#{cols.join(", ")})", [])
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def page(query)
|
|
121
|
+
p = Params.new(@dialect)
|
|
122
|
+
total = @executor.call("SELECT COUNT(*) AS n FROM #{q(@table)}#{where(query, p)}", p.values).first
|
|
123
|
+
p = Params.new(@dialect)
|
|
124
|
+
sql = "SELECT * FROM #{q(@table)}#{where(query, p)}"
|
|
125
|
+
order = (query.sort.empty? ? [[model.pk, false]] : query.sort).map { |name, desc| "#{q(name)} #{desc ? "DESC" : "ASC"}" }
|
|
126
|
+
sql += " ORDER BY #{order.join(", ")} LIMIT #{p << query.limit} OFFSET #{p << query.offset}"
|
|
127
|
+
[Integer(total.values.first.to_s, 10), @executor.call(sql, p.values).map { |row| record(row) }]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def get(pk)
|
|
131
|
+
p = Params.new(@dialect)
|
|
132
|
+
row = @executor.call("SELECT * FROM #{q(@table)} WHERE #{q(model.pk)} = #{p << pk}", p.values).first
|
|
133
|
+
row && record(row)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def add(record)
|
|
137
|
+
record = store(record)
|
|
138
|
+
p = Params.new(@dialect)
|
|
139
|
+
values = record.values.map { |v| p << v }
|
|
140
|
+
@executor.call("INSERT INTO #{q(@table)} (#{record.keys.map { |k| q(k) }.join(", ")}) VALUES (#{values.join(", ")})", p.values)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def update(pk, record)
|
|
144
|
+
record = store(record)
|
|
145
|
+
return if record.empty?
|
|
146
|
+
|
|
147
|
+
p = Params.new(@dialect)
|
|
148
|
+
sets = record.map { |k, v| "#{q(k)} = #{p << v}" }
|
|
149
|
+
@executor.call("UPDATE #{q(@table)} SET #{sets.join(", ")} WHERE #{q(model.pk)} = #{p << pk}", p.values)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def delete(pks)
|
|
153
|
+
return if pks.empty?
|
|
154
|
+
|
|
155
|
+
p = Params.new(@dialect)
|
|
156
|
+
marks = pks.map { |pk| p << pk }
|
|
157
|
+
@executor.call("DELETE FROM #{q(@table)} WHERE #{q(model.pk)} IN (#{marks.join(", ")})", p.values)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# A quoted identifier; the closing quote is doubled.
|
|
161
|
+
def q(identifier)
|
|
162
|
+
open, close = QUOTES.fetch(@dialect)
|
|
163
|
+
"#{open}#{identifier.gsub(close, close * 2)}#{close}"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
# Values as columns hold them: a JSON value as its text
|
|
169
|
+
def store(record)
|
|
170
|
+
keys(record).except(model.pk).to_h { |k, v| [k, model.fields[k]&.json? && !v.nil? ? JSON.generate(v) : v] }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def where(query, p)
|
|
174
|
+
clauses = query.filters.map do |sql, params|
|
|
175
|
+
# the developer's own SQL, as written; with params, each '?' is one of them, bound like every other value
|
|
176
|
+
pieces = params.empty? ? [sql] : sql.split("?", -1)
|
|
177
|
+
raise ArgumentError, "filter: #{pieces.size - 1} '?' for #{params.size} params in #{sql.inspect}" if pieces.size - 1 != params.size
|
|
178
|
+
|
|
179
|
+
"(#{pieces.first}#{params.zip(pieces.drop(1)).map { |v, piece| (p << v) + piece }.join})"
|
|
180
|
+
end
|
|
181
|
+
clauses += query.conditions.map do |c|
|
|
182
|
+
col = q(c.field)
|
|
183
|
+
values = Array(c.value)
|
|
184
|
+
case c.op
|
|
185
|
+
when *OPS.keys then "#{col} #{OPS[c.op]} #{p << c.value}"
|
|
186
|
+
when "LIKE", "NOT_LIKE"
|
|
187
|
+
# '!' rather than a backslash: MySQL reads a backslash inside a string literal as an escape itself;
|
|
188
|
+
# ILIKE because PostgreSQL's LIKE is case sensitive, the others' (and the memory repository's) is not
|
|
189
|
+
pattern = "%#{c.value.to_s.gsub(/[!%_]/) { |ch| "!#{ch}" }}%"
|
|
190
|
+
"#{col} #{"NOT " if c.op == "NOT_LIKE"}#{@dialect == :postgres ? "ILIKE" : "LIKE"} #{p << pattern} ESCAPE '!'"
|
|
191
|
+
when "RANGE" then "#{col} BETWEEN #{p << values[0]} AND #{p << values[1]}"
|
|
192
|
+
when "IN", "NOT_IN"
|
|
193
|
+
next(c.op == "IN" ? "1=0" : "1=1") if values.empty?
|
|
194
|
+
|
|
195
|
+
"#{col} #{"NOT " if c.op == "NOT_IN"}IN (#{values.map { |v| p << v }.join(", ")})"
|
|
196
|
+
when "NULL" then "#{col} IS NULL"
|
|
197
|
+
when "NOT_NULL" then "#{col} IS NOT NULL"
|
|
198
|
+
else raise ArgumentError, "illegal operator '#{c.op}'"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
clauses.empty? ? "" : " WHERE #{clauses.join(" AND ")}"
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# A driver row as a record: columns matched to fields case-insensitively, values normalized by field type
|
|
205
|
+
# (pg returns text, mysql2 Time / BigDecimal, sqlite3 what was stored).
|
|
206
|
+
def record(row)
|
|
207
|
+
row = row.to_h { |k, v| [@columns.fetch(k.to_s.downcase, k.to_s), v] }
|
|
208
|
+
model.fields.each_value.to_h do |f|
|
|
209
|
+
value = row[f.name]
|
|
210
|
+
[f.name, value.nil? ? nil : normalize(f, value)]
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def normalize(field, value)
|
|
215
|
+
return value.is_a?(String) ? JSON.parse(value) : value if field.json?
|
|
216
|
+
|
|
217
|
+
case field.type
|
|
218
|
+
when EditType::BOOLEAN then [true, 1, "1", "t", "true"].include?(value)
|
|
219
|
+
when EditType::DATE then Erupt.parse_time(value).to_date
|
|
220
|
+
when DATE_TIME then Erupt.parse_time(value)
|
|
221
|
+
when EditType::NUMBER, *REFERENCES then Erupt.number(value)
|
|
222
|
+
else field.pk? ? Erupt.number(value) : value
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
data/lib/erupt/types.rb
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Generated by tools/gen_types.rb from erupt-annotations.schema.json; do not edit.
|
|
4
|
+
module Erupt
|
|
5
|
+
# Values of +AttachmentTypeType+: +BASE+ instead of the string "BASE".
|
|
6
|
+
module AttachmentTypeType
|
|
7
|
+
BASE = "BASE"
|
|
8
|
+
IMAGE = "IMAGE"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Values of +BoolTypeType+: +AUTO+ instead of the string "AUTO".
|
|
12
|
+
module BoolTypeType
|
|
13
|
+
AUTO = "AUTO"
|
|
14
|
+
RADIO = "RADIO"
|
|
15
|
+
SWITCH = "SWITCH"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Values of +CalendarViewMode+: +MONTH+ instead of the string "MONTH".
|
|
19
|
+
module CalendarViewMode
|
|
20
|
+
MONTH = "MONTH"
|
|
21
|
+
WEEK = "WEEK"
|
|
22
|
+
YEAR = "YEAR"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Values of +CalloutTypeStyle+: +CARD+ instead of the string "CARD".
|
|
26
|
+
module CalloutTypeStyle
|
|
27
|
+
CARD = "CARD"
|
|
28
|
+
INFO = "INFO"
|
|
29
|
+
SUCCESS = "SUCCESS"
|
|
30
|
+
WARNING = "WARNING"
|
|
31
|
+
ERROR = "ERROR"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Values of +CardViewCoverEffect+: +FIT+ instead of the string "FIT".
|
|
35
|
+
module CardViewCoverEffect
|
|
36
|
+
FIT = "FIT"
|
|
37
|
+
CLIP = "CLIP"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Values of +ChoiceTypeType+: +SELECT+ instead of the string "SELECT".
|
|
41
|
+
module ChoiceTypeType
|
|
42
|
+
SELECT = "SELECT"
|
|
43
|
+
RADIO = "RADIO"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Values of +CubeChartType+: +LINE+ instead of the string "LINE".
|
|
47
|
+
module CubeChartType
|
|
48
|
+
LINE = "LINE"
|
|
49
|
+
AREA = "AREA"
|
|
50
|
+
BAR = "BAR"
|
|
51
|
+
COLUMN = "COLUMN"
|
|
52
|
+
PIE = "PIE"
|
|
53
|
+
SCATTER = "SCATTER"
|
|
54
|
+
RADAR = "RADAR"
|
|
55
|
+
FUNNEL = "FUNNEL"
|
|
56
|
+
DUAL_AXES = "DUAL_AXES"
|
|
57
|
+
GAUGE = "GAUGE"
|
|
58
|
+
WATERFALL = "WATERFALL"
|
|
59
|
+
WORD_CLOUD = "WORD_CLOUD"
|
|
60
|
+
ROSE = "ROSE"
|
|
61
|
+
RADIAL_BAR = "RADIAL_BAR"
|
|
62
|
+
SANKEY = "SANKEY"
|
|
63
|
+
CHORD = "CHORD"
|
|
64
|
+
MAP = "MAP"
|
|
65
|
+
BUBBLE = "BUBBLE"
|
|
66
|
+
TREEMAP = "TREEMAP"
|
|
67
|
+
HEATMAP = "HEATMAP"
|
|
68
|
+
TINY_LINE = "TINY_LINE"
|
|
69
|
+
TINY_AREA = "TINY_AREA"
|
|
70
|
+
TINY_COLUMN = "TINY_COLUMN"
|
|
71
|
+
PROGRESS = "PROGRESS"
|
|
72
|
+
RING_PROGRESS = "RING_PROGRESS"
|
|
73
|
+
TABLE = "TABLE"
|
|
74
|
+
PIVOT_TABLE = "PIVOT_TABLE"
|
|
75
|
+
KPI = "KPI"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Values of +DateTypePickerMode+: +ALL+ instead of the string "ALL".
|
|
79
|
+
module DateTypePickerMode
|
|
80
|
+
ALL = "ALL"
|
|
81
|
+
FUTURE = "FUTURE"
|
|
82
|
+
HISTORY = "HISTORY"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Values of +DateTypeType+: +AUTO+ instead of the string "AUTO".
|
|
86
|
+
module DateTypeType
|
|
87
|
+
AUTO = "AUTO"
|
|
88
|
+
DATE = "DATE"
|
|
89
|
+
TIME = "TIME"
|
|
90
|
+
DATE_TIME = "DATE_TIME"
|
|
91
|
+
MONTH = "MONTH"
|
|
92
|
+
WEEK = "WEEK"
|
|
93
|
+
QUARTER = "QUARTER"
|
|
94
|
+
YEAR = "YEAR"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Values of +DynamicCtrl+: +SHOW+ instead of the string "SHOW".
|
|
98
|
+
module DynamicCtrl
|
|
99
|
+
SHOW = "SHOW"
|
|
100
|
+
HIDE = "HIDE"
|
|
101
|
+
NOTNULL = "NOTNULL"
|
|
102
|
+
READONLY = "READONLY"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Values of +EditType+: +AUTO+ instead of the string "AUTO".
|
|
106
|
+
module EditType
|
|
107
|
+
AUTO = "AUTO"
|
|
108
|
+
INPUT = "INPUT"
|
|
109
|
+
PASSWORD = "PASSWORD"
|
|
110
|
+
NUMBER = "NUMBER"
|
|
111
|
+
SLIDER = "SLIDER"
|
|
112
|
+
COLOR = "COLOR"
|
|
113
|
+
ICON = "ICON"
|
|
114
|
+
KEY_VALUE = "KEY_VALUE"
|
|
115
|
+
RATE = "RATE"
|
|
116
|
+
DATE = "DATE"
|
|
117
|
+
BOOLEAN = "BOOLEAN"
|
|
118
|
+
CHOICE = "CHOICE"
|
|
119
|
+
MULTI_CHOICE = "MULTI_CHOICE"
|
|
120
|
+
TAGS = "TAGS"
|
|
121
|
+
AUTO_COMPLETE = "AUTO_COMPLETE"
|
|
122
|
+
TEXTAREA = "TEXTAREA"
|
|
123
|
+
HTML_EDITOR = "HTML_EDITOR"
|
|
124
|
+
CODE_EDITOR = "CODE_EDITOR"
|
|
125
|
+
MARKDOWN = "MARKDOWN"
|
|
126
|
+
ATTACHMENT = "ATTACHMENT"
|
|
127
|
+
MAP = "MAP"
|
|
128
|
+
TPL = "TPL"
|
|
129
|
+
DIVIDE = "DIVIDE"
|
|
130
|
+
GROUP = "GROUP"
|
|
131
|
+
CALLOUT = "CALLOUT"
|
|
132
|
+
BUTTON = "BUTTON"
|
|
133
|
+
HIDDEN = "HIDDEN"
|
|
134
|
+
EMPTY = "EMPTY"
|
|
135
|
+
SIGNATURE = "SIGNATURE"
|
|
136
|
+
REFERENCE_TREE = "REFERENCE_TREE"
|
|
137
|
+
REFERENCE_TABLE = "REFERENCE_TABLE"
|
|
138
|
+
CHECKBOX = "CHECKBOX"
|
|
139
|
+
TRANSFER = "TRANSFER"
|
|
140
|
+
TAB_TREE = "TAB_TREE"
|
|
141
|
+
TAB_TABLE_REFER = "TAB_TABLE_REFER"
|
|
142
|
+
TAB_TABLE_ADD = "TAB_TABLE_ADD"
|
|
143
|
+
MULTI_FORM = "MULTI_FORM"
|
|
144
|
+
COMBINE = "COMBINE"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Values of +HtmlEditorTypeType+: +CKEDITOR+ instead of the string "CKEDITOR".
|
|
148
|
+
module HtmlEditorTypeType
|
|
149
|
+
CKEDITOR = "CKEDITOR"
|
|
150
|
+
UEDITOR = "UEDITOR"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Values of +LayoutFormSize+: +DEFAULT+ instead of the string "DEFAULT".
|
|
154
|
+
module LayoutFormSize
|
|
155
|
+
DEFAULT = "DEFAULT"
|
|
156
|
+
FULL_LINE = "FULL_LINE"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Values of +LayoutPagingType+: +BACKEND+ instead of the string "BACKEND".
|
|
160
|
+
module LayoutPagingType
|
|
161
|
+
BACKEND = "BACKEND"
|
|
162
|
+
FRONT = "FRONT"
|
|
163
|
+
NONE = "NONE"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Values of +MultiChoiceTypeType+: +SELECT+ instead of the string "SELECT".
|
|
167
|
+
module MultiChoiceTypeType
|
|
168
|
+
SELECT = "SELECT"
|
|
169
|
+
CHECKBOX = "CHECKBOX"
|
|
170
|
+
TRANSFER = "TRANSFER"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Values of +OpenWay+: +DRAWER+ instead of the string "DRAWER".
|
|
174
|
+
module OpenWay
|
|
175
|
+
DRAWER = "DRAWER"
|
|
176
|
+
MODAL = "MODAL"
|
|
177
|
+
ROUTER = "ROUTER"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Values of +PageEmbedType+: +IFRAME+ instead of the string "IFRAME".
|
|
181
|
+
module PageEmbedType
|
|
182
|
+
IFRAME = "IFRAME"
|
|
183
|
+
MICRO_FRONTEND = "MICRO_FRONTEND"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Values of +Placement+: +TOP+ instead of the string "TOP".
|
|
187
|
+
module Placement
|
|
188
|
+
TOP = "TOP"
|
|
189
|
+
BOTTOM = "BOTTOM"
|
|
190
|
+
LEFT = "LEFT"
|
|
191
|
+
RIGHT = "RIGHT"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Values of +QueryExpression+: +AUTO+ instead of the string "AUTO".
|
|
195
|
+
module QueryExpression
|
|
196
|
+
AUTO = "AUTO"
|
|
197
|
+
EQ = "EQ"
|
|
198
|
+
NEQ = "NEQ"
|
|
199
|
+
GT = "GT"
|
|
200
|
+
GTE = "GTE"
|
|
201
|
+
LT = "LT"
|
|
202
|
+
LTE = "LTE"
|
|
203
|
+
LIKE = "LIKE"
|
|
204
|
+
NOT_LIKE = "NOT_LIKE"
|
|
205
|
+
RANGE = "RANGE"
|
|
206
|
+
IN = "IN"
|
|
207
|
+
NOT_IN = "NOT_IN"
|
|
208
|
+
NULL = "NULL"
|
|
209
|
+
NOT_NULL = "NOT_NULL"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Values of +RowOperationIfExprBehavior+: +HIDE+ instead of the string "HIDE".
|
|
213
|
+
module RowOperationIfExprBehavior
|
|
214
|
+
HIDE = "HIDE"
|
|
215
|
+
DISABLE = "DISABLE"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Values of +RowOperationMode+: +SINGLE+ instead of the string "SINGLE".
|
|
219
|
+
module RowOperationMode
|
|
220
|
+
SINGLE = "SINGLE"
|
|
221
|
+
MULTI = "MULTI"
|
|
222
|
+
MULTI_ONLY = "MULTI_ONLY"
|
|
223
|
+
BUTTON = "BUTTON"
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Values of +RowOperationType+: +ERUPT+ instead of the string "ERUPT".
|
|
227
|
+
module RowOperationType
|
|
228
|
+
ERUPT = "ERUPT"
|
|
229
|
+
TPL = "TPL"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Values of +TimelineViewMode+: +LEFT+ instead of the string "LEFT".
|
|
233
|
+
module TimelineViewMode
|
|
234
|
+
LEFT = "LEFT"
|
|
235
|
+
ALTERNATE = "ALTERNATE"
|
|
236
|
+
RIGHT = "RIGHT"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Values of +ViewStatistic+: +NONE+ instead of the string "NONE".
|
|
240
|
+
module ViewStatistic
|
|
241
|
+
NONE = "NONE"
|
|
242
|
+
COUNT = "COUNT"
|
|
243
|
+
DISTINCT_COUNT = "DISTINCT_COUNT"
|
|
244
|
+
SUM = "SUM"
|
|
245
|
+
AVG = "AVG"
|
|
246
|
+
MAX = "MAX"
|
|
247
|
+
MIN = "MIN"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Values of +ViewType+: +AUTO+ instead of the string "AUTO".
|
|
251
|
+
module ViewType
|
|
252
|
+
AUTO = "AUTO"
|
|
253
|
+
TEXT = "TEXT"
|
|
254
|
+
COLOR = "COLOR"
|
|
255
|
+
ICON = "ICON"
|
|
256
|
+
KEY_VALUE = "KEY_VALUE"
|
|
257
|
+
SAFE_TEXT = "SAFE_TEXT"
|
|
258
|
+
IMAGE = "IMAGE"
|
|
259
|
+
AVATAR = "AVATAR"
|
|
260
|
+
IMAGE_BASE64 = "IMAGE_BASE64"
|
|
261
|
+
SWF = "SWF"
|
|
262
|
+
HTML = "HTML"
|
|
263
|
+
MOBILE_HTML = "MOBILE_HTML"
|
|
264
|
+
QR_CODE = "QR_CODE"
|
|
265
|
+
LINK = "LINK"
|
|
266
|
+
LINK_DIALOG = "LINK_DIALOG"
|
|
267
|
+
DOWNLOAD = "DOWNLOAD"
|
|
268
|
+
ATTACHMENT = "ATTACHMENT"
|
|
269
|
+
ATTACHMENT_DIALOG = "ATTACHMENT_DIALOG"
|
|
270
|
+
DATE = "DATE"
|
|
271
|
+
DATE_TIME = "DATE_TIME"
|
|
272
|
+
BOOLEAN = "BOOLEAN"
|
|
273
|
+
NUMBER = "NUMBER"
|
|
274
|
+
PROGRESS = "PROGRESS"
|
|
275
|
+
MAP = "MAP"
|
|
276
|
+
CODE = "CODE"
|
|
277
|
+
TAB_VIEW = "TAB_VIEW"
|
|
278
|
+
MARKDOWN = "MARKDOWN"
|
|
279
|
+
PASSWORD = "PASSWORD"
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Values of +VisFieldVisibility+: +INCLUDE+ instead of the string "INCLUDE".
|
|
283
|
+
module VisFieldVisibility
|
|
284
|
+
INCLUDE = "INCLUDE"
|
|
285
|
+
EXCLUDE = "EXCLUDE"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Values of +VisType+: +TABLE+ instead of the string "TABLE".
|
|
289
|
+
module VisType
|
|
290
|
+
TABLE = "TABLE"
|
|
291
|
+
GANTT = "GANTT"
|
|
292
|
+
CARD = "CARD"
|
|
293
|
+
BOARD = "BOARD"
|
|
294
|
+
CALENDAR = "CALENDAR"
|
|
295
|
+
TIMELINE = "TIMELINE"
|
|
296
|
+
MAP = "MAP"
|
|
297
|
+
CUBE = "CUBE"
|
|
298
|
+
TPL = "TPL"
|
|
299
|
+
end
|
|
300
|
+
end
|
data/lib/erupt-sdk.rb
ADDED
data/lib/erupt.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Erupt node SDK: give any Ruby service an erupt admin UI through an erupt cloud server.
|
|
4
|
+
require_relative "erupt/version"
|
|
5
|
+
require_relative "erupt/types"
|
|
6
|
+
require_relative "erupt/schema"
|
|
7
|
+
require_relative "erupt/signature"
|
|
8
|
+
require_relative "erupt/model"
|
|
9
|
+
require_relative "erupt/repository"
|
|
10
|
+
require_relative "erupt/sql"
|
|
11
|
+
require_relative "erupt/http_server"
|
|
12
|
+
require_relative "erupt/node"
|