[js] add UUID and type-handlers

Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
方而静 2024-02-10 14:16:27 +08:00
parent 7c156dacb7
commit a03cf1b2a1
Signed by: szTom
GPG Key ID: 072D999D60C6473C
8 changed files with 586 additions and 0 deletions

1
shared/bstruct/index.mjs Normal file
View File

@ -0,0 +1 @@
import { VirtualMethodNotImplementedError } from '@og/error-utils';

View File

@ -0,0 +1,9 @@
{
"name": "@og/binary-struct",
"type": "module",
"main": "index.mjs",
"dependencies": {
"@og/error-utils": "file:../error-utils",
"@og/uuid": "file:../uuid"
}
}

View File

@ -0,0 +1,455 @@
import { VirtualMethodNotImplementedError } from '@og/error-utils';
export class BasicTypeHandler {
sizeof(value) {
throw new VirtualMethodNotImplementedError();
}
serialize(view, offset, value) {
throw new VirtualMethodNotImplementedError();
}
deserialize(view, offset) {
throw new VirtualMethodNotImplementedError();
}
};
export class Int8Handler extends BasicTypeHandler {
sizeof(_value) {
return 1;
}
/**
*
* @param {DataView} view
* @param {number} offset
* @param {number} value
*/
serialize(view, offset, value) {
view.setInt8(offset, value);
}
/**
*
* @param {DataView} view
* @param {number} offset
* @returns {number}
*/
deserialize(view, offset) {
return view.getInt8(offset);
}
}
export class Int16Handler extends BasicTypeHandler {
/**
* @param {number} _value
* @returns {number}
*/
sizeof(_value) {
return 2;
}
/**
* @param {DataView} view
* @param {number} offset
* @param {number} value
*/
serialize(view, offset, value) {
view.setInt16(offset, value);
}
/**
* @param {DataView} view
* @param {number} offset
* @returns {number}
*/
deserialize(view, offset) {
return view.getInt16(offset);
}
}
export class Int32Handler extends BasicTypeHandler {
/**
* @param {number} _value
* @returns {number}
*/
sizeof(_value) {
return 4;
}
/**
* @param {DataView} view
* @param {number} offset
* @param {number} value
*/
serialize(view, offset, value) {
view.setInt32(offset, value);
}
/**
* @param {DataView} view
* @param {number} offset
* @returns {number}
*/
deserialize(view, offset) {
return view.getInt32(offset);
}
}
export class Int64Handler extends BasicTypeHandler {
/**
* @param {number} _value
* @returns {number}
*/
sizeof(_value) {
return 8;
}
/**
* @param {DataView} view
* @param {number} offset
* @param {BigInt} value
*/
serialize(view, offset, value) {
view.setBigInt64(offset, value);
}
/**
* @param {DataView} view
* @param {number} offset
* @returns {BigInt}
*/
deserialize(view, offset) {
return view.getBigInt64(offset);
}
}
export class Uint8Handler extends BasicTypeHandler {
/**
* @param {number} _value
* @returns {number}
*/
sizeof(_value) {
return 1;
}
/**
* @param {DataView} view
* @param {number} offset
* @param {number} value
*/
serialize(view, offset, value) {
view.setUint8(offset, value);
}
/**
* @param {DataView} view
* @param {number} offset
* @returns {number}
*/
deserialize(view, offset) {
return view.getUint8(offset);
}
}
export class Uint16Handler extends BasicTypeHandler {
/**
* @param {number} _value
* @returns {number}
*/
sizeof(_value) {
return 2;
}
/**
* @param {DataView} view
* @param {number} offset
* @param {number} value
*/
serialize(view, offset, value) {
view.setUint16(offset, value);
}
/**
* @param {DataView} view
* @param {number} offset
* @returns {number}
*/
deserialize(view, offset) {
return view.getUint16(offset);
}
}
export class Uint32Handler extends BasicTypeHandler {
/**
* @param {number} _value
* @returns {number}
*/
sizeof(_value) {
return 4;
}
/**
* @param {DataView} view
* @param {number} offset
* @param {number} value
*/
serialize(view, offset, value) {
view.setUint32(offset, value);
}
/**
* @param {DataView} view
* @param {number} offset
* @returns {number}
*/
deserialize(view, offset) {
return view.getUint32(offset);
}
}
export class Uint64Handler extends BasicTypeHandler {
/**
* @param {number} _value
* @returns {number}
*/
sizeof(_value) {
return 8;
}
/**
* @param {DataView} view
* @param {number} offset
* @param {BigInt} value
*/
serialize(view, offset, value) {
view.setBigUint64(offset, value);
}
/**
* @param {DataView} view
* @param {number} offset
* @returns {BigInt}
*/
deserialize(view, offset) {
return view.getBigUint64(offset);
}
}
export class Float32Handler extends BasicTypeHandler {
/**
* @param {number} _value
* @returns {number}
*/
sizeof(_value) {
return 4;
}
/**
* @param {DataView} view
* @param {number} offset
* @param {number} value
*/
serialize(view, offset, value) {
view.setFloat32(offset, value);
}
/**
* @param {DataView} view
* @param {number} offset
* @returns {number}
*/
deserialize(view, offset) {
return view.getFloat32(offset);
}
}
export class Float64Handler extends BasicTypeHandler {
/**
* @param {number} _value
* @returns {number}
*/
sizeof(_value) {
return 8;
}
/**
* @param {DataView} view
* @param {number} offset
* @param {number} value
*/
serialize(view, offset, value) {
view.setFloat64(offset, value);
}
/**
* @param {DataView} view
* @param {number} offset
* @returns {number}
*/
deserialize(view, offset) {
return view.getFloat64(offset);
}
}
export class BoolHandler extends BasicTypeHandler {
/**
* @param {boolean} _value
* @returns {number}
*/
sizeof(_value) {
return 1;
}
/**
* @param {DataView} view
* @param {number} offset
* @param {boolean} value
*/
serialize(view, offset, value) {
view.setUint8(offset, value ? 1 : 0);
}
/**
* @param {DataView} view
* @param {number} offset
* @returns {boolean}
*/
deserialize(view, offset) {
return view.getUint8(offset) !== 0;
}
}
export class VoidHandler extends BasicTypeHandler {
/**
* @returns {number}
*/
sizeof() {
return 0;
}
/**
* @param {DataView} _view
* @param {number} _offset
* @param {undefined} _value
*/
serialize(_view, _offset, _value) {
// Do nothing for Void
}
/**
* @param {DataView} _view
* @param {number} _offset
* @returns {undefined}
*/
deserialize(_view, _offset) {
// Do nothing for Void
}
}
export class UUIDHandler extends BasicTypeHandler {
/**
* @param {UUID} _value
* @returns {number}
*/
sizeof(_value) {
return 16;
}
/**
* @param {DataView} view
* @param {number} offset
* @param {UUID} value
*/
serialize(view, offset, value) {
const buffer = value.buffer;
for (let i = 0; i < buffer.length; i++) {
view.setUint8(offset + i, buffer[i]);
}
}
/**
* @param {DataView} view
* @param {number} offset
* @returns {UUID}
*/
deserialize(view, offset) {
const buffer = new Uint8Array(16);
for (let i = 0; i < 16; i++) {
buffer[i] = view.getUint8(offset + i);
}
return new UUID(buffer);
}
}
export class StringHandler extends BasicTypeHandler {
/**
* @param {string} _value
* @returns {number}
*/
sizeof(_value) {
// Calculate the size of string in bytes including the 4 bytes for length
return 4 + this.utf8ByteLength(_value);
}
/**
* @param {DataView} view
* @param {number} offset
* @param {string} value
*/
serialize(view, offset, value) {
const utf8_bytes = this.encodeUTF8(value);
// Write string length as 4-byte unsigned integer
view.setUint32(offset, utf8_bytes.length, true);
// Write UTF-8 bytes
for (let i = 0; i < utf8_bytes.length; i++) {
view.setUint8(offset + 4 + i, utf8_bytes[i]);
}
}
/**
* @param {DataView} view
* @param {number} offset
* @returns {string}
*/
deserialize(view, offset) {
// Read string length as 4-byte unsigned integer
const length = view.getUint32(offset, true);
// Read UTF-8 bytes
const utf8_bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
utf8_bytes[i] = view.getUint8(offset + 4 + i);
}
// Decode UTF-8 bytes to string
return this.decodeUTF8(utf8_bytes);
}
/**
* Encodes a string to UTF-8 bytes.
* @param {string} str
* @returns {Uint8Array}
*/
encodeUTF8(str) {
const encoder = new TextEncoder();
return encoder.encode(str);
}
/**
* Decodes UTF-8 bytes to a string.
* @param {Uint8Array} bytes
* @returns {string}
*/
decodeUTF8(bytes) {
const decoder = new TextDecoder();
return decoder.decode(bytes);
}
/**
* Calculates the byte length of a string encoded in UTF-8.
* @param {string} str
* @returns {number}
*/
utf8ByteLength(str) {
return new TextEncoder().encode(str).length;
}
}

