@arkiv-network/sdk 0.8.1-dev.1 → 0.9.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +180 -61
- package/dist/chains/index.d.ts +17 -17
- package/dist/changeOwnership-Cy9gfyjN.d.cts +1160 -0
- package/dist/changeOwnership-Cy9gfyjN.d.cts.map +1 -0
- package/dist/changeOwnership-gXf-lBqI.d.ts +1160 -0
- package/dist/changeOwnership-gXf-lBqI.d.ts.map +1 -0
- package/dist/entity/index.cjs +4 -10
- package/dist/entity/index.d.cts +3 -3
- package/dist/entity/index.d.ts +3 -3
- package/dist/entity/index.js +3 -4
- package/dist/entity-BM1unrQf.cjs +41 -0
- package/dist/{entity-DJ0Gml9c.cjs → entity-BgaFJSaV.cjs} +4 -4
- package/dist/entity-CFAI1KyG.js +36 -0
- package/dist/entity-CFAI1KyG.js.map +1 -0
- package/dist/{entity-CniBgBSd.js → entity-CGqx_AU0.js} +4 -4
- package/dist/entity-CGqx_AU0.js.map +1 -0
- package/dist/{payload-AfUfSctb.d.ts → expirationTime-C7PZK49K.d.ts} +8 -37
- package/dist/expirationTime-C7PZK49K.d.ts.map +1 -0
- package/dist/expirationTime-CkIGNAg8.cjs +140 -0
- package/dist/{payload-D167BlJR.d.cts → expirationTime-DOB8V4B8.d.cts} +8 -37
- package/dist/expirationTime-DOB8V4B8.d.cts.map +1 -0
- package/dist/{expiry-Cgdb_dbq.js → expirationTime-UKPuA-LM.js} +62 -27
- package/dist/expirationTime-UKPuA-LM.js.map +1 -0
- package/dist/expiry-B48I-yTA.d.cts +40 -0
- package/dist/expiry-B48I-yTA.d.cts.map +1 -0
- package/dist/expiry-S2PUL8Hg.d.ts +40 -0
- package/dist/expiry-S2PUL8Hg.d.ts.map +1 -0
- package/dist/index-C-mMF5WZ.d.cts +735 -0
- package/dist/index-C-mMF5WZ.d.cts.map +1 -0
- package/dist/{index-BkqSnGJH.d.ts → index-C4zZqJ9S.d.ts} +5 -27
- package/dist/index-C4zZqJ9S.d.ts.map +1 -0
- package/dist/{index-CLhsFyVH.d.cts → index-CcVWYB6v.d.cts} +5 -27
- package/dist/index-CcVWYB6v.d.cts.map +1 -0
- package/dist/index-DCx2XC6S.d.ts +489 -0
- package/dist/index-DCx2XC6S.d.ts.map +1 -0
- package/dist/index-DLb7BfIw.d.ts +735 -0
- package/dist/index-DLb7BfIw.d.ts.map +1 -0
- package/dist/index-DzriOUm8.d.cts +489 -0
- package/dist/index-DzriOUm8.d.cts.map +1 -0
- package/dist/index.cjs +162 -146
- package/dist/index.d.cts +180 -133
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +180 -133
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +93 -83
- package/dist/index.js.map +1 -1
- package/dist/params-BuIrJA9a.cjs +45 -0
- package/dist/params-DDJOnsNi.js +22 -0
- package/dist/params-DDJOnsNi.js.map +1 -0
- package/dist/query/index.cjs +9 -4
- package/dist/query/index.d.cts +6 -6
- package/dist/query/index.d.ts +6 -6
- package/dist/query/index.js +4 -5
- package/dist/query-D0KtuFh2.cjs +1625 -0
- package/dist/query-DpRDozmj.js +1417 -0
- package/dist/query-DpRDozmj.js.map +1 -0
- package/dist/schema/index.cjs +46 -0
- package/dist/schema/index.d.cts +2 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +3 -0
- package/dist/{attr-BXTkpnoc.cjs → schema-B9AyXcgU.cjs} +513 -246
- package/dist/{attr-tmyREHtw.js → schema-DY4sqehl.js} +427 -244
- package/dist/schema-DY4sqehl.js.map +1 -0
- package/dist/types/index.cjs +1 -1
- package/dist/types/index.d.cts +5 -5
- package/dist/types/index.d.ts +5 -5
- package/dist/types/index.js +1 -1
- package/dist/utils/index.cjs +3 -5
- package/dist/utils/index.d.cts +3 -3
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.js +3 -3
- package/package.json +9 -12
- package/src/actions/public/getEntity.test.ts +69 -0
- package/src/actions/public/getEntity.ts +34 -20
- package/src/actions/public/predictEntityKeys.ts +8 -7
- package/src/actions/public/query.test.ts +58 -0
- package/src/actions/public/query.ts +41 -13
- package/src/actions/wallet/createEntity.ts +34 -63
- package/src/actions/wallet/executeBatch.test.ts +38 -0
- package/src/actions/wallet/executeBatch.ts +26 -9
- package/src/actions/wallet/extendEntity.ts +9 -8
- package/src/actions/wallet/patchEntity.ts +43 -52
- package/src/clients/createPublicClient.test.ts +10 -2
- package/src/clients/createWalletClient.ts +4 -1
- package/src/clients/decorators/arkivPublic.ts +123 -80
- package/src/clients/decorators/arkivWallet.ts +113 -65
- package/src/entity/create.test.ts +25 -21
- package/src/entity/expiry.ts +8 -6
- package/src/entity/flags.ts +1 -1
- package/src/entity/index.ts +2 -4
- package/src/entity/key.ts +3 -3
- package/src/entity/operations.ts +1 -1
- package/src/errors.ts +2 -11
- package/src/index.ts +18 -6
- package/src/query/builderBase.ts +166 -0
- package/src/query/countBuilder.test.ts +91 -0
- package/src/query/countBuilder.ts +67 -0
- package/src/query/engine.ts +9 -3
- package/src/query/errors.ts +24 -14
- package/src/query/expression.test.ts +129 -21
- package/src/query/expression.ts +323 -94
- package/src/query/index.ts +26 -4
- package/src/query/injection.test.ts +5 -11
- package/src/query/queryBuilder.test.ts +467 -58
- package/src/query/queryBuilder.ts +191 -98
- package/src/query/queryResult.ts +35 -9
- package/src/query/selection.test.ts +278 -24
- package/src/query/selection.ts +427 -86
- package/src/schema/attributes.test.ts +266 -0
- package/src/schema/attributes.ts +212 -0
- package/src/{attr → schema}/codec.test.ts +37 -5
- package/src/{attr → schema}/codec.ts +40 -11
- package/src/schema/column.ts +193 -0
- package/src/{attr → schema}/errors.ts +67 -10
- package/src/schema/index.ts +76 -0
- package/src/{attr → schema}/names.ts +4 -2
- package/src/schema/schema.test.ts +171 -0
- package/src/schema/schema.ts +250 -0
- package/src/{attr → schema}/types.ts +36 -30
- package/src/{attr → schema}/values.test.ts +62 -3
- package/src/{attr → schema}/values.ts +246 -65
- package/src/types/entity.test.ts +1 -44
- package/src/types/entity.ts +9 -55
- package/src/types/events.ts +1 -1
- package/src/types/index.ts +11 -4
- package/src/types/rpcSchema.ts +22 -16
- package/src/utils/arkivTransactions.test.ts +356 -116
- package/src/utils/arkivTransactions.ts +30 -38
- package/src/utils/entities.ts +10 -9
- package/src/utils/expirationTime.ts +5 -5
- package/src/utils/index.ts +0 -1
- package/src/utils/logger.test.ts +45 -0
- package/src/utils/logger.ts +5 -39
- package/src/utils/revert.test.ts +25 -0
- package/src/utils/revert.ts +28 -3
- package/dist/attr/index.cjs +0 -34
- package/dist/attr/index.d.cts +0 -2
- package/dist/attr/index.d.ts +0 -2
- package/dist/attr/index.js +0 -3
- package/dist/attr-tmyREHtw.js.map +0 -1
- package/dist/entity-B0t6sN7F.cjs +0 -71
- package/dist/entity-BIp_df9u.js +0 -67
- package/dist/entity-BIp_df9u.js.map +0 -1
- package/dist/entity-CniBgBSd.js.map +0 -1
- package/dist/expiry-BxOlvVWC.d.ts +0 -79
- package/dist/expiry-BxOlvVWC.d.ts.map +0 -1
- package/dist/expiry-CG9GhVXv.cjs +0 -135
- package/dist/expiry-Cgdb_dbq.js.map +0 -1
- package/dist/expiry-DRV29F1r.d.cts +0 -79
- package/dist/expiry-DRV29F1r.d.cts.map +0 -1
- package/dist/flags-DCD26uhe.cjs +0 -43
- package/dist/flags-DUT2gHEU.js +0 -33
- package/dist/flags-DUT2gHEU.js.map +0 -1
- package/dist/index-BkqSnGJH.d.ts.map +0 -1
- package/dist/index-C7fbakoI.d.cts +0 -278
- package/dist/index-C7fbakoI.d.cts.map +0 -1
- package/dist/index-CIcHPx2j.d.cts +0 -424
- package/dist/index-CIcHPx2j.d.cts.map +0 -1
- package/dist/index-CLhsFyVH.d.cts.map +0 -1
- package/dist/index-Cd2aaWme.d.ts +0 -424
- package/dist/index-Cd2aaWme.d.ts.map +0 -1
- package/dist/index-Cq6fa8AI.d.ts +0 -278
- package/dist/index-Cq6fa8AI.d.ts.map +0 -1
- package/dist/index-DaNeJUw8.d.ts +0 -955
- package/dist/index-DaNeJUw8.d.ts.map +0 -1
- package/dist/index-Dv16LO2s.d.cts +0 -955
- package/dist/index-Dv16LO2s.d.cts.map +0 -1
- package/dist/payload-AfUfSctb.d.ts.map +0 -1
- package/dist/payload-D167BlJR.d.cts.map +0 -1
- package/dist/payload-DE7wMztL.js +0 -92
- package/dist/payload-DE7wMztL.js.map +0 -1
- package/dist/payload-DqFweqiO.cjs +0 -108
- package/dist/query-9oojUdM7.cjs +0 -1128
- package/dist/query-Ber-lOJQ.js +0 -968
- package/dist/query-Ber-lOJQ.js.map +0 -1
- package/src/actions/public/getEntityCount.ts +0 -10
- package/src/attr/attributes.test.ts +0 -120
- package/src/attr/attributes.ts +0 -196
- package/src/attr/index.ts +0 -50
- package/src/types/mimeTypes.ts +0 -27
- package/src/utils/compression.test.ts +0 -32
- package/src/utils/compression.ts +0 -102
- package/src/utils/payload.ts +0 -36
- /package/src/{attr → schema}/decimal.ts +0 -0
|
@@ -1,47 +1,75 @@
|
|
|
1
1
|
import type { ArkivClient } from "../../clients/baseClient"
|
|
2
2
|
import { runQuery } from "../../query/engine"
|
|
3
3
|
import type { Expression } from "../../query/expression"
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
type FullEntity,
|
|
6
|
+
projectSelection,
|
|
7
|
+
type ResultOf,
|
|
8
|
+
type SelectArg,
|
|
9
|
+
type SelectionOf,
|
|
10
|
+
toRow,
|
|
11
|
+
} from "../../query/selection"
|
|
6
12
|
|
|
7
|
-
export type QueryOptions = {
|
|
8
|
-
/** What to return. Defaults to everything. */
|
|
9
|
-
select?:
|
|
13
|
+
export type QueryOptions<TSelection extends SelectArg = SelectArg> = {
|
|
14
|
+
/** What to return — the same selection `select()` takes. Defaults to everything. */
|
|
15
|
+
select?: TSelection | undefined
|
|
10
16
|
/** Block height to read at. Defaults to the head. */
|
|
11
17
|
atBlock?: bigint | undefined
|
|
12
|
-
/** Page size, up to the node maximum of
|
|
18
|
+
/** Page size, up to the node maximum of 100. */
|
|
13
19
|
limit?: number | undefined
|
|
14
20
|
/** Cursor from a previous page. */
|
|
15
21
|
cursor?: string | undefined
|
|
22
|
+
/** Whether to also count every entity the query matches, returned as `count`. */
|
|
23
|
+
count?: boolean | undefined
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
/**
|
|
27
|
+
* What one result of a `query()` reads as for selection `S`: the same row `select(S)` returns
|
|
28
|
+
* without a schema — every field when everything is selected, and every field optional when the
|
|
29
|
+
* selection is only known at runtime.
|
|
30
|
+
*/
|
|
31
|
+
export type QueryRow<S> = ResultOf<SelectionOf<S>, undefined>
|
|
32
|
+
|
|
33
|
+
export type QueryReturnType<TResult = FullEntity> = {
|
|
34
|
+
entities: TResult[]
|
|
20
35
|
/** The cursor for the next page, or `undefined` when no pages remain. */
|
|
21
36
|
cursor: string | undefined
|
|
22
37
|
/** The block the page was read at. */
|
|
23
38
|
blockNumber: bigint
|
|
39
|
+
/** How many entities the query matches, if `count` was set. */
|
|
40
|
+
count: bigint | undefined
|
|
24
41
|
}
|
|
25
42
|
|
|
26
43
|
/**
|
|
27
44
|
* Runs a single query and returns one page, with no builder in between.
|
|
28
45
|
*
|
|
46
|
+
* The selection means what it does in `select()`, and each result is the same flat row a query
|
|
47
|
+
* without a schema returns — typed from the selection, with every attribute an `ArkivValue` or
|
|
48
|
+
* `undefined`.
|
|
49
|
+
*
|
|
29
50
|
* Takes either an {@link Expression} or a raw query string. The string form is an escape hatch for
|
|
30
51
|
* a query built elsewhere — it goes to the node exactly as written, with none of the name, type or
|
|
31
52
|
* operator checks the expression combinators apply.
|
|
32
53
|
*
|
|
54
|
+
* @throws If the selection asks for nothing, or names an attribute outside the name grammar.
|
|
33
55
|
* @throws {QueryError} If the node rejects the query.
|
|
34
56
|
*/
|
|
35
|
-
export function query(
|
|
57
|
+
export async function query<const S extends SelectArg = "*">(
|
|
36
58
|
client: ArkivClient,
|
|
37
59
|
query: Expression | string,
|
|
38
|
-
options: QueryOptions = {},
|
|
39
|
-
): Promise<QueryReturnType
|
|
40
|
-
|
|
60
|
+
options: QueryOptions<S> = {},
|
|
61
|
+
): Promise<QueryReturnType<QueryRow<S>>> {
|
|
62
|
+
const projection = projectSelection(options.select)
|
|
63
|
+
const page = await runQuery(client, {
|
|
41
64
|
query: query.toString(),
|
|
42
|
-
select:
|
|
65
|
+
select: projection.select,
|
|
43
66
|
limit: options.limit,
|
|
44
67
|
cursor: options.cursor,
|
|
45
68
|
atBlock: options.atBlock,
|
|
69
|
+
count: options.count,
|
|
46
70
|
})
|
|
71
|
+
return {
|
|
72
|
+
...page,
|
|
73
|
+
entities: page.entities.map((entity) => toRow(entity, projection) as QueryRow<S>),
|
|
74
|
+
}
|
|
47
75
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Hash, Hex } from "viem"
|
|
2
|
-
import type { AttributeInputs } from "../../attr"
|
|
3
2
|
import type { ArkivClient } from "../../clients/baseClient"
|
|
4
3
|
import type { CreationFlags, Expiry, NO_SALT, SaltInput } from "../../entity"
|
|
5
4
|
import { EntityMutationError } from "../../errors"
|
|
6
|
-
import type {
|
|
5
|
+
import type { CreateEntityValues, Schema } from "../../schema"
|
|
6
|
+
import type { TxParams } from "../../types"
|
|
7
7
|
import { sendArkivTransaction } from "../../utils/arkivTransactions"
|
|
8
8
|
import type { ExpirationTime } from "../../utils/expirationTime"
|
|
9
9
|
import { getLogger } from "../../utils/logger"
|
|
@@ -11,69 +11,27 @@ import { getLogger } from "../../utils/logger"
|
|
|
11
11
|
const logger = getLogger("actions:wallet:create-entity")
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* An entity always carries contents and a type for them, plus a lifetime. Attributes, flags and a
|
|
17
|
-
* salt are optional on top of that.
|
|
14
|
+
* How an created entity lives: its lifetime, plus the flags and salt it is created with.
|
|
18
15
|
*
|
|
19
16
|
* @example
|
|
20
|
-
*
|
|
21
|
-
* attributes: {
|
|
22
|
-
* level: i32(10),
|
|
23
|
-
* balance: u256(1_000_000n),
|
|
24
|
-
* score: dec("3.5"),
|
|
25
|
-
* name: "Bob", // bare string -> str
|
|
26
|
-
* flagged: true, // bare boolean -> bool
|
|
27
|
-
* },
|
|
28
|
-
* payload: jsonToPayload({ hello: "world" }),
|
|
29
|
-
* contentType: "application/json",
|
|
30
|
-
* expires: ExpirationTime.fromDays(30),
|
|
31
|
-
* flags: { readonly: true },
|
|
32
|
-
* })
|
|
17
|
+
* { expiration: ExpirationTime.fromDays(30) }
|
|
33
18
|
*
|
|
34
|
-
* @example Pin an absolute deadline, but
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* expires: ExpirationTime.atDate(someDeadline, {
|
|
19
|
+
* @example Pin an absolute deadline, but never let it leave the entity with less than a day:
|
|
20
|
+
* {
|
|
21
|
+
* expiration: ExpirationTime.atDate(someDeadline, {
|
|
38
22
|
* atLeast: ExpirationTime.fromDays(1), // ...but live at least a day regardless
|
|
39
23
|
* }),
|
|
40
|
-
* }
|
|
41
|
-
*
|
|
42
|
-
* @example An entity that is nothing but queryable attributes still declares its (empty) contents:
|
|
43
|
-
* await client.createEntity({
|
|
44
|
-
* attributes: { parent: key(parentKey), rank: i32(3) },
|
|
45
|
-
* payload: new Uint8Array(),
|
|
46
|
-
* contentType: "application/octet-stream",
|
|
47
|
-
* expires: ExpirationTime.fromDays(30),
|
|
48
|
-
* })
|
|
24
|
+
* flags: { readonly: true },
|
|
25
|
+
* }
|
|
49
26
|
*/
|
|
50
|
-
export type
|
|
27
|
+
export type CreateEntityOptions = {
|
|
51
28
|
/**
|
|
52
29
|
* How long the entity should live, built with {@link ExpirationTime}.
|
|
53
30
|
*
|
|
54
31
|
* `ExpirationTime.fromDays(30)` is the everyday form. `atBlock` / `atDate` pin an absolute
|
|
55
32
|
* deadline instead, and either can carry `{ atLeast }` to guarantee a minimum life on top of it.
|
|
56
33
|
*/
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* The entity's opaque payload. Travels to the engine as the `$payload` system attribute; on this
|
|
60
|
-
* surface it is just the entity's contents.
|
|
61
|
-
*
|
|
62
|
-
* Pass an empty `Uint8Array` for an entity that is nothing but queryable attributes — it is
|
|
63
|
-
* written as an empty payload rather than left unset, so what you pass is what is stored.
|
|
64
|
-
*/
|
|
65
|
-
payload: Uint8Array
|
|
66
|
-
/** The payload's MIME type, e.g. `"application/json"`. Sent as the `$contentType` cell. */
|
|
67
|
-
contentType: MimeType | (string & {})
|
|
68
|
-
/**
|
|
69
|
-
* The entity's queryable attributes, keyed by name. Values are the tagged constructors from
|
|
70
|
-
* `@arkiv-network/sdk/attr` — `i32`, `u64`, `u256`, `dec`, `str`, `addr`, `key`, `bytes32`,
|
|
71
|
-
* `bool` — or a bare `boolean`, `number`, `bigint` or `string` where the type is unambiguous.
|
|
72
|
-
*
|
|
73
|
-
* @throws {InvalidAttributeNameError} If a name violates the attribute-name grammar.
|
|
74
|
-
* @throws {InvalidValueError} If a value does not fit the type it names or defaults to.
|
|
75
|
-
*/
|
|
76
|
-
attributes?: AttributeInputs | undefined
|
|
34
|
+
expiration: Expiry
|
|
77
35
|
/**
|
|
78
36
|
* Properties fixed at creation: `readonly` and `permissionlessExtension`. No operation changes
|
|
79
37
|
* them afterwards, so an entity is whatever it was created as for life. Both default to `false`.
|
|
@@ -89,6 +47,21 @@ export type CreateEntityParameters = {
|
|
|
89
47
|
salt?: SaltInput | undefined
|
|
90
48
|
}
|
|
91
49
|
|
|
50
|
+
/**
|
|
51
|
+
* One entity to create, as a batch carries it: the schema, the values, and the {@link CreateEntityOptions}.
|
|
52
|
+
*
|
|
53
|
+
* @typeParam S - The schema the values are typed by.
|
|
54
|
+
*/
|
|
55
|
+
export type CreateEntityParameters<S extends Schema = Schema> = CreateEntityOptions & {
|
|
56
|
+
/** The schema the entity is written through. */
|
|
57
|
+
schema: S
|
|
58
|
+
/**
|
|
59
|
+
* The entity's values, keyed by column. Every required column must be given; an optional one may
|
|
60
|
+
* be left out, and a defaulted one left out is written with its default.
|
|
61
|
+
*/
|
|
62
|
+
values: CreateEntityValues<S>
|
|
63
|
+
}
|
|
64
|
+
|
|
92
65
|
/**
|
|
93
66
|
* The result of creating an entity.
|
|
94
67
|
*/
|
|
@@ -97,25 +70,23 @@ export type CreateEntityReturnType = {
|
|
|
97
70
|
entityKey: Hex
|
|
98
71
|
txHash: Hash
|
|
99
72
|
/**
|
|
100
|
-
* The block the entity
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* The engine resolves a duration against the block the transaction actually lands in, so for
|
|
104
|
-
* `ExpirationTime.fromDays(30)` and friends this is a lower bound: the real expiry is later by
|
|
105
|
-
* however many blocks passed between building and inclusion. An `atBlock` deadline is exact.
|
|
73
|
+
* The block the entity expires at, as the engine recorded it — read from the `EntityCreated`
|
|
74
|
+
* event, so a duration is already resolved against the block the transaction landed in.
|
|
106
75
|
*/
|
|
107
76
|
expiresAt: bigint
|
|
108
77
|
}
|
|
109
78
|
|
|
110
|
-
export async function createEntity(
|
|
79
|
+
export async function createEntity<S extends Schema>(
|
|
111
80
|
client: ArkivClient,
|
|
112
|
-
|
|
81
|
+
schema: S,
|
|
82
|
+
values: CreateEntityValues<S>,
|
|
83
|
+
options: CreateEntityOptions,
|
|
113
84
|
txParams?: TxParams,
|
|
114
85
|
): Promise<CreateEntityReturnType> {
|
|
115
|
-
logger("createEntity %o",
|
|
86
|
+
logger("createEntity %o", values)
|
|
116
87
|
const { receipt, createdEntityKeys, createdExpiries } = await sendArkivTransaction(
|
|
117
88
|
client,
|
|
118
|
-
{ creates: [
|
|
89
|
+
{ creates: [{ ...options, schema, values }] },
|
|
119
90
|
txParams,
|
|
120
91
|
)
|
|
121
92
|
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "bun:test"
|
|
2
|
+
import { decodeAbiParameters, parseAbiParameters, toHex } from "viem"
|
|
2
3
|
import type { ArkivClient } from "../../clients/baseClient"
|
|
4
|
+
import { OperationType } from "../../entity/params"
|
|
5
|
+
import { bytes, defineSchema, i32, str } from "../../schema"
|
|
3
6
|
import { executeBatch } from "./executeBatch"
|
|
4
7
|
|
|
5
8
|
const ENTITY_KEY = `0x${"ab".repeat(32)}` as const
|
|
9
|
+
const OTHER_KEY = `0x${"cd".repeat(32)}` as const
|
|
6
10
|
const NEW_OWNER = "0x2222222222222222222222222222222222222222"
|
|
7
11
|
|
|
12
|
+
const PATCH_PARAMS = parseAbiParameters(
|
|
13
|
+
"(bytes32 entityKey, (bytes32 name, uint8 typeId, bytes value)[] mutations)",
|
|
14
|
+
)
|
|
15
|
+
|
|
8
16
|
function makeClient() {
|
|
9
17
|
const writeContract = vi.fn().mockResolvedValue("0xdeadbeef")
|
|
10
18
|
return {
|
|
@@ -74,3 +82,33 @@ describe("the empty-batch guard", () => {
|
|
|
74
82
|
expect(writeContract.mock.calls[0][0].args[0]).toHaveLength(1)
|
|
75
83
|
})
|
|
76
84
|
})
|
|
85
|
+
|
|
86
|
+
describe("schemas in a batch", () => {
|
|
87
|
+
it("types each patch by its own schema", async () => {
|
|
88
|
+
const Posts = defineSchema({ title: str(), score: i32().indexed() })
|
|
89
|
+
const Files = defineSchema({ path: str().indexed(), body: bytes().optional() })
|
|
90
|
+
const { client, writeContract } = makeClient()
|
|
91
|
+
|
|
92
|
+
const result = await executeBatch(client, {
|
|
93
|
+
patches: [
|
|
94
|
+
{ schema: Posts, entityKey: ENTITY_KEY, set: { score: 5 } },
|
|
95
|
+
{ schema: Files, entityKey: OTHER_KEY, set: { path: "/a" }, unset: ["body"] },
|
|
96
|
+
],
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
expect(result.patchedEntities).toEqual([ENTITY_KEY, OTHER_KEY])
|
|
100
|
+
const ops = writeContract.mock.calls[0][0].args[0] as {
|
|
101
|
+
operation: number
|
|
102
|
+
operationData: `0x${string}`
|
|
103
|
+
}[]
|
|
104
|
+
expect(ops.map((op) => op.operation)).toEqual([OperationType.Patch, OperationType.Patch])
|
|
105
|
+
const [posts, files] = ops.map((op) => decodeAbiParameters(PATCH_PARAMS, op.operationData)[0])
|
|
106
|
+
expect(posts.mutations.map((m) => [m.name, m.typeId])).toEqual([
|
|
107
|
+
[toHex("score", { size: 32 }), 0x80 | 2],
|
|
108
|
+
])
|
|
109
|
+
expect(files.mutations.map((m) => [m.name, m.typeId])).toEqual([
|
|
110
|
+
[toHex("body", { size: 32 }), 0],
|
|
111
|
+
[toHex("path", { size: 32 }), 0x80 | 8],
|
|
112
|
+
])
|
|
113
|
+
})
|
|
114
|
+
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Hash, Hex } from "viem"
|
|
2
2
|
import type { ArkivClient } from "../../clients/baseClient"
|
|
3
|
+
import type { Schema } from "../../schema"
|
|
3
4
|
import type { TxParams } from "../../types"
|
|
4
5
|
import { sendArkivTransaction } from "../../utils/arkivTransactions"
|
|
5
6
|
import { getLogger } from "../../utils/logger"
|
|
@@ -16,18 +17,27 @@ const logger = getLogger("actions:wallet:execute-batch")
|
|
|
16
17
|
*
|
|
17
18
|
* At least one operation is required; a batch with nothing in it throws rather than spending gas on
|
|
18
19
|
* an empty transaction.
|
|
20
|
+
*
|
|
21
|
+
* Each create and patch names its own schema, and is typed by it — a batch can write entities of
|
|
22
|
+
* several schemas at once.
|
|
23
|
+
*
|
|
24
|
+
* @typeParam TCreates - The schema of each create, in order. Inferred.
|
|
25
|
+
* @typeParam TPatches - The schema of each patch, in order. Inferred.
|
|
19
26
|
*/
|
|
20
|
-
export type ExecuteBatchParameters
|
|
27
|
+
export type ExecuteBatchParameters<
|
|
28
|
+
TCreates extends readonly Schema[] = readonly Schema[],
|
|
29
|
+
TPatches extends readonly Schema[] = readonly Schema[],
|
|
30
|
+
> = {
|
|
21
31
|
/** The entities to create. */
|
|
22
|
-
creates?: CreateEntityParameters[]
|
|
32
|
+
creates?: { readonly [I in keyof TCreates]: CreateEntityParameters<TCreates[I]> } | undefined
|
|
23
33
|
/** The patches to apply. */
|
|
24
|
-
patches?: PatchEntityParameters[]
|
|
34
|
+
patches?: { readonly [I in keyof TPatches]: PatchEntityParameters<TPatches[I]> } | undefined
|
|
25
35
|
/** The entities to delete. */
|
|
26
|
-
deletes?: DeleteEntityParameters[]
|
|
36
|
+
deletes?: readonly DeleteEntityParameters[] | undefined
|
|
27
37
|
/** The expiries to set. */
|
|
28
|
-
extensions?: ExtendEntityParameters[]
|
|
38
|
+
extensions?: readonly ExtendEntityParameters[] | undefined
|
|
29
39
|
/** The ownership transfers to perform. */
|
|
30
|
-
ownershipChanges?: ChangeOwnershipParameters[]
|
|
40
|
+
ownershipChanges?: readonly ChangeOwnershipParameters[] | undefined
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
/** Return type for the executeBatch function. */
|
|
@@ -46,12 +56,19 @@ export type ExecuteBatchReturnType = {
|
|
|
46
56
|
ownershipChanges: Hex[]
|
|
47
57
|
}
|
|
48
58
|
|
|
49
|
-
export async function executeBatch
|
|
59
|
+
export async function executeBatch<
|
|
60
|
+
const TCreates extends readonly Schema[] = [],
|
|
61
|
+
const TPatches extends readonly Schema[] = [],
|
|
62
|
+
>(
|
|
50
63
|
client: ArkivClient,
|
|
51
|
-
data: ExecuteBatchParameters,
|
|
64
|
+
data: ExecuteBatchParameters<TCreates, TPatches>,
|
|
52
65
|
txParams?: TxParams,
|
|
53
66
|
): Promise<ExecuteBatchReturnType> {
|
|
54
|
-
const { receipt, createdEntityKeys } = await sendArkivTransaction(
|
|
67
|
+
const { receipt, createdEntityKeys } = await sendArkivTransaction(
|
|
68
|
+
client,
|
|
69
|
+
data as ExecuteBatchParameters,
|
|
70
|
+
txParams,
|
|
71
|
+
)
|
|
55
72
|
|
|
56
73
|
logger("Receipt from executeBatch %o", receipt)
|
|
57
74
|
|
|
@@ -17,17 +17,18 @@ const logger = getLogger("actions:wallet:extend-entity")
|
|
|
17
17
|
* `ExpirationTime.fromDays(30)` means "alive for thirty more days from now" whatever the entity's
|
|
18
18
|
* remaining life happened to be.
|
|
19
19
|
*
|
|
20
|
-
* The engine rejects an extension that would
|
|
21
|
-
* less than it already has left is an error rather than a silent shortening.
|
|
20
|
+
* The engine rejects an extension that would move the expiry earlier, so extending an entity by
|
|
21
|
+
* less than it already has left is an error rather than a silent shortening. One that resolves to
|
|
22
|
+
* the current expiry changes nothing.
|
|
22
23
|
*
|
|
23
24
|
* @example
|
|
24
|
-
* await client.extendEntity({ entityKey,
|
|
25
|
-
* await client.extendEntity({ entityKey,
|
|
25
|
+
* await client.extendEntity({ entityKey, expiration: ExpirationTime.fromDays(30) })
|
|
26
|
+
* await client.extendEntity({ entityKey, expiration: ExpirationTime.atBlock(1_200_000n) })
|
|
26
27
|
*/
|
|
27
28
|
export type ExtendEntityParameters = {
|
|
28
29
|
entityKey: Hex
|
|
29
30
|
/** The entity's new lifetime, built with {@link ExpirationTime}. */
|
|
30
|
-
|
|
31
|
+
expiration: Expiry
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -37,9 +38,9 @@ export type ExtendEntityReturnType = {
|
|
|
37
38
|
entityKey: Hex
|
|
38
39
|
txHash: Hash
|
|
39
40
|
/**
|
|
40
|
-
* The block the entity
|
|
41
|
-
*
|
|
42
|
-
*
|
|
41
|
+
* The block the entity now expires at, as the engine recorded it — read from the
|
|
42
|
+
* `ExpiryExtended` event, so a duration is already resolved against the block the transaction
|
|
43
|
+
* landed in.
|
|
43
44
|
*/
|
|
44
45
|
expiresAt: bigint
|
|
45
46
|
}
|
|
@@ -1,90 +1,81 @@
|
|
|
1
1
|
import type { Hash, Hex } from "viem"
|
|
2
|
-
import type { AttributeInputs } from "../../attr"
|
|
3
2
|
import type { ArkivClient } from "../../clients/baseClient"
|
|
4
|
-
import type {
|
|
3
|
+
import type { OptionalColumnName, PatchEntityValues, Schema } from "../../schema"
|
|
4
|
+
import type { TxParams } from "../../types"
|
|
5
5
|
import { sendArkivTransaction } from "../../utils/arkivTransactions"
|
|
6
6
|
import { getLogger } from "../../utils/logger"
|
|
7
7
|
|
|
8
8
|
const logger = getLogger("actions:wallet:patch-entity")
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* What a patch changes.
|
|
12
12
|
*
|
|
13
|
-
* A patch names only what changes.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* is changing a different field.
|
|
13
|
+
* A patch names only what changes. Columns it does not mention keep whatever value they have — so
|
|
14
|
+
* a caller can change one column without first reading the entity to learn what the others are,
|
|
15
|
+
* and without racing anyone else who is changing a different one.
|
|
17
16
|
*
|
|
18
|
-
*
|
|
19
|
-
* expiry. Use `extendEntity` for the expiry and `changeOwnership` for the owner.
|
|
20
|
-
*
|
|
21
|
-
* @example Change one attribute and remove another, leaving the payload alone:
|
|
22
|
-
* await client.patchEntity({
|
|
23
|
-
* entityKey,
|
|
24
|
-
* set: { status: "published", level: i32(11) },
|
|
25
|
-
* unset: ["draft"],
|
|
26
|
-
* })
|
|
17
|
+
* @typeParam S - The schema the changes are typed by.
|
|
27
18
|
*
|
|
28
|
-
* @example
|
|
29
|
-
*
|
|
30
|
-
* entityKey,
|
|
31
|
-
* payload: jsonToPayload({ hello: "again" }),
|
|
32
|
-
* })
|
|
19
|
+
* @example
|
|
20
|
+
* { set: { title: "Cells, not entities", score: dec("4.2") }, unset: ["thumbnail"] }
|
|
33
21
|
*/
|
|
34
|
-
export type
|
|
35
|
-
/** The entity to patch. */
|
|
36
|
-
entityKey: Hex
|
|
22
|
+
export type PatchEntityChanges<S extends Schema = Schema> = {
|
|
37
23
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
24
|
+
* Columns to write, keyed by name. Each is written with the indexed flag its column declares,
|
|
25
|
+
* so writing a column whose indexing the schema has since changed changes it on the entity too.
|
|
26
|
+
* Defaults do not apply: a patch writes only what it names.
|
|
40
27
|
*
|
|
41
|
-
*
|
|
42
|
-
* `dec`, `str`, `addr`, `key`, `bytes32`, `bool` — or a bare `boolean`, `number`, `bigint` or
|
|
43
|
-
* `string` where the type is unambiguous.
|
|
44
|
-
*
|
|
45
|
-
* @throws {InvalidAttributeNameError} If a name violates the attribute-name grammar.
|
|
46
|
-
* @throws {InvalidValueError} If a value does not fit the type it names or defaults to.
|
|
28
|
+
* @throws {SchemaMismatchError} If a name is not a column, or a value is not of its column's type.
|
|
47
29
|
*/
|
|
48
|
-
set?:
|
|
30
|
+
set?: PatchEntityValues<S> | undefined
|
|
49
31
|
/**
|
|
50
|
-
*
|
|
51
|
-
* is the same either way, and a caller clearing a
|
|
32
|
+
* Optional columns to remove. Naming one the entity does not carry is not an error — the outcome
|
|
33
|
+
* is the same either way, and a caller clearing a column should not have to know whether it was
|
|
52
34
|
* ever set.
|
|
53
35
|
*
|
|
54
36
|
* @throws {ConflictingMutationError} If a name also appears in `set`.
|
|
55
37
|
*/
|
|
56
|
-
unset?: readonly
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
38
|
+
unset?: readonly OptionalColumnName<S>[] | undefined
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* One patch, as a batch carries it: the schema, the entity, and the {@link PatchEntityChanges}.
|
|
43
|
+
*
|
|
44
|
+
* The entity's identity is fixed: a patch never moves the key, the owner, the creation flags or the
|
|
45
|
+
* expiry. Use `extendEntity` for the expiry and `changeOwnership` for the owner.
|
|
46
|
+
*/
|
|
47
|
+
export type PatchEntityParameters<S extends Schema = Schema> = PatchEntityChanges<S> & {
|
|
48
|
+
/** The schema the changes are typed by. */
|
|
49
|
+
schema: S
|
|
50
|
+
/** The entity to patch. */
|
|
51
|
+
entityKey: Hex
|
|
67
52
|
}
|
|
68
53
|
|
|
69
|
-
/** The result of
|
|
54
|
+
/** The result of a patch. */
|
|
70
55
|
export type PatchEntityReturnType = {
|
|
71
56
|
/** The patched entity's key — unchanged, since a patch never moves it. */
|
|
72
57
|
entityKey: Hex
|
|
73
58
|
txHash: Hash
|
|
74
59
|
}
|
|
75
60
|
|
|
76
|
-
export async function patchEntity(
|
|
61
|
+
export async function patchEntity<S extends Schema>(
|
|
77
62
|
client: ArkivClient,
|
|
78
|
-
|
|
63
|
+
schema: S,
|
|
64
|
+
entityKey: Hex,
|
|
65
|
+
changes: PatchEntityChanges<S>,
|
|
79
66
|
txParams?: TxParams,
|
|
80
67
|
): Promise<PatchEntityReturnType> {
|
|
81
|
-
logger("patchEntity %o",
|
|
82
|
-
const { receipt } = await sendArkivTransaction(
|
|
68
|
+
logger("patchEntity %s %o", entityKey, changes)
|
|
69
|
+
const { receipt } = await sendArkivTransaction(
|
|
70
|
+
client,
|
|
71
|
+
{ patches: [{ ...changes, schema, entityKey }] },
|
|
72
|
+
txParams,
|
|
73
|
+
)
|
|
83
74
|
|
|
84
75
|
logger("Receipt from patchEntity %o", receipt)
|
|
85
76
|
|
|
86
77
|
return {
|
|
87
78
|
txHash: receipt.transactionHash as Hash,
|
|
88
|
-
entityKey
|
|
79
|
+
entityKey,
|
|
89
80
|
}
|
|
90
81
|
}
|
|
@@ -22,8 +22,6 @@ test("creates", () => {
|
|
|
22
22
|
expect(typeof client.select).toBe("function")
|
|
23
23
|
expect(client.query).toBeDefined()
|
|
24
24
|
expect(typeof client.query).toBe("function")
|
|
25
|
-
expect(client.getEntityCount).toBeDefined()
|
|
26
|
-
expect(typeof client.getEntityCount).toBe("function")
|
|
27
25
|
expect(client.getBlockTiming).toBeDefined()
|
|
28
26
|
expect(typeof client.getBlockTiming).toBe("function")
|
|
29
27
|
expect(client.watchEntityEvents).toBeDefined()
|
|
@@ -37,3 +35,13 @@ test("creates", () => {
|
|
|
37
35
|
expect(client.changeOwnership).not.toBeDefined()
|
|
38
36
|
expect(client.executeBatch).not.toBeDefined()
|
|
39
37
|
})
|
|
38
|
+
|
|
39
|
+
test("select() rejects a $ name that is not an entity field, at compile time and at runtime", () => {
|
|
40
|
+
const client = createPublicClient({ transport: mockTransport })
|
|
41
|
+
// @ts-expect-error - $title is not an entity field; an attribute name cannot start with $
|
|
42
|
+
expect(() => client.select({ $title: true })).toThrow(/not an entity field/)
|
|
43
|
+
// @ts-expect-error - $attributes reads every attribute; select the ones you want by name
|
|
44
|
+
expect(() => client.select({ $attributes: { title: true } })).toThrow(/select each by name/)
|
|
45
|
+
// Without the $, `owner` is an attribute like any other, read alongside the entity field.
|
|
46
|
+
expect(() => client.select({ $owner: true, owner: true })).not.toThrow()
|
|
47
|
+
})
|
|
@@ -28,7 +28,10 @@ export type WalletArkivClient<
|
|
|
28
28
|
*
|
|
29
29
|
* A Wallet Client signs and sends transactions, so it carries an `account` and exposes everything
|
|
30
30
|
* that mutates state — `createEntity`, `patchEntity`, `deleteEntity`, `extendEntity`,
|
|
31
|
-
* `changeOwnership` and `executeBatch` —
|
|
31
|
+
* `changeOwnership` and `executeBatch` — alongside the viem actions those need, such as
|
|
32
|
+
* `getBlockNumber`, `readContract` and `waitForTransactionReceipt`. It does not have the Arkiv
|
|
33
|
+
* read actions (`getEntity`, `query`, `select`, `watchEntityEvents`, `predictEntityKeys`); read
|
|
34
|
+
* through a Public Client from `createPublicClient` for those.
|
|
32
35
|
*
|
|
33
36
|
* @param parameters - Configuration object for the wallet client (chain, transport, account, etc.)
|
|
34
37
|
* @returns An Arkiv Wallet Client. {@link WalletArkivClient}
|