UNPKG

@arkiv-network/sdk

Version:

TypeScript client library for Arkiv's blockchains interactions

1,581 lines • 62.2 kB
const require_schema = require('./schema-AVHCdblQ.cjs');
const require_errors = require('./errors-CVkdB3AT.cjs');
const require_entity = require('./entity-BlzyJ-D7.cjs');
let viem = require("viem");

//#region src/utils/logger.ts
const ROOT_NAMESPACE = "arkiv";
function getDebugEnv() {
	if (typeof process !== "undefined") {
		const debugEnv = process?.env?.DEBUG;
		if (debugEnv) return debugEnv;
	}
	if (typeof globalThis !== "undefined" && "localStorage" in globalThis) try {
		return globalThis.localStorage?.getItem("DEBUG") || "";
	} catch {}
	return "";
}
function matchesPattern(namespace, pattern) {
	if (pattern === namespace) return true;
	if (pattern.endsWith("*")) {
		const prefix = pattern.slice(0, -1);
		return namespace.startsWith(prefix);
	}
	return false;
}
function isEnabled(namespace) {
	const debugEnv = getDebugEnv();
	if (!debugEnv) return false;
	const patterns = debugEnv.split(/[\s,]+/).filter((p) => p.length > 0);
	if (patterns.length === 0) return false;
	let enabled = false;
	for (const pattern of patterns) if (pattern.startsWith("-")) {
		if (matchesPattern(namespace, pattern.slice(1))) return false;
	} else if (matchesPattern(namespace, pattern)) enabled = true;
	return enabled;
}
const COLOR_PALETTE = [
	"#0000FF",
	"#008000",
	"#FF0000",
	"#FF00FF",
	"#FFA500",
	"#800080",
	"#00CED1",
	"#FF1493",
	"#32CD32",
	"#1E90FF",
	"#FF4500",
	"#9370DB",
	"#00FA9A",
	"#FFD700",
	"#DC143C",
	"#00BFFF",
	"#FF69B4",
	"#20B2AA",
	"#DA70D6",
	"#4682B4"
];
const isNode = typeof process !== "undefined" && process.versions?.node != null;
const ANSI_COLORS = [
	"\x1B[94m",
	"\x1B[92m",
	"\x1B[91m",
	"\x1B[95m",
	"\x1B[93m",
	"\x1B[96m",
	"\x1B[35m",
	"\x1B[36m",
	"\x1B[32m",
	"\x1B[34m",
	"\x1B[33m",
	"\x1B[31m",
	"\x1B[90m",
	"\x1B[37m",
	"\x1B[1m"
];
const ANSI_RESET = "\x1B[0m";
const ANSI_BOLD = "\x1B[1m";
const namespaceColors = /* @__PURE__ */ new Map();
function getColorForNamespace(namespace) {
	const cachedColor = namespaceColors.get(namespace);
	if (cachedColor) return cachedColor;
	let hash = 0;
	for (let i = 0; i < namespace.length; i++) {
		const char = namespace.charCodeAt(i);
		hash = (hash << 5) - hash + char;
		hash = hash & hash;
	}
	const colorIndex = Math.abs(hash) % COLOR_PALETTE.length;
	const color = {
		css: COLOR_PALETTE[colorIndex] ?? "inherit",
		ansi: ANSI_COLORS[colorIndex % ANSI_COLORS.length] ?? ""
	};
	namespaceColors.set(namespace, color);
	return color;
}
function createDebugLogger(namespace) {
	const colors = getColorForNamespace(namespace);
	return (...args) => {
		if (isEnabled(namespace)) if (isNode) {
			const coloredNamespace = `${ANSI_BOLD}${colors.ansi}${namespace}${ANSI_RESET}`;
			console.log(coloredNamespace, ...args);
		} else {
			const style1 = `color: ${colors.css}; font-weight: bold`;
			console.log(`%c${namespace}%c`, style1, "color: inherit; font-weight: normal", ...args);
		}
	};
}
const getLogger = (scope) => {
	return createDebugLogger(`${ROOT_NAMESPACE}:${scope}`);
};

