@wharfkit/contract
Advanced tools
Comparing version 0.1.0 to 0.2.0
@@ -1,71 +0,69 @@ | ||
import { API, NameType, ABISerializableConstructor, ABI, Name, ABIDef, APIClient, Session, ABISerializableObject, TransactResult, ABICacheInterface } from '@wharfkit/session'; | ||
import { API, ABIDef, APIClient, ABI, NameType, Name, BytesType, ABISerializableObject, PermissionLevelType, Action } from '@greymass/eosio'; | ||
import { ABICacheInterface } from '@wharfkit/abicache'; | ||
interface TableCursorParams { | ||
table: Table; | ||
tableParams: API.v1.GetTableRowsParams; | ||
next_key?: API.v1.TableIndexType | string; | ||
indexPositionField?: string; | ||
/** Mashup of valid types for an APIClient call to v1.chain.get_table_rows */ | ||
type TableRowParamsTypes = API.v1.GetTableRowsParams | API.v1.GetTableRowsParamsKeyed | API.v1.GetTableRowsParamsTyped; | ||
interface TableCursorArgs { | ||
/** The ABI for the contract this table belongs to */ | ||
abi: ABIDef; | ||
/** The APIClient instance to use for API requests */ | ||
client: APIClient; | ||
/** The parameters used for the v1/chain/get_table_rows call */ | ||
params: TableRowParamsTypes; | ||
/** The maximum number of rows the cursor should retrieve */ | ||
maxRows?: number; | ||
} | ||
/** | ||
* Represents a cursor for a table in the blockchain. Provides methods for | ||
* iterating over the rows of the table. | ||
* | ||
* @typeparam TableRow The type of rows in the table. | ||
*/ | ||
declare class TableCursor<TableRow> { | ||
private table; | ||
declare class TableCursor<RowType = any> { | ||
/** The ABI for the contract this table belongs to */ | ||
readonly abi: ABI; | ||
/** The type of the table, as defined in the ABI */ | ||
readonly type: string; | ||
/** The parameters used for the v1/chain/get_table_rows call */ | ||
readonly params: TableRowParamsTypes; | ||
/** The APIClient instance to use for API requests */ | ||
readonly client: APIClient; | ||
/** For iterating on the cursor, the next key to query against lower_bounds */ | ||
private next_key; | ||
private tableParams; | ||
/** Whether or not the cursor believes it has reached the end of its results */ | ||
private endReached; | ||
private indexPositionField?; | ||
/** The number of rows the cursor has retrieved */ | ||
private rowsCount; | ||
/** The maximum number of rows the cursor should retrieve */ | ||
private maxRows; | ||
/** | ||
* @param {TableCursorParams} params - Parameters for creating a new table cursor. | ||
* Create a new TableCursor instance. | ||
* | ||
* @param {TableRow[]} params.rows - An array of rows that the cursor will iterate over. | ||
* Each row represents an entry in the table. | ||
* | ||
* @param {Table} params.table - The table that the rows belong to. | ||
* | ||
* @param {API.v1.GetTableRowsParams} params.tableParams - Parameters for the `get_table_rows` | ||
* API call, which are used to fetch the rows from the blockchain. | ||
* | ||
* @param {(Name | UInt64 | undefined)} [params.next_key] - The key for the next set of rows | ||
* that the cursor can fetch. This is used for pagination when there are more rows than can be | ||
* fetched in a single API call. | ||
* @param args.abi The ABI for the contract. | ||
* @param args.client The APIClient instance to use for API requests. | ||
* @param args.params The parameters to use for the table query. | ||
* @param args.maxRows The maximum number of rows to fetch. | ||
* @returns A new TableCursor instance. | ||
*/ | ||
constructor({ table, tableParams, indexPositionField, next_key }: TableCursorParams); | ||
constructor(args: TableCursorArgs); | ||
/** | ||
* Implements the async iterator protocol for the cursor. | ||
* | ||
* @returns An iterator for the rows in the table. | ||
* @returns An iterator for all rows in the table. | ||
*/ | ||
[Symbol.asyncIterator](): AsyncGenerator<Awaited<TableRow>, void, unknown>; | ||
[Symbol.asyncIterator](): AsyncGenerator<Awaited<RowType>, void, unknown>; | ||
/** | ||
* Fetches more rows from the table and appends them to the cursor. | ||
* Fetch the next batch of rows from the cursor. | ||
* | ||
* @returns The new rows. | ||
* @param rowsPerAPIRequest The number of rows to fetch per API request. | ||
* @returns A promise containing the next batch of rows. | ||
*/ | ||
next(): Promise<TableRow[]>; | ||
next(rowsPerAPIRequest?: number): Promise<RowType[]>; | ||
/** | ||
* Resets the cursor to the beginning of the table and returns the first rows. | ||
* | ||
* @returns The first rows in the table. | ||
* Reset the internal state of the cursor | ||
*/ | ||
reset(): Promise<void>; | ||
/** | ||
* Returns all rows in the cursor query. | ||
* Fetch all rows from the cursor by recursively calling next() until the end is reached. | ||
* | ||
* @returns All rows in the cursor query. | ||
* @returns A promise containing all rows for the cursor. | ||
*/ | ||
all(): Promise<TableRow[]>; | ||
/** | ||
* Returns a new cursor with updated parameters. | ||
* | ||
* @returns A new cursor with updated parameters. | ||
*/ | ||
query(query: Query, { limit }?: QueryOptions): TableCursor<unknown>; | ||
all(): Promise<RowType[]>; | ||
} | ||
interface FindOptions { | ||
interface QueryOptions { | ||
index?: string; | ||
@@ -75,9 +73,7 @@ scope?: NameType; | ||
} | ||
interface QueryOptions extends FindOptions { | ||
limit?: number; | ||
interface Query extends QueryOptions { | ||
from?: API.v1.TableIndexType | string | number; | ||
to?: API.v1.TableIndexType | string | number; | ||
rowsPerAPIRequest?: number; | ||
} | ||
interface Query { | ||
from: API.v1.TableIndexType | string; | ||
to: API.v1.TableIndexType | string; | ||
} | ||
interface FieldToIndex { | ||
@@ -90,6 +86,9 @@ [key: string]: { | ||
interface TableParams<TableRow = any> { | ||
contract: Contract; | ||
abi: ABIDef; | ||
account: NameType; | ||
client: APIClient; | ||
name: NameType; | ||
rowType?: TableRow; | ||
fieldToIndex?: FieldToIndex; | ||
defaultRowLimit?: number; | ||
} | ||
@@ -106,12 +105,15 @@ interface GetTableRowsOptions { | ||
*/ | ||
declare class Table<TableRow extends ABISerializableConstructor = ABISerializableConstructor> { | ||
readonly abi: ABI.Table; | ||
declare class Table<RowType = any> { | ||
readonly abi: ABI; | ||
readonly account: Name; | ||
readonly client: APIClient; | ||
readonly name: Name; | ||
readonly contract: Contract; | ||
readonly rowType?: TableRow; | ||
readonly rowType?: RowType; | ||
readonly tableABI: ABI.Table; | ||
private fieldToIndex?; | ||
defaultRowLimit: number; | ||
/** | ||
* Constructs a new `Table` instance. | ||
* | ||
* @param {TableParams<TableRow>} tableParams - Parameters for the table. | ||
* @param {TableParams} tableParams - Parameters for the table. | ||
* The parameters should include: | ||
@@ -124,3 +126,3 @@ * - `contract`: Name of the contract that this table is associated with. | ||
*/ | ||
constructor({ contract, name, rowType, fieldToIndex }: TableParams<TableRow>); | ||
constructor(args: TableParams); | ||
/** | ||
@@ -149,3 +151,3 @@ * Creates a new `Table` instance from the given parameters. | ||
*/ | ||
query(query: Query, { limit, scope, index, key_type }?: QueryOptions): TableCursor<TableRow>; | ||
query(query: Query): TableCursor<RowType>; | ||
/** | ||
@@ -158,3 +160,3 @@ * Retrieves the row from the table that matches the given parameters. | ||
*/ | ||
get(queryValue: API.v1.TableIndexType | string, { scope, index, key_type }?: QueryOptions): Promise<TableRow>; | ||
get(queryValue: API.v1.TableIndexType | string, { scope, index, key_type }?: QueryOptions): Promise<RowType>; | ||
/** | ||
@@ -168,8 +170,8 @@ * Retrieves all the rows from the table. | ||
*/ | ||
first(limit: number): TableCursor<TableRow>; | ||
first(maxRows: number, options?: QueryOptions): TableCursor<RowType>; | ||
/** | ||
* Returns a cursor to get every single rows on the table. | ||
* @returns {TableCursor<TableRow>} | ||
* @returns {TableCursor} | ||
*/ | ||
cursor(): TableCursor<TableRow>; | ||
cursor(): TableCursor<RowType>; | ||
/** | ||
@@ -179,6 +181,7 @@ * Returns all the rows from the table. | ||
*/ | ||
all(): Promise<TableRow[]>; | ||
all(): Promise<RowType[]>; | ||
getFieldToIndex(): any; | ||
} | ||
type ActionDataType = BytesType | ABISerializableObject | Record<string, any>; | ||
interface ContractArgs { | ||
@@ -189,5 +192,10 @@ abi: ABIDef; | ||
} | ||
interface ContractOptions { | ||
session?: Session; | ||
interface ActionArgs { | ||
name: NameType; | ||
data: ActionDataType; | ||
authorization?: PermissionLevelType[]; | ||
} | ||
interface ActionOptions { | ||
authorization?: PermissionLevelType[]; | ||
} | ||
/** | ||
@@ -202,3 +210,2 @@ * Represents a smart contract deployed to a specific blockchain. | ||
readonly client: APIClient; | ||
readonly session?: Session; | ||
/** | ||
@@ -210,22 +217,15 @@ * Constructs a new `Contract` instance. | ||
*/ | ||
constructor(args: ContractArgs, options?: ContractOptions); | ||
static from(args: ContractArgs, options?: ContractOptions): Contract; | ||
get tables(): string[]; | ||
constructor(args: ContractArgs); | ||
get tableNames(): string[]; | ||
hasTable(name: NameType): boolean; | ||
table(name: NameType): Table<any>; | ||
/** | ||
* Calls a contract action. | ||
* | ||
* @param {NameType} name - The name of the action. | ||
* @param {ABISerializableObject | {[key: string]: any}} data - The data for the action. | ||
* @param {Session} session - The session object to use to sign the transaction. | ||
* @return {Promise<TransactResult>} A promise that resolves with the transaction data. | ||
*/ | ||
call(name: NameType, data: ABISerializableObject | { | ||
[key: string]: any; | ||
}, session: Session): Promise<TransactResult>; | ||
get actionNames(): string[]; | ||
hasAction(name: NameType): boolean; | ||
action(name: NameType, data: ActionDataType, options?: ActionOptions): Action; | ||
actions(actions: ActionArgs[], options?: ActionOptions): Action[]; | ||
ricardian(name: NameType): string; | ||
} | ||
interface ContractKitArgs { | ||
client?: APIClient; | ||
session?: Session; | ||
client: APIClient; | ||
} | ||
@@ -253,2 +253,2 @@ interface ABIDefinition { | ||
export { ABIDefinition, Contract, ContractArgs, ContractKit, ContractKitArgs, ContractKitOptions, ContractOptions, FindOptions, GetTableRowsOptions, Query, QueryOptions, Table, TableCursor, ContractKit as default }; | ||
export { ABIDefinition, ActionArgs, ActionDataType, ActionOptions, Contract, ContractArgs, ContractKit, ContractKitArgs, ContractKitOptions, GetTableRowsOptions, Query, QueryOptions, Table, TableCursor, TableCursorArgs, TableRowParamsTypes, ContractKit as default }; |
@@ -5,4 +5,6 @@ 'use strict'; | ||
var eosio = require('@greymass/eosio'); | ||
var eosioSigningRequest = require('eosio-signing-request'); | ||
var tslib = require('tslib'); | ||
var session = require('@wharfkit/session'); | ||
var abicache = require('@wharfkit/abicache'); | ||
@@ -27,23 +29,35 @@ function indexPositionInWords(index) { | ||
} | ||
if (session.isInstanceOf(value, session.UInt128) || | ||
session.isInstanceOf(value, session.UInt64) || | ||
session.isInstanceOf(value, session.Float64) || | ||
session.isInstanceOf(value, session.Checksum256) || | ||
session.isInstanceOf(value, session.Checksum160)) { | ||
if (eosio.isInstanceOf(value, eosio.UInt128) || | ||
eosio.isInstanceOf(value, eosio.UInt64) || | ||
eosio.isInstanceOf(value, eosio.Float64) || | ||
eosio.isInstanceOf(value, eosio.Checksum256) || | ||
eosio.isInstanceOf(value, eosio.Checksum160)) { | ||
return value; | ||
} | ||
if (typeof value === 'number') { | ||
return session.UInt64.from(value); | ||
return eosio.UInt64.from(value); | ||
} | ||
return session.Name.from(value); | ||
return eosio.Name.from(value); | ||
} | ||
const defaultParams = { | ||
json: false, | ||
limit: 1000, | ||
}; | ||
class TableCursor { | ||
constructor({ table, tableParams, indexPositionField, next_key }) { | ||
constructor(args) { | ||
this.endReached = false; | ||
this.rowsCount = 0; | ||
this.table = table; | ||
this.tableParams = tableParams; | ||
this.next_key = next_key; | ||
this.indexPositionField = indexPositionField; | ||
this.maxRows = Number.MAX_SAFE_INTEGER; | ||
this.abi = eosio.ABI.from(args.abi); | ||
this.client = args.client; | ||
this.params = Object.assign(Object.assign({}, defaultParams), args.params); | ||
if (args.maxRows) { | ||
this.maxRows = args.maxRows; | ||
} | ||
const table = this.abi.tables.find((t) => t.name === String(this.params.table)); | ||
if (!table) { | ||
throw new Error('Table not found'); | ||
} | ||
this.type = table.type; | ||
} | ||
@@ -63,3 +77,3 @@ [Symbol.asyncIterator]() { | ||
} | ||
next() { | ||
next(rowsPerAPIRequest = Number.MAX_SAFE_INTEGER) { | ||
return tslib.__awaiter(this, void 0, void 0, function* () { | ||
@@ -69,21 +83,23 @@ if (this.endReached) { | ||
} | ||
let lower_bound = this.tableParams.lower_bound; | ||
const upper_bound = this.tableParams.upper_bound; | ||
let lower_bound = this.params.lower_bound; | ||
if (this.next_key) { | ||
lower_bound = this.next_key; | ||
} | ||
let indexPosition = this.tableParams.index_position || 'primary'; | ||
if (this.indexPositionField) { | ||
const fieldToIndexMapping = this.table.getFieldToIndex(); | ||
if (!fieldToIndexMapping[this.indexPositionField]) { | ||
throw new Error(`Field ${this.indexPositionField} is not a valid index.`); | ||
} | ||
indexPosition = fieldToIndexMapping[this.indexPositionField].index_position; | ||
} | ||
const { rows, next_key } = yield this.table.contract.client.v1.chain.get_table_rows(Object.assign(Object.assign({}, this.tableParams), { limit: Math.min(this.tableParams.limit - this.rowsCount, 1000000), lower_bound: wrapIndexValue(lower_bound), upper_bound: wrapIndexValue(upper_bound), index_position: indexPosition })); | ||
this.next_key = next_key; | ||
if (!next_key || rows.length === 0 || this.rowsCount === this.tableParams.limit) { | ||
const rowsRemaining = this.maxRows - this.rowsCount; | ||
const limit = Math.min(rowsRemaining, rowsPerAPIRequest, this.params.limit); | ||
const query = Object.assign(Object.assign({}, this.params), { limit, lower_bound: wrapIndexValue(lower_bound), upper_bound: wrapIndexValue(this.params.upper_bound) }); | ||
const result = yield this.client.v1.chain.get_table_rows(query); | ||
const requiresDecoding = this.params.json === false && !query.type; | ||
const rows = requiresDecoding | ||
? result.rows.map((data) => eosio.Serializer.decode({ | ||
data, | ||
abi: this.abi, | ||
type: this.type, | ||
})) | ||
: result.rows; | ||
this.next_key = result.next_key; | ||
this.rowsCount += rows.length; | ||
if (!result.next_key || rows.length === 0 || this.rowsCount === this.maxRows) { | ||
this.endReached = true; | ||
} | ||
this.rowsCount += rows.length; | ||
return rows; | ||
@@ -126,21 +142,18 @@ }); | ||
} | ||
query(query, { limit } = {}) { | ||
return new TableCursor({ | ||
table: this.table, | ||
tableParams: Object.assign(Object.assign({}, this.tableParams), { limit: limit || this.tableParams.limit, lower_bound: query.from || this.tableParams.lower_bound, upper_bound: query.to || this.tableParams.upper_bound }), | ||
}); | ||
} | ||
} | ||
class Table { | ||
constructor({ contract, name, rowType, fieldToIndex }) { | ||
this.name = session.Name.from(name); | ||
const abi = contract.abi.tables.find((table) => this.name.equals(table.name)); | ||
if (!abi) { | ||
constructor(args) { | ||
this.defaultRowLimit = 1000; | ||
this.abi = eosio.ABI.from(args.abi); | ||
this.account = eosio.Name.from(args.account); | ||
this.name = eosio.Name.from(args.name); | ||
this.client = args.client; | ||
this.rowType = args.rowType; | ||
this.fieldToIndex = args.fieldToIndex; | ||
const tableABI = this.abi.tables.find((table) => this.name.equals(table.name)); | ||
if (!tableABI) { | ||
throw new Error(`Table ${this.name} not found in ABI`); | ||
} | ||
this.abi = abi; | ||
this.rowType = rowType; | ||
this.fieldToIndex = fieldToIndex; | ||
this.contract = contract; | ||
this.tableABI = tableABI; | ||
} | ||
@@ -150,21 +163,28 @@ static from(tableParams) { | ||
} | ||
query(query, { limit = 10, scope = this.contract.account, index, key_type } = {}) { | ||
const { from, to } = query; | ||
query(query) { | ||
const { from, to, rowsPerAPIRequest } = query; | ||
const tableRowsParams = { | ||
table: this.name, | ||
code: this.contract.account, | ||
scope, | ||
code: this.account, | ||
scope: query.scope || this.account, | ||
type: this.rowType, | ||
limit, | ||
limit: rowsPerAPIRequest || this.defaultRowLimit, | ||
lower_bound: wrapIndexValue(from), | ||
upper_bound: wrapIndexValue(to), | ||
key_type: key_type, | ||
key_type: query.key_type, | ||
}; | ||
if (query.index) { | ||
const fieldToIndexMapping = this.getFieldToIndex(); | ||
if (!fieldToIndexMapping[query.index]) { | ||
throw new Error(`Field ${query.index} is not a valid index.`); | ||
} | ||
tableRowsParams.index_position = fieldToIndexMapping[query.index].index_position; | ||
} | ||
return new TableCursor({ | ||
table: this, | ||
tableParams: tableRowsParams, | ||
indexPositionField: index, | ||
abi: this.abi, | ||
client: this.client, | ||
params: tableRowsParams, | ||
}); | ||
} | ||
get(queryValue, { scope = this.contract.account, index, key_type } = {}) { | ||
get(queryValue, { scope = this.account, index, key_type } = {}) { | ||
return tslib.__awaiter(this, void 0, void 0, function* () { | ||
@@ -174,3 +194,3 @@ const fieldToIndexMapping = this.getFieldToIndex(); | ||
table: this.name, | ||
code: this.contract.account, | ||
code: this.account, | ||
scope, | ||
@@ -183,17 +203,30 @@ type: this.rowType, | ||
key_type: key_type, | ||
json: false, | ||
}; | ||
const { rows } = yield this.contract.client.v1.chain.get_table_rows(tableRowsParams); | ||
let { rows } = yield this.client.v1.chain.get_table_rows(tableRowsParams); | ||
if (!this.rowType) { | ||
rows = [ | ||
eosio.Serializer.decode({ | ||
data: rows[0], | ||
abi: this.abi, | ||
type: this.tableABI.type, | ||
}), | ||
]; | ||
} | ||
return rows[0]; | ||
}); | ||
} | ||
first(limit) { | ||
first(maxRows, options = {}) { | ||
const tableRowsParams = { | ||
table: this.name, | ||
limit, | ||
code: this.contract.account, | ||
limit: maxRows, | ||
code: this.account, | ||
type: this.rowType, | ||
scope: options.scope, | ||
}; | ||
return new TableCursor({ | ||
table: this, | ||
tableParams: tableRowsParams, | ||
abi: this.abi, | ||
client: this.client, | ||
maxRows, | ||
params: tableRowsParams, | ||
}); | ||
@@ -204,9 +237,10 @@ } | ||
table: this.name, | ||
code: this.contract.account, | ||
code: this.account, | ||
type: this.rowType, | ||
limit: 1000000, | ||
limit: this.defaultRowLimit, | ||
}; | ||
return new TableCursor({ | ||
table: this, | ||
tableParams: tableRowsParams, | ||
abi: this.abi, | ||
client: this.client, | ||
params: tableRowsParams, | ||
}); | ||
@@ -224,5 +258,5 @@ } | ||
const fieldToIndex = {}; | ||
for (let i = 0; i < this.abi.key_names.length; i++) { | ||
fieldToIndex[this.abi.key_names[i]] = { | ||
type: this.abi.key_types[i], | ||
for (let i = 0; i < this.tableABI.key_names.length; i++) { | ||
fieldToIndex[this.tableABI.key_names[i]] = { | ||
type: this.tableABI.key_types[i], | ||
index_position: indexPositionInWords(i), | ||
@@ -236,36 +270,60 @@ }; | ||
class Contract { | ||
constructor(args, options = {}) { | ||
this.abi = session.ABI.from(args.abi); | ||
this.account = session.Name.from(args.account); | ||
constructor(args) { | ||
this.abi = eosio.ABI.from(args.abi); | ||
this.account = eosio.Name.from(args.account); | ||
this.client = args.client; | ||
if (options.session) { | ||
this.session = options.session; | ||
} | ||
} | ||
static from(args, options = {}) { | ||
return new this(args, options); | ||
} | ||
get tables() { | ||
get tableNames() { | ||
return this.abi.tables.map((table) => String(table.name)); | ||
} | ||
hasTable(name) { | ||
return this.tableNames.includes(String(name)); | ||
} | ||
table(name) { | ||
if (!this.tables.includes(String(name))) { | ||
if (!this.hasTable(name)) { | ||
throw new Error(`Contract (${this.account}) does not have a table named (${name})`); | ||
} | ||
return Table.from({ | ||
contract: this, | ||
abi: this.abi, | ||
account: this.account, | ||
client: this.client, | ||
name, | ||
}); | ||
} | ||
call(name, data, session$1) { | ||
return tslib.__awaiter(this, void 0, void 0, function* () { | ||
const action = session.Action.from({ | ||
account: this.account, | ||
name, | ||
authorization: [], | ||
data, | ||
}); | ||
return session$1.transact({ action }); | ||
}); | ||
get actionNames() { | ||
return this.abi.actions.map((action) => String(action.name)); | ||
} | ||
hasAction(name) { | ||
return this.actionNames.includes(String(name)); | ||
} | ||
action(name, data, options) { | ||
if (!this.hasAction(name)) { | ||
throw new Error(`Contract (${this.account}) does not have an action named (${name})`); | ||
} | ||
let authorization = [eosioSigningRequest.PlaceholderAuth]; | ||
if (options && options.authorization) { | ||
authorization = options.authorization.map((auth) => eosio.PermissionLevel.from(auth)); | ||
} | ||
return eosio.Action.from({ | ||
account: this.account, | ||
name, | ||
authorization, | ||
data, | ||
}, this.abi); | ||
} | ||
actions(actions, options) { | ||
return actions.map((action) => this.action(action.name, action.data, { | ||
authorization: action.authorization || (options === null || options === void 0 ? void 0 : options.authorization), | ||
})); | ||
} | ||
ricardian(name) { | ||
if (!this.hasAction(name)) { | ||
throw new Error(`Contract (${this.account}) does not have an action named (${name})`); | ||
} | ||
const action = this.abi.actions.find((action) => eosio.Name.from(action.name).equals(name)); | ||
if (!action || !action.ricardian_contract) { | ||
throw new Error(`Contract (${this.account}) action named (${name}) does not have a defined ricardian contract`); | ||
} | ||
return action.ricardian_contract; | ||
} | ||
} | ||
@@ -279,7 +337,4 @@ | ||
} | ||
else if (args.session) { | ||
this.client = args.session.client; | ||
} | ||
else { | ||
throw new Error('Either a `client` or `session` must be passed when initializing the ContractKit.'); | ||
throw new Error('A `client` must be passed when initializing the ContractKit.'); | ||
} | ||
@@ -289,10 +344,7 @@ if (options.abiCache) { | ||
} | ||
else if (args.session) { | ||
this.abiCache = args.session.abiCache; | ||
} | ||
else { | ||
this.abiCache = new session.ABICache(this.client); | ||
this.abiCache = new abicache.ABICache(this.client); | ||
} | ||
if (options.abis) { | ||
options.abis.forEach(({ name, abi }) => this.abiCache.setAbi(session.Name.from(name), session.ABI.from(abi))); | ||
options.abis.forEach(({ name, abi }) => this.abiCache.setAbi(eosio.Name.from(name), eosio.ABI.from(abi))); | ||
} | ||
@@ -302,6 +354,6 @@ } | ||
return tslib.__awaiter(this, void 0, void 0, function* () { | ||
const account = session.Name.from(contract); | ||
const account = eosio.Name.from(contract); | ||
const abiDef = yield this.abiCache.getAbi(account); | ||
return new Contract({ | ||
abi: session.ABI.from(abiDef), | ||
abi: eosio.ABI.from(abiDef), | ||
account, | ||
@@ -308,0 +360,0 @@ client: this.client, |
@@ -1,2 +0,4 @@ | ||
import { isInstanceOf, UInt128, UInt64, Float64, Checksum256, Checksum160, Name, ABI, Action, ABICache } from '@wharfkit/session'; | ||
import { isInstanceOf, UInt128, UInt64, Float64, Checksum256, Checksum160, Name, ABI, Serializer, PermissionLevel, Action } from '@greymass/eosio'; | ||
import { PlaceholderAuth } from 'eosio-signing-request'; | ||
import { ABICache } from '@wharfkit/abicache'; | ||
@@ -34,10 +36,25 @@ function indexPositionInWords(index) { | ||
const defaultParams = { | ||
json: false, | ||
limit: 1000, | ||
}; | ||
class TableCursor { | ||
constructor({ table, tableParams, indexPositionField, next_key }) { | ||
constructor(args) { | ||
this.endReached = false; | ||
this.rowsCount = 0; | ||
this.table = table; | ||
this.tableParams = tableParams; | ||
this.next_key = next_key; | ||
this.indexPositionField = indexPositionField; | ||
this.maxRows = Number.MAX_SAFE_INTEGER; | ||
this.abi = ABI.from(args.abi); | ||
this.client = args.client; | ||
this.params = { | ||
...defaultParams, | ||
...args.params, | ||
}; | ||
if (args.maxRows) { | ||
this.maxRows = args.maxRows; | ||
} | ||
const table = this.abi.tables.find((t) => t.name === String(this.params.table)); | ||
if (!table) { | ||
throw new Error('Table not found'); | ||
} | ||
this.type = table.type; | ||
} | ||
@@ -55,31 +72,32 @@ async *[Symbol.asyncIterator]() { | ||
} | ||
async next() { | ||
async next(rowsPerAPIRequest = Number.MAX_SAFE_INTEGER) { | ||
if (this.endReached) { | ||
return []; | ||
} | ||
let lower_bound = this.tableParams.lower_bound; | ||
const upper_bound = this.tableParams.upper_bound; | ||
let lower_bound = this.params.lower_bound; | ||
if (this.next_key) { | ||
lower_bound = this.next_key; | ||
} | ||
let indexPosition = this.tableParams.index_position || 'primary'; | ||
if (this.indexPositionField) { | ||
const fieldToIndexMapping = this.table.getFieldToIndex(); | ||
if (!fieldToIndexMapping[this.indexPositionField]) { | ||
throw new Error(`Field ${this.indexPositionField} is not a valid index.`); | ||
} | ||
indexPosition = fieldToIndexMapping[this.indexPositionField].index_position; | ||
} | ||
const { rows, next_key } = await this.table.contract.client.v1.chain.get_table_rows({ | ||
...this.tableParams, | ||
limit: Math.min(this.tableParams.limit - this.rowsCount, 1000000), | ||
const rowsRemaining = this.maxRows - this.rowsCount; | ||
const limit = Math.min(rowsRemaining, rowsPerAPIRequest, this.params.limit); | ||
const query = { | ||
...this.params, | ||
limit, | ||
lower_bound: wrapIndexValue(lower_bound), | ||
upper_bound: wrapIndexValue(upper_bound), | ||
index_position: indexPosition, | ||
}); | ||
this.next_key = next_key; | ||
if (!next_key || rows.length === 0 || this.rowsCount === this.tableParams.limit) { | ||
upper_bound: wrapIndexValue(this.params.upper_bound), | ||
}; | ||
const result = await this.client.v1.chain.get_table_rows(query); | ||
const requiresDecoding = this.params.json === false && !query.type; | ||
const rows = requiresDecoding | ||
? result.rows.map((data) => Serializer.decode({ | ||
data, | ||
abi: this.abi, | ||
type: this.type, | ||
})) | ||
: result.rows; | ||
this.next_key = result.next_key; | ||
this.rowsCount += rows.length; | ||
if (!result.next_key || rows.length === 0 || this.rowsCount === this.maxRows) { | ||
this.endReached = true; | ||
} | ||
this.rowsCount += rows.length; | ||
return rows; | ||
@@ -99,26 +117,18 @@ } | ||
} | ||
query(query, { limit } = {}) { | ||
return new TableCursor({ | ||
table: this.table, | ||
tableParams: { | ||
...this.tableParams, | ||
limit: limit || this.tableParams.limit, | ||
lower_bound: query.from || this.tableParams.lower_bound, | ||
upper_bound: query.to || this.tableParams.upper_bound, | ||
}, | ||
}); | ||
} | ||
} | ||
class Table { | ||
constructor({ contract, name, rowType, fieldToIndex }) { | ||
this.name = Name.from(name); | ||
const abi = contract.abi.tables.find((table) => this.name.equals(table.name)); | ||
if (!abi) { | ||
constructor(args) { | ||
this.defaultRowLimit = 1000; | ||
this.abi = ABI.from(args.abi); | ||
this.account = Name.from(args.account); | ||
this.name = Name.from(args.name); | ||
this.client = args.client; | ||
this.rowType = args.rowType; | ||
this.fieldToIndex = args.fieldToIndex; | ||
const tableABI = this.abi.tables.find((table) => this.name.equals(table.name)); | ||
if (!tableABI) { | ||
throw new Error(`Table ${this.name} not found in ABI`); | ||
} | ||
this.abi = abi; | ||
this.rowType = rowType; | ||
this.fieldToIndex = fieldToIndex; | ||
this.contract = contract; | ||
this.tableABI = tableABI; | ||
} | ||
@@ -128,25 +138,32 @@ static from(tableParams) { | ||
} | ||
query(query, { limit = 10, scope = this.contract.account, index, key_type } = {}) { | ||
const { from, to } = query; | ||
query(query) { | ||
const { from, to, rowsPerAPIRequest } = query; | ||
const tableRowsParams = { | ||
table: this.name, | ||
code: this.contract.account, | ||
scope, | ||
code: this.account, | ||
scope: query.scope || this.account, | ||
type: this.rowType, | ||
limit, | ||
limit: rowsPerAPIRequest || this.defaultRowLimit, | ||
lower_bound: wrapIndexValue(from), | ||
upper_bound: wrapIndexValue(to), | ||
key_type: key_type, | ||
key_type: query.key_type, | ||
}; | ||
if (query.index) { | ||
const fieldToIndexMapping = this.getFieldToIndex(); | ||
if (!fieldToIndexMapping[query.index]) { | ||
throw new Error(`Field ${query.index} is not a valid index.`); | ||
} | ||
tableRowsParams.index_position = fieldToIndexMapping[query.index].index_position; | ||
} | ||
return new TableCursor({ | ||
table: this, | ||
tableParams: tableRowsParams, | ||
indexPositionField: index, | ||
abi: this.abi, | ||
client: this.client, | ||
params: tableRowsParams, | ||
}); | ||
} | ||
async get(queryValue, { scope = this.contract.account, index, key_type } = {}) { | ||
async get(queryValue, { scope = this.account, index, key_type } = {}) { | ||
const fieldToIndexMapping = this.getFieldToIndex(); | ||
const tableRowsParams = { | ||
table: this.name, | ||
code: this.contract.account, | ||
code: this.account, | ||
scope, | ||
@@ -159,16 +176,29 @@ type: this.rowType, | ||
key_type: key_type, | ||
json: false, | ||
}; | ||
const { rows } = await this.contract.client.v1.chain.get_table_rows(tableRowsParams); | ||
let { rows } = await this.client.v1.chain.get_table_rows(tableRowsParams); | ||
if (!this.rowType) { | ||
rows = [ | ||
Serializer.decode({ | ||
data: rows[0], | ||
abi: this.abi, | ||
type: this.tableABI.type, | ||
}), | ||
]; | ||
} | ||
return rows[0]; | ||
} | ||
first(limit) { | ||
first(maxRows, options = {}) { | ||
const tableRowsParams = { | ||
table: this.name, | ||
limit, | ||
code: this.contract.account, | ||
limit: maxRows, | ||
code: this.account, | ||
type: this.rowType, | ||
scope: options.scope, | ||
}; | ||
return new TableCursor({ | ||
table: this, | ||
tableParams: tableRowsParams, | ||
abi: this.abi, | ||
client: this.client, | ||
maxRows, | ||
params: tableRowsParams, | ||
}); | ||
@@ -179,9 +209,10 @@ } | ||
table: this.name, | ||
code: this.contract.account, | ||
code: this.account, | ||
type: this.rowType, | ||
limit: 1000000, | ||
limit: this.defaultRowLimit, | ||
}; | ||
return new TableCursor({ | ||
table: this, | ||
tableParams: tableRowsParams, | ||
abi: this.abi, | ||
client: this.client, | ||
params: tableRowsParams, | ||
}); | ||
@@ -197,5 +228,5 @@ } | ||
const fieldToIndex = {}; | ||
for (let i = 0; i < this.abi.key_names.length; i++) { | ||
fieldToIndex[this.abi.key_names[i]] = { | ||
type: this.abi.key_types[i], | ||
for (let i = 0; i < this.tableABI.key_names.length; i++) { | ||
fieldToIndex[this.tableABI.key_names[i]] = { | ||
type: this.tableABI.key_types[i], | ||
index_position: indexPositionInWords(i), | ||
@@ -209,34 +240,60 @@ }; | ||
class Contract { | ||
constructor(args, options = {}) { | ||
constructor(args) { | ||
this.abi = ABI.from(args.abi); | ||
this.account = Name.from(args.account); | ||
this.client = args.client; | ||
if (options.session) { | ||
this.session = options.session; | ||
} | ||
} | ||
static from(args, options = {}) { | ||
return new this(args, options); | ||
} | ||
get tables() { | ||
get tableNames() { | ||
return this.abi.tables.map((table) => String(table.name)); | ||
} | ||
hasTable(name) { | ||
return this.tableNames.includes(String(name)); | ||
} | ||
table(name) { | ||
if (!this.tables.includes(String(name))) { | ||
if (!this.hasTable(name)) { | ||
throw new Error(`Contract (${this.account}) does not have a table named (${name})`); | ||
} | ||
return Table.from({ | ||
contract: this, | ||
abi: this.abi, | ||
account: this.account, | ||
client: this.client, | ||
name, | ||
}); | ||
} | ||
async call(name, data, session) { | ||
const action = Action.from({ | ||
get actionNames() { | ||
return this.abi.actions.map((action) => String(action.name)); | ||
} | ||
hasAction(name) { | ||
return this.actionNames.includes(String(name)); | ||
} | ||
action(name, data, options) { | ||
if (!this.hasAction(name)) { | ||
throw new Error(`Contract (${this.account}) does not have an action named (${name})`); | ||
} | ||
let authorization = [PlaceholderAuth]; | ||
if (options && options.authorization) { | ||
authorization = options.authorization.map((auth) => PermissionLevel.from(auth)); | ||
} | ||
return Action.from({ | ||
account: this.account, | ||
name, | ||
authorization: [], | ||
authorization, | ||
data, | ||
}); | ||
return session.transact({ action }); | ||
}, this.abi); | ||
} | ||
actions(actions, options) { | ||
return actions.map((action) => this.action(action.name, action.data, { | ||
authorization: action.authorization || options?.authorization, | ||
})); | ||
} | ||
ricardian(name) { | ||
if (!this.hasAction(name)) { | ||
throw new Error(`Contract (${this.account}) does not have an action named (${name})`); | ||
} | ||
const action = this.abi.actions.find((action) => Name.from(action.name).equals(name)); | ||
if (!action || !action.ricardian_contract) { | ||
throw new Error(`Contract (${this.account}) action named (${name}) does not have a defined ricardian contract`); | ||
} | ||
return action.ricardian_contract; | ||
} | ||
} | ||
@@ -250,7 +307,4 @@ | ||
} | ||
else if (args.session) { | ||
this.client = args.session.client; | ||
} | ||
else { | ||
throw new Error('Either a `client` or `session` must be passed when initializing the ContractKit.'); | ||
throw new Error('A `client` must be passed when initializing the ContractKit.'); | ||
} | ||
@@ -260,5 +314,2 @@ if (options.abiCache) { | ||
} | ||
else if (args.session) { | ||
this.abiCache = args.session.abiCache; | ||
} | ||
else { | ||
@@ -265,0 +316,0 @@ this.abiCache = new ABICache(this.client); |
{ | ||
"name": "@wharfkit/contract", | ||
"description": "ContractKit for Wharf", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"homepage": "https://github.com/wharfkit/contract", | ||
@@ -22,7 +22,10 @@ "license": "BSD-3-Clause", | ||
"dependencies": { | ||
"@wharfkit/mock-data": "^1.0.0-beta11", | ||
"@wharfkit/session": "^1.0.0-beta4", | ||
"rollup-plugin-cleanup": "^3.2.1", | ||
"@greymass/eosio": "^0.6.10", | ||
"@wharfkit/abicache": "^1.0.0", | ||
"eosio-signing-request": "^2.5.3", | ||
"tslib": "^2.1.0" | ||
}, | ||
"resolutions": { | ||
"@greymass/eosio": "^0.6.10" | ||
}, | ||
"devDependencies": { | ||
@@ -43,2 +46,4 @@ "@babel/preset-env": "^7.20.2", | ||
"@typescript-eslint/parser": "^5.20.0", | ||
"@wharfkit/mock-data": "^1.0.0-beta11", | ||
"@wharfkit/session": "^1.0.0-beta4", | ||
"assert": "^2.0.0", | ||
@@ -45,0 +50,0 @@ "chai": "^4.3.4", |
# Contract Kit | ||
Greymass TypeScript library template, intended for libraries that work in any JavaScript context (node.js, Browser, React native), `@types/node` are installed only for tests, don't rely on any node.js types or imports inside `src/` (no `buffer`, `crypto` imports etc, they can be filled for browser but will bloat the bundle 100x) | ||
A library to simplify interactions with Antelope-based smart contracts. | ||
Features: | ||
- Instantiate instances of a `Contract` in your frontend application | ||
- Retrieve smart contract data with `Table` instances. | ||
- Create action data by accessing actions directly through the `.action` method of a `Contract` | ||
- Retrieve Ricardian Contracts for specific actions through the `.ricardian` method of a `Contract` | ||
- Cache and optimize ABI call patterns automatically in your application. | ||
- Generate frontend code based on a smart contracts ABI. | ||
## Installation | ||
The `contract` package is distributed as a module on [npm](https://www.npmjs.com/package/contract). | ||
The `@wharfkit/contract` package is distributed as a module on [npm](https://www.npmjs.com/package/@wharfkit/contract). | ||
``` | ||
yarn add contract | ||
yarn add @wharfkit/contract | ||
# or | ||
npm install --save contract | ||
npm install --save @wharfkit/ | ||
``` | ||
## Usage | ||
To generate the Contract helper for a specific Antelope contract, use the `generate` make target: | ||
```bash | ||
make generate contract=<CONTRACT_NAME_HERE> | ||
``` | ||
and your contract helper will be added to `contracts/CONTRACT_NAME.ts`. | ||
```typescript | ||
## Developing | ||
@@ -36,2 +33,1 @@ | ||
Made with ☕️ & ❤️ by [Greymass](https://greymass.com), if you find this useful please consider [supporting us](https://greymass.com/support-us). | ||
``` |
@@ -6,3 +6,3 @@ import * as ts from 'typescript' | ||
import {capitalize} from '../utils' | ||
import {APIClient} from '@wharfkit/session' | ||
import {APIClient} from '@greymass/eosio' | ||
@@ -9,0 +9,0 @@ export async function generateTableClass(contractName, namespaceName, table, abi) { |
@@ -7,10 +7,14 @@ import { | ||
APIClient, | ||
BytesType, | ||
Name, | ||
NameType, | ||
Session, | ||
TransactResult, | ||
} from '@wharfkit/session' | ||
PermissionLevel, | ||
PermissionLevelType, | ||
} from '@greymass/eosio' | ||
import {PlaceholderAuth} from 'eosio-signing-request' | ||
import {Table} from './contract/table' | ||
export type ActionDataType = BytesType | ABISerializableObject | Record<string, any> | ||
export interface ContractArgs { | ||
@@ -22,6 +26,12 @@ abi: ABIDef | ||
export interface ContractOptions { | ||
session?: Session | ||
export interface ActionArgs { | ||
name: NameType | ||
data: ActionDataType | ||
authorization?: PermissionLevelType[] | ||
} | ||
export interface ActionOptions { | ||
authorization?: PermissionLevelType[] | ||
} | ||
/** | ||
@@ -36,3 +46,2 @@ * Represents a smart contract deployed to a specific blockchain. | ||
readonly client: APIClient | ||
readonly session?: Session | ||
@@ -45,26 +54,24 @@ /** | ||
*/ | ||
constructor(args: ContractArgs, options: ContractOptions = {}) { | ||
constructor(args: ContractArgs) { | ||
this.abi = ABI.from(args.abi) | ||
this.account = Name.from(args.account) | ||
this.client = args.client | ||
if (options.session) { | ||
this.session = options.session | ||
} | ||
} | ||
static from(args: ContractArgs, options: ContractOptions = {}): Contract { | ||
return new this(args, options) | ||
public get tableNames(): string[] { | ||
return this.abi.tables.map((table) => String(table.name)) | ||
} | ||
get tables(): string[] { | ||
return this.abi.tables.map((table) => String(table.name)) | ||
public hasTable(name: NameType): boolean { | ||
return this.tableNames.includes(String(name)) | ||
} | ||
public table(name: NameType) { | ||
if (!this.tables.includes(String(name))) { | ||
if (!this.hasTable(name)) { | ||
throw new Error(`Contract (${this.account}) does not have a table named (${name})`) | ||
} | ||
return Table.from({ | ||
contract: this, | ||
abi: this.abi, | ||
account: this.account, | ||
client: this.client, | ||
name, | ||
@@ -74,26 +81,51 @@ }) | ||
// TODO: reimplement call method | ||
/** | ||
* Calls a contract action. | ||
* | ||
* @param {NameType} name - The name of the action. | ||
* @param {ABISerializableObject | {[key: string]: any}} data - The data for the action. | ||
* @param {Session} session - The session object to use to sign the transaction. | ||
* @return {Promise<TransactResult>} A promise that resolves with the transaction data. | ||
*/ | ||
async call( | ||
name: NameType, | ||
data: ABISerializableObject | {[key: string]: any}, | ||
session: Session | ||
): Promise<TransactResult> { | ||
const action: Action = Action.from({ | ||
account: this.account, | ||
name, | ||
authorization: [], | ||
data, | ||
}) | ||
public get actionNames(): string[] { | ||
return this.abi.actions.map((action) => String(action.name)) | ||
} | ||
// Trigger the transaction using the session kit | ||
return session.transact({action}) | ||
public hasAction(name: NameType): boolean { | ||
return this.actionNames.includes(String(name)) | ||
} | ||
public action(name: NameType, data: ActionDataType, options?: ActionOptions): Action { | ||
if (!this.hasAction(name)) { | ||
throw new Error(`Contract (${this.account}) does not have an action named (${name})`) | ||
} | ||
let authorization = [PlaceholderAuth] | ||
if (options && options.authorization) { | ||
authorization = options.authorization.map((auth) => PermissionLevel.from(auth)) | ||
} | ||
return Action.from( | ||
{ | ||
account: this.account, | ||
name, | ||
authorization, | ||
data, | ||
}, | ||
this.abi | ||
) | ||
} | ||
public actions(actions: ActionArgs[], options?: ActionOptions): Action[] { | ||
return actions.map((action) => | ||
this.action(action.name, action.data, { | ||
authorization: action.authorization || options?.authorization, | ||
}) | ||
) | ||
} | ||
public ricardian(name: NameType) { | ||
if (!this.hasAction(name)) { | ||
throw new Error(`Contract (${this.account}) does not have an action named (${name})`) | ||
} | ||
const action = this.abi.actions.find((action) => Name.from(action.name).equals(name)) | ||
if (!action || !action.ricardian_contract) { | ||
throw new Error( | ||
`Contract (${this.account}) action named (${name}) does not have a defined ricardian contract` | ||
) | ||
} | ||
return action.ricardian_contract | ||
} | ||
} |
@@ -1,46 +0,70 @@ | ||
import {API} from '@wharfkit/session' | ||
import {ABI, ABIDef, API, APIClient, Serializer} from '@greymass/eosio' | ||
import {wrapIndexValue} from '../utils' | ||
import {Query, QueryOptions, Table} from './table' | ||
interface TableCursorParams { | ||
table: Table | ||
tableParams: API.v1.GetTableRowsParams | ||
next_key?: API.v1.TableIndexType | string | ||
indexPositionField?: string | ||
/** Mashup of valid types for an APIClient call to v1.chain.get_table_rows */ | ||
export type TableRowParamsTypes = | ||
| API.v1.GetTableRowsParams | ||
| API.v1.GetTableRowsParamsKeyed | ||
| API.v1.GetTableRowsParamsTyped | ||
export interface TableCursorArgs { | ||
/** The ABI for the contract this table belongs to */ | ||
abi: ABIDef | ||
/** The APIClient instance to use for API requests */ | ||
client: APIClient | ||
/** The parameters used for the v1/chain/get_table_rows call */ | ||
params: TableRowParamsTypes | ||
/** The maximum number of rows the cursor should retrieve */ | ||
maxRows?: number | ||
} | ||
/** | ||
* Represents a cursor for a table in the blockchain. Provides methods for | ||
* iterating over the rows of the table. | ||
* | ||
* @typeparam TableRow The type of rows in the table. | ||
*/ | ||
export class TableCursor<TableRow> { | ||
private table: Table | ||
/** The default parameters to use on a v1/chain/get_table_rows call */ | ||
const defaultParams = { | ||
json: false, | ||
limit: 1000, | ||
} | ||
export class TableCursor<RowType = any> { | ||
/** The ABI for the contract this table belongs to */ | ||
readonly abi: ABI | ||
/** The type of the table, as defined in the ABI */ | ||
readonly type: string | ||
/** The parameters used for the v1/chain/get_table_rows call */ | ||
readonly params: TableRowParamsTypes | ||
/** The APIClient instance to use for API requests */ | ||
readonly client: APIClient | ||
/** For iterating on the cursor, the next key to query against lower_bounds */ | ||
private next_key: API.v1.TableIndexType | string | undefined | ||
private tableParams: API.v1.GetTableRowsParams | ||
/** Whether or not the cursor believes it has reached the end of its results */ | ||
private endReached = false | ||
private indexPositionField?: string | ||
/** The number of rows the cursor has retrieved */ | ||
private rowsCount = 0 | ||
/** The maximum number of rows the cursor should retrieve */ | ||
private maxRows: number = Number.MAX_SAFE_INTEGER | ||
/** | ||
* @param {TableCursorParams} params - Parameters for creating a new table cursor. | ||
* Create a new TableCursor instance. | ||
* | ||
* @param {TableRow[]} params.rows - An array of rows that the cursor will iterate over. | ||
* Each row represents an entry in the table. | ||
* | ||
* @param {Table} params.table - The table that the rows belong to. | ||
* | ||
* @param {API.v1.GetTableRowsParams} params.tableParams - Parameters for the `get_table_rows` | ||
* API call, which are used to fetch the rows from the blockchain. | ||
* | ||
* @param {(Name | UInt64 | undefined)} [params.next_key] - The key for the next set of rows | ||
* that the cursor can fetch. This is used for pagination when there are more rows than can be | ||
* fetched in a single API call. | ||
* @param args.abi The ABI for the contract. | ||
* @param args.client The APIClient instance to use for API requests. | ||
* @param args.params The parameters to use for the table query. | ||
* @param args.maxRows The maximum number of rows to fetch. | ||
* @returns A new TableCursor instance. | ||
*/ | ||
constructor({table, tableParams, indexPositionField, next_key}: TableCursorParams) { | ||
this.table = table | ||
this.tableParams = tableParams | ||
this.next_key = next_key | ||
this.indexPositionField = indexPositionField | ||
constructor(args: TableCursorArgs) { | ||
this.abi = ABI.from(args.abi) | ||
this.client = args.client | ||
this.params = { | ||
...defaultParams, | ||
...args.params, | ||
} | ||
if (args.maxRows) { | ||
this.maxRows = args.maxRows | ||
} | ||
const table = this.abi.tables.find((t) => t.name === String(this.params.table)) | ||
if (!table) { | ||
throw new Error('Table not found') | ||
} | ||
this.type = table.type | ||
} | ||
@@ -51,3 +75,3 @@ | ||
* | ||
* @returns An iterator for the rows in the table. | ||
* @returns An iterator for all rows in the table. | ||
*/ | ||
@@ -62,3 +86,2 @@ async *[Symbol.asyncIterator]() { | ||
// If no rows are returned or next_key is undefined, we have exhausted all rows | ||
if (rows.length === 0 || !this.next_key) { | ||
@@ -71,7 +94,9 @@ return | ||
/** | ||
* Fetches more rows from the table and appends them to the cursor. | ||
* Fetch the next batch of rows from the cursor. | ||
* | ||
* @returns The new rows. | ||
* @param rowsPerAPIRequest The number of rows to fetch per API request. | ||
* @returns A promise containing the next batch of rows. | ||
*/ | ||
async next(): Promise<TableRow[]> { | ||
async next(rowsPerAPIRequest: number = Number.MAX_SAFE_INTEGER): Promise<RowType[]> { | ||
// If the cursor has deemed its at the end, return an empty array | ||
if (this.endReached) { | ||
@@ -81,5 +106,4 @@ return [] | ||
let lower_bound = this.tableParams.lower_bound | ||
const upper_bound = this.tableParams.upper_bound | ||
// Set the lower_bound, and override if the cursor has a next_key value | ||
let lower_bound = this.params.lower_bound | ||
if (this.next_key) { | ||
@@ -89,30 +113,44 @@ lower_bound = this.next_key | ||
let indexPosition = this.tableParams.index_position || 'primary' | ||
// Determine the maximum number of remaining rows for the cursor | ||
const rowsRemaining = this.maxRows - this.rowsCount | ||
if (this.indexPositionField) { | ||
const fieldToIndexMapping = this.table.getFieldToIndex() | ||
// Find the lowest amount between rows remaining, rows per request, or the provided query params limit | ||
const limit = Math.min(rowsRemaining, rowsPerAPIRequest, this.params.limit) | ||
if (!fieldToIndexMapping[this.indexPositionField]) { | ||
throw new Error(`Field ${this.indexPositionField} is not a valid index.`) | ||
} | ||
indexPosition = fieldToIndexMapping[this.indexPositionField].index_position | ||
// Assemble and perform the v1/chain/get_table_rows query | ||
const query = { | ||
...this.params, | ||
limit, | ||
lower_bound: wrapIndexValue(lower_bound), | ||
upper_bound: wrapIndexValue(this.params.upper_bound), | ||
} | ||
const {rows, next_key} = await this.table.contract.client!.v1.chain.get_table_rows({ | ||
...this.tableParams, | ||
limit: Math.min(this.tableParams.limit - this.rowsCount, 1000000), | ||
lower_bound: wrapIndexValue(lower_bound), | ||
upper_bound: wrapIndexValue(upper_bound), | ||
index_position: indexPosition, | ||
}) | ||
const result = await this.client!.v1.chain.get_table_rows(query) | ||
this.next_key = next_key | ||
// Determine if we need to decode the rows, based on if: | ||
// - json parameter is false, meaning hex data will be returned | ||
// - type parameter is not set, meaning the APIClient will not automatically decode | ||
const requiresDecoding = | ||
this.params.json === false && !(query as API.v1.GetTableRowsParamsTyped).type | ||
if (!next_key || rows.length === 0 || this.rowsCount === this.tableParams.limit) { | ||
// Retrieve the rows from the result, decoding if needed | ||
const rows: RowType[] = requiresDecoding | ||
? result.rows.map((data) => | ||
Serializer.decode({ | ||
data, | ||
abi: this.abi, | ||
type: this.type, | ||
}) | ||
) | ||
: result.rows | ||
// Persist cursor state for subsequent calls | ||
this.next_key = result.next_key | ||
this.rowsCount += rows.length | ||
// Determine if we've reached the end of the cursor | ||
if (!result.next_key || rows.length === 0 || this.rowsCount === this.maxRows) { | ||
this.endReached = true | ||
} | ||
this.rowsCount += rows.length | ||
return rows | ||
@@ -122,5 +160,3 @@ } | ||
/** | ||
* Resets the cursor to the beginning of the table and returns the first rows. | ||
* | ||
* @returns The first rows in the table. | ||
* Reset the internal state of the cursor | ||
*/ | ||
@@ -134,8 +170,8 @@ async reset() { | ||
/** | ||
* Returns all rows in the cursor query. | ||
* Fetch all rows from the cursor by recursively calling next() until the end is reached. | ||
* | ||
* @returns All rows in the cursor query. | ||
* @returns A promise containing all rows for the cursor. | ||
*/ | ||
async all() { | ||
const rows: TableRow[] = [] | ||
async all(): Promise<RowType[]> { | ||
const rows: RowType[] = [] | ||
for await (const row of this) { | ||
@@ -146,19 +182,2 @@ rows.push(row) | ||
} | ||
/** | ||
* Returns a new cursor with updated parameters. | ||
* | ||
* @returns A new cursor with updated parameters. | ||
*/ | ||
query(query: Query, {limit}: QueryOptions = {}) { | ||
return new TableCursor({ | ||
table: this.table, | ||
tableParams: { | ||
...this.tableParams, | ||
limit: limit || this.tableParams.limit, | ||
lower_bound: query.from || this.tableParams.lower_bound, | ||
upper_bound: query.to || this.tableParams.upper_bound, | ||
}, | ||
}) | ||
} | ||
} |
@@ -1,7 +0,6 @@ | ||
import {ABI, ABISerializableConstructor, API, Name, NameType} from '@wharfkit/session' | ||
import type {Contract} from '../contract' | ||
import {ABI, ABIDef, API, APIClient, Name, NameType, Serializer} from '@greymass/eosio' | ||
import {indexPositionInWords, wrapIndexValue} from '../utils' | ||
import {TableCursor} from './table-cursor' | ||
export interface FindOptions { | ||
export interface QueryOptions { | ||
index?: string | ||
@@ -12,11 +11,8 @@ scope?: NameType | ||
export interface QueryOptions extends FindOptions { | ||
limit?: number | ||
export interface Query extends QueryOptions { | ||
from?: API.v1.TableIndexType | string | number | ||
to?: API.v1.TableIndexType | string | number | ||
rowsPerAPIRequest?: number | ||
} | ||
export interface Query { | ||
from: API.v1.TableIndexType | string | ||
to: API.v1.TableIndexType | string | ||
} | ||
interface FieldToIndex { | ||
@@ -29,6 +25,9 @@ [key: string]: { | ||
interface TableParams<TableRow = any> { | ||
contract: Contract | ||
abi: ABIDef | ||
account: NameType | ||
client: APIClient | ||
name: NameType | ||
rowType?: TableRow | ||
fieldToIndex?: FieldToIndex | ||
defaultRowLimit?: number | ||
} | ||
@@ -47,14 +46,18 @@ | ||
*/ | ||
export class Table<TableRow extends ABISerializableConstructor = ABISerializableConstructor> { | ||
readonly abi: ABI.Table | ||
export class Table<RowType = any> { | ||
readonly abi: ABI | ||
readonly account: Name | ||
readonly client: APIClient | ||
readonly name: Name | ||
readonly contract: Contract | ||
readonly rowType?: TableRow | ||
readonly rowType?: RowType | ||
readonly tableABI: ABI.Table | ||
private fieldToIndex?: any | ||
public defaultRowLimit = 1000 | ||
/** | ||
* Constructs a new `Table` instance. | ||
* | ||
* @param {TableParams<TableRow>} tableParams - Parameters for the table. | ||
* @param {TableParams} tableParams - Parameters for the table. | ||
* The parameters should include: | ||
@@ -67,14 +70,14 @@ * - `contract`: Name of the contract that this table is associated with. | ||
*/ | ||
constructor({contract, name, rowType, fieldToIndex}: TableParams<TableRow>) { | ||
this.name = Name.from(name) | ||
const abi = contract.abi.tables.find((table) => this.name.equals(table.name)) | ||
if (!abi) { | ||
constructor(args: TableParams) { | ||
this.abi = ABI.from(args.abi) | ||
this.account = Name.from(args.account) | ||
this.name = Name.from(args.name) | ||
this.client = args.client | ||
this.rowType = args.rowType | ||
this.fieldToIndex = args.fieldToIndex | ||
const tableABI = this.abi.tables.find((table) => this.name.equals(table.name)) | ||
if (!tableABI) { | ||
throw new Error(`Table ${this.name} not found in ABI`) | ||
} | ||
this.abi = abi | ||
this.rowType = rowType | ||
this.fieldToIndex = fieldToIndex | ||
this.contract = contract | ||
this.tableABI = tableABI | ||
} | ||
@@ -108,23 +111,30 @@ | ||
*/ | ||
query( | ||
query: Query, | ||
{limit = 10, scope = this.contract.account, index, key_type}: QueryOptions = {} | ||
): TableCursor<TableRow> { | ||
const {from, to} = query | ||
query(query: Query): TableCursor<RowType> { | ||
const {from, to, rowsPerAPIRequest} = query | ||
const tableRowsParams = { | ||
const tableRowsParams: any = { | ||
table: this.name, | ||
code: this.contract.account, | ||
scope, | ||
code: this.account, | ||
scope: query.scope || this.account, | ||
type: this.rowType, | ||
limit, | ||
limit: rowsPerAPIRequest || this.defaultRowLimit, | ||
lower_bound: wrapIndexValue(from), | ||
upper_bound: wrapIndexValue(to), | ||
key_type: key_type, | ||
key_type: query.key_type, | ||
} | ||
return new TableCursor({ | ||
table: this, | ||
tableParams: tableRowsParams, | ||
indexPositionField: index, | ||
if (query.index) { | ||
const fieldToIndexMapping = this.getFieldToIndex() | ||
if (!fieldToIndexMapping[query.index]) { | ||
throw new Error(`Field ${query.index} is not a valid index.`) | ||
} | ||
tableRowsParams.index_position = fieldToIndexMapping[query.index].index_position | ||
} | ||
return new TableCursor<RowType>({ | ||
abi: this.abi, | ||
client: this.client, | ||
params: tableRowsParams, | ||
}) | ||
@@ -142,4 +152,4 @@ } | ||
queryValue: API.v1.TableIndexType | string, | ||
{scope = this.contract.account, index, key_type}: QueryOptions = {} | ||
): Promise<TableRow> { | ||
{scope = this.account, index, key_type}: QueryOptions = {} | ||
): Promise<RowType> { | ||
const fieldToIndexMapping = this.getFieldToIndex() | ||
@@ -149,3 +159,3 @@ | ||
table: this.name, | ||
code: this.contract.account, | ||
code: this.account, | ||
scope, | ||
@@ -158,6 +168,17 @@ type: this.rowType!, | ||
key_type: key_type, | ||
json: false, | ||
} | ||
const {rows} = await this.contract.client!.v1.chain.get_table_rows(tableRowsParams) | ||
let {rows} = await this.client!.v1.chain.get_table_rows(tableRowsParams) | ||
if (!this.rowType) { | ||
rows = [ | ||
Serializer.decode({ | ||
data: rows[0], | ||
abi: this.abi, | ||
type: this.tableABI.type, | ||
}), | ||
] | ||
} | ||
return rows[0] | ||
@@ -174,13 +195,16 @@ } | ||
*/ | ||
first(limit: number): TableCursor<TableRow> { | ||
first(maxRows: number, options: QueryOptions = {}): TableCursor<RowType> { | ||
const tableRowsParams = { | ||
table: this.name, | ||
limit, | ||
code: this.contract.account, | ||
limit: maxRows, | ||
code: this.account, | ||
type: this.rowType, | ||
scope: options.scope, | ||
} | ||
return new TableCursor({ | ||
table: this, | ||
tableParams: tableRowsParams, | ||
return new TableCursor<RowType>({ | ||
abi: this.abi, | ||
client: this.client, | ||
maxRows, | ||
params: tableRowsParams, | ||
}) | ||
@@ -191,15 +215,16 @@ } | ||
* Returns a cursor to get every single rows on the table. | ||
* @returns {TableCursor<TableRow>} | ||
* @returns {TableCursor} | ||
*/ | ||
cursor(): TableCursor<TableRow> { | ||
cursor(): TableCursor<RowType> { | ||
const tableRowsParams = { | ||
table: this.name, | ||
code: this.contract.account, | ||
code: this.account, | ||
type: this.rowType, | ||
limit: 1000000, | ||
limit: this.defaultRowLimit, | ||
} | ||
return new TableCursor({ | ||
table: this, | ||
tableParams: tableRowsParams, | ||
return new TableCursor<RowType>({ | ||
abi: this.abi, | ||
client: this.client, | ||
params: tableRowsParams, | ||
}) | ||
@@ -212,3 +237,3 @@ } | ||
*/ | ||
async all(): Promise<TableRow[]> { | ||
async all(): Promise<RowType[]> { | ||
return this.cursor().all() | ||
@@ -224,5 +249,5 @@ } | ||
for (let i = 0; i < this.abi.key_names.length; i++) { | ||
fieldToIndex[this.abi.key_names[i]] = { | ||
type: this.abi.key_types[i], | ||
for (let i = 0; i < this.tableABI.key_names.length; i++) { | ||
fieldToIndex[this.tableABI.key_names[i]] = { | ||
type: this.tableABI.key_types[i], | ||
index_position: indexPositionInWords(i), | ||
@@ -229,0 +254,0 @@ } |
@@ -1,16 +0,8 @@ | ||
import { | ||
ABI, | ||
ABICache, | ||
ABICacheInterface, | ||
ABIDef, | ||
APIClient, | ||
Name, | ||
NameType, | ||
Session, | ||
} from '@wharfkit/session' | ||
import {ABI, ABIDef, APIClient, Name, NameType} from '@greymass/eosio' | ||
import {ABICache} from '@wharfkit/abicache' | ||
import type {ABICacheInterface} from '@wharfkit/abicache' | ||
import {Contract} from './contract' | ||
export interface ContractKitArgs { | ||
client?: APIClient | ||
session?: Session | ||
client: APIClient | ||
} | ||
@@ -35,18 +27,11 @@ | ||
constructor(args: ContractKitArgs, options: ContractKitOptions = defaultContractKitOptions) { | ||
// Use either the client given or get it from the session. | ||
if (args.client) { | ||
this.client = args.client | ||
} else if (args.session) { | ||
this.client = args.session.client | ||
} else { | ||
throw new Error( | ||
'Either a `client` or `session` must be passed when initializing the ContractKit.' | ||
) | ||
throw new Error('A `client` must be passed when initializing the ContractKit.') | ||
} | ||
// Use either the specified cache, the cache from the session, or create one | ||
// Use either the specified cache or create one | ||
if (options.abiCache) { | ||
this.abiCache = options.abiCache | ||
} else if (args.session) { | ||
this.abiCache = args.session.abiCache | ||
} else { | ||
@@ -53,0 +38,0 @@ this.abiCache = new ABICache(this.client) |
@@ -10,3 +10,3 @@ import { | ||
UInt64, | ||
} from '@wharfkit/session' | ||
} from '@greymass/eosio' | ||
@@ -13,0 +13,0 @@ export function pascalCase(value: string): string { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
116802
2426
35
33
+ Added@greymass/eosio@^0.6.10
+ Added@wharfkit/abicache@^1.0.0
+ Addedeosio-signing-request@^2.5.3
+ Added@greymass/eosio@0.6.11(transitive)
+ Addedeosio-signing-request@2.5.3(transitive)
- Removed@wharfkit/mock-data@^1.0.0-beta11
- Removed@wharfkit/session@^1.0.0-beta4
- Removedrollup-plugin-cleanup@^3.2.1
- Removed@rollup/rollup-android-arm-eabi@4.34.8(transitive)
- Removed@rollup/rollup-android-arm64@4.34.8(transitive)
- Removed@rollup/rollup-darwin-arm64@4.34.8(transitive)
- Removed@rollup/rollup-darwin-x64@4.34.8(transitive)
- Removed@rollup/rollup-freebsd-arm64@4.34.8(transitive)
- Removed@rollup/rollup-freebsd-x64@4.34.8(transitive)
- Removed@rollup/rollup-linux-arm-gnueabihf@4.34.8(transitive)
- Removed@rollup/rollup-linux-arm-musleabihf@4.34.8(transitive)
- Removed@rollup/rollup-linux-arm64-gnu@4.34.8(transitive)
- Removed@rollup/rollup-linux-arm64-musl@4.34.8(transitive)
- Removed@rollup/rollup-linux-loongarch64-gnu@4.34.8(transitive)
- Removed@rollup/rollup-linux-powerpc64le-gnu@4.34.8(transitive)
- Removed@rollup/rollup-linux-riscv64-gnu@4.34.8(transitive)
- Removed@rollup/rollup-linux-s390x-gnu@4.34.8(transitive)
- Removed@rollup/rollup-linux-x64-gnu@4.34.8(transitive)
- Removed@rollup/rollup-linux-x64-musl@4.34.8(transitive)
- Removed@rollup/rollup-win32-arm64-msvc@4.34.8(transitive)
- Removed@rollup/rollup-win32-ia32-msvc@4.34.8(transitive)
- Removed@rollup/rollup-win32-x64-msvc@4.34.8(transitive)
- Removed@types/estree@1.0.6(transitive)
- Removed@wharfkit/common@1.4.2(transitive)
- Removed@wharfkit/mock-data@1.3.0(transitive)
- Removed@wharfkit/session@1.5.0(transitive)
- Removed@wharfkit/wallet-plugin-privatekey@1.1.0(transitive)
- Removedestree-walker@0.6.1(transitive)
- Removedfsevents@2.3.3(transitive)
- Removedjs-cleanup@1.2.0(transitive)
- Removedmagic-string@0.25.9(transitive)
- Removednode-fetch@2.7.0(transitive)
- Removedperf-regexes@1.0.1(transitive)
- Removedrollup@4.34.8(transitive)
- Removedrollup-plugin-cleanup@3.2.1(transitive)
- Removedrollup-pluginutils@2.8.2(transitive)
- Removedskip-regex@1.0.2(transitive)
- Removedsourcemap-codec@1.4.8(transitive)
- Removedtr46@0.0.3(transitive)
- Removedwebidl-conversions@3.0.1(transitive)
- Removedwhatwg-url@5.0.0(transitive)