Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add DuplexBuffer interface #865

Merged
merged 1 commit into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'] } }
});
Loading