//#endregion
//#region src/query/selection.ts
/**
* The entity fields a row can carry. Each is `$`-prefixed, like the system attributes `where`
* filters on: an attribute name cannot start with `$`, so no attribute can collide with one.
*/
const SYSTEM_FIELDS = [
	"$key",
	"$owner",
	"$creator",
	"$createdAt",
	"$updatedAt",
	"$expiresAt",
	"$creationFlags"
];
/** The selection keys with a fixed meaning, in the order they are written into the RPC `select`. */
const SELECTION_KEYS = [
	...SYSTEM_FIELDS,
	"$attributeSchema",
	"$attributes"
];
/**
* Converts a selection into the RPC `select` object — the same request {@link projectSelection}
* makes without a schema.
*
* Every field is opt-in — anything not explicitly selected is sent as `false`, including the key.
* Sending the full object rather than omitting the `false`s keeps what the SDK asked for legible in
* a request log, and pins the answer even if a node's defaults change.
*
* `undefined` or `"*"` selects everything.
*
* @throws If the selection asks for nothing, or names an attribute outside the name grammar.
*
* @example
* toRpcSelect()                                     // everything
* toRpcSelect({ $key: true })                       // only the key
* toRpcSelect({ $key: true, projectId: true })      // the key and one attribute
*/
function toRpcSelect(selection) {
	return projectSelection(selection).select;
}
/**
* A frozen copy of a selection, so that editing the object passed to `select()` afterwards cannot
* change what the builder sends.
*/
function snapshotSelection(selection) {
	if (typeof selection !== "object" || selection === null) return selection;
	return Object.freeze({ ...selection });
}
/**
* Resolves a selection, through a schema or without one. A key that does not start with `$` names
* an attribute — through a schema, it must be a column.
*
* `undefined` or `"*"` selects every entity field and, through a schema, every column; without one,
* every attribute as the `$attributes` map. The `$attributeSchema` is only selected by name.
*
* Attributes are fetched by name where possible, so the ones a row does not carry stay on the node.
* A named schema's tag is fetched too, so each entity can be checked against it.
*
* @throws If the selection asks for nothing, names a `$` key that is not an entity field, names an
* attribute outside the name grammar, or — through a schema — names an attribute that is not a
* column.
*/
function projectSelection(selection, schema) {
	const fields = [];
	const columns = [];
	let attributes = false;
	let attributeSchema = false;
	if (selection === void 0 || selection === "*") {
		fields.push(...SYSTEM_FIELDS);
		if (schema === void 0) attributes = true;
		else columns.push(...Object.entries(schema.columns));
	} else {
		if (typeof selection !== "object" || selection === null || Array.isArray(selection)) throw new TypeError("select() takes \"*\" or an object of fields set to true");
		for (const [name, wanted] of Object.entries(selection)) {
			if (typeof wanted !== "boolean" && wanted !== void 0) throw new Error(name === "$attributes" ? "select() takes true or false for \"$attributes\", which reads every attribute. To read some of them, select each by name: select({ title: true, body: true })." : `select() takes true or false for "${name}", got ${typeof wanted}.`);
			const fixed = SELECTION_KEYS.includes(name);
			if (!fixed && name.startsWith("$")) throw new Error(`select() names "${name}", which is not an entity field. The entity fields are ${SYSTEM_FIELDS.join(", ")}, and $attributes and $attributeSchema read the attributes as a whole.`);
			const column = fixed || schema === void 0 ? void 0 : require_schema.lookupColumn(schema, name);
			if (!fixed) {
				if (schema !== void 0 && column === void 0) throw new Error(`select() names "${name}", which is not a column of the schema. Its columns are ${require_schema.describeColumns(schema)}.`);
				require_schema.validateAttributeName(name);
			}
			if (!wanted) continue;
			if (name === "$attributes") attributes = true;
			else if (name === "$attributeSchema") attributeSchema = true;
			else if (fixed) fields.push(name);
			else columns.push([name, column]);
		}
		if (fields.length === 0 && columns.length === 0 && !attributes && !attributeSchema) throw nothingSelected();
	}
	const select = {};
	for (const field of SYSTEM_FIELDS) select[entityField(field)] = fields.includes(field);
	select.attributeSchema = attributeSchema;
	if (attributes) select.attributes = true;
	else {
		const names = new Set(columns.map(([name]) => name));
		if (schema?.tag !== void 0) names.add(require_schema.SCHEMA_ATTRIBUTE);
		select.attributes = names.size > 0 ? Object.fromEntries([...names].map((n) => [n, true])) : false;
	}
	return {
		select,
		fields,
		columns,
		attributes,
		attributeSchema,
		tag: schema?.tag
	};
}
/**
* Lays an entity out as a row of `projection` — checking it against the schema, when there is one.
*
* @throws {SchemaMismatchError} If a named schema's entity is not tagged with its name, or the
* entity lacks a selected required column or holds one with a type other than the schema's — it
* was not written through this schema, or was patched since.
*/
function toRow(entity, projection) {
	const subject = entity.key === void 0 ? "the entity" : `entity ${entity.key}`;
	const { tag } = projection;
	if (tag !== void 0) {
		const found = attributeOf(entity, require_schema.SCHEMA_ATTRIBUTE);
		if (found?.type !== "str" || found.value !== tag.value) throw new require_schema.SchemaMismatchError(require_schema.SCHEMA_ATTRIBUTE, `${subject} is ${describeTag(found)}, not ${JSON.stringify(tag.value)}, so it was not written through this schema`);
	}
	const row = {};
	for (const field of projection.fields) row[field] = entity[entityField(field)];
	for (const [name, column] of projection.columns) {
		const value = attributeOf(entity, name);
		if (column !== void 0) {
			if (value === void 0) {
				if (!column.isOptional) throw new require_schema.SchemaMismatchError(name, `${subject} has no value for this required ${column.type} column, so it does not match the schema. Declare the column .optional() if entities may leave it unset`);
			} else if (value.type !== column.type) throw new require_schema.SchemaMismatchError(name, `${subject} holds a ${value.type}, not the ${column.type} the schema declares`);
		}
		row[name] = value;
	}
	if (projection.attributes) row.$attributes = withoutTag(entity.attributes ?? {}, tag);
	if (projection.attributeSchema) row.$attributeSchema = withoutTag(entity.attributeSchema ?? {}, tag);
	return row;
}
/** The {@link Entity} property, and RPC `select` key, a system field reads: its name without `$`. */
function entityField(field) {
	return field.slice(1);
}
/**
* A copy of `record` without a named schema's tag: the tag is how a query finds the schema's
* entities, not one of their attributes, so a row never carries it — and a map read back from a
* row drops into a write without sending the reserved name.
*/
function withoutTag(record, tag) {
	if (tag === void 0 || !Object.hasOwn(record, require_schema.SCHEMA_ATTRIBUTE)) return record;
	const { [require_schema.SCHEMA_ATTRIBUTE]: _tag,...rest } = record;
	return rest;
}
/**
* The entity's attribute `name`, or `undefined` if it has none. An own-property read: a column may
* be named `constructor` or `toString`, and an entity that leaves it unset must not find the
* inherited one instead.
*/
function attributeOf(entity, name) {
	const { attributes } = entity;
	return attributes !== void 0 && Object.hasOwn(attributes, name) ? attributes[name] : void 0;
}
/** How an entity's schema tag reads in a mismatch — whatever type another writer gave it. */
function describeTag(found) {
	if (found === void 0) return "not tagged with a schema";
	if (found.type === "str") return `tagged ${JSON.stringify(found.value)}`;
	return `tagged with a value of type ${found.type}`;
}
function nothingSelected() {
	return /* @__PURE__ */ new Error("select() requires at least one field to be selected. Pass select() or select(\"*\") to select everything.");
}

//#endregion
//#region src/entity/flags.ts
/** Bit positions in the `creationFlags` byte. Bits 2-7 are reserved and must be zero. */
const READONLY_BIT = 1;
const PERMISSIONLESS_EXTENSION_BIT = 2;
/**
* Packs creation flags into the `creationFlags` byte.
*
* @throws {InvalidCreationFlagsError} If a value is not a boolean.
*/
function encodeCreationFlags(flags = {}) {
	for (const [name, value] of Object.entries(flags)) if (value !== void 0 && typeof value !== "boolean") throw new require_errors.InvalidCreationFlagsError(`"${name}" must be a boolean, got ${typeof value}`);
	return (flags.readonly ? READONLY_BIT : 0) | (flags.permissionlessExtension ? PERMISSIONLESS_EXTENSION_BIT : 0);
}
/**
* Reads the `creationFlags` byte back.
*
* @param raw - The `creationFlags` byte.
* @throws {InvalidCreationFlagsError} Malformed input
*/
function decodeCreationFlags(raw) {
	if (!Number.isInteger(raw) || raw < 0 || raw > 255) throw new require_errors.InvalidCreationFlagsError(`${raw} is not a byte`);
	return {
		readonly: (raw & READONLY_BIT) !== 0,
		permissionlessExtension: (raw & PERMISSIONLESS_EXTENSION_BIT) !== 0,
		raw
	};
}

//#endregion
//#region src/utils/entities.ts
const logger$1 = getLogger("utils:entities");
/**
* Turns the attribute array a query returns into a name-keyed map of typed values.
*
* Attribute names are unique per entity, so the map loses nothing and gives callers
* `entity.attributes.level.value` instead of a linear search.
*/
function decodeAttributes(rpcAttributes) {
	const attributes = {};
	for (const { name, type, value } of rpcAttributes) attributes[name] = require_schema.decodeRpcValue(type, value);
	return attributes;
}
/** The same, for the values-free `attributeSchema` projection. */
function decodeSchema(entries) {
	const schema = {};
	for (const { name, type, indexed } of entries) schema[name] = {
		type: require_schema.asTypeTag(type),
		indexed: typeof indexed === "boolean" ? indexed : void 0
	};
	return schema;
}
/**
* Builds an {@link Entity} from a query result row.
*
* A field the query did not select is absent from the response and stays `undefined` here — the
* decoder never substitutes an empty value for one that was not asked for.
*/
function entityFromRpcResult(rpcEntity) {
	logger$1("entityFromRpcResult %o", rpcEntity);
	return new require_entity.Entity({
		key: rpcEntity.key,
		owner: toAddress(rpcEntity.owner),
		creator: toAddress(rpcEntity.creator),
		createdAt: toBlock(rpcEntity.createdAt),
		updatedAt: toBlock(rpcEntity.updatedAt),
		expiresAt: toBlock(rpcEntity.expiresAt),
		creationFlags: decodeFlags(rpcEntity.creationFlags),
		attributeSchema: rpcEntity.attributeSchema !== void 0 ? decodeSchema(rpcEntity.attributeSchema) : void 0,
		attributes: rpcEntity.attributes !== void 0 ? decodeAttributes(rpcEntity.attributes) : void 0
	});
}
function toBlock(value) {
	return value === void 0 ? void 0 : BigInt(value);
}
function toAddress(value) {
	if (value === void 0) return void 0;
	if (!/^0x[0-9a-fA-F]{40}$/.test(value)) {
		logger$1("unreadable address %o", value);
		return value;
	}
	return (0, viem.checksumAddress)(value);
}
/**
* Resolves the `creationFlags` projection, preferring the byte.
*/
function decodeFlags(value) {
	if (value === void 0) return void 0;
	const raw = toFlagsByte(typeof value === "number" ? value : value.raw);
	if (raw !== void 0) return decodeCreationFlags(raw);
	if (typeof value === "object" && (value.readonly ?? value.permissionlessExtension) !== void 0) return decodeCreationFlags(encodeCreationFlags({
		readonly: value.readonly ?? false,
		permissionlessExtension: value.permissionlessExtension ?? false
	}));
	logger$1("unreadable creationFlags %o", value);
}
/**
* A flags byte in whichever spelling arrived: a JSON number, or the `0x` QUANTITY that every other
* numeric field on {@link RpcEntity} already uses. `undefined` for anything that is not a byte.
*/
function toFlagsByte(value) {
	const parsed = typeof value === "number" ? value : typeof value === "string" && /^(0x[0-9a-fA-F]+|\d+)$/.test(value) ? Number(value) : NaN;
	return Number.isInteger(parsed) && parsed >= 0 && parsed <= 255 ? parsed : void 0;
}

