first version of todo app with toy database
This commit is contained in:
parent
21c911ffd3
commit
7dc3475e35
5 changed files with 204 additions and 13 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
99
src/karaxdb/client.nim
Normal 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
17
src/karaxdb/common.nim
Normal 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
56
src/karaxdb/server.nim
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue