UNPKG

@arkiv-network/sdk

Version:

TypeScript client library for Arkiv's blockchains interactions

112 lines (106 loc) • 3.53 kB
import { InvalidAttributeNameError } from "./errors" /** The longest attribute name, in bytes — the width of the on-chain `Ident32` name cell. */ export const MAX_NAME_BYTES = 32 /** * The attribute-name grammar (`Ident32`): a leading letter, then letters, digits, `.`, `-` or `_`. * * Names are **case-sensitive** — `Level` and `level` are two different attributes. * * The charset is ASCII-only, so a name's length in characters is also its length in bytes. */ const NAME_RE = /^[A-Za-z][A-Za-z0-9._-]*$/ /** * Words the query language reserves. They are rejected as attribute names because an attribute * named `not` or `str` could be written but never queried — the parser would read the name as an * operator or a type tag. * * Matched **case-insensitively**: query keywords are case-insensitive, so `NOT` and `not` are the * same operator, and the type tags are held to the same rule so that what is reserved does not * depend on how you happen to capitalise it. */ const RESERVED = new Set([ // operators and keywords "and", "or", "not", "true", "false", "between", "startswith", "exists", "typeof", "order", "by", "asc", "desc", // type tags "bool", "i32", "u64", "u256", "dec", "bytes", "str", "addr", "key", ]) /** * Validates an attribute name against the engine's `Ident32` grammar and the query language's * reserved words. * * @param name - The attribute name. * @throws {InvalidAttributeNameError} If the name is empty, starts with `$` (reserved for system * attributes), too long, uses characters outside the grammar, contains `--` (which opens a query * comment), or collides with a reserved word. */ export function validateAttributeName(name: string): void { if (typeof name !== "string" || name.length === 0) { throw new InvalidAttributeNameError(String(name), "the name is empty") } if (name.startsWith("$")) { throw new InvalidAttributeNameError( name, '"$" is reserved for system attributes, which the engine sets and you cannot write', ) } if (!NAME_RE.test(name)) { const chars = [...name] const at = chars.findIndex( (char, index) => !(index === 0 ? /[A-Za-z]/ : /[A-Za-z0-9._-]/).test(char), ) throw new InvalidAttributeNameError( name, at === 0 ? `it starts with "${chars[0]}" rather than a letter` : `it contains "${chars[at]}" at position ${at}`, ) } if (name.length > MAX_NAME_BYTES) { throw new InvalidAttributeNameError(name, `the name is ${name.length} bytes long`) } // `-` is a legal name character and `--` opens a comment that runs to the end of the line, so a // name containing one renders a query whose whole remainder is discarded if (name.includes("--")) { throw new InvalidAttributeNameError( name, '"--" opens a comment in the query language, so everything after it in a query would be ' + "discarded — an attribute with this name could be written but never safely queried", ) } if (RESERVED.has(name.toLowerCase())) { throw new InvalidAttributeNameError( name, `"${name}" is reserved by the query language, so an attribute with this name could never ` + "be queried", ) } } /** Whether `name` is a valid attribute name — the non-throwing form of {@link validateAttributeName}. */ export function isValidAttributeName(name: string): boolean { try { validateAttributeName(name) return true } catch { return false } }