//#endregion
//#region src/query/errors.ts
/**
* A predicate asked for an operator the query language does not define for that type.
*
* The operator × type matrix is part of the language, not a policy: a prefix test on a type other
* than `str` is a **type error** at the node (a {@link QueryError} of kind `"type"`), not
* an empty result. Catching it here turns a round trip and an RPC error code into a stack trace
* pointing at the call site.
*
* | | `=` `<` `<=` `>` `>=` `BETWEEN` | `STARTSWITH` |
* | --- | --- | --- |
* | `str` | yes | yes |
* | every other indexable type | yes | no |
*/
var UnsupportedOperatorError = class extends Error {
	/** The attribute the predicate names. */
	attributeName;
	/** The operator, as it would have been written in the query. */
	operator;
	/** The type of the value it was applied to. */
	tag;
	constructor(attributeName, operator, tag, hint) {
		super(`The query language does not define ${operator} for a ${tag}, so "${attributeName} ${operator} ..." cannot be expressed.${hint ? ` ${hint}` : ""}`);
		this.name = "UnsupportedOperatorError";
		this.attributeName = attributeName;
		this.operator = operator;
		this.tag = tag;
	}
};
/**
* A predicate or query could not be expressed in the query language for a reason other than its
* operator. The main causes:
*
* - a `$` name that is not a queryable system attribute — one returned in results but never
*   indexed (`$createdAt`, `$updatedAt`, `$creationFlags`), or none at all;
* - a system attribute compared against the wrong type, or tested with `exists()` / `hasType()`,
*   which on a system attribute would be constants;
* - a comparison against a `bytes` value, which is never indexed;
* - a `BETWEEN` whose bounds have different types;
* - a `hasType()` tag that is not an indexable type;
* - a combinator with nothing to combine;
* - a query with no filter at all — a named schema's tag counts as one;
* - through `.from(schema)`, a filter that does not fit the schema: a name that is not a column, a
*   column that is not indexed, or a value of another type than the column's;
* - a sort key that cannot be sent: an `orderBy()` with no key or more than one, a key on a bytes
*   column or on something that is not a column of the schema, one with no type and no schema to
*   take it from, or one whose type is not the attribute's.
*/
var InvalidPredicateError = class extends Error {
	/** The attribute the predicate names, where there is one. */
	attributeName;
	constructor(message, attributeName) {
		super(message);
		this.name = "InvalidPredicateError";
		this.attributeName = attributeName;
	}
};
/** The JSON-RPC error codes `arkiv_query` answers with, and what each one means. */
const QUERY_ERROR_KINDS = {
	[-32001]: "parse",
	[-32002]: "type",
	[-32003]: "literal",
	[-32004]: "limits"
};
/**
* The node rejected a query.
*
* These codes are part of the frozen `arkiv_query` surface, so {@link kind} and {@link data} are
* stable enough to branch on — which the underlying JSON-RPC error is not, because the codes
* overlap Ethereum's own EIP-1474 numbering and get wrapped accordingly on the way up.
*
* @example
* try {
*   await client.query("category = 'docs'")
* } catch (error) {
*   if (error instanceof QueryError && error.kind === "parse") {
*     console.log(error.data) // { position, expected, got }
*   }
* }
*/
var QueryError = class extends Error {
	/** Which failure this is. */
	kind;
	/** The JSON-RPC error code. */
	code;
	/** The machine-readable detail the node sent, if any — e.g. `{ position, expected, got }`. */
	data;
	/** The query string that was rejected. */
	query;
	constructor(kind, code, query, data, detail) {
		super(`Query rejected (${kind}, ${code})${detail ? `: ${detail}` : ""}. Query: ${query}`, { cause: data });
		this.name = "QueryError";
		this.kind = kind;
		this.code = code;
		this.data = data;
		this.query = query;
	}
};
/**
* Recognises an `arkiv_query` failure in whatever the transport threw.
*
* The codes are dug out of the error chain rather than read off the top: viem maps this range onto
* its own EIP-1474 error classes (`-32001` is its "resource not found"), so by the time an error
* surfaces the Arkiv meaning is one or more `cause` links down.
*
* @returns The typed error, or `undefined` if this was not a query rejection.
*/
function asQueryError(error, query) {
	let code;
	let data;
	let detail;
	let current = error;
	for (let depth = 0; current != null && depth < 8; depth++) {
		const link = current;
		if (code === void 0 && typeof link.code === "number") code = link.code;
		if (data === void 0 && link.data !== void 0) data = link.data;
		if (detail === void 0 && typeof link.details === "string" && link.details !== "") detail = link.details;
		current = link.cause;
	}
	if (detail === void 0) {
		const message = data?.message;
		if (typeof message === "string" && message !== "") detail = message;
	}
	const kind = code === void 0 ? void 0 : QUERY_ERROR_KINDS[code];
	if (kind === void 0 || code === void 0) return void 0;
	return new QueryError(kind, code, query, data, detail);
}

//#endregion
//#region src/query/engine.ts
const logger = getLogger("query:engine");
const MAX_LIMIT = 100;
/**
* Runs one query and decodes the page it returns.
*
* @throws If `limit` is not an integer from 1 to {@link MAX_LIMIT}, or `offset` is not a
* non-negative integer.
* @throws {QueryError} If the node rejects the query.
*/
async function runQuery(client, request) {
	const { query, select, limit, offset, count } = request;
	if (limit !== void 0) {
		if (!Number.isInteger(limit) || limit < 1) throw new Error(`limit must be a positive integer, got ${limit}.`);
		if (limit > MAX_LIMIT) throw new Error(`limit ${limit} exceeds the node maximum of ${MAX_LIMIT}. Page through the results with an offset instead.`);
	}
	if (offset !== void 0 && (!Number.isSafeInteger(offset) || offset < 0)) throw new Error(`offset must be a non-negative integer, got ${offset}.`);
	const queryOptions = {
		select: count ? {
			...select,
			count: true
		} : select,
		...limit !== void 0 && { limit: (0, viem.numberToHex)(limit) },
		...offset !== void 0 && { offset: (0, viem.numberToHex)(offset) }
	};
	logger("arkiv_query %s %o", query, queryOptions);
	const result = await client.request({
		method: "arkiv_query",
		params: [query, queryOptions]
	}).catch((error) => {
		throw asQueryError(error, query) ?? error;
	});
	logger("Raw result from query %o", result);
	return {
		entities: result.data.map((rpcEntity) => entityFromRpcResult(rpcEntity)),
		blockNumber: BigInt(result.blockNumber),
		count: result.count === void 0 ? void 0 : BigInt(result.count)
	};
}

//#endregion
//#region src/query/expression.ts
/**
* The system attributes a query may filter on, and the type each one carries.
*
* Every other system attribute (`$createdAt`, `$updatedAt`, `$creationFlags`) is returned in
* query results — selected and read back as `createdAt`, `updatedAt` and `creationFlags` — but is
* not indexed, so it cannot appear in a predicate.
*/
const QUERYABLE_SYSTEM_ATTRIBUTES = {
	$key: "key",
	$owner: "addr",
	$creator: "addr",
	$expiresAt: "u64"
};
/** System attributes a query can ask for but not filter on — they carry no index. */
const RESULT_ONLY_SYSTEM_ATTRIBUTES = [
	"$createdAt",
	"$updatedAt",
	"$creationFlags"
];
/**
* Checks an attribute reference, returning its fixed type when it names a system attribute.
*
* User attribute names go through the same grammar the write path enforces, so a name that could
* never have been written is rejected here rather than matching nothing.
*
* @param use - What the reference is for, `"filter"` or `"sort"`, which the error messages name.
*/
function resolveReference(name, use = "filter") {
	if (typeof name !== "string" || !name.startsWith("$")) {
		require_schema.validateAttributeName(name);
		return;
	}
	const system = QUERYABLE_SYSTEM_ATTRIBUTES[name];
	if (system !== void 0) return system;
	if (RESULT_ONLY_SYSTEM_ATTRIBUTES.includes(name)) {
		const field = name.slice(1);
		throw new InvalidPredicateError(`"${name}" is returned in query results but ${use === "filter" ? "carries no index, so it cannot be filtered on" : "cannot be sorted on"}. Select it as \`${field}\` and ${use} the results in JavaScript instead.`, name);
	}
	throw new InvalidPredicateError(`"${name}" is not a system attribute. The queryable ones are ${Object.keys(QUERYABLE_SYSTEM_ATTRIBUTES).join(", ")}. "\$" is reserved for these, so a user attribute cannot be named this.`, name);
}
/**
* Whether a string names a type that can be indexed — every type but `bytes`.
*
* Takes a `string` rather than an `IndexableTypeTag` on purpose — the callers' static types
* already say as much, and this is the runtime check for the code that has no types.
*/
function isIndexableTag(tag) {
	return Object.hasOwn(require_schema.TYPE_IDS, tag) && require_schema.isIndexable(tag);
}
/** The indexable type tags, listed for error messages: "bool, i32, u64, …". */
const INDEXABLE_TAGS = Object.keys(require_schema.TYPE_IDS).filter(require_schema.isIndexable).join(", ");
/** Freezes a node and gives it a (non-enumerable, so still `toEqual`-able) rendering. */
function node(value) {
	Object.defineProperty(value, "toString", {
		value: () => render(value),
		enumerable: false
	});
	return Object.freeze(value);
}
/**
* Resolves a value a predicate compares `name` against, and checks it can be: not `bytes`, which is
* never indexed, and of the fixed type when `name` is a system attribute.
*/
function predicateValue(name, system, input) {
	const value = require_schema.toValue(input, name);
	if (value.type === "bytes") throw new InvalidPredicateError(`"${name}" is compared against a bytes value, and bytes are stored but never indexed, so they cannot be filtered on. Index a hash of them, as a hex str, instead.`, name);
	if (system !== void 0 && value.type !== system) throw new InvalidPredicateError(`"${name}" is a ${system}, but the value is a ${value.type}. System attributes have a fixed type — write ${system}(...) around the value.`, name);
	return value;
}
function comparison(name, operator, input) {
	return node({
		kind: "comparison",
		name,
		operator,
		value: predicateValue(name, resolveReference(name), input)
	});
}
/**
* Attribute equals value — set with this exact type, and holding this value.
*
* Bare booleans, numbers, bigints and strings take the same default types they take when written
* (`number` is an `i32`, `bigint` a `u256`, `string` a `str`); anything else names its type.
*
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
*
* @example
* eq("status", str("open"))     // status = str('open')
* eq("flagged", true)           // flagged = true
* eq("$owner", addr(account))   // $owner = addr(0xd8dA…)
*/
function eq(name, value) {
	return comparison(name, "=", value);
}
/**
* Attribute is greater than value, in index order — see {@link ComparisonOperator}.
*
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
*
* @example
* gt("level", i32(10))              // level > i32(10)
* gt("$expiresAt", u64(1_200_000n)) // $expiresAt > u64(1200000)
*/
function gt(name, value) {
	return comparison(name, ">", value);
}
/**
* Attribute is greater than or equal to value, in index order — see {@link ComparisonOperator}.
*
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
*
* @example
* gte("score", dec("3.5")) // score >= dec(3.5)
*/
function gte(name, value) {
	return comparison(name, ">=", value);
}
/**
* Attribute is less than value, in index order — see {@link ComparisonOperator}.
*
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
*
* @example
* lt("level", i32(10)) // level < i32(10)
*/
function lt(name, value) {
	return comparison(name, "<", value);
}
/**
* Attribute is less than or equal to value, in index order — see {@link ComparisonOperator}.
*
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
* attribute, a system attribute is compared against the wrong type, or the value is `bytes`.
*
* @example
* lte("score", dec("5")) // score <= dec(5)
*/
function lte(name, value) {
	return comparison(name, "<=", value);
}
/**
* Attribute lies between two values, **inclusive** at both ends: it matches exactly what
* `and(gte(name, low), lte(name, high))` matches, in one predicate. Both bounds must have one type,
* and a `low` above `high` matches nothing.
*
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
* @throws {InvalidPredicateError} If the bounds have different types, the name starts with `$` but
* is not a queryable system attribute, a system attribute is compared against the wrong type, or a
* bound is `bytes`.
*
* @example
* between("score", dec("3.5"), dec("5"))  // score BETWEEN dec(3.5) AND dec(5)
* between("$expiresAt", u64(100n), u64(200n))
*/
function between(name, low, high) {
	const system = resolveReference(name);
	const lowValue = predicateValue(name, system, low);
	const highValue = predicateValue(name, system, high);
	if (lowValue.type !== highValue.type) throw new InvalidPredicateError(`BETWEEN on "${name}" has a ${lowValue.type} and a ${highValue.type} bound. Both bounds must have one type, the type the attribute is compared as.`, name);
	return node({
		kind: "between",
		name,
		low: lowValue,
		high: highValue
	});
}
/**
* Attribute is a `str` beginning with this prefix.
*
* The match is on **raw UTF-8 bytes** with no normalization — it is the index encoding, so
* `startsWith("name", "é")` matches only the same byte sequence, not another normal form of it.
*
* @param prefix - The prefix, as a bare string or a `str` value.
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
* @throws {InvalidPredicateError} If the name starts with `$` but is not a queryable system
* attribute.
* @throws {UnsupportedOperatorError} If given a value that is not a `str`, or a system attribute
* (none of which is a `str`).
*
* @example
* startsWith("desc", "ab") // desc STARTSWITH str('ab')
*/
function startsWith(name, prefix) {
	const system = resolveReference(name);
	const value = typeof prefix === "string" ? require_schema.str(prefix) : require_schema.toValue(prefix, name);
	if (value.type !== "str") throw new UnsupportedOperatorError(name, "STARTSWITH", value.type, "Only str carries a prefix index.");
	if (system !== void 0) throw new UnsupportedOperatorError(name, "STARTSWITH", system, "No queryable system attribute is a str.");
	return node({
		kind: "startsWith",
		name,
		value
	});
}
/**
* The attribute is set, with any type.
*
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
* @throws {InvalidPredicateError} If given a system attribute — every entity has all of them, so
* the test would be a constant — or a `$` name that is not one.
*
* @example
* exists("reviewedBy")        // EXISTS(reviewedBy)
*/
function exists(name) {
	if (resolveReference(name) !== void 0) throw new InvalidPredicateError(`"${name}" is a system attribute, so it is set on every entity and EXISTS(${name}) is always true.`, name);
	return node({
		kind: "exists",
		name
	});
}
/**
* The attribute is set with exactly this type, whatever its value.
*
* Useful when the same name is written with different types across entities — a comparison already
* asserts the type, so this is for asking about the type alone.
*
* @param tag - The type tag: `bool`, `i32`, `u64`, `u256`, `dec`, `str`, `addr` or `key` — any
* type but `bytes`, which is never indexed.
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
* @throws {InvalidPredicateError} If the tag is not an indexable type, the name is a system
* attribute (whose type is fixed), or the name starts with `$` but is not a system attribute.
*
* @example
* hasType("age", "i32") // TYPEOF(age) = i32
*/
function hasType(name, tag) {
	const system = resolveReference(name);
	if (system !== void 0) throw new InvalidPredicateError(`"${name}" is always a ${system} — a system attribute has a fixed type, so TYPEOF(${name}) is a constant.`, name);
	if (!isIndexableTag(tag)) throw new InvalidPredicateError(`"${tag}" is not an indexable attribute type. Use one of ${INDEXABLE_TAGS}.`, name);
	return node({
		kind: "typeOf",
		name,
		tag
	});
}
function and(...expressions) {
	return combine("and", expressions.flat());
}
function or(...expressions) {
	return combine("or", expressions.flat());
}
function combine(kind, expressions) {
	if (expressions.length === 0) throw new InvalidPredicateError(`${kind}() needs at least one expression. An empty ${kind === "and" ? "conjunction" : "disjunction"} has no query-language spelling — leave the ${kind}() out. A query still needs at least one filter, so to match broadly narrow by owner or creator, or read through a named schema, whose tag counts as one.`);
	const only = expressions[0];
	if (expressions.length === 1 && only !== void 0) return only;
	return node(kind === "and" ? {
		kind: "and",
		expressions: Object.freeze(expressions)
	} : {
		kind: "or",
		expressions: Object.freeze(expressions)
	});
}
/**
* Checks every attribute an expression names against a schema — the runtime half of the check
* {@link SchemaRef} makes at compile time, for the callers the type system does not reach.
*
* @throws {InvalidPredicateError} If a predicate names something that is not a column of the
* schema, a column that is not indexed, or compares a column against a value of another type.
*/
function checkAgainstSchema(expression, schema) {
	switch (expression.kind) {
		case "and":
		case "or":
			for (const sub of expression.expressions) checkAgainstSchema(sub, schema);
			return;
	}
	const { name } = expression;
	if (name.startsWith("$")) return;
	const column = sortableColumn(schema, name, "filter");
	if (!column.isIndexed) throw new InvalidPredicateError(`"${name}" is not indexed, and only indexed attributes can be filtered on. Declare it ${column.type}().indexed() — entities written before that are indexed by their next patch of it — or select it and filter the results in JavaScript.`, name);
	const columnType = column.type;
	const asserted = expression.kind === "comparison" || expression.kind === "startsWith" ? expression.value.type : expression.kind === "between" ? expression.low.type : expression.kind === "typeOf" ? expression.tag : void 0;
	if (asserted !== void 0 && asserted !== columnType) throw new InvalidPredicateError(`"${name}" is a ${columnType} column, but the predicate asserts a ${asserted}, so it could never match. Write ${columnType}(...) around the value.`, name);
}
/**
* The column a filter or sort through a schema names, which must be of any type but bytes. A sort
* reads it whether or not it is indexed; a filter also needs it indexed, which the caller checks.
*
* @param use - What the column is for, `"filter"` or `"sort"`, which the error messages name.
* @throws {InvalidPredicateError} If the name is not a column of the schema, or names a bytes one.
*/
function sortableColumn(schema, name, use) {
	const column = require_schema.lookupColumn(schema, name);
	if (column === void 0) throw new InvalidPredicateError(`"${name}" is not a column of the schema, so ${use === "filter" ? "a predicate on it can only match entities written some other way" : "it cannot be sorted on"}. Its columns are ${require_schema.describeColumns(schema)}.`, name);
	if (column.type === "bytes") throw new InvalidPredicateError(use === "filter" ? `"${name}" is a bytes column, and bytes are stored but never indexed, so it cannot be filtered on. Index a hash of it, as a hex str, instead, or select it and filter the results in JavaScript.` : `"${name}" is a bytes column, and bytes cannot be sorted on. Select it and sort the results in JavaScript.`, name);
	return {
		type: column.type,
		isIndexed: column.isIndexed
	};
}
/**
* Binding strength, tightest last — `AND` binds tighter than `OR`. A leaf never needs parentheses,
* so it sits above them both.
*/
const PRECEDENCE = {
	or: 1,
	and: 2,
	leaf: 3
};
function precedenceOf(expression) {
	switch (expression.kind) {
		case "or": return PRECEDENCE.or;
		case "and": return PRECEDENCE.and;
		default: return PRECEDENCE.leaf;
	}
}
/** Renders a subexpression, parenthesising it only where the precedence would otherwise change it. */
function nested(expression, minimum) {
	const rendered = render(expression);
	return precedenceOf(expression) < minimum ? `(${rendered})` : rendered;
}
/**
* Renders an expression as the query string the node parses. Equivalent to `String(expression)`.
*
* Literals are always written tagged, even where the language would infer them: an untagged number
* defaults to `i32`, but spelling it `i32(10)` keeps the exact-type assertion visible in the query
* that actually went over the wire.
*/
function render(expression) {
	switch (expression.kind) {
		case "comparison": return `${expression.name} ${expression.operator} ${renderLiteral(expression.value)}`;
		case "between": return `${expression.name} BETWEEN ${renderLiteral(expression.low)} AND ${renderLiteral(expression.high)}`;
		case "startsWith": return `${expression.name} STARTSWITH ${renderLiteral(expression.value)}`;
		case "exists": return `EXISTS(${expression.name})`;
		case "typeOf": return `TYPEOF(${expression.name}) = ${expression.tag}`;
		case "and": return expression.expressions.map((sub) => nested(sub, PRECEDENCE.and)).join(" AND ");
		case "or": return expression.expressions.map((sub) => nested(sub, PRECEDENCE.or)).join(" OR ");
	}
}
/** Renders a typed value as its query literal. `bool` is the one type written bare. */
function renderLiteral(value) {
	switch (value.type) {
		case "bool": return value.value ? "true" : "false";
		case "i32": return `i32(${value.value})`;
		case "u64": return `u64(${value.value})`;
		case "u256": return `u256(${value.value})`;
		case "dec": return `dec(${value.value})`;
		case "key": return `key(${value.value})`;
		case "addr": return `addr(${value.value})`;
		case "str": return `str('${value.value.replaceAll("'", "''")}')`;
	}
}

