@arkiv-network/sdk
Version:
TypeScript client library for Arkiv's blockchains interactions
156 lines (141 loc) • 6.04 kB
text/typescript
import type { Address, Hex } from "viem"
import type { decUnits } from "./values"
/**
* The protocol's attribute type vocabulary, mapping each short **tag** (the name used by the
* client, the query language and this SDK) to its **typeId** (the byte carried on the wire and in
* the entity record).
*
* The typeIds are consensus-critical: they are written into entity records and mixed into index
* keys, so this table must never drift from the node's `AttributeType`.
*
* | tag | typeId | ABI wire type | when indexed |
* | ------- | ------ | -------------------- | ------------------------- |
* | `bool` | 1 | `bool` | equality + range |
* | `i32` | 2 | `int32` | equality + range |
* | `u64` | 3 | `uint64` | equality + range |
* | `u256` | 4 | `uint256` | equality + range |
* | `dec` | 5 | `int256` (18 dp) | equality + range |
* | — | 6 | `bytes32` | reserved |
* | `bytes` | 7 | `bytes` (<= 128 KiB) | never indexed |
* | `str` | 8 | `string` (<= 128 B) | equality + range + prefix |
* | `addr` | 9 | `address` | equality + range |
* | `key` | 10 | `bytes32` | equality + range |
*
* typeId 6 is the node's `bytes32` type, which this SDK does not expose: it is reserved here, so
* `TYPE_IDS` leaves it out, and a value of that type is read as a `key`.
*
* On the wire the typeId shares its byte with the indexed flag — see {@link INDEXED_FLAG}.
*/
export const TYPE_IDS = {
bool: 1,
i32: 2,
u64: 3,
u256: 4,
dec: 5,
bytes: 7,
str: 8,
addr: 9,
key: 10,
} as const
/**
* Bit 7 of the `typeId` byte: set when the node should index the attribute. The low seven bits are
* the type itself, so `str` travels as `8` unindexed and `0x88` indexed.
*/
export const INDEXED_FLAG = 0x80
/** Every type tag. */
export type TypeTag = keyof typeof TYPE_IDS
/**
* The tags an attribute may be indexed with — every type except `bytes`, which is stored but never
* indexed, and so can never be filtered on.
*/
export type IndexableTypeTag = Exclude<TypeTag, "bytes">
/** Whether attributes of this type may be indexed — everything except `bytes`. */
export function isIndexable(tag: TypeTag): tag is IndexableTypeTag {
return tag !== "bytes"
}
/**
* Marks a value as having passed its constructor's validation. The symbol is module-private, so a
* hand-written object literal is not assignable to {@link ArkivValue} — every value crossing the
* wire has been range-, length- and format-checked by `i32`, `u256`, `dec`, and friends.
*/
declare const validated: unique symbol
/**
* A typed attribute value: the tag it was created with, plus the JavaScript representation of that
* type. Constructed only by the tagged constructors in `@arkiv-network/sdk/schema`.
*
* @typeParam tag - The type tag, which also discriminates the {@link ArkivValue} union.
* @typeParam value - The decoded JavaScript representation.
*/
export type Value<tag extends TypeTag, value> = {
readonly type: tag
readonly value: value
readonly [validated]: tag
}
/** A boolean. */
export type BoolValue = Value<"bool", boolean>
/** A 32-bit signed integer — the default for an untagged number. */
export type I32Value = Value<"i32", number>
/**
* A 64-bit unsigned integer — block heights, the
* entity-nonce and `$expiresAt` are all `u64` */
export type U64Value = Value<"u64", bigint>
/** A 256-bit unsigned integer. */
export type U256Value = Value<"u256", bigint>
/**
* A fixed-point decimal with 18 decimal places, held as its canonical decimal string (`"3.5"`).
* Use {@link decUnits} for the exact scaled integer when you need to do arithmetic.
*/
export type DecValue = Value<"dec", string>
/** A UTF-8 string of at most 128 bytes. */
export type StrValue = Value<"str", string>
/** A 20-byte Ethereum address, as EIP-55 checksummed hex. */
export type AddrValue = Value<"addr", Address>
/** A reference to another entity. Weak by spec: dangling references are permitted. */
export type KeyValue = Value<"key", Hex>
/**
* Opaque variable-length bytes, as lowercase `0x` hex — a blob. Stored but never indexed, so it can
* be selected but not filtered on.
*/
export type BytesValue = Value<"bytes", Hex>
/** Any attribute value. */
export type ArkivValue =
| BoolValue
| I32Value
| U64Value
| U256Value
| DecValue
| BytesValue
| StrValue
| AddrValue
| KeyValue
/** Any value of a type that can be indexed, and so can appear in a query — everything but `bytes`. */
export type IndexableValue = Exclude<ArkivValue, BytesValue>
/** The value type for a given tag — e.g. `ValueOf<"u256">` is {@link U256Value}. */
export type ValueOf<tag extends TypeTag> = Extract<ArkivValue, { type: tag }>
/** The JavaScript representation carried by a value — e.g. `Decoded<U256Value>` is `bigint`. */
export type Decoded<value extends ArkivValue> = value["value"]
/**
* Builds a validated value. Module-internal: the constructors in `values.ts` call this **after**
* validating, which is what makes the {@link validated} brand meaningful.
*/
export function makeValue<tag extends TypeTag>(
type: tag,
value: Decoded<ValueOf<tag>>,
): ValueOf<tag> {
return { type, value } as unknown as ValueOf<tag>
}
/**
* Whether `input` has the *shape* of an {@link ArkivValue} — a known type tag and a value.
*/
export function isArkivValue(input: unknown): input is ArkivValue {
return (
typeof input === "object" &&
input !== null &&
"type" in input &&
"value" in input &&
typeof (input as { type: unknown }).type === "string" &&
// `Object.hasOwn`, not `in`: `in` walks the prototype chain, so `{ type: "constructor" }` would
// be accepted as a typed value and then decode to nothing.
Object.hasOwn(TYPE_IDS, (input as { type: string }).type)
)
}