View File

@ -0,0 +1,5 @@
export class VirtualMethodNotImplementedError extends Error {
constructor() {
super('virtual method not implemented.');
}
};

View File

@ -0,0 +1,6 @@
{
"name": "@og/error-utils",
"type": "module",
"main": "index.mjs",
"dependencies": {}
}

50
shared/uuid/index.mjs Normal file
View File

@ -0,0 +1,50 @@
import { v4 as uuidv4, v5 as uuidv5, stringify, parse } from 'uuid';
import { Buffer } from 'buffer';
/**
* Represents a UUID (Universally Unique Identifier) object.
*/
export class UUID {
/**
* Constructs a UUID object.
* @param {string|ArrayBuffer} input - The input to construct the UUID from. It can be either a hexadecimal string or an ArrayBuffer.
* @throws {Error} Will throw an error if the input type is invalid. Input must be either a string or an ArrayBuffer.
*/
constructor(input) {
if (typeof input === 'string') {
this.buffer = parse(input);
} else if (input instanceof ArrayBuffer) {
this.buffer = Buffer.from(input);
} else if (input instanceof UUID) {
this.buffer = Buffer.from(input.buffer);
} else {
throw new Error('Invalid input type. Input must be either a string or an ArrayBuffer.');
}
}
/**
* Generates a random UUID using version 4 (random).
* @returns {UUID} A UUID object representing a randomly generated UUID.
*/
static v4() {
return new UUID(uuidv4());
}
/**
* Generates a UUID using version 5 (namespace-based) with the specified namespace and name.
* @param {string} name - The name to be used for generating the UUID.
* @param {string|Buffer|UUID} namespace - The namespace UUID in string or Buffer format.
* @returns {UUID} A UUID object representing a version 5 UUID.
*/
static v5(name, namespace) {
return new UUID(uuidv5(name, new UUID(namespace).toString()));
}
/**
* Converts the UUID object to a string representation.
* @returns {string} A hexadecimal string representation of the UUID.
*/
toString() {
return stringify(this.buffer);
}
};

