alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
186 lines (161 loc) • 4.81 kB
text/typescript
import { AlephaError } from "../errors/AlephaError.ts";
import { $inject } from "../primitives/$inject.ts";
import { JsonSchemaCodec } from "./JsonSchemaCodec.ts";
import { KeylessJsonSchemaCodec } from "./KeylessJsonSchemaCodec.ts";
import type { SchemaCodec } from "./SchemaCodec.ts";
import { SchemaValidator, type ValidateOptions } from "./SchemaValidator.ts";
import type { Static, StaticEncode, TSchema } from "./TypeProvider.ts";
export type Encoding = "object" | "string" | "binary";
export interface EncodeOptions<T extends Encoding = Encoding> {
/**
* The output encoding format:
* - 'string': Returns JSON string
* - 'binary': Returns Uint8Array (for protobuf, msgpack, etc.)
*
* @default "string"
*/
as?: T;
/**
* The encoder to use (e.g., 'json', 'protobuf', 'msgpack')
*
* @default "json"
*/
encoder?: string;
/**
* Validation options to apply before encoding.
*/
validation?: ValidateOptions | false;
}
export type EncodeResult<
T extends TSchema,
E extends Encoding,
> = E extends "string"
? string
: E extends "binary"
? Uint8Array
: StaticEncode<T>;
export interface DecodeOptions {
/**
* The encoder to use (e.g., 'json', 'protobuf', 'msgpack')
*
* @default "json"
*/
encoder?: string;
/**
* Validation options to apply before encoding.
*/
validation?: ValidateOptions | false;
}
/**
* CodecManager manages multiple codec formats and provides a unified interface
* for encoding and decoding data with different formats.
*/
export class CodecManager {
protected readonly codecs: Map<string, SchemaCodec> = new Map();
protected readonly jsonCodec = $inject(JsonSchemaCodec);
protected readonly keylessCodec = $inject(KeylessJsonSchemaCodec);
protected readonly schemaValidator = $inject(SchemaValidator);
public default = "json";
constructor() {
// Register default JSON codec
this.register({
name: "json",
codec: this.jsonCodec,
default: true,
});
// Register keyless JSON codec (smaller, faster decoding)
this.register({
name: "keyless",
codec: this.keylessCodec,
});
}
/**
* Register a new codec format.
*/
public register(opts: CodecRegisterOptions): void {
this.codecs.set(opts.name, opts.codec);
if (opts.default) {
this.default = opts.name;
}
}
/**
* Get a specific codec by name.
*
* @param name - The name of the codec
* @returns The codec instance
* @throws {AlephaError} If the codec is not found
*/
public getCodec(name: string): SchemaCodec {
const codec = this.codecs.get(name);
if (!codec) {
throw new AlephaError(
`Codec "${name}" not found. Available codecs: ${Array.from(this.codecs.keys()).join(", ")}`,
);
}
return codec;
}
/**
* Encode data using the specified codec and output format.
*/
public encode<T extends TSchema, E extends Encoding = "object">(
schema: T,
value: unknown,
options?: EncodeOptions<E>,
): EncodeResult<T, E> {
const codec = this.getCodec(options?.encoder ?? this.default);
const as = options?.as ?? "object";
if (options?.validation !== false) {
value = this.schemaValidator.validate(schema, value, options?.validation);
}
if (as === "object") {
// Return the validated object as-is
return value as EncodeResult<T, E>;
}
if (as === "binary") {
// not used by JSON, but for other codecs like Protobuf, MsgPack, etc.
return codec.encodeToBinary(schema, value as Static<T>) as EncodeResult<
T,
E
>;
}
// encode directly to string
return codec.encodeToString(schema, value as Static<T>) as EncodeResult<
T,
E
>;
}
/**
* Decode data using the specified codec.
*/
public decode<T extends TSchema>(
schema: T,
data: any,
options?: DecodeOptions,
): Static<T> {
const encoderName = options?.encoder ?? this.default;
const codec = this.getCodec(encoderName);
let value = codec.decode(schema, data);
if (options?.validation !== false) {
value = this.schemaValidator.validate(schema, value, options?.validation);
}
return value as Static<T>;
}
/**
* Validate decoded data against the schema.
*
* This is automatically called before encoding or after decoding.
*/
public validate<T extends TSchema>(
schema: T,
value: unknown,
options?: ValidateOptions,
): Static<T> {
return this.schemaValidator.validate(schema, value, options);
}
}
// ---------------------------------------------------------------------------------------------------------------------
export interface CodecRegisterOptions {
name: string;
codec: SchemaCodec;
default?: boolean;
}