Skip to content

Commit e3a7b72

Browse files
committed
Initial code implementation
1 parent 971cb4b commit e3a7b72

File tree

8 files changed

+755
-0
lines changed

8 files changed

+755
-0
lines changed

Diff for: mod.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "./src/mod.ts";

Diff for: src/classes/emitter.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
export default class Emitter {
2+
3+
#events = {};
4+
5+
on(e, func) {
6+
if (typeof this.#events[e] !== "object") {
7+
this.#events[e] = [];
8+
}
9+
this.#events[e].push(func);
10+
}
11+
12+
off(e, func) {
13+
if (typeof this.#events[e] === "object") {
14+
const idx = this.#events[e].indexOf(func);
15+
if (idx > -1) {
16+
this.#events[e].splice(idx, 1);
17+
}
18+
}
19+
}
20+
21+
once(e, func) {
22+
this.on(e, function f(...args) {
23+
this.off(e, f);
24+
func.apply(this, args);
25+
});
26+
}
27+
28+
emit(e, ...args) {
29+
if (typeof this.#events[e] === "object") {
30+
this.#events[e].forEach(func => {
31+
func.apply(this, args);
32+
});
33+
}
34+
}
35+
36+
removeAllListeners(e) {
37+
if (e) {
38+
if (typeof this.#events[e] === "object") {
39+
this.#events[e] = [];
40+
}
41+
} else {
42+
for (const e in this.#events) {
43+
this.#events[e] = [];
44+
}
45+
}
46+
}
47+
48+
}

Diff for: src/classes/live.ts

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import Emitter from "./emitter.ts";
2+
3+
export default class Live extends Emitter {
4+
5+
#id = undefined;
6+
7+
#db = undefined;
8+
9+
#sql = undefined;
10+
11+
#vars = undefined;
12+
13+
constructor(db, sql, vars) {
14+
15+
super()
16+
17+
this.#db = db;
18+
19+
this.#sql = sql;
20+
21+
this.#vars = vars;
22+
23+
if (this.#db.ready) {
24+
this.open();
25+
}
26+
27+
this.#db.on("opened", e => {
28+
this.open();
29+
});
30+
31+
this.#db.on("closed", e => {
32+
this.#id = undefined;
33+
});
34+
35+
this.#db.on("notify", e => {
36+
if (e.query === this.#id) {
37+
switch (e.action) {
38+
case "CREATE":
39+
return this.emit("create", e.result);
40+
case "UPDATE":
41+
return this.emit("update", e.result);
42+
case "DELETE":
43+
return this.emit("delete", e.result);
44+
}
45+
}
46+
});
47+
48+
}
49+
50+
// If we want to kill the live query
51+
// then we can kill it. Once a query
52+
// has been killed it can be opened
53+
// again by calling the open() method.
54+
55+
kill() {
56+
57+
if (this.#id === undefined) return;
58+
59+
let res = this.#db.kill(this.#id);
60+
61+
this.#id = undefined;
62+
63+
return res;
64+
65+
}
66+
67+
// If the live query has been manually
68+
// killed, then calling the open()
69+
// method will re-enable the query.
70+
71+
open() {
72+
73+
if (this.#id !== undefined) return;
74+
75+
return this.#db.query(this.#sql, this.#vars).then(res => {
76+
if (res[0] && res[0].result && res[0].result[0]) {
77+
this.#id = res[0].result[0];
78+
}
79+
});
80+
81+
}
82+
83+
}

Diff for: src/classes/pinger.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export default class Pinger {
2+
3+
#pinger = undefined;
4+
5+
#interval = undefined;
6+
7+
constructor(interval = 30000) {
8+
this.#interval = interval;
9+
}
10+
11+
start(func, ...args) {
12+
this.#pinger = setInterval(func, this.#interval);
13+
}
14+
15+
stop() {
16+
clearInterval(this.#pinger);
17+
}
18+
19+
}

Diff for: src/classes/socket.ts

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import Emitter from "./emitter.ts";
2+
3+
const OPENED = Symbol("Opened");
4+
const CLOSED = Symbol("Closed");
5+
6+
export default class Socket extends Emitter {
7+
8+
#ws = null;
9+
10+
#url = null;
11+
12+
#closed = false;
13+
14+
#status = CLOSED;
15+
16+
constructor(url) {
17+
18+
super();
19+
20+
this.#init();
21+
22+
this.#url = String(url)
23+
.replace("http://", "ws://")
24+
.replace("https://", "wss://")
25+
;
26+
27+
}
28+
29+
#init() {
30+
31+
this.ready = new Promise(resolve => {
32+
this.resolve = resolve;
33+
});
34+
35+
}
36+
37+
open() {
38+
39+
this.#ws = new WebSocket(this.#url);
40+
41+
// Setup event listeners so that the
42+
// Surreal instance can listen to the
43+
// necessary event types.
44+
45+
this.#ws.addEventListener("message", (e) => {
46+
this.emit("message", e);
47+
});
48+
49+
this.#ws.addEventListener("error", (e) => {
50+
this.emit("error", e);
51+
});
52+
53+
this.#ws.addEventListener("close", (e) => {
54+
this.emit("close", e);
55+
});
56+
57+
this.#ws.addEventListener("open", (e) => {
58+
this.emit("open", e);
59+
});
60+
61+
// If the WebSocket connection with the
62+
// database was disconnected, then we need
63+
// to reset the ready promise.
64+
65+
this.#ws.addEventListener("close", (e) => {
66+
if (this.#status === OPENED) {
67+
this.#init();
68+
}
69+
});
70+
71+
// When the WebSocket is opened or closed
72+
// then we need to store the connection
73+
// status within the status property.
74+
75+
this.#ws.addEventListener("close", (e) => {
76+
this.#status = CLOSED;
77+
});
78+
79+
this.#ws.addEventListener("open", (e) => {
80+
this.#status = OPENED;
81+
});
82+
83+
// If the connection is closed, then we
84+
// need to attempt to reconnect on a
85+
// regular basis until we are successful.
86+
87+
this.#ws.addEventListener("close", (e) => {
88+
if (this.#closed === false) {
89+
setTimeout( () => {
90+
this.open();
91+
}, 2500);
92+
}
93+
});
94+
95+
// When the WebSocket successfully opens
96+
// then let's resolve the ready promise so
97+
// that promise based code can continue.
98+
99+
this.#ws.addEventListener("open", (e) => {
100+
this.resolve();
101+
});
102+
103+
}
104+
105+
send(data) {
106+
this.#ws.send(data);
107+
}
108+
109+
close(code=1000, reason="Some reason") {
110+
this.#closed = true;
111+
this.#ws.close(code, reason);
112+
}
113+
114+
}

Diff for: src/errors/mod.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export class AuthenticationError extends Error {
2+
constructor(message) {
3+
super(message);
4+
this.name = "AuthenticationError";
5+
}
6+
}
7+
8+
export class PermissionError extends Error {
9+
constructor(message) {
10+
super(message);
11+
this.name = "PermissionError";
12+
}
13+
}
14+
15+
export class RecordError extends Error {
16+
constructor(message) {
17+
super(message);
18+
this.name = "RecordError";
19+
}
20+
}
21+
22+
export default {
23+
AuthenticationError: AuthenticationError,
24+
PermissionError: PermissionError,
25+
RecordError: RecordError,
26+
}

0 commit comments

Comments
 (0)