9
shared/uuid/package.json Normal file
View File

@ -0,0 +1,9 @@
{
"name": "@og/uuid",
"type": "module",
"main": "index.mjs",
"dependencies": {
"buffer": "^6.0.3",
"uuid": "^9.0.1"
}
}

View File

@ -0,0 +1,51 @@
import { UUID } from '../index.mjs';
import assert from 'node:assert';
describe('UUID', () => {
describe('#constructor()', () => {
it('should create UUID from string', () => {
const uuid_str = '123e4567-e89b-12d3-a456-426614171440';
const uuid = new UUID(uuid_str);
assert.strictEqual(uuid.toString(), uuid_str);
});
it('should create UUID from ArrayBuffer', () => {
const buffer = new Uint8Array([
18, 62, 69, 103, 232, 155, 18, 211,
164, 86, 66, 102, 20, 23, 20, 64
]).buffer;
const uuid = new UUID(buffer);
assert.strictEqual(uuid.toString(), '123e4567-e89b-12d3-a456-426614171440');
});
it('should throw error for invalid input type', () => {
assert.throws(() => {
new UUID({}); // passing invalid input
}, /^Error: Invalid input type/);
});
});
describe('#v4()', () => {
it('should generate a version 4 UUID', () => {
const uuid = UUID.v4();
assert.strictEqual(uuid.toString().length, 36);
});
});
describe('#v5()', () => {
it('should generate a version 5 UUID', () => {
const namespace = '123e4567-e89b-12d3-a456-426614171440'; // Sample namespace
const name = 'test';
const uuid = UUID.v5(name, namespace);
assert.strictEqual(uuid.toString().length, 36);
});
});
describe('#toString()', () => {
it('should convert UUID to string', () => {
const uuid_str = '123e4567-e89b-12d3-a456-426614174000';
const uuid = new UUID(uuid_str);
assert.strictEqual(uuid.toString(), uuid_str);
});
});
});