init branch
This commit is contained in:
parent
5f872e8287
commit
cddf92e432
|
@ -14,10 +14,15 @@
|
||||||
devShells = forEachSupportedSystem ({ pkgs }: {
|
devShells = forEachSupportedSystem ({ pkgs }: {
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
|
# Frontend
|
||||||
bun
|
bun
|
||||||
sqlite
|
sqlite
|
||||||
sqls
|
sqls
|
||||||
sql-studio
|
sql-studio
|
||||||
|
# Backend
|
||||||
|
dotnetCorePackages.sdk_9_0
|
||||||
|
dotnetCorePackages.aspnetcore_9_0
|
||||||
|
nuget
|
||||||
|
|
||||||
# LSP
|
# LSP
|
||||||
typescript-language-server
|
typescript-language-server
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
import { type } from "./common"
|
|
||||||
import _ from "lodash"
|
|
||||||
import { expect, test } from "bun:test"
|
|
||||||
|
|
||||||
const CYCLES = 10000
|
|
||||||
test("Test Integer and Unsigned Integer", () => {
|
|
||||||
for (let i = 0; i < CYCLES; i++) {
|
|
||||||
|
|
||||||
// Unsigned Integer
|
|
||||||
expect(type.UInteger.safeParse(_.random(0, Math.pow(2, 53), false)).success).toBeTrue()
|
|
||||||
expect(type.UInt8.safeParse(_.random(0, Math.pow(2, 8) - 1, false)).success).toBeTrue()
|
|
||||||
expect(type.UInt8.safeParse(_.random(Math.pow(2, 8), Math.pow(2, 53), false)).success).toBeFalse()
|
|
||||||
expect(type.UInt16.safeParse(_.random(0, Math.pow(2, 16) - 1, false)).success).toBeTrue()
|
|
||||||
expect(type.UInt16.safeParse(_.random(Math.pow(2, 16), Math.pow(2, 53), false)).success).toBeFalse()
|
|
||||||
expect(type.UInt32.safeParse(_.random(0, Math.pow(2, 32) - 1, false)).success).toBeTrue()
|
|
||||||
expect(type.UInt32.safeParse(_.random(Math.pow(2, 32), Math.pow(2, 53), false)).success).toBeFalse()
|
|
||||||
|
|
||||||
// Integer
|
|
||||||
expect(type.Integer.safeParse(_.random(-Math.pow(2, 52), Math.pow(2, 52) - 1, false)).success).toBeTrue()
|
|
||||||
expect(type.Int8.safeParse(_.random(-Math.pow(2, 7), Math.pow(2, 7) - 1, false)).success).toBeTrue()
|
|
||||||
expect(type.Int8.safeParse(_.random(Math.pow(2, 7), Math.pow(2, 52), false)).success).toBeFalse()
|
|
||||||
expect(type.Int8.safeParse(_.random(-Math.pow(2, 52), -Math.pow(2, 7) - 1, false)).success).toBeFalse()
|
|
||||||
expect(type.Int16.safeParse(_.random(-Math.pow(2, 15), Math.pow(2, 15) - 1, false)).success).toBeTrue()
|
|
||||||
expect(type.Int16.safeParse(_.random(Math.pow(2, 15), Math.pow(2, 52), false)).success).toBeFalse()
|
|
||||||
expect(type.Int16.safeParse(_.random(-Math.pow(2, 52), -Math.pow(2, 15) - 1, false)).success).toBeFalse()
|
|
||||||
expect(type.Int32.safeParse(_.random(-Math.pow(2, 31), Math.pow(2, 31) - 1, false)).success).toBeTrue()
|
|
||||||
expect(type.Int32.safeParse(_.random(Math.pow(2, 31), Math.pow(2, 52), false)).success).toBeFalse()
|
|
||||||
expect(type.Int32.safeParse(_.random(-Math.pow(2, 52), -Math.pow(2, 31) - 1, false)).success).toBeFalse()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Test Number Processor Function", () => {
|
|
||||||
// Convert Number to Uint8Array
|
|
||||||
expect(type.numberToBytes(0xFF, 1).unwrap()[0]).toBe(255)
|
|
||||||
expect(type.numberToBytes(0xAAAA, 2).unwrap()).toEqual(new Uint8Array([0xAA, 0xAA]))
|
|
||||||
expect(type.numberToBytes(0x12345678, 4).unwrap()).toEqual(new Uint8Array([0x78, 0x56, 0x34, 0x12]))
|
|
||||||
expect(type.numberToBytes(0x12345678, 4, true).unwrap()).toEqual(new Uint8Array([0x12, 0x34, 0x56, 0x78]))
|
|
||||||
|
|
||||||
// Convert Uint8Array to Number
|
|
||||||
expect(type.bytesToNumber(new Uint8Array([0xFF]))).toBe(255)
|
|
||||||
expect(type.bytesToNumber(new Uint8Array([0xAA, 0xAA]))).toEqual(0xAAAA)
|
|
||||||
expect(type.bytesToNumber(new Uint8Array([0x78, 0x56, 0x34, 0x12]))).toEqual(0x12345678)
|
|
||||||
expect(type.bytesToNumber(new Uint8Array([0x12, 0x34, 0x56, 0x78]), true)).toEqual(0x12345678)
|
|
||||||
|
|
||||||
|
|
||||||
// Number Match
|
|
||||||
for (let i = 0; i < CYCLES; i++) {
|
|
||||||
const num1 = _.random(CYCLES / 2, false)
|
|
||||||
const num2 = _.random(CYCLES / 2, false)
|
|
||||||
|
|
||||||
expect(type.numberMatch(num1, num2)).toBe((num1 & num2) === num2 ? true : false)
|
|
||||||
expect(type.numberMatch(num1, num2, "True", "False")).toBe((num1 & num2) === num2 ? "True" : "False")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number Set, Unset, Toggle and Get Bit
|
|
||||||
expect(type.numberSetBit(0, 5)).toBe(0b10000)
|
|
||||||
expect(type.numberUnsetBit(0b1111, 3)).toBe(0b1011)
|
|
||||||
expect(type.numberToggleBit(0b1010, 3)).toBe(0b1110)
|
|
||||||
expect(type.numberBit(0b1100, 2)).toBe(0)
|
|
||||||
|
|
||||||
// Get High / Low Bits Num
|
|
||||||
expect(type.numberHighBitsNum(0xFF).unwrap()).toBe(8)
|
|
||||||
expect(type.numberHighBitsNum(0xAA).unwrap()).toBe(4)
|
|
||||||
expect(type.numberLowBitsNum(0xFF, 8).unwrap()).toBe(0)
|
|
||||||
expect(type.numberLowBitsNum(0xAA, 8).unwrap()).toBe(4)
|
|
||||||
})
|
|
191
server/common.ts
191
server/common.ts
|
@ -1,191 +0,0 @@
|
||||||
import _ from "lodash"
|
|
||||||
import { Option, Some, None, Result, Ok, Err } from "ts-results-es";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export function UNUSED(_: unknown): void { }
|
|
||||||
|
|
||||||
export namespace type {
|
|
||||||
|
|
||||||
const NUMBER_MAX_LENGTH = 32
|
|
||||||
|
|
||||||
|
|
||||||
export const ErrorTypeSchema = z.union([
|
|
||||||
z.literal("Not Integer"),
|
|
||||||
z.literal("Not Unsigned Integer"),
|
|
||||||
z.literal("Not 32Bits Integer")
|
|
||||||
])
|
|
||||||
|
|
||||||
export type ErrorType = z.infer<typeof ErrorTypeSchema>
|
|
||||||
|
|
||||||
export const Integer = z.number().int()
|
|
||||||
export const UInteger = z.number().int().nonnegative()
|
|
||||||
|
|
||||||
export const UInt8 = UInteger.lt(Math.pow(2, 8))
|
|
||||||
export const UInt16 = UInteger.lt(Math.pow(2, 16))
|
|
||||||
export const UInt32 = UInteger.lt(Math.pow(2, 32))
|
|
||||||
|
|
||||||
export const Int8 = Integer.lt(Math.pow(2, 7)).gte(-Math.pow(2, 8))
|
|
||||||
export const Int16 = Integer.lt(Math.pow(2, 15)).gte(-Math.pow(2, 16))
|
|
||||||
export const Int32 = Integer.lt(Math.pow(2, 31)).gte(-Math.pow(2, 32))
|
|
||||||
|
|
||||||
export function numberToBytes(num: number, bytesLength: number, isRightHigh: boolean = false)
|
|
||||||
: Result<Uint8Array, ErrorType> {
|
|
||||||
// Check Integer
|
|
||||||
if (!Int32.safeParse(num).success && !UInt32.lte(32).safeParse(bytesLength).success) {
|
|
||||||
console.error(`Number : ${num}, 2 ^ 31 = ${2 ^ 31}`)
|
|
||||||
console.error(Int32.safeParse(num).error?.message)
|
|
||||||
return new Err("Not 32Bits Integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
var array = new Uint8Array(bytesLength)
|
|
||||||
|
|
||||||
if (isRightHigh) {
|
|
||||||
for (let i = 0; i < bytesLength; i++) {
|
|
||||||
array[bytesLength - 1 - i] = ((num >> (i << 3)) & 0xFF)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (let i = 0; i < bytesLength; i++) {
|
|
||||||
array[i] = ((num >> (i << 3)) & 0xFF)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Ok(array)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function bytesToNumber(bytes: Uint8Array, isRightHigh: boolean = false): number {
|
|
||||||
let num = 0
|
|
||||||
const len = bytes.length
|
|
||||||
if (isRightHigh) {
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
num += bytes[len - 1 - i] << (i << 3)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
num += bytes[i] << (i << 3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function numberMatch(
|
|
||||||
srcNum: number,
|
|
||||||
destNum: number
|
|
||||||
): boolean;
|
|
||||||
|
|
||||||
export function numberMatch<T>(
|
|
||||||
srcNum: number,
|
|
||||||
destNum: number,
|
|
||||||
True: T,
|
|
||||||
False: T
|
|
||||||
): T;
|
|
||||||
|
|
||||||
export function numberMatch<T>(
|
|
||||||
srcNum: number,
|
|
||||||
destNum: number,
|
|
||||||
True: T = true as T,
|
|
||||||
False: T = false as T
|
|
||||||
): T {
|
|
||||||
const ret = (srcNum & destNum) === destNum;
|
|
||||||
return ret ? True : False;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberMatchEnum() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberSetBit(num: number, loc: number): number {
|
|
||||||
return num | (1 << (loc - 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberUnsetBit(num: number, loc: number): number {
|
|
||||||
return num & ~(1 << (loc - 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberToggleBit(num: number, loc: number): number {
|
|
||||||
return num ^ (1 << (loc - 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberBit(num: number, loc: number): number {
|
|
||||||
return (num >> (loc - 1)) & 1
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberHighBitsNum(num: number, maxLen: number = NUMBER_MAX_LENGTH): Result<number, ErrorType> {
|
|
||||||
if (!Int32.safeParse(num).success && maxLen > 32) {
|
|
||||||
return new Err("Not 32Bits Integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
let cnt = 0
|
|
||||||
for (let i = 0; i < maxLen; i++) {
|
|
||||||
if (num & (1 << i)) {
|
|
||||||
cnt++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Ok(cnt)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberLowBitsNum(num: number, maxLen: number): Result<number, ErrorType> {
|
|
||||||
if (!Int32.safeParse(num).success && maxLen > 32) {
|
|
||||||
return new Err("Not 32Bits Integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
let cnt = 0
|
|
||||||
for (let i = 0; i < maxLen; i++) {
|
|
||||||
if (!(num & (1 << i))) {
|
|
||||||
cnt++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Ok(cnt)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isStringArray(obj: any): obj is Array<string> {
|
|
||||||
return z.string().array().safeParse(obj).success
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isNumberArray(obj: any): obj is Array<number> {
|
|
||||||
return z.number().array().safeParse(obj).success
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace fun {
|
|
||||||
|
|
||||||
export function randomFromArray(array: Array<any>) {
|
|
||||||
return array[_.random(0, array.length - 1, false)]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sqlConditionFromArray(
|
|
||||||
columnName: string,
|
|
||||||
array: Array<string>,
|
|
||||||
type: "AND" | "OR"
|
|
||||||
): Option<string> {
|
|
||||||
let condition: string = ""
|
|
||||||
const len = array.length
|
|
||||||
if (len == 0) {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
condition.concat(`${columnName}=${array[i]}`)
|
|
||||||
if (i != len - 1) {
|
|
||||||
condition.concat(` ${type} `)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Some(condition)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sqlConditionFromString(
|
|
||||||
columnName: string,
|
|
||||||
str: string,
|
|
||||||
type: "AND" | "OR",
|
|
||||||
delimiter: string = " "
|
|
||||||
): Option<string> {
|
|
||||||
if (str.length == 0) {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
const array = str.split(delimiter)
|
|
||||||
return sqlConditionFromArray(columnName, array, type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
import { test, expect } from "bun:test"
|
|
||||||
import * as db from "./database.ts"
|
|
||||||
import _, { rearg } from "lodash"
|
|
||||||
import { None, Ok, Option, Some } from "ts-results-es"
|
|
||||||
|
|
||||||
// Test basic function for database
|
|
||||||
test("DataBase", () => {
|
|
||||||
const allTables = db.allTables()
|
|
||||||
expect(allTables).toBeArray()
|
|
||||||
expect(allTables).toEqual(["Users", "Boards"])
|
|
||||||
expect(db.BoardTable.countAll()).toBe(0)
|
|
||||||
expect(db.UserTable.countAll()).toBe(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Test Boards table function
|
|
||||||
const boardsNumber = 10
|
|
||||||
const rooms = ["A1", "A1", "A1", "A1", "A1", "A2", "A2", "A2", "A2", "A2"]
|
|
||||||
test("Find something empty", () => {
|
|
||||||
const findEmptyByID = db.BoardTable.find(_.random(0, boardsNumber))
|
|
||||||
expect(findEmptyByID).toEqual(Ok(None))
|
|
||||||
const findEmptyByName = db.BoardTable.find("Hello", "World")
|
|
||||||
expect(findEmptyByName).toEqual(Ok(None))
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Add some boards", () => {
|
|
||||||
const boardsArray: Array<db.Board> = []
|
|
||||||
for (let i = 0; i < boardsNumber; i++) {
|
|
||||||
boardsArray.push({
|
|
||||||
id: i,
|
|
||||||
name: `Board ${i}`,
|
|
||||||
room: rooms[i],
|
|
||||||
ipv4: `192.168.172.${i}`,
|
|
||||||
port: i,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const retAdd = db.BoardTable.addFromArray(boardsArray)
|
|
||||||
const isAddOk = retAdd.isOk()
|
|
||||||
expect(isAddOk).toBeTrue()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Get boards table column unique element", () => {
|
|
||||||
expect(db.BoardTable.rooms().unwrap()).toEqual(["A1", "A2"])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Find something from boards table", () => {
|
|
||||||
expect(db.BoardTable.find(1)).toEqual(Ok(Some({
|
|
||||||
id: 1,
|
|
||||||
name: `Board ${1}`,
|
|
||||||
room: rooms[1],
|
|
||||||
ipv4: `192.168.172.${1}`,
|
|
||||||
port: 1,
|
|
||||||
} as db.Board)))
|
|
||||||
expect(db.BoardTable.find("Board 3", "A1")).toEqual(Ok(Some({
|
|
||||||
id: 3,
|
|
||||||
name: `Board ${3}`,
|
|
||||||
room: rooms[3],
|
|
||||||
ipv4: `192.168.172.${3}`,
|
|
||||||
port: 3,
|
|
||||||
} as db.Board)))
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Get count of elements from boards table", () => {
|
|
||||||
expect(db.BoardTable.countByName("Board 1")).toBe(1)
|
|
||||||
expect(db.BoardTable.countByRoom("A1")).toBe(5)
|
|
||||||
})
|
|
|
@ -1,478 +0,0 @@
|
||||||
import { Database, type Changes } from "bun:sqlite";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { Ok, Err, Result, None, Some, Option } from "ts-results-es";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { fun, type } from "./common";
|
|
||||||
|
|
||||||
const db = new Database("lab.sqlite", { strict: true })
|
|
||||||
initDB(db);
|
|
||||||
|
|
||||||
// Error Type
|
|
||||||
const BoardErrorTypeSchema = z.union([
|
|
||||||
z.literal("Wrong Type"),
|
|
||||||
z.literal("No Such Board(s)"),
|
|
||||||
z.literal("ID Conflict"),
|
|
||||||
z.literal("Name Conflict")
|
|
||||||
])
|
|
||||||
|
|
||||||
export type BoardErrorType = z.infer<typeof BoardErrorTypeSchema>
|
|
||||||
|
|
||||||
const boardSchema = z.object({
|
|
||||||
id: z.number().nonnegative(),
|
|
||||||
name: z.string(),
|
|
||||||
room: z.string(),
|
|
||||||
ipv4: z.string().ip({ version: "v4" }),
|
|
||||||
ipv6: z.string().ip({ version: "v6" }),
|
|
||||||
port: z.number().nonnegative().lte(65535),
|
|
||||||
cmdID: z.number().nonnegative()
|
|
||||||
}).partial({
|
|
||||||
ipv6: true,
|
|
||||||
cmdID: true
|
|
||||||
})
|
|
||||||
|
|
||||||
export type Board = z.infer<typeof boardSchema>
|
|
||||||
export type BoardColumn = keyof Board
|
|
||||||
|
|
||||||
export function isBoard(obj: any): obj is Board {
|
|
||||||
return boardSchema.safeParse(obj).success
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isBoardArray(obj: any): obj is Array<Board> {
|
|
||||||
return boardSchema.array().safeParse(obj).success
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isBoardColumn(obj: any): obj is BoardColumn {
|
|
||||||
return boardSchema.keyof().safeParse(obj).success
|
|
||||||
}
|
|
||||||
|
|
||||||
const userSchema = z.object({
|
|
||||||
id: z.number().nonnegative(),
|
|
||||||
name: z.string(),
|
|
||||||
password: z.string(),
|
|
||||||
boardID: z.number(),
|
|
||||||
}).partial({
|
|
||||||
boardID: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
export type User = z.infer<typeof userSchema>
|
|
||||||
export type UserColumn = keyof User
|
|
||||||
|
|
||||||
export function isUser(obj: any): obj is User {
|
|
||||||
return userSchema.safeParse(obj).success
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isUserArray(obj: any): obj is Array<User> {
|
|
||||||
return userSchema.array().safeParse(obj).success
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isUserColumn(obj: any): obj is UserColumn {
|
|
||||||
return userSchema.keyof().safeParse(obj).success
|
|
||||||
}
|
|
||||||
|
|
||||||
function initDB(db: Database) {
|
|
||||||
const tables = allTables()
|
|
||||||
|
|
||||||
if (!tables.includes("Users")) {
|
|
||||||
db.query(`
|
|
||||||
CREATE TABLE Users(
|
|
||||||
id INT PRIMARY KEY NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
password TEXT NOT NULL
|
|
||||||
);
|
|
||||||
`).run();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tables.includes("Boards"))
|
|
||||||
db.query(`
|
|
||||||
CREATE TABLE Boards(
|
|
||||||
id INT PRIMARY KEY NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
room TEXT NOT NULL,
|
|
||||||
ipv4 CHAR(16) NOT NULL,
|
|
||||||
ipv6 CHAR(46) ,
|
|
||||||
port INT NOT NULL
|
|
||||||
)
|
|
||||||
`).run();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function allTables(): Array<string> {
|
|
||||||
const query = db.query(`SELECT name FROM sqlite_master WHERE type='table'`)
|
|
||||||
var tables = new Array()
|
|
||||||
// Flaten array
|
|
||||||
for (const item of query.values()) {
|
|
||||||
tables.push(item[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
query.finalize()
|
|
||||||
return tables
|
|
||||||
}
|
|
||||||
|
|
||||||
export function tableColumnName(table: string): Array<string> {
|
|
||||||
const query = db.query(`PRAGMA table_info(${table})`)
|
|
||||||
|
|
||||||
var columnName = new Array()
|
|
||||||
for (const column of query.values()) {
|
|
||||||
columnName.push(column[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
return columnName
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace BoardTable {
|
|
||||||
|
|
||||||
export function add(board: Board): Result<Changes, BoardErrorType> {
|
|
||||||
if (!isBoard(board)) {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure no id conflict
|
|
||||||
if (board.id == 0) {
|
|
||||||
const cnt = countAll()
|
|
||||||
board.id = cnt + 1
|
|
||||||
} else {
|
|
||||||
const retID = includes(board.id)
|
|
||||||
if (retID.isOk()) {
|
|
||||||
if (retID.value) return new Err("ID Conflict")
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure no name conflict in the same room
|
|
||||||
{
|
|
||||||
const retName = includes(board.name, board.room)
|
|
||||||
if (retName.isOk()) {
|
|
||||||
if (retName.value) {
|
|
||||||
return new Err("Name Conflict")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = db.query(`
|
|
||||||
INSERT INTO Boards VALUES
|
|
||||||
(${board.id},
|
|
||||||
'${board.name}',
|
|
||||||
'${board.room}',
|
|
||||||
'${board.ipv4}',
|
|
||||||
'${_.isUndefined(board.ipv6) ? "NULL" : board.ipv6}',
|
|
||||||
${board.port});
|
|
||||||
`)
|
|
||||||
|
|
||||||
return Ok(query.run())
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addFromArray(array: Array<Board>)
|
|
||||||
: Result<Array<Changes>, BoardErrorType> {
|
|
||||||
|
|
||||||
let arrayChanges: Array<Changes> = []
|
|
||||||
for (const item of array) {
|
|
||||||
const ret = add(item)
|
|
||||||
if (ret.isErr()) {
|
|
||||||
return ret
|
|
||||||
} else {
|
|
||||||
arrayChanges.push(ret.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Ok(arrayChanges)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function all(): Option<Array<Board>> {
|
|
||||||
const query = db.query(`SELECT * FROM Boards`)
|
|
||||||
const ret = query.all()
|
|
||||||
query.finalize()
|
|
||||||
|
|
||||||
if (isBoardArray(ret)) {
|
|
||||||
return Some(ret)
|
|
||||||
} else {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function countAll(): number {
|
|
||||||
const query = db.query(`SELECT COUNT(*) FROM Boards`)
|
|
||||||
return query.values()[0][0] as number
|
|
||||||
}
|
|
||||||
|
|
||||||
export function countByName(name: string): number {
|
|
||||||
const query = db.query(`SELECT * FROM Boards WHERE name=${name}`)
|
|
||||||
return query.values()[0][0] as number
|
|
||||||
}
|
|
||||||
|
|
||||||
export function countByRoom(room: string): number {
|
|
||||||
const query = db.query(`SELECT * FROM Boards WHERE room=${room}`)
|
|
||||||
return query.values()[0][0] as number
|
|
||||||
}
|
|
||||||
|
|
||||||
export function remove(name: string, room: string): Result<Board, BoardErrorType>
|
|
||||||
export function remove(id: number): Result<Board, BoardErrorType>
|
|
||||||
export function remove(board: Board): Result<Board, BoardErrorType>
|
|
||||||
export function remove(arg1: any, arg2?: any): Result<Board, BoardErrorType> {
|
|
||||||
let retBoard
|
|
||||||
let condition: string
|
|
||||||
if (isBoard(arg1)) {
|
|
||||||
retBoard = _.cloneDeep(arg1)
|
|
||||||
condition = `id=${arg1.id}`
|
|
||||||
|
|
||||||
} else if (_.isNumber(arg1)) {
|
|
||||||
retBoard = find(arg1)
|
|
||||||
if (retBoard.isOk() && retBoard.value.isSome()) {
|
|
||||||
retBoard = _.cloneDeep(retBoard.value.value)
|
|
||||||
condition = `id=${arg1}`
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (_.isString(arg1) && _.isString(arg2)) {
|
|
||||||
retBoard = find(arg1, arg2)
|
|
||||||
if (retBoard.isOk() && retBoard.value.isSome()) {
|
|
||||||
retBoard = _.cloneDeep(retBoard.value.value)
|
|
||||||
condition = `name=${arg1}`
|
|
||||||
} else {
|
|
||||||
return new Err("No Such Board(s)")
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = db.query(`DELETE FROM Boards WHERE ${condition}`)
|
|
||||||
query.run()
|
|
||||||
|
|
||||||
return new Ok(retBoard)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeAll(): Option<Array<Board>> {
|
|
||||||
const array = all()
|
|
||||||
const query = db.query(`DELETE FROM Boards`)
|
|
||||||
query.run()
|
|
||||||
|
|
||||||
|
|
||||||
if (array.isSome()) {
|
|
||||||
return new Some(array.value)
|
|
||||||
} else {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeByCondition(condition: string): Result<Array<Board>, BoardErrorType> {
|
|
||||||
const rsArr = findByCondition(condition)
|
|
||||||
if (rsArr.isNone()) {
|
|
||||||
return new Err("No Such Board(s)")
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = db.query(`DELETE FROM Boards WHERE ${condition}`)
|
|
||||||
query.run()
|
|
||||||
|
|
||||||
return new Ok(rsArr.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeByValue(columnName: string, val: string | Array<string>)
|
|
||||||
: Result<Array<Board>, BoardErrorType> {
|
|
||||||
|
|
||||||
if (!isBoardColumn(columnName)) {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
let condition: string
|
|
||||||
if (_.isString(val)) {
|
|
||||||
const retCond = fun.sqlConditionFromString(columnName, val, "OR")
|
|
||||||
if (retCond.isSome()) {
|
|
||||||
condition = retCond.value
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
} else if (type.isStringArray(val)) {
|
|
||||||
const retCond = fun.sqlConditionFromArray(columnName, val, "OR")
|
|
||||||
if (retCond.isSome()) {
|
|
||||||
condition = retCond.value
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return removeByCondition(condition)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeByName(name: string | Array<string>)
|
|
||||||
: Result<Array<Board>, BoardErrorType> {
|
|
||||||
return removeByValue("name", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeByRoom(room: string | Array<string>)
|
|
||||||
: Result<Array<Board>, BoardErrorType> {
|
|
||||||
|
|
||||||
return removeByValue("room", room)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function rooms(): Option<Array<string>> {
|
|
||||||
const query = db.query(`SELECT DISTINCT room FROM Boards`)
|
|
||||||
let rooms: Array<string> = []
|
|
||||||
const retVal = query.values()
|
|
||||||
|
|
||||||
if (retVal.length > 0) {
|
|
||||||
for (const item of retVal) {
|
|
||||||
rooms.push(item[0] as string)
|
|
||||||
}
|
|
||||||
return new Some(rooms)
|
|
||||||
} else {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function find(name: string, room: string): Result<Option<Board>, BoardErrorType>
|
|
||||||
export function find(id: number): Result<Option<Board>, BoardErrorType>
|
|
||||||
export function find(arg1: any, arg2?: any): Result<Option<Board>, BoardErrorType> {
|
|
||||||
let condition: string
|
|
||||||
if (_.isNumber(arg1)) {
|
|
||||||
condition = `id=${arg1}`
|
|
||||||
|
|
||||||
} else if (_.isString(arg1) && _.isString(arg2)) {
|
|
||||||
condition = `name='${arg1}' AND room='${arg2}'`
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = db.query(`SELECT * FROM Boards WHERE ${condition}`)
|
|
||||||
|
|
||||||
const spRet = boardSchema.safeParse(query.get())
|
|
||||||
|
|
||||||
if (spRet.success) {
|
|
||||||
return new Ok(Some(spRet.data))
|
|
||||||
} else {
|
|
||||||
return new Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findByName(name: string | Array<string>): Result<Option<Array<Board>>, BoardErrorType> {
|
|
||||||
let condition: string
|
|
||||||
if (_.isString(name)) {
|
|
||||||
const retCond = fun.sqlConditionFromString("name", name, "OR")
|
|
||||||
if (retCond.isSome()) {
|
|
||||||
condition = retCond.value
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
} else if (type.isStringArray(name)) {
|
|
||||||
const retCond = fun.sqlConditionFromArray("name", name, "OR")
|
|
||||||
if (retCond.isSome()) {
|
|
||||||
condition = retCond.value
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Ok(findByCondition(condition))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findByCondition(condition: string): Option<Array<Board>> {
|
|
||||||
const query = db.query(`SELECT * FROM Boards WHERE ${condition}`)
|
|
||||||
const ret = query.all()
|
|
||||||
if (isBoardArray(ret)) {
|
|
||||||
return new Some(ret)
|
|
||||||
} else {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findByValue(columnName: string, val: string | Array<string>)
|
|
||||||
: Result<Option<Array<Board>>, BoardErrorType> {
|
|
||||||
|
|
||||||
if (!isBoardColumn(columnName)) {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
let condition: string
|
|
||||||
if (_.isString(val)) {
|
|
||||||
const retCond = fun.sqlConditionFromString(columnName, val, "OR")
|
|
||||||
if (retCond.isSome()) {
|
|
||||||
condition = retCond.value
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
} else if (type.isStringArray(val)) {
|
|
||||||
const retCond = fun.sqlConditionFromArray(columnName, val, "OR")
|
|
||||||
if (retCond.isSome()) {
|
|
||||||
condition = retCond.value
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Ok(findByCondition(condition))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function includes(name: string, room?: string): Result<boolean, BoardErrorType>
|
|
||||||
export function includes(id: number): Result<boolean, BoardErrorType>
|
|
||||||
export function includes(arg1: any, arg2?: any): Result<boolean, BoardErrorType> {
|
|
||||||
let condition: string
|
|
||||||
if (_.isUndefined(arg2)) {
|
|
||||||
if (_.isNumber(arg1)) {
|
|
||||||
condition = `id=${arg1}`
|
|
||||||
|
|
||||||
} else if (_.isString(arg1)) {
|
|
||||||
condition = `name='${arg1}'`
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (_.isString(arg1) && _.isString(arg2)) {
|
|
||||||
condition = `name='${arg1} AND room=${arg2}'`
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = db.query(`SELECT COUNT(*) FROM Boards WHERE ${condition}`)
|
|
||||||
const num = query.values()[0][0] as number
|
|
||||||
|
|
||||||
return new Ok(num > 0 ? true : false)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace UserTable {
|
|
||||||
|
|
||||||
export function countAll(): number {
|
|
||||||
const query = db.query(`SELECT COUNT(*) FROM Users`)
|
|
||||||
return query.values()[0][0] as number
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function find(id: number): Result<Option<User>, "Wrong Type">
|
|
||||||
export function find(name: string): Result<Option<User>, "Wrong Type">
|
|
||||||
export function find(arg: any): Result<Option<User>, "Wrong Type"> {
|
|
||||||
let condition: string
|
|
||||||
if (_.isNumber(arg)) {
|
|
||||||
condition = `id=${arg}`
|
|
||||||
} else if (_.isString(arg)) {
|
|
||||||
condition = `name=${arg}`
|
|
||||||
} else {
|
|
||||||
return new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = db.query(`SELECT * FROM Users WHERE name='${arg}'`)
|
|
||||||
|
|
||||||
const spRet = userSchema.safeParse(query.get())
|
|
||||||
|
|
||||||
if (spRet.success) {
|
|
||||||
return new Ok(Some(spRet.data))
|
|
||||||
} else {
|
|
||||||
return new Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
import { createBunServeHandler } from "trpc-bun-adapter";
|
|
||||||
import { appRouter } from "./router.ts"
|
|
||||||
|
|
||||||
Bun.serve(createBunServeHandler({
|
|
||||||
router: appRouter,
|
|
||||||
responseMeta() {
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Methods": "GET, PUT, POST, OPTIONS",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
port: 3002,
|
|
||||||
fetch() {
|
|
||||||
return Response.json({ message: "Not Found" }, { status: 404 });
|
|
||||||
}
|
|
||||||
}))
|
|
|
@ -1 +0,0 @@
|
||||||
import { MsgProtocol } from "./msgProtocol";
|
|
|
@ -1,79 +0,0 @@
|
||||||
import { type TransferListItem } from "worker_threads";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { UNUSED } from "./common";
|
|
||||||
|
|
||||||
export namespace MsgProtocol {
|
|
||||||
const QueryDataSchema = z.object({ type: z.literal("Query"), args: z.any() })
|
|
||||||
const ResultDataSchema = z.object({ type: z.literal("Result"), result: z.any() })
|
|
||||||
const ErrorDataSchema = z.object({ type: z.literal("Error"), error: z.string() })
|
|
||||||
|
|
||||||
const MessageSchema = z.object({
|
|
||||||
command: z.string(),
|
|
||||||
data: z.discriminatedUnion("type", [QueryDataSchema, ResultDataSchema, ErrorDataSchema]),
|
|
||||||
dest: z.string(),
|
|
||||||
src: z.string(),
|
|
||||||
})
|
|
||||||
const MessageQuerySchema = z.object({
|
|
||||||
command: z.string(),
|
|
||||||
data: QueryDataSchema,
|
|
||||||
dest: z.string(),
|
|
||||||
src: z.string(),
|
|
||||||
})
|
|
||||||
const MessageResultSchema = z.object({
|
|
||||||
command: z.string(),
|
|
||||||
data: ResultDataSchema,
|
|
||||||
dest: z.string(),
|
|
||||||
src: z.string(),
|
|
||||||
})
|
|
||||||
const MessageErrorSchema = z.object({
|
|
||||||
command: z.string(),
|
|
||||||
data: ErrorDataSchema,
|
|
||||||
dest: z.string(),
|
|
||||||
src: z.string(),
|
|
||||||
})
|
|
||||||
const MessageHandlerSchema = z.function()
|
|
||||||
.args(z.union([MessageResultSchema, MessageErrorSchema]))
|
|
||||||
.returns(z.void())
|
|
||||||
|
|
||||||
export type Message = z.infer<typeof MessageSchema>
|
|
||||||
export type MessageQuery = z.infer<typeof MessageQuerySchema>
|
|
||||||
export type MessageResult = z.infer<typeof MessageResultSchema>
|
|
||||||
export type MessageError = z.infer<typeof MessageErrorSchema>
|
|
||||||
export type MessageHandler = z.infer<typeof MessageHandlerSchema>
|
|
||||||
|
|
||||||
export function isMessage(obj: any): obj is Message {
|
|
||||||
return MessageSchema.safeParse(obj).success
|
|
||||||
}
|
|
||||||
export function isMessageQuery(obj: any): obj is MessageQuery {
|
|
||||||
return MessageQuerySchema.safeParse(obj).success
|
|
||||||
}
|
|
||||||
export function isMessageResult(obj: any): obj is MessageResult {
|
|
||||||
return MessageResultSchema.safeParse(obj).success
|
|
||||||
}
|
|
||||||
export function isMessageError(obj: any): obj is MessageError {
|
|
||||||
return MessageErrorSchema.safeParse(obj).success
|
|
||||||
}
|
|
||||||
|
|
||||||
export function genMessageResult(result: any, srcMsg: MessageQuery): MessageResult {
|
|
||||||
return {
|
|
||||||
command: srcMsg.command,
|
|
||||||
dest: srcMsg.src,
|
|
||||||
src: srcMsg.dest,
|
|
||||||
data: {
|
|
||||||
type: "Result",
|
|
||||||
result: result
|
|
||||||
}
|
|
||||||
} as MessageResult
|
|
||||||
}
|
|
||||||
export function genMessageError(error: string, srcMsg: MessageQuery): MessageError {
|
|
||||||
return {
|
|
||||||
command: srcMsg.command,
|
|
||||||
dest: srcMsg.src,
|
|
||||||
src: srcMsg.dest,
|
|
||||||
data: {
|
|
||||||
type: "Error",
|
|
||||||
error: error
|
|
||||||
}
|
|
||||||
} as MessageError
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { router, publicProcedure } from "./trpc.ts"
|
|
||||||
|
|
||||||
export const appRouter = router({
|
|
||||||
api: router({
|
|
||||||
status: publicProcedure.query(() => "OK"),
|
|
||||||
signUp: publicProcedure.query((opts) => {
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export type AppRouter = typeof appRouter;
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { initTRPC } from '@trpc/server';
|
|
||||||
|
|
||||||
const t = initTRPC.create();
|
|
||||||
|
|
||||||
export const router = t.router;
|
|
||||||
export const publicProcedure = t.procedure;
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { expect, test } from "bun:test"
|
|
||||||
import { udpServer, udpClient } from "./udp"
|
|
||||||
|
|
||||||
const udpServerAddr = "127.0.0.1"
|
|
||||||
const udpServerPort = await udpServer.port()
|
|
||||||
|
|
||||||
console.info("Ready to Test UDP")
|
|
||||||
|
|
||||||
test("Test Send String", async () => {
|
|
||||||
const str = "Hello My Server"
|
|
||||||
const retSend = await udpClient.sendString(str, udpServerPort, udpServerAddr)
|
|
||||||
expect(retSend).toBe(true)
|
|
||||||
|
|
||||||
const retReceive = await udpServer.lastestData(udpServerAddr)
|
|
||||||
if (retReceive.isSome()) {
|
|
||||||
const data = retReceive.value
|
|
||||||
expect(data.body).toBe(str)
|
|
||||||
expect(data.address).toBe(udpServerAddr)
|
|
||||||
} else {
|
|
||||||
expect().fail("Not Receive Anything")
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,80 +0,0 @@
|
||||||
import { resolve } from "path";
|
|
||||||
import Tinypool from "tinypool";
|
|
||||||
import type { UDPReceiveProcotol, UDPSendProtocol } from "./udpProtocol";
|
|
||||||
import type { udp } from "bun";
|
|
||||||
import { Option } from "ts-results-es";
|
|
||||||
|
|
||||||
const SERVER_PATH = resolve(__dirname, "./udpServer.ts")
|
|
||||||
const CLIENT_PATH = resolve(__dirname, "./udpClient.ts")
|
|
||||||
|
|
||||||
export type UDPBodyType = (
|
|
||||||
udp.Data |
|
|
||||||
UDPSendProtocol.CmdPackage |
|
|
||||||
UDPSendProtocol.DataPackage |
|
|
||||||
UDPReceiveProcotol.ReadPackage |
|
|
||||||
UDPReceiveProcotol.WritePackage
|
|
||||||
)
|
|
||||||
|
|
||||||
type UDPDataType<T extends UDPBodyType = UDPBodyType> = {
|
|
||||||
address: string,
|
|
||||||
body: T,
|
|
||||||
port: number,
|
|
||||||
date?: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const udpClientsPool = new Tinypool({
|
|
||||||
filename: CLIENT_PATH,
|
|
||||||
workerData: {}
|
|
||||||
})
|
|
||||||
|
|
||||||
const udpServerPool = new Tinypool({
|
|
||||||
filename: SERVER_PATH,
|
|
||||||
workerData: {},
|
|
||||||
maxThreads: 1,
|
|
||||||
minThreads: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
export namespace udpServer {
|
|
||||||
|
|
||||||
export async function port(): Promise<number> {
|
|
||||||
return udpServerPool.run(null, { name: "port" })
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function lastestData(address: string): Promise<Option<UDPDataType<udp.Data>>> {
|
|
||||||
return udpServerPool.run(address, { name: "lastestData" })
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function oldestData(address: string): Promise<Option<UDPDataType<udp.Data>>> {
|
|
||||||
return udpServerPool.run(address, { name: "oldestData" })
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace udpClient {
|
|
||||||
|
|
||||||
export async function sendString(data: string, port: number, address: string): Promise<boolean> {
|
|
||||||
return udpClientsPool.run({
|
|
||||||
body: data,
|
|
||||||
port: port,
|
|
||||||
address: address
|
|
||||||
} as UDPDataType<string>, { name: "sendString" })
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function sendUint8Array(data: Uint8Array, port: number, address: string): Promise<boolean> {
|
|
||||||
return udpClientsPool.run({
|
|
||||||
body: data,
|
|
||||||
port: port,
|
|
||||||
address: address
|
|
||||||
} as UDPDataType<Uint8Array>, { name: "sendUint8Array" })
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function sendBunData(data: udp.Data, port: number, address: string): Promise<boolean> {
|
|
||||||
return udpClientsPool.run({
|
|
||||||
body: data,
|
|
||||||
port: port,
|
|
||||||
address: address
|
|
||||||
} as UDPDataType<udp.Data>, { name: "sendUint8Array" })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export type { UDPDataType }
|
|
|
@ -1,17 +0,0 @@
|
||||||
import type { udp } from "bun"
|
|
||||||
import type { UDPDataType } from "./udp"
|
|
||||||
|
|
||||||
const udpClient = await Bun.udpSocket({})
|
|
||||||
|
|
||||||
export function sendString(data: UDPDataType<string>): boolean {
|
|
||||||
return udpClient.send(data.body, data.port, data.address)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendUint8Array(data: UDPDataType<Uint8Array>): boolean {
|
|
||||||
return udpClient.send(data.body, data.port, data.address)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendBunData(data: UDPDataType<udp.Data>): boolean {
|
|
||||||
return udpClient.send(data.body, data.port, data.address)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,201 +0,0 @@
|
||||||
import { type } from "./common";
|
|
||||||
import { z } from "zod";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { Err, Ok, type Result } from "ts-results-es";
|
|
||||||
import { isUint8Array } from "util/types";
|
|
||||||
|
|
||||||
const PKG_SIGN_ADDR = 0x00
|
|
||||||
const PKG_SIGN_DATA = 0xFF
|
|
||||||
const PKG_SIGN_READ = 0x0F
|
|
||||||
const PKG_SIGN_WRITE = 0xF0
|
|
||||||
|
|
||||||
export namespace UDPSendProtocol {
|
|
||||||
|
|
||||||
const CMDLOC_BURST_TYPE = 5
|
|
||||||
const CMDLOC_TASK_ID = 3
|
|
||||||
const CMDLOC_READ_WRITE_TYPE = 0
|
|
||||||
|
|
||||||
const CmdPackageOptionsSchema = z.object({
|
|
||||||
burstType: z.enum(["Fixed", "Extend"]),
|
|
||||||
taskID: z.number().nonnegative().lt(4),
|
|
||||||
readWriteType: z.enum(["r", "w"])
|
|
||||||
})
|
|
||||||
|
|
||||||
export type CmdPackageOptions = z.infer<typeof CmdPackageOptionsSchema>
|
|
||||||
|
|
||||||
export function isCmdPackageOptions(obj: any): obj is CmdPackageOptions {
|
|
||||||
return CmdPackageOptionsSchema.safeParse(obj).success
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CmdPackage {
|
|
||||||
private ID: number = PKG_SIGN_ADDR
|
|
||||||
private commandType: number = 0
|
|
||||||
private burstLength: number = 0
|
|
||||||
private _reserved: number = 0
|
|
||||||
private address: number = 0
|
|
||||||
|
|
||||||
|
|
||||||
constructor(array: Uint8Array)
|
|
||||||
constructor(options: CmdPackageOptions)
|
|
||||||
constructor(arg: any) {
|
|
||||||
if (isCmdPackageOptions(arg)) {
|
|
||||||
this.setCommandType(arg)
|
|
||||||
} else if (isUint8Array(arg)) {
|
|
||||||
this.ID = arg[0]
|
|
||||||
this.commandType = arg[1]
|
|
||||||
this.burstLength = arg[2]
|
|
||||||
this._reserved = arg[3]
|
|
||||||
this.address = type.bytesToNumber(arg.slice(4))
|
|
||||||
} else {
|
|
||||||
throw new Err("Wrong Type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setCommandType(options: CmdPackageOptions) {
|
|
||||||
const validOptions = CmdPackageOptionsSchema.parse(options)
|
|
||||||
this.commandType = (
|
|
||||||
((validOptions.burstType === "Fixed") ? 1 << CMDLOC_BURST_TYPE : 0) |
|
|
||||||
(validOptions.taskID << CMDLOC_TASK_ID) |
|
|
||||||
((validOptions.readWriteType === "r") ? 1 << CMDLOC_READ_WRITE_TYPE : 0)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setBurstLength(len: number): Result<null, "Not 8Bits Unsigned Integer"> {
|
|
||||||
if (!type.UInt8.safeParse(len).success) {
|
|
||||||
return new Err("Not 8Bits Unsigned Integer")
|
|
||||||
}
|
|
||||||
this.burstLength = len
|
|
||||||
return new Ok(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
setAddress(addr: number): Result<null, "Not 32Bits Unsigned Integer"> {
|
|
||||||
if (!type.UInt32.safeParse(addr).success) {
|
|
||||||
return new Err("Not 32Bits Unsigned Integer")
|
|
||||||
}
|
|
||||||
this.address = addr
|
|
||||||
return new Ok(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
options(): CmdPackageOptions {
|
|
||||||
return {
|
|
||||||
burstType: type.numberMatch(this.commandType, CMDLOC_BURST_TYPE, "Extend", "Fixed"),
|
|
||||||
taskID: (this.commandType >> CMDLOC_TASK_ID) & 0b0011,
|
|
||||||
readWriteType: type.numberMatch(this.commandType, CMDLOC_READ_WRITE_TYPE, "w", "r")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toUint8Array(): Uint8Array {
|
|
||||||
var array = new Uint8Array(8)
|
|
||||||
|
|
||||||
array[0] = this.ID
|
|
||||||
array[1] = this.commandType
|
|
||||||
array[2] = this.burstLength
|
|
||||||
array[3] = this._reserved
|
|
||||||
|
|
||||||
// Already check address length at the begining of set address
|
|
||||||
let addressBytes = type.numberToBytes(this.address, 4).unwrap()
|
|
||||||
array.set(addressBytes, 4)
|
|
||||||
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCmdPackage(obj: any): obj is CmdPackage {
|
|
||||||
return obj instanceof CmdPackage
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DataPackage {
|
|
||||||
private ID: number = PKG_SIGN_DATA
|
|
||||||
private _reserved: number = 0
|
|
||||||
private body: Uint8Array
|
|
||||||
|
|
||||||
constructor(body: Uint8Array) {
|
|
||||||
this.body = body
|
|
||||||
}
|
|
||||||
|
|
||||||
toUint8Array(): Uint8Array {
|
|
||||||
var array = new Uint8Array(4 + this.body.length)
|
|
||||||
|
|
||||||
array[0] = this.ID
|
|
||||||
array.fill(this._reserved, 1, 4)
|
|
||||||
array.set(this.body, 4)
|
|
||||||
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isDataPackage(obj: any): obj is DataPackage {
|
|
||||||
return obj instanceof DataPackage
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace UDPReceiveProcotol {
|
|
||||||
|
|
||||||
export class ReadPackage {
|
|
||||||
private ID: number = PKG_SIGN_READ
|
|
||||||
private taskID: number = 0
|
|
||||||
private resp: number = 0
|
|
||||||
private _reserved: number = 0
|
|
||||||
body: Uint8Array
|
|
||||||
|
|
||||||
constructor(array: Uint8Array) {
|
|
||||||
if (array.length < 5) {
|
|
||||||
throw new Err("Not Long Enough")
|
|
||||||
}
|
|
||||||
this.ID = array[0]
|
|
||||||
this.taskID = array[1]
|
|
||||||
this.resp = array[2]
|
|
||||||
this._reserved = array[3]
|
|
||||||
this.body = new Uint8Array(array.slice(4))
|
|
||||||
}
|
|
||||||
|
|
||||||
toUint8Array(): Uint8Array {
|
|
||||||
let array = new Uint8Array(4 + this.body.length)
|
|
||||||
|
|
||||||
array[0] = this.ID
|
|
||||||
array[1] = this.taskID
|
|
||||||
array[2] = this.resp
|
|
||||||
array[3] = this._reserved
|
|
||||||
array.set(this.body, 4)
|
|
||||||
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isReadPackage(obj: any): obj is ReadPackage {
|
|
||||||
return obj instanceof ReadPackage
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WritePackage {
|
|
||||||
private ID: number = PKG_SIGN_WRITE
|
|
||||||
private taskID: number = 0
|
|
||||||
private resp: number = 0
|
|
||||||
private _reserved: number = 0
|
|
||||||
|
|
||||||
|
|
||||||
constructor(array: Uint8Array) {
|
|
||||||
if (array.length < 4) {
|
|
||||||
throw new Err("Not Long Enough")
|
|
||||||
}
|
|
||||||
this.ID = array[0]
|
|
||||||
this.taskID = array[1]
|
|
||||||
this.resp = array[2]
|
|
||||||
this._reserved = array[3]
|
|
||||||
}
|
|
||||||
|
|
||||||
toUint8Array(): Uint8Array {
|
|
||||||
return new Uint8Array([
|
|
||||||
this.ID,
|
|
||||||
this.taskID,
|
|
||||||
this.resp,
|
|
||||||
this._reserved
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isWritePackage(obj: any): obj is WritePackage {
|
|
||||||
return obj instanceof WritePackage
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
import _ from "lodash"
|
|
||||||
import { type udp } from "bun"
|
|
||||||
import type { UDPDataType } from "./udp";
|
|
||||||
import { None, Some, type Option } from "ts-results-es";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
// Bun Data Type
|
|
||||||
interface BinaryTypeList {
|
|
||||||
arraybuffer: ArrayBuffer;
|
|
||||||
buffer: Buffer;
|
|
||||||
uint8array: Uint8Array;
|
|
||||||
}
|
|
||||||
type BinaryType = keyof BinaryTypeList;
|
|
||||||
|
|
||||||
const receivedData: Map<string, Array<{
|
|
||||||
body: udp.Data,
|
|
||||||
port: number,
|
|
||||||
date: string
|
|
||||||
}>> = new Map()
|
|
||||||
|
|
||||||
const udpServer = await Bun.udpSocket({
|
|
||||||
port: 33000,
|
|
||||||
socket: {
|
|
||||||
data(
|
|
||||||
_socket: udp.Socket<BinaryType>,
|
|
||||||
data: BinaryTypeList[BinaryType],
|
|
||||||
port: number,
|
|
||||||
address: string,
|
|
||||||
) {
|
|
||||||
// Add Received Data
|
|
||||||
let arrayData = receivedData.get(address)
|
|
||||||
if (_.isUndefined(arrayData)) {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
receivedData.set(address, [])
|
|
||||||
arrayData.push({
|
|
||||||
body: data,
|
|
||||||
port: port,
|
|
||||||
date: new Date().toUTCString()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function port(): number {
|
|
||||||
return udpServer.port
|
|
||||||
}
|
|
||||||
|
|
||||||
function lastestData(address: string): Option<UDPDataType<udp.Data>> {
|
|
||||||
if (!z.string().ip().safeParse(address).success) {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayData = receivedData.get(address)
|
|
||||||
if (_.isUndefined(arrayData)) {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = arrayData.pop()
|
|
||||||
if (_.isUndefined(data)) {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some({
|
|
||||||
address: address,
|
|
||||||
body: data.body,
|
|
||||||
port: data.port,
|
|
||||||
date: data.date,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function oldestData(address: string): Option<UDPDataType<udp.Data>> {
|
|
||||||
if (!z.string().ip().safeParse(address).success) {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayData = receivedData.get(address)
|
|
||||||
if (_.isUndefined(arrayData)) {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = arrayData.shift()
|
|
||||||
if (_.isUndefined(data)) {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some({
|
|
||||||
address: address,
|
|
||||||
body: data.body,
|
|
||||||
port: data.port,
|
|
||||||
date: data.date,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export { port, lastestData, oldestData }
|
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
// Enable latest features
|
|
||||||
"lib": [
|
|
||||||
"ESNext"
|
|
||||||
],
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"allowJs": true,
|
|
||||||
// Bundler mode
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"noEmit": true,
|
|
||||||
// Best practices
|
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
// Some stricter flags
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noPropertyAccessFromIndexSignature": true
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue