Skip to content

Commit

Permalink
refactor: add DuplexBuffer interface
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranet committed Jan 31, 2025
1 parent 8a922c4 commit edf5923
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 19 deletions.
3 changes: 2 additions & 1 deletion packages/string-store/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type * from './lib/buffer/DuplexBuffer';
export * from './lib/buffer/UnalignedUint16Array';
export * from './lib/schema/Schema';
export * from './lib/schema/SchemaStore';
export * from './lib/shared/Pointer';
export * from './lib/types';
export * from './lib/UnalignedUint16Array';
export * from './lib/utilities';
45 changes: 45 additions & 0 deletions packages/string-store/src/lib/buffer/DuplexBuffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { PointerLike } from '../shared/Pointer';

export interface DuplexBuffer {
at(index: number): number | undefined;

get maxLength(): number;
get maxBitLength(): number;
get length(): number;
get bitLength(): number;

writeBit(value: number): void;
writeInt2(value: number): void;
writeInt4(value: number): void;
writeInt8(value: number): void;
writeInt16(value: number): void;
writeInt32(value: number): void;
writeInt64(value: number): void;
writeBigInt32(value: bigint): void;
writeBigInt64(value: bigint): void;
writeFloat32(value: number): void;
writeFloat64(value: number): void;

readBit(offset: PointerLike): 0 | 1;
readInt2(offset: PointerLike): number;
readUint2(offset: PointerLike): number;
readInt4(offset: PointerLike): number;
readUint4(offset: PointerLike): number;
readInt8(offset: PointerLike): number;
readUint8(offset: PointerLike): number;
readInt16(offset: PointerLike): number;
readUint16(offset: PointerLike): number;
readInt32(offset: PointerLike): number;
readUint32(offset: PointerLike): number;
readInt64(offset: PointerLike): number;
readUint64(offset: PointerLike): number;
readBigInt32(offset: PointerLike): bigint;
readBigUint32(offset: PointerLike): bigint;
readBigInt64(offset: PointerLike): bigint;
readBigUint64(offset: PointerLike): bigint;
readFloat32(offset: PointerLike): number;
readFloat64(offset: PointerLike): number;

toString(): string;
toArray(): Uint16Array;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Pointer, type PointerLike } from './shared/Pointer';
import { Pointer, type PointerLike } from '../shared/Pointer';
import type { DuplexBuffer } from './DuplexBuffer';

const ConverterUint8 = new Uint8Array(8);
const ConverterUint16 = new Uint16Array(ConverterUint8.buffer);
Expand All @@ -11,7 +12,7 @@ const ConverterInt64 = new BigInt64Array(ConverterUint8.buffer);
const ConverterFloat = new Float32Array(ConverterUint8.buffer);
const ConverterDouble = new Float64Array(ConverterUint8.buffer);

