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.
@@ -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
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Erupt
4
+ VERSION = "0.1.0"
5
+ end
data/lib/erupt-sdk.rb ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Lets Bundler auto-require the gem by its name.
4
+ require_relative "erupt"
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"