//#endregion
//#region src/query/order.ts
/** Whether a value is a key built by {@link asc} or {@link desc}. */
function isOrderTerm(value) {
	return typeof value === "object" && value !== null && value.kind === "order";
}
/**
* Checks a key that did not come from {@link asc} or {@link desc} — parsed from a request, say —
* as they check theirs, and returns it as they would have built it.
*
* @throws {InvalidAttributeNameError} If the name is outside the attribute name grammar.
* @throws {InvalidPredicateError} If the value is not a key, its direction is not `"asc"` or
* `"desc"`, or its name or type is one `asc` and `desc` reject.
*/
function checkedOrderTerm(value) {
	if (!isOrderTerm(value)) throw new InvalidPredicateError("A sort key is an asc(...) or a desc(...).");
	const { name, type, direction } = value;
	return orderTerm(name, type, direction);
}
function orderTerm(name, type, direction) {
	const system = resolveReference(name, "sort");
	if (direction !== "asc" && direction !== "desc") throw new InvalidPredicateError(`A sort runs "asc" or "desc", not ${JSON.stringify(direction)}.`, name);
	if (type !== void 0 && (typeof type !== "string" || !isIndexableTag(type))) throw new InvalidPredicateError(`"${name}" cannot be sorted as "${type}": a sort reads one of ${INDEXABLE_TAGS}.`, name);
	if (system !== void 0 && type !== void 0 && type !== system) throw new InvalidPredicateError(`"${name}" is always a ${system}, so it cannot be sorted as a ${type}. Leave the type out — a system attribute's is fixed.`, name);
	return Object.freeze({
		kind: "order",
		name,
		type: type ?? system,
		direction
	});
}
function asc(name, type) {
	return orderTerm(name, type, "asc");
}
function desc(name, type) {
	return orderTerm(name, type, "desc");
}
/**
* Renders the `ORDER BY` clause the node parses, taking the key's type from the schema where the
* key leaves it out — the runtime half of {@link OrderCheck}, for the callers the type system does
* not reach.
*
* @throws {InvalidPredicateError} If the key has no type and there is no schema to give it one,
* or — through a schema — names a bytes column or something that is not a column, or asserts
* another type than the column's.
*/
function renderOrderBy(term, schema) {
	return `ORDER BY ${term.name}:${resolveOrderType(term, schema)} ${term.direction.toUpperCase()}`;
}
function resolveOrderType(term, schema) {
	const { name, type } = term;
	if (name.startsWith("$")) {
		if (type !== void 0) return type;
		throw new InvalidPredicateError(`The key on "${name}" has no type, though a system attribute's is fixed. Build it with asc() or desc(), which give it one.`, name);
	}
	if (schema === void 0) {
		if (type !== void 0) return type;
		throw new InvalidPredicateError(`"${name}" needs its type to be sorted on: the node sorts it as one type, and without a schema there is no column to take it from. Write it, as in desc("${name}", "u256").`, name);
	}
	const columnType = sortableColumn(schema, name, "sort").type;
	if (type !== void 0 && type !== columnType) throw new InvalidPredicateError(`"${name}" is a ${columnType} column, but the sort asserts a ${type}. Leave the type out to sort it as the column's.`, name);
	return columnType;
}