export class UnalignedUint16Array {
export class UnalignedUint16Array implements DuplexBuffer {
#buffer: Uint16Array;
#bitLength = 0;
#wordIndex = 0;
Expand Down Expand Up @@ -300,8 +301,8 @@ export class UnalignedUint16Array {
this.#wordLength++;
}

public static from(value: string | UnalignedUint16Array): UnalignedUint16Array {
if (value instanceof UnalignedUint16Array) return value;
public static from(value: string | DuplexBuffer): DuplexBuffer {
if (typeof value !== 'string') return value;

const buffer = new UnalignedUint16Array(value.length);
for (let i = 0; i < value.length; i++) {
Expand Down
9 changes: 5 additions & 4 deletions packages/string-store/src/lib/schema/Schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { DuplexBuffer } from '../buffer/DuplexBuffer';
import { UnalignedUint16Array } from '../buffer/UnalignedUint16Array';
import { Pointer, type PointerLike } from '../shared/Pointer';
import { t, type IType } from '../types/index';
import { UnalignedUint16Array } from '../UnalignedUint16Array';

export class Schema<Id extends number = number, Entries extends object = object> {
readonly #id: Id;
Expand Down Expand Up @@ -83,7 +84,7 @@ export class Schema<Id extends number = number, Entries extends object = object>
* @param defaultMaximumArrayLength The default maximum array length, if any
* @returns The newly created buffer.
*/
public serializeRaw(value: Readonly<SerializeValueEntries<Entries>>, defaultMaximumArrayLength = 100): UnalignedUint16Array {
public serializeRaw(value: Readonly<SerializeValueEntries<Entries>>, defaultMaximumArrayLength = 100): DuplexBuffer {
const buffer = new UnalignedUint16Array(this.totalBitSize ?? defaultMaximumArrayLength);
this.serializeInto(buffer, value);
return buffer;
Expand All @@ -100,7 +101,7 @@ export class Schema<Id extends number = number, Entries extends object = object>
* The schema's ID is written to the buffer first, followed by each property
* in the schema.
*/
public serializeInto(buffer: UnalignedUint16Array, value: Readonly<SerializeValueEntries<Entries>>): void {
public serializeInto(buffer: DuplexBuffer, value: Readonly<SerializeValueEntries<Entries>>): void {
buffer.writeInt16(this.#id);
for (const [name, type] of this) {
(type as IType<any, number | null>).serialize(buffer, (value as any)[name]);
Expand All @@ -119,7 +120,7 @@ export class Schema<Id extends number = number, Entries extends object = object>
* Unlike {@link Schema.serializeInto}, this method does not read the schema's ID
* from the buffer, that is reserved for the {@link SchemaStore}.
*/
public deserialize(buffer: UnalignedUint16Array | string, pointer: PointerLike): UnwrapSchemaEntries<Entries> {
public deserialize(buffer: DuplexBuffer | string, pointer: PointerLike): UnwrapSchemaEntries<Entries> {
buffer = UnalignedUint16Array.from(buffer);
pointer = Pointer.from(pointer);
const result = Object.create(null) as UnwrapSchemaEntries<Entries>;
Expand Down
9 changes: 5 additions & 4 deletions packages/string-store/src/lib/schema/SchemaStore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { DuplexBuffer } from '../buffer/DuplexBuffer';
import { UnalignedUint16Array } from '../buffer/UnalignedUint16Array';
import { Pointer } from '../shared/Pointer';
import { UnalignedUint16Array } from '../UnalignedUint16Array';
import { Schema, type SerializeValue, type UnwrapSchema } from './Schema';

export class SchemaStore<Entries extends object = object> {
Expand Down Expand Up @@ -72,7 +73,7 @@ export class SchemaStore<Entries extends object = object> {
* @param value The value to serialize
* @returns The serialized buffer
*/
public serializeRaw<const Id extends KeyOfStore<this>>(id: Id, value: SerializeValue<Entries[Id] & object>): UnalignedUint16Array {
public serializeRaw<const Id extends KeyOfStore<this>>(id: Id, value: SerializeValue<Entries[Id] & object>): DuplexBuffer {
const schema = this.get(id) as Schema<Id, object>;
return schema.serializeRaw(value, this.defaultMaximumArrayLength);
}
Expand All @@ -83,7 +84,7 @@ export class SchemaStore<Entries extends object = object> {
* @param buffer The buffer to deserialize
* @returns The resolved value, including the id of the schema used for deserialization
*/
public deserialize(buffer: string | UnalignedUint16Array): DeserializationResult<Entries> {
public deserialize(buffer: string | DuplexBuffer): DeserializationResult<Entries> {
buffer = UnalignedUint16Array.from(buffer);
const pointer = new Pointer();
const id = buffer.readInt16(pointer) as KeyOfStore<this>;
Expand All @@ -101,7 +102,7 @@ export class SchemaStore<Entries extends object = object> {
*
* If an empty value is passed, a {@linkcode RangeError} will be thrown.
*/
public getIdentifier(buffer: string | UnalignedUint16Array): KeyOfStore<this> {
public getIdentifier(buffer: string | DuplexBuffer): KeyOfStore<this> {
if (buffer.length === 0) {
throw new RangeError('Expected a non-empty value');
}
Expand Down
6 changes: 3 additions & 3 deletions packages/string-store/src/lib/types/base/IType.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DuplexBuffer } from '../../buffer/DuplexBuffer';
import type { Pointer } from '../../shared/Pointer';
import type { UnalignedUint16Array } from '../../UnalignedUint16Array';

export interface IType<ValueType, BitSize extends number | null, InputValue = ValueType> {
/**
Expand All @@ -8,15 +8,15 @@ export interface IType<ValueType, BitSize extends number | null, InputValue = Va
* @param buffer The buffer to write to
* @param value The value to write
*/
serialize(buffer: UnalignedUint16Array, value: InputValue): void;
serialize(buffer: DuplexBuffer, value: InputValue): void;

/**
* Deserialize a value from a buffer.
*
* @param buffer The buffer to read from
* @param pointer The pointer indicating the current position in the buffer
*/
deserialize(buffer: UnalignedUint16Array, pointer: Pointer): ValueType;
deserialize(buffer: DuplexBuffer, pointer: Pointer): ValueType;

/**
* The size of the value in bits, or `null` if the size is variable.
Expand Down
4 changes: 2 additions & 2 deletions packages/string-store/tests/lib/SchemaStore.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Float64Type, Schema, SchemaStore, UnalignedUint16Array } from '../../src';
import { Float64Type, Schema, SchemaStore, UnalignedUint16Array, type DuplexBuffer } from '../../src';

describe('SchemaStore', () => {
test('GIVEN an empty SchemaStore THEN it should be empty', () => {
Expand Down Expand Up @@ -35,7 +35,7 @@ describe('SchemaStore', () => {
expect<2>(store.getIdentifier(buffer)).toBe(2);
expect<2>(store.getIdentifier(buffer.toString())).toBe(2);

expectTypeOf(buffer).toEqualTypeOf<UnalignedUint16Array>();
expectTypeOf(buffer).toEqualTypeOf<DuplexBuffer>();
});

test('GIVEN a schema and a value THEN it serializes and deserializes the binary string correctly', () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/string-store/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { createVitestConfig } from '../../scripts/vitest.config';

export default createVitestConfig();
export default createVitestConfig({
test: { coverage: { exclude: ['src/lib/buffer/DuplexBuffer.ts', 'src/lib/types/base/IType.ts'] } }
});

0 comments on commit edf5923

Please sign in to comment.