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
|
type
|
||||||
Filter = enum
|
Filter = enum
|
||||||
|
|
@ -9,6 +9,12 @@ var
|
||||||
selectedEntry = -1
|
selectedEntry = -1
|
||||||
filter: Filter
|
filter: Filter
|
||||||
entriesLen: int
|
entriesLen: int
|
||||||
|
data: Db
|
||||||
|
|
||||||
|
registerOnUpdate proc(newDb: Db) =
|
||||||
|
merge(newDb, data)
|
||||||
|
data = newDb
|
||||||
|
redraw()
|
||||||
|
|
||||||
const
|
const
|
||||||
contentSuffix = cstring"content"
|
contentSuffix = cstring"content"
|
||||||
|
|
@ -16,25 +22,23 @@ const
|
||||||
lenSuffix = cstring"entriesLen"
|
lenSuffix = cstring"entriesLen"
|
||||||
|
|
||||||
proc getEntryContent(pos: int): cstring =
|
proc getEntryContent(pos: int): cstring =
|
||||||
result = getItem(&pos & contentSuffix)
|
extract(data, &pos, contentSuffix)
|
||||||
if result == cstring"null":
|
|
||||||
result = nil
|
|
||||||
|
|
||||||
proc isCompleted(pos: int): bool =
|
proc isCompleted(pos: int): bool =
|
||||||
var value = getItem(&pos & completedSuffix)
|
var value = extract(data, &pos, completedSuffix)
|
||||||
result = value == cstring"true"
|
result = value == cstring"true"
|
||||||
|
|
||||||
proc setEntryContent(pos: int, content: cstring) =
|
proc setEntryContent(pos: int, content: cstring) =
|
||||||
setItem(&pos & contentSuffix, content)
|
insert(data, &pos, contentSuffix, content)
|
||||||
|
|
||||||
proc markAsCompleted(pos: int, completed: bool) =
|
proc markAsCompleted(pos: int, completed: bool) =
|
||||||
setItem(&pos & completedSuffix, &completed)
|
insert(data, &pos, completedSuffix, &completed)
|
||||||
|
|
||||||
proc addEntry(content: cstring, completed: bool) =
|
proc addEntry(content: cstring, completed: bool) =
|
||||||
setEntryContent(entriesLen, content)
|
setEntryContent(entriesLen, content)
|
||||||
markAsCompleted(entriesLen, completed)
|
markAsCompleted(entriesLen, completed)
|
||||||
inc entriesLen
|
inc entriesLen
|
||||||
setItem(lenSuffix, &entriesLen)
|
insert(data, lenSuffix, "equals", &entriesLen)
|
||||||
|
|
||||||
proc updateEntry(pos: int, content: cstring, completed: bool) =
|
proc updateEntry(pos: int, content: cstring, completed: bool) =
|
||||||
setEntryContent(pos, content)
|
setEntryContent(pos, content)
|
||||||
|
|
@ -61,7 +65,7 @@ proc toggleEntry(ev: Event; n: VNode) =
|
||||||
markAsCompleted(id, not isCompleted(id))
|
markAsCompleted(id, not isCompleted(id))
|
||||||
|
|
||||||
proc onAllDone(ev: Event; n: VNode) =
|
proc onAllDone(ev: Event; n: VNode) =
|
||||||
clear()
|
insert(data, lenSuffix, "equals", "0")
|
||||||
selectedEntry = -1
|
selectedEntry = -1
|
||||||
|
|
||||||
proc clearCompleted(ev: Event, n: VNode) =
|
proc clearCompleted(ev: Event, n: VNode) =
|
||||||
|
|
@ -150,8 +154,5 @@ setOnHashChange(proc(hash: cstring) =
|
||||||
elif hash == "#/active": filter = active
|
elif hash == "#/active": filter = active
|
||||||
)
|
)
|
||||||
|
|
||||||
if hasItem(lenSuffix):
|
|
||||||
entriesLen = parseInt getItem(lenSuffix)
|
|
||||||
else:
|
|
||||||
entriesLen = 0
|
entriesLen = 0
|
||||||
setRenderer createDom
|
setRenderer createDom
|
||||||
|
|
|
||||||
|
|
@ -48,3 +48,21 @@ proc ajaxGet*(url: cstring; headers: openarray[(cstring, cstring)];
|
||||||
|
|
||||||
proc toJson*[T](data: T): cstring {.importc: "JSON.stringify".}
|
proc toJson*[T](data: T): cstring {.importc: "JSON.stringify".}
|
||||||
proc fromJson*[T](blob: cstring): T {.importc: "JSON.parse".}
|
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