//#endregion
//#region src/query/builderBase.ts
/**
* What every query builder shares: the client, the filters — `where`, `ownedBy`, `createdBy` and a
* named schema's tag — and the block to read at. `client.select()` and `client.count()` build on it,
* so the same filters always send the same query string, whichever of them runs it.
*
* Not constructed directly: create a builder with `client.select(...)` or `client.count()`.
*
* @typeParam TSchema - The schema named with `from()`, if any, which `where` is checked against.
*/
var QueryBuilderBase = class {
	_client;
	_schema;
	_filters = [];
	_ownedBy;
	_createdBy;
	constructor(client) {
		this._client = client;
	}
	/**
	* This builder's state in a new builder of the same class, re-typed by the caller. Every field is
	* carried over, so one a subclass adds is not dropped by `from()`; the filter list, which is
	* mutated in place, is copied rather than shared.
	*
	* A copy, not `this` re-typed: a builder typed for one schema must never be the same object as
	* one typed for another, or `base.from(Posts)` and `base.from(Users)` would share their state.
	*/
	copy() {
		const copy = Object.create(Object.getPrototypeOf(this));
		return Object.assign(copy, this, { _filters: [...this._filters] });
	}
	/**
	* Adds filters. Everything passed here, and across repeated calls, must hold.
	*
	* After `from()`, every attribute a filter names must be an indexed column of the schema,
	* compared against a value of its type — anything else is a compile error.
	*
	* @param expressions - The expressions, as separate arguments or one array.
	*
	* @example
	* builder.where(eq("category", "docs"))
	* builder.where(gte("level", i32(10)), lt("level", i32(20)))
	* builder.where(or(eq("status", "open"), exists("reviewedBy")))
	*/
	where(...expressions) {
		this._filters.push(...expressions.flat());
		return this;
	}
	/**
	* Restricts the query to entities this account owns — shorthand for
	* `where(eq("$owner", addr(owner)))`. Calling it again replaces the filter.
	*
	* @example
	* builder.ownedBy("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
	*/
	ownedBy(owner) {
		this._ownedBy = eq("$owner", require_schema.addr(owner));
		return this;
	}
	/**
	* Restricts the query to entities this account created — shorthand for
	* `where(eq("$creator", addr(creator)))`. Calling it again replaces the filter.
	*
	* The creator never changes; the owner does, so these differ after a transfer.
	*/
	createdBy(creator) {
		this._createdBy = eq("$creator", require_schema.addr(creator));
		return this;
	}
	/**
	* The query string this builder will send — the same text the node parses: its filters,
	* `ownedBy` and `createdBy`, and a named schema's tag, ANDed.
	*
	* @throws {InvalidPredicateError} If no filter has been added, or — after `from()` — a filter
	* does not fit the schema. A named schema's own tag filter counts as a filter.
	*/
	toString() {
		const filters = [...this._filters];
		if (this._ownedBy) filters.push(this._ownedBy);
		if (this._createdBy) filters.push(this._createdBy);
		const schema = this._schema;
		if (schema !== void 0) {
			for (const filter of filters) checkAgainstSchema(filter, schema);
			if (schema.tag !== void 0) filters.unshift(eq(require_schema.SCHEMA_ATTRIBUTE, schema.tag));
		}
		if (filters.length === 0) throw new InvalidPredicateError("A query needs at least one filter — the language has no spelling for 'match every entity'. Add a where(...) predicate, or narrow by ownedBy(...) / createdBy(...).");
		return and(filters).toString();
	}
};

//#endregion
//#region src/query/countBuilder.ts
/**
* Builds and runs a count: how many entities a query matches, without fetching any of them.
*
* It filters exactly as `client.select()` does — {@link from}, `where`, `ownedBy` and `createdBy`
* are the same methods, from the same base — but has nothing to select and no pages, so
* {@link fetch} resolves to a `bigint`. Create one with `client.count()`.
*
* To show the total next to a page of rows, use `select(...).withCount()` instead, which counts in
* the same request.
*
* @typeParam TSchema - The schema named with {@link from}, if any.
*
* @example
* const total = await client.count().from(Posts).ownedBy(account.address).fetch()
*/
var CountQueryBuilder = class extends QueryBuilderBase {
	/**
	* Counts through a schema: `where` is checked against its indexed columns, and a named schema
	* counts only entities tagged with its name — so it needs no other filter.
	*
	* Returns a new builder, carrying whatever this one already holds; this one is left as it was.
	*
	* @throws {SchemaDefinitionError} If `schema` was not built with `defineSchema`.
	*
	* @example
	* client.count().from(Posts)   // every post
	*/
	from(schema) {
		if (!require_schema.isSchema(schema)) throw new require_schema.SchemaDefinitionError("from() takes a schema built with defineSchema(...)");
		const typed = this.copy();
		typed._schema = schema;
		return typed;
	}
	/**
	* Runs the count.
	*
	* Counts every entity the filters match at the block, whether or not it fits the schema — what
	* `select().from(schema)` leaves out of its pages is still counted.
	*
	* @throws {InvalidPredicateError} If no filter has been added, or a filter does not fit the
	* schema.
	* @throws {QueryError} If the node rejects the query.
	* @throws If the node returns no count — it predates the count projection.
	*/
	async fetch() {
		const response = await runQuery(this._client, {
			query: this.toString(),
			select: {},
			count: true
		});
		if (response.count === void 0) throw new Error("The node returned no count. It may predate the count projection.");
		return response.count;
	}
};

