init branch

This commit is contained in:
SikongJueluo 2025-03-29 13:05:01 +08:00
parent 5f872e8287
commit cddf92e432
No known key found for this signature in database
16 changed files with 5 additions and 1362 deletions

View File

@ -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

View File

@ -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)
})

View File

@ -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)
}
}

View File

@ -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)
})

View File

@ -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)
}
}
}

View File

@ -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 });
}
}))

View File

@ -1 +0,0 @@
import { MsgProtocol } from "./msgProtocol";

View File

@ -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
}
}

View File

@ -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;

View File

@ -1,6 +0,0 @@
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
export const router = t.router;
export const publicProcedure = t.procedure;

View File

@ -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")
}
})

View File

@ -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 }

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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 }

View File

@ -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
}
}