first version of todo app with toy database

This commit is contained in:
Andreas Rumpf 2017-05-06 21:10:13 +02:00
commit 7dc3475e35
5 changed files with 204 additions and 13 deletions

View file

@ -1,5 +1,5 @@
import vdom, karax, karaxdsl, jstrutils, components, localstorage
import vdom, karax, karaxdsl, jstrutils, components, karaxdb/client
type
Filter = enum
@ -9,6 +9,12 @@ var
selectedEntry = -1
filter: Filter
entriesLen: int
data: Db
registerOnUpdate proc(newDb: Db) =
merge(newDb, data)
data = newDb
redraw()
const
contentSuffix = cstring"content"
@ -16,25 +22,23 @@ const
lenSuffix = cstring"entriesLen"
proc getEntryContent(pos: int): cstring =
result = getItem(&pos & contentSuffix)
if result == cstring"null":
result = nil
extract(data, &pos, contentSuffix)
proc isCompleted(pos: int): bool =
var value = getItem(&pos & completedSuffix)
var value = extract(data, &pos, completedSuffix)
result = value == cstring"true"
proc setEntryContent(pos: int, content: cstring) =
setItem(&pos & contentSuffix, content)
insert(data, &pos, contentSuffix, content)
proc markAsCompleted(pos: int, completed: bool) =
setItem(&pos & completedSuffix, &completed)
insert(data, &pos, completedSuffix, &completed)
proc addEntry(content: cstring, completed: bool) =
setEntryContent(entriesLen, content)
markAsCompleted(entriesLen, completed)
inc entriesLen
setItem(lenSuffix, &entriesLen)
insert(data, lenSuffix, "equals", &entriesLen)
proc updateEntry(pos: int, content: cstring, completed: bool) =
setEntryContent(pos, content)
@ -61,7 +65,7 @@ proc toggleEntry(ev: Event; n: VNode) =
markAsCompleted(id, not isCompleted(id))
proc onAllDone(ev: Event; n: VNode) =
clear()
insert(data, lenSuffix, "equals", "0")
selectedEntry = -1
proc clearCompleted(ev: Event, n: VNode) =
@ -150,8 +154,5 @@ setOnHashChange(proc(hash: cstring) =
elif hash == "#/active": filter = active
)
if hasItem(lenSuffix):
entriesLen = parseInt getItem(lenSuffix)
else:
entriesLen = 0
entriesLen = 0
setRenderer createDom

View file

@ -48,3 +48,21 @@ proc ajaxGet*(url: cstring; headers: openarray[(cstring, cstring)];
proc toJson*[T](data: T): cstring {.importc: "JSON.stringify".}
proc fromJson*[T](blob: cstring): T {.importc: "JSON.parse".}
type
MessageEvent* {.importc.} = ref object
data*: cstring
ErrorEvent* {.importc.} = ref object
CloseEvent* {.importc.} = ref object
code*: int
reason*: cstring
WebSocket* {.importc.} = ref object
onopen*: proc()
onmessage*: proc(ev: MessageEvent)
onclose*: proc(ev: CloseEvent)
onerror*: proc(ev: ErrorEvent)
proc newWebsocket*(url, protocol: cstring): WebSocket {.importc: "new WebSocket".}
proc send*(socket: WebSocket, data: cstring) {.importcpp.}

99
src/karaxdb/client.nim Normal file
View file

@ -0,0 +1,99 @@
import "../kajax", "../jdict", common
export common
from "../karax" import kout
type
Db* = ref object
data: seq[Triple]
version*: int
next: Db
Query* = object
constraints*: set[TripleKind]
t*: Triple
Message {.importc.} = ref object
kind: MessageKind
data: seq[Triple]
version: int
id: MessageId
# RequestMessage {.importc.} = ref object
let conn = newWebSocket("ws://localhost:8080", "karaxdb")
var gid: MessageId
var pendingIds = newJDict[MessageId, Message]()
var pending: int
#proc loadDb*(url: cstring): Db =
# result = nil
proc newTransaction*(): Db =
result = Db(data: @[])
proc insert*(head, newdb: Db) =
newdb.next = head
#result = newdb
let expectedVersion = if not head.isNil: head.version + 1 else: 1
inc gid.int
let m = Message(kind: NewData, data: newdb.data, version: expectedVersion, id: gid)
pendingIds[gid] = m
inc pending
conn.send(toJson(m))
proc merge*(newer, older: Db) =
newer.next = older
proc registerOnUpdate*(update: proc(db: Db)) =
conn.onmessage =
proc (e: MessageEvent) =
let msg = fromJson[Message](e.data)
case msg.kind
of Conflict:
# conflict, so throw away the sent data, don't apply the changes:
if pending > 0:
pendingIds.del msg.id
dec pending
# server sent data that caused the conflict:
if msg.data.len > 0:
let db = Db(data: msg.data, version: msg.version)
update(db)
of Accepted:
kout cstring"accepted", pending
if pending > 0:
let d = pendingIds[msg.id]
# data was not submitted again, so we use the in-memory version
# of the data:
let db = Db(data: d.data, version: d.version)
pendingIds.del msg.id
dec pending
update(db)
of Newdata:
let db = Db(data: msg.data, version: msg.version)
update(db)
iterator list*(db: Db, q: Query): Triple =
# XXX here the datamodel comes in! We must not
# yield outdated data!
var it = db
while it != nil:
for d in it.data:
var match = true
for k in q.constraints:
if d[k] != q.t[k]: match = false
if match: yield d
it = it.next
proc extract*(db: Db, q: Query): Triple =
for x in list(db, q):
result = x
break
proc extract*(db: Db, subj, pred: kstring): kstring =
let q = Query(constraints: {Subj, Pred}, t: [subj, pred, ""])
result = extract(db, q)[Obj]
proc insert*(db: Db, subj, pred, obj: kstring) =
let result = Db()
result.data.add([subj, pred, obj])
insert(db, result)

17
src/karaxdb/common.nim Normal file
View file

@ -0,0 +1,17 @@
when defined(js):
type kstring* = cstring
else:
type kstring* = string
type
TripleKind* = enum
Subj, Pred, Obj
DbValue* = kstring
Triple* = array[TripleKind, DbValue]
MessageId* = distinct int
MessageKind* = enum
Newdata, Conflict, Accepted

56
src/karaxdb/server.nim Normal file
View file

@ -0,0 +1,56 @@
import asynchttpserver, asyncdispatch, asyncnet, "../../../websocket/websocket", common, json
type
Message = object
kind: MessageKind
id: MessageId
data: seq[Triple]
version: int
proc `%`(id: MessageId): JsonNode = %BiggestInt(id)
proc `%`(k: MessageKind): JsonNode = %BiggestInt(k)
proc triplesFromJson(j: JsonNode): seq[Triple] =
result = newSeq[Triple](j.len)
var i = 0
for t in j:
doAssert t.kind == JArray
result[i] = [t[0].str, t[1].str, t[2].str]
inc i
proc messageFromJson(j: JsonNode): Message =
Message(kind: MessageKind(j["kind"].num), id: MessageId(j["id"].num),
data: triplesFromJson(j["data"]), version: j["version"].num.int)
var server = newAsyncHttpServer()
proc cb(req: Request) {.async.} =
let (success, error) = await(verifyWebsocketRequest(req, "karaxdb"))
if not success:
echo "WS negotiation failed: " & error
await req.respond(Http400, "Websocket negotiation failed: " & error)
req.client.close
else:
echo "New websocket customer arrived!"
while true:
try:
var f = await req.client.readData(false)
echo "(opcode: " & $f.opcode & ", data: " & $f.data.len & ")"
let m = messageFromJson(f.data.parseJson)
let om = Message(kind: Accepted, id: m.id, data: @[], version: m.version)
let oms = $(%*om)
echo "OUTPUT ", oms
if f.opcode == Opcode.Text:
waitFor req.client.sendText(oms, false)
else:
echo "protocol error"
#waitFor req.client.sendBinary(f.data, false)
except:
echo getCurrentExceptionMsg()
break
req.client.close()
echo ".. socket went away."
waitFor server.serve(Port(8080), cb)