//#endregion
//#region src/query/queryResult.ts
/**
* One page of query results.
*
* A page is one request: to read further, run the query again with an `offset()` past this page.
*
* @typeParam TEntity - The shape of each row, inferred from the selection by `client.select()`.
* @typeParam TCount - `bigint` when the query was built with `withCount()`, and `undefined` when it
* was not.
*
* @example
* const page = await client
*   .select({ $key: true })
*   .where(eq("category", "docs"))
*   .orderBy(asc("$key"))
*   .limit(20)
*   .offset(40)
*   .fetch()
*/
var QueryResult = class {
	/** The rows on this page — flat, as the selection lays them out. */
	entities;
	/**
	* The block this page was read at. Every request reads the latest block, so two pages of one
	* query can come from different blocks.
	*/
	blockNumber;
	/**
	* How many matching entities this page left out because they did not fit the schema — always
	* `0` without a schema, or with `.from(schema, { onMismatch: "throw" })`.
	*
	* The node counts them toward the limit and the offset, so `entities.length + skipped` is how
	* many matches this page covered: add it to the offset to reach the next page. With an explicit
	* `limit()`, a page that covered fewer than the limit was the last. An empty page is not the
	* end on its own: every match on it may have been skipped.
	*/
	skipped;
	/**
	* How many entities the query matches in all, regardless of the limit and offset — a `bigint`
	* when the query was built with `withCount()`, and `undefined` otherwise. It includes entities
	* left out for not fitting the schema, which {@link skipped} reports per page.
	*/
	count;
	constructor(entities, blockNumber, skipped, count) {
		this.entities = entities;
		this.blockNumber = blockNumber;
		this.skipped = skipped;
		this.count = count;
	}
};

//#endregion
//#region src/query/queryBuilder.ts
/**
* Builds and runs a query.
*
* The selection is fixed when the builder is created and the filter is added by chaining, so the
* result type is known before a single predicate is written. Create one with `client.select(...)`
* rather than constructing it directly — that is what infers the result shape from the selection.
*
* The query returns flat rows: the selected entity fields and attributes side by side. Naming a
* schema with {@link from} changes what the selection means for nothing but the selected
* attributes — they come back typed and checked as the schema declares — and has `where` checked
* against it, so that filtering on a column the schema does not index, or comparing one against the
* wrong type, is a compile error. A named schema also restricts the query to entities tagged with
* its name, and leaves that tag out of the rows' `$attributes` and `$attributeSchema`.
*
* Every filter added with {@link where}, {@link ownedBy} and {@link createdBy} must hold; use
* {@link or} for alternatives inside one call. {@link orderBy} sorts what they match.
*
* @typeParam TSelection - The selection, inferred by `client.select()`.
* @typeParam TSchema - The schema named with {@link from}, if any.
*
* @example
* import { createPublicClient } from "@arkiv-network/sdk"
* import { dec } from "@arkiv-network/sdk/schema"
* import { rabdi } from "@arkiv-network/sdk/chains"
* import { and, desc, eq, gt } from "@arkiv-network/sdk/query"
* import { http } from "viem"
*
* const client = createPublicClient({ chain: rabdi({ accessKey: "..." }), transport: http() })
*
* const page = await client
*   .select({ $key: true, author: true, title: true })
*   .from(Posts)
*   .where(and(eq("category", "docs"), gt("score", dec("4.0"))))
*   .orderBy(desc("score"))
*   .limit(20)
*   .fetch()
*
* for (const row of page.entities) console.log(row.$key, row.title.value)
*/
var SelectQueryBuilder = class extends QueryBuilderBase {
	_selection;
	_projection;
	_onMismatch = "skip";
	_limit;
	_offset;
	_withCount = false;
	_orderBy;
	/**
	* @throws If the selection is not `"*"` or an object, asks for nothing, or names an attribute
	* outside the name grammar.
	*/
	constructor(client, selection) {
		super(client);
		this._selection = snapshotSelection(selection);
		this._projection = projectSelection(this._selection);
	}
	/**
	* Runs the query through a schema. The selection keeps its meaning and the rows their shape;
	* each selected attribute becomes a column — typed as the schema declares, and checked against
	* it — and `where` is checked against the schema's indexed columns.
	*
	* A named schema also filters: only entities tagged with its name match, so a query through it
	* sees only entities written through it, and needs no other filter. The tag is how the query finds
	* them rather than one of their attributes, so rows leave it out of `$attributes` and
	* `$attributeSchema`. An unnamed schema types the query without filtering it — `where` alone
	* decides which entities match.
	*
	* A matching entity that does not fit — a selected required column missing or of another type,
	* or, for a named schema, a tag that does not match — is left out of the page, or fails the fetch
	* with a {@link SchemaMismatchError} with `{ onMismatch: "throw" }`. Columns the selection leaves
	* out are not checked.
	*
	* Returns a new builder, carrying whatever this one already holds; this one is left as it was.
	*
	* @param schema - The schema, built with `defineSchema`.
	* @param options - How to treat entities that do not fit. {@link FromOptions}
	* @throws {SchemaDefinitionError} If `schema` was not built with `defineSchema`.
	* @throws If the selection names something that is neither an entity field nor a column.
	*
	* @example
	* client.select({ $key: true, title: true }).from(Posts)   // rows typed { $key; title }
	* client.select().from(Posts)                               // every field and every column
	* client.select().from(Posts, { onMismatch: "throw" })      // fail on entities that do not fit
	*/
	from(schema, options = {}) {
		if (!require_schema.isSchema(schema)) throw new require_schema.SchemaDefinitionError("from() takes a schema built with defineSchema(...)");
		const { onMismatch = "skip" } = options;
		if (onMismatch !== "throw" && onMismatch !== "skip") throw new TypeError(`onMismatch must be "throw" or "skip", got ${String(onMismatch)}`);
		const projection = projectSelection(this._selection, schema);
		const typed = this.copy();
		typed._schema = schema;
		typed._projection = projection;
		typed._onMismatch = onMismatch;
		return typed;
	}
	orderBy(...keys) {
		if (keys.length !== 1) throw new InvalidPredicateError("orderBy() takes one key, an asc(...) or desc(...).");
		this._orderBy = checkedOrderTerm(keys[0]);
		return this;
	}
	/**
	* The query string this builder will send: the filters, as {@link QueryBuilderBase.toString}
	* renders them, followed by the {@link orderBy} clause, if any.
	*
	* @throws {InvalidPredicateError} If no filter has been added, a filter or sort key does not fit
	* the schema, or — without a schema — a sort key on a user attribute has no type.
	*/
	toString() {
		const filters = super.toString();
		if (this._orderBy === void 0) return filters;
		return `${filters} ${renderOrderBy(this._orderBy, this._schema)}`;
	}
	/**
	* Sets the page size, up to the node maximum of 100. Without it the node picks the page size.
	*
	* @example
	* builder.limit(100)
	*/
	limit(limit) {
		this._limit = limit;
		return this;
	}
	/**
	* Skips the first `offset` matches. With {@link limit}, this takes one page: page `n` of size
	* `size` is `.limit(size).offset(n * size)`.
	*
	* Every request reads the latest block, so an entity created or deleted between two requests
	* shifts the matches after it, and a row can repeat or be missed. Sort by a key that does not
	* change, such as `asc("$key")`, so that pages line up at least while nothing is written; without
	* {@link orderBy}, the order is the node's own. Through a schema, the offset counts the entities a
	* page skips as well as its rows.
	*
	* To read every match exactly once while others write, page by key instead: sort by
	* `asc("$key")` and filter on `gt("$key", key(lastKey))`, with `lastKey` the last `$key` of the
	* previous page. Through a schema, use `{ onMismatch: "throw" }` for that: a page of skipped
	* entities returns no rows, so no key to continue from.
	*
	* @example
	* builder.orderBy(asc("$key")).limit(20).offset(40) // the third page of 20
	*/
	offset(offset) {
		this._offset = offset;
		return this;
	}
	/**
	* Also counts every entity the query matches, regardless of the limit and offset. The page then
	* carries the total as {@link QueryResult.count}, typed `bigint`, so a paginated list can show it
	* without a second query. To count without fetching rows, use `client.count()` instead.
	*
	* The node counts what the filters match, at the block the page was read at. Through a schema,
	* that includes entities the page leaves out for not fitting it. Later pages read later blocks,
	* so the count can be out of date by the time they arrive.
	*
	* @throws If, when fetched, the node returns no count — it predates the count projection.
	*
	* Returns a new builder, carrying whatever this one already holds; this one is left as it was.
	*
	* @example
	* const page = await builder.withCount().limit(20).fetch()
	* page.count // every match, not just these 20
	*/
	withCount() {
		const counted = this.copy();
		counted._withCount = true;
		return counted;
	}
	/**
	* Runs the query and returns one page: up to {@link limit} matches, past the first
	* {@link offset}.
	*
	* @throws {InvalidPredicateError} If no filter has been added, or a filter or sort key does not
	* fit the schema.
	* @throws If the {@link limit} is not an integer from 1 to 100, or the {@link offset} is not a
	* non-negative integer.
	* @throws {SchemaMismatchError} If, through a schema, a matching entity does not fit the selected
	* columns or a named schema's tag, and `from()` was given `{ onMismatch: "throw" }`.
	* @throws {QueryError} If the node rejects the query.
	*
	* @example
	* const page = await client.select({ $key: true }).where(eq("category", "docs")).fetch()
	* page.entities
	*/
	async fetch() {
		const projection = this._projection;
		const response = await runQuery(this._client, {
			query: this.toString(),
			select: projection.select,
			limit: this._limit,
			offset: this._offset,
			count: this._withCount
		});
		if (this._withCount && response.count === void 0) throw new Error("The node returned no count. It may predate the count projection.");
		const rows = [];
		let skipped = 0;
		for (const entity of response.entities) try {
			rows.push(toRow(entity, projection));
		} catch (error) {
			if (this._onMismatch === "skip" && error instanceof require_schema.SchemaMismatchError) skipped++;
			else throw error;
		}
		return new QueryResult(rows, response.blockNumber, skipped, response.count);
	}
};

