@ckb-ccc/core
Version:
Core of CCC - CKBer's Codebase
183 lines (170 loc) • 5.26 kB
text/typescript
import { Bytes, bytesEq, BytesLike } from "../bytes/index.js";
import { hashCkb } from "../hasher/index.js";
import { Hex } from "../hex/index.js";
import { Constructor } from "../utils/index.js";
import { Codec } from "./codec.js";
/**
* The base class of CCC to create a serializable instance. This should be used with the {@link codec} decorator.
* @public
*/
export abstract class Entity {
/**
* Generate a base class of CCC to create a serializable instance.
* This should be used with the {@link codec} decorator.
* @public
*/
static Base<SubTypeLike, SubType = SubTypeLike>() {
abstract class Impl {
/**
* The bytes length of the entity, if it is fixed, otherwise undefined
* @public
* @static
*/
static byteLength?: number;
/**
* Encode the entity into bytes
* @public
* @static
* @param _ - The entity to encode
* @returns The encoded bytes
* @throws Will throw an error if the entity is not serializable
*/
static encode(_: SubTypeLike): Bytes {
throw new Error(
"encode not implemented, use @ccc.mol.codec to decorate your type",
);
}
/**
* Decode the entity from bytes
* @public
* @static
* @param _ - The bytes to decode
* @returns The decoded entity
* @throws Will throw an error if the entity is not serializable
*/
static decode(_: BytesLike): SubType {
throw new Error(
"decode not implemented, use @ccc.mol.codec to decorate your type",
);
}
/**
* Create an entity from bytes
* @public
* @static
* @param _ - The bytes to create the entity from
* @returns The created entity
* @throws Will throw an error if the entity is not serializable
*/
static fromBytes(_bytes: BytesLike): SubType {
throw new Error(
"fromBytes not implemented, use @ccc.mol.codec to decorate your type",
);
}
/**
* Create an entity from a serializable object
* @public
* @static
* @param _ - The serializable object to create the entity from
* @returns The created entity
* @throws Will throw an error if the entity is not serializable
*/
static from(_: SubTypeLike): SubType {
throw new Error("from not implemented");
}
/**
* Convert the entity to bytes
* @public
* @returns The bytes representation of the entity
*/
toBytes(): Bytes {
return (this.constructor as typeof Impl).encode(
this as unknown as SubTypeLike,
);
}
/**
* Create a clone of the entity
* @public
* @returns A clone of the entity
*/
clone(): SubType {
return (this.constructor as typeof Impl).fromBytes(
this.toBytes(),
) as unknown as SubType;
}
/**
* Check if the entity is equal to another entity
* @public
* @param other - The other entity to compare with
* @returns True if the entities are equal, false otherwise
*/
eq(other: SubTypeLike): boolean {
if (this === (other as unknown as this)) {
return true;
}
return bytesEq(
this.toBytes(),
/* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any */
(
((this.constructor as any)?.from(other) ?? other) as unknown as Impl
).toBytes(),
/* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any */
);
}
/**
* Calculate the hash of the entity
* @public
* @returns The hash of the entity
*/
hash(): Hex {
return hashCkb(this.toBytes());
}
}
return Impl;
}
abstract toBytes(): Bytes;
abstract hash(): Hex;
abstract clone(): Entity;
}
/**
* A class decorator to add methods implementation on the {@link Entity.Base} class
* @example
* ```typescript
* @mol.codec(
* mol.table({
* codeHash: mol.Byte32,
* hashType: HashTypeCodec,
* args: mol.Bytes,
* }),
* )
* export class Script extends mol.Entity.Base<ScriptLike, Script>() {
* from(scriptLike: ScriptLike): Script {}
* }
* ```
*/
export function codec<
Encodable,
TypeLike extends Encodable,
Decoded extends TypeLike,
Type extends object & TypeLike,
ConstructorType extends Constructor<Type> & {
from(decoded: TypeLike): Type;
byteLength?: number;
encode(encodable: TypeLike): Bytes;
decode(bytesLike: BytesLike): TypeLike;
fromBytes(bytes: BytesLike): Type;
},
>(codec: Codec<Encodable, Decoded>) {
return function (Constructor: ConstructorType) {
Constructor.byteLength = codec.byteLength;
Constructor.encode = function (encodable: TypeLike) {
return codec.encode(encodable);
};
Constructor.decode = function (bytesLike: BytesLike) {
return Constructor.from(codec.decode(bytesLike));
};
Constructor.fromBytes = function (bytes: BytesLike) {
return Constructor.from(codec.decode(bytes));
};
return Constructor;
};
}