//#endregion
Object.defineProperty(exports, 'CountQueryBuilder', {
  enumerable: true,
  get: function () {
    return CountQueryBuilder;
  }
});
Object.defineProperty(exports, 'InvalidPredicateError', {
  enumerable: true,
  get: function () {
    return InvalidPredicateError;
  }
});
Object.defineProperty(exports, 'MAX_LIMIT', {
  enumerable: true,
  get: function () {
    return MAX_LIMIT;
  }
});
Object.defineProperty(exports, 'QueryError', {
  enumerable: true,
  get: function () {
    return QueryError;
  }
});
Object.defineProperty(exports, 'QueryResult', {
  enumerable: true,
  get: function () {
    return QueryResult;
  }
});
Object.defineProperty(exports, 'SYSTEM_FIELDS', {
  enumerable: true,
  get: function () {
    return SYSTEM_FIELDS;
  }
});
Object.defineProperty(exports, 'SelectQueryBuilder', {
  enumerable: true,
  get: function () {
    return SelectQueryBuilder;
  }
});
Object.defineProperty(exports, 'UnsupportedOperatorError', {
  enumerable: true,
  get: function () {
    return UnsupportedOperatorError;
  }
});
Object.defineProperty(exports, 'and', {
  enumerable: true,
  get: function () {
    return and;
  }
});
Object.defineProperty(exports, 'asc', {
  enumerable: true,
  get: function () {
    return asc;
  }
});
Object.defineProperty(exports, 'between', {
  enumerable: true,
  get: function () {
    return between;
  }
});
Object.defineProperty(exports, 'checkAgainstSchema', {
  enumerable: true,
  get: function () {
    return checkAgainstSchema;
  }
});
Object.defineProperty(exports, 'checkedOrderTerm', {
  enumerable: true,
  get: function () {
    return checkedOrderTerm;
  }
});
Object.defineProperty(exports, 'decodeCreationFlags', {
  enumerable: true,
  get: function () {
    return decodeCreationFlags;
  }
});
Object.defineProperty(exports, 'desc', {
  enumerable: true,
  get: function () {
    return desc;
  }
});
Object.defineProperty(exports, 'encodeCreationFlags', {
  enumerable: true,
  get: function () {
    return encodeCreationFlags;
  }
});
Object.defineProperty(exports, 'entityFromRpcResult', {
  enumerable: true,
  get: function () {
    return entityFromRpcResult;
  }
});
Object.defineProperty(exports, 'eq', {
  enumerable: true,
  get: function () {
    return eq;
  }
});
Object.defineProperty(exports, 'exists', {
  enumerable: true,
  get: function () {
    return exists;
  }
});
Object.defineProperty(exports, 'getLogger', {
  enumerable: true,
  get: function () {
    return getLogger;
  }
});
Object.defineProperty(exports, 'gt', {
  enumerable: true,
  get: function () {
    return gt;
  }
});
Object.defineProperty(exports, 'gte', {
  enumerable: true,
  get: function () {
    return gte;
  }
});
Object.defineProperty(exports, 'hasType', {
  enumerable: true,
  get: function () {
    return hasType;
  }
});
Object.defineProperty(exports, 'lt', {
  enumerable: true,
  get: function () {
    return lt;
  }
});
Object.defineProperty(exports, 'lte', {
  enumerable: true,
  get: function () {
    return lte;
  }
});
Object.defineProperty(exports, 'or', {
  enumerable: true,
  get: function () {
    return or;
  }
});
Object.defineProperty(exports, 'projectSelection', {
  enumerable: true,
  get: function () {
    return projectSelection;
  }
});
Object.defineProperty(exports, 'render', {
  enumerable: true,
  get: function () {
    return render;
  }
});
Object.defineProperty(exports, 'renderOrderBy', {
  enumerable: true,
  get: function () {
    return renderOrderBy;
  }
});
Object.defineProperty(exports, 'runQuery', {
  enumerable: true,
  get: function () {
    return runQuery;
  }
});
Object.defineProperty(exports, 'startsWith', {
  enumerable: true,
  get: function () {
    return startsWith;
  }
});
Object.defineProperty(exports, 'toRow', {
  enumerable: true,
  get: function () {
    return toRow;
  }
});
Object.defineProperty(exports, 'toRpcSelect', {
  enumerable: true,
  get: function () {
    return toRpcSelect;
  }
});