@arkiv-network/sdk 0.6.8-dev.5 → 0.7.0-dev.1
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/dist/chains/index.d.ts +25 -25
- package/dist/consts-Bz0LZQZY.js +7 -0
- package/dist/consts-Bz0LZQZY.js.map +1 -0
- package/dist/{consts-AUoL-v42.cjs → consts-CxT1-RXU.cjs} +1 -1
- package/dist/entity-BhSXaB23.cjs +81 -0
- package/dist/{entity-B6g_EPm1.d.cts → entity-CN41IOrq.d.cts} +53 -34
- package/dist/entity-CN41IOrq.d.cts.map +1 -0
- package/dist/entity-DGfVyB-a.js +71 -0
- package/dist/entity-DGfVyB-a.js.map +1 -0
- package/dist/{entity-qoLMAuuk.d.ts → entity-NnEVlePN.d.ts} +53 -34
- package/dist/entity-NnEVlePN.d.ts.map +1 -0
- package/dist/{index-DdspxNAA.d.ts → index-ClkpS6s_.d.ts} +2 -2
- package/dist/{index-DdspxNAA.d.ts.map → index-ClkpS6s_.d.ts.map} +1 -1
- package/dist/{index-Ds9dwqb5.d.cts → index-w4qop0Pe.d.cts} +2 -2
- package/dist/{index-Ds9dwqb5.d.cts.map → index-w4qop0Pe.d.cts.map} +1 -1
- package/dist/index.cjs +281 -264
- package/dist/index.d.cts +13 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +14 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +279 -266
- package/dist/index.js.map +1 -1
- package/dist/{payload-Claf2jUO.js → payload-BpNUPOnY.js} +3 -3
- package/dist/payload-BpNUPOnY.js.map +1 -0
- package/dist/{payload-B2kFJyah.d.ts → payload-C2KHOUoj.d.ts} +1 -1
- package/dist/{payload-B2kFJyah.d.ts.map → payload-C2KHOUoj.d.ts.map} +1 -1
- package/dist/{payload-DpA5PA-w.cjs → payload-D8YLKcJj.cjs} +1 -1
- package/dist/query/index.cjs +3 -2
- package/dist/query/index.d.cts +2 -2
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +3 -2
- package/dist/query/index.js.map +1 -1
- package/dist/{queryBuilder-D2uwP9jI.js → queryBuilder-BE_XhLLa.js} +98 -67
- package/dist/queryBuilder-BE_XhLLa.js.map +1 -0
- package/dist/{queryBuilder-UW2MCaX4.cjs → queryBuilder-J37buGCi.cjs} +137 -64
- package/dist/types/index.cjs +4 -1
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +4 -1
- package/dist/utils/index.cjs +2 -2
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +2 -2
- package/package.json +1 -1
- package/src/actions/public/subscribeEntityEvents.ts +25 -51
- package/src/actions/wallet/changeOwnership.ts +6 -6
- package/src/actions/wallet/createEntity.ts +7 -4
- package/src/actions/wallet/deleteEntity.ts +2 -3
- package/src/actions/wallet/extendEntity.ts +3 -4
- package/src/actions/wallet/mutateEntities.ts +11 -54
- package/src/actions/wallet/updateEntity.ts +3 -4
- package/src/clients/decorators/arkivWallet.ts +5 -1
- package/src/consts.ts +2 -1
- package/src/errors.ts +29 -1
- package/src/query/engine.test.ts +45 -0
- package/src/query/engine.ts +6 -1
- package/src/query/selectQueryBuilder.test.ts +5 -2
- package/src/types/attributes.ts +17 -5
- package/src/types/entity.ts +12 -2
- package/src/types/events.ts +14 -16
- package/src/types/index.ts +1 -0
- package/src/types/rpcSchema.ts +7 -2
- package/src/utils/arkivTransactions.test.ts +234 -36
- package/src/utils/arkivTransactions.ts +255 -101
- package/src/utils/entities.ts +17 -7
- package/src/utils/payload.ts +4 -2
- package/src/utils/validation.test.ts +52 -6
- package/src/utils/validation.ts +54 -6
- package/dist/consts-BV4PR-de.js +0 -7
- package/dist/consts-BV4PR-de.js.map +0 -1
- package/dist/entity-B6g_EPm1.d.cts.map +0 -1
- package/dist/entity-qoLMAuuk.d.ts.map +0 -1
- package/dist/payload-Claf2jUO.js.map +0 -1
- package/dist/queryBuilder-D2uwP9jI.js.map +0 -1
- /package/dist/{types-H-TSmRpm.js → types-Ci4BRClw.js} +0 -0
- /package/dist/{types-o3zWarRp.cjs → types-a8gm_IaQ.cjs} +0 -0
package/src/types/events.ts
CHANGED
|
@@ -1,40 +1,38 @@
|
|
|
1
|
-
import type { Hex } from "viem"
|
|
1
|
+
import type { Address, Hex } from "viem"
|
|
2
2
|
|
|
3
3
|
export type OnEntityCreatedEvent = {
|
|
4
4
|
entityKey: Hex
|
|
5
|
-
owner:
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
owner: Address
|
|
6
|
+
expiresAt: number
|
|
7
|
+
entityHash: Hex
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export type OnEntityUpdatedEvent = {
|
|
11
11
|
entityKey: Hex
|
|
12
|
-
owner:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
cost: bigint
|
|
12
|
+
owner: Address
|
|
13
|
+
expiresAt: number
|
|
14
|
+
entityHash: Hex
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
export type OnEntityDeletedEvent = {
|
|
19
18
|
entityKey: Hex
|
|
20
|
-
owner:
|
|
19
|
+
owner: Address
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
export type OnEntityExpiredEvent = {
|
|
24
23
|
entityKey: Hex
|
|
25
|
-
owner:
|
|
24
|
+
owner: Address
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
export type OnEntityExpiresInExtendedEvent = {
|
|
29
28
|
entityKey: Hex
|
|
30
|
-
owner:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
cost: bigint
|
|
29
|
+
owner: Address
|
|
30
|
+
expiresAt: number
|
|
31
|
+
entityHash: Hex
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
export type OnEntityOwnerChangedEvent = {
|
|
37
35
|
entityKey: Hex
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
owner: Address
|
|
37
|
+
entityHash: Hex
|
|
40
38
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export type {
|
|
|
19
19
|
export type { UpdateEntityParameters, UpdateEntityReturnType } from "../actions/wallet/updateEntity"
|
|
20
20
|
export type { Attribute } from "./attributes"
|
|
21
21
|
export type { Entity } from "./entity"
|
|
22
|
+
export { EntityOperationType } from "./entity"
|
|
22
23
|
export type {
|
|
23
24
|
OnEntityCreatedEvent,
|
|
24
25
|
OnEntityDeletedEvent,
|
package/src/types/rpcSchema.ts
CHANGED
|
@@ -12,8 +12,13 @@ export type RpcEntity = {
|
|
|
12
12
|
operationIndexInTransaction: Hex
|
|
13
13
|
owner: Hex
|
|
14
14
|
creator: Hex
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
attributes?: { key: string; value: string; valueType: RpcAttributeValueType }[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export enum RpcAttributeValueType {
|
|
19
|
+
Uint = 1,
|
|
20
|
+
String = 2,
|
|
21
|
+
EntityKey = 3,
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
export type RpcOrderByAttribute = {
|
|
@@ -1,6 +1,57 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test"
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { describe, expect, it, vi } from "bun:test"
|
|
2
|
+
import { stringToBytes, toBytes, toHex } from "viem"
|
|
3
|
+
import type { ArkivClient } from "../clients/baseClient"
|
|
4
|
+
import { DuplicateAttributeError, InvalidAttributeError, InvalidExpirationError } from "../errors"
|
|
5
|
+
import { sendArkivTransaction } from "./arkivTransactions"
|
|
6
|
+
|
|
7
|
+
const ZERO_32 = `0x${"00".repeat(32)}`
|
|
8
|
+
|
|
9
|
+
function encodeStringTo128(str: string): string[] {
|
|
10
|
+
const padded = new Uint8Array(128)
|
|
11
|
+
// stringToBytes, not toBytes — mirrors encodeAttribute: string values are always
|
|
12
|
+
// UTF-8 encoded, never hex-decoded, even when they look like hex
|
|
13
|
+
padded.set(stringToBytes(str).slice(0, 128))
|
|
14
|
+
return [
|
|
15
|
+
toHex(padded.slice(0, 32)),
|
|
16
|
+
toHex(padded.slice(32, 64)),
|
|
17
|
+
toHex(padded.slice(64, 96)),
|
|
18
|
+
toHex(padded.slice(96, 128)),
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function makeClient() {
|
|
23
|
+
const writeContract = vi.fn().mockResolvedValue("0xdeadbeef")
|
|
24
|
+
const waitForTransactionReceipt = vi.fn().mockResolvedValue({
|
|
25
|
+
status: "success",
|
|
26
|
+
transactionHash: "0xdeadbeef",
|
|
27
|
+
})
|
|
28
|
+
return {
|
|
29
|
+
client: {
|
|
30
|
+
account: { address: "0x1111111111111111111111111111111111111111" },
|
|
31
|
+
chain: { id: 1 },
|
|
32
|
+
getBlockNumber: vi.fn().mockResolvedValue(100n),
|
|
33
|
+
readContract: vi.fn().mockResolvedValue(0),
|
|
34
|
+
writeContract,
|
|
35
|
+
waitForTransactionReceipt,
|
|
36
|
+
} as unknown as ArkivClient,
|
|
37
|
+
writeContract,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const BASE_CREATE = {
|
|
42
|
+
payload: new Uint8Array([1, 2, 3]),
|
|
43
|
+
contentType: "application/octet-stream",
|
|
44
|
+
expiresIn: 3600,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function captureAttributes(attributes: { key: string; value: string | number }[]) {
|
|
48
|
+
const { client, writeContract } = makeClient()
|
|
49
|
+
await sendArkivTransaction(client, {
|
|
50
|
+
creates: [{ ...BASE_CREATE, attributes }],
|
|
51
|
+
})
|
|
52
|
+
const callArgs = writeContract.mock.calls[0][0]
|
|
53
|
+
return callArgs.args[0][0].attributes as { name: string; valueType: number; value: string[] }[]
|
|
54
|
+
}
|
|
4
55
|
|
|
5
56
|
const validCreate = {
|
|
6
57
|
payload: new Uint8Array([1, 2, 3]),
|
|
@@ -9,54 +60,201 @@ const validCreate = {
|
|
|
9
60
|
expiresIn: 1000,
|
|
10
61
|
}
|
|
11
62
|
|
|
12
|
-
describe("
|
|
13
|
-
it("
|
|
14
|
-
|
|
63
|
+
describe("sendArkivTransaction expiration validation", () => {
|
|
64
|
+
it("sends a transaction for a valid create", async () => {
|
|
65
|
+
const { client, writeContract } = makeClient()
|
|
66
|
+
const result = await sendArkivTransaction(client, { creates: [validCreate] })
|
|
67
|
+
expect(writeContract).toHaveBeenCalledTimes(1)
|
|
68
|
+
expect(result.receipt.status).toBe("success")
|
|
15
69
|
})
|
|
16
70
|
|
|
17
|
-
it("
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
71
|
+
it("rejects when a create expiresIn is not a multiple of the block time", async () => {
|
|
72
|
+
const { client } = makeClient()
|
|
73
|
+
await expect(
|
|
74
|
+
sendArkivTransaction(client, { creates: [{ ...validCreate, expiresIn: 51 }] }),
|
|
75
|
+
).rejects.toThrow(InvalidExpirationError)
|
|
21
76
|
})
|
|
22
77
|
|
|
23
|
-
it("
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
).toThrow(InvalidExpirationError)
|
|
78
|
+
it("rejects when a create expiresIn is not an integer", async () => {
|
|
79
|
+
const { client } = makeClient()
|
|
80
|
+
await expect(
|
|
81
|
+
sendArkivTransaction(client, { creates: [{ ...validCreate, expiresIn: 51.5 }] }),
|
|
82
|
+
).rejects.toThrow(InvalidExpirationError)
|
|
29
83
|
})
|
|
30
84
|
|
|
31
|
-
it("
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
85
|
+
it("rejects when an extension expiresIn is not a multiple of the block time", async () => {
|
|
86
|
+
const { client } = makeClient()
|
|
87
|
+
await expect(
|
|
88
|
+
sendArkivTransaction(client, { extensions: [{ entityKey: "0x123", expiresIn: 999 }] }),
|
|
89
|
+
).rejects.toThrow(InvalidExpirationError)
|
|
35
90
|
})
|
|
36
91
|
})
|
|
37
92
|
|
|
38
|
-
describe("
|
|
39
|
-
it("
|
|
40
|
-
|
|
41
|
-
|
|
93
|
+
describe("sendArkivTransaction attribute validation", () => {
|
|
94
|
+
it("rejects when a numeric attribute value is not an integer", async () => {
|
|
95
|
+
const { client } = makeClient()
|
|
96
|
+
await expect(
|
|
97
|
+
sendArkivTransaction(client, {
|
|
42
98
|
creates: [{ ...validCreate, attributes: [{ key: "n", value: 1.5 }] }],
|
|
43
99
|
}),
|
|
44
|
-
).toThrow(InvalidAttributeError)
|
|
100
|
+
).rejects.toThrow(InvalidAttributeError)
|
|
45
101
|
})
|
|
46
102
|
|
|
47
|
-
it("accepts integer numeric attribute values", () => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
).
|
|
103
|
+
it("accepts integer numeric attribute values", async () => {
|
|
104
|
+
const { client, writeContract } = makeClient()
|
|
105
|
+
await sendArkivTransaction(client, {
|
|
106
|
+
creates: [{ ...validCreate, attributes: [{ key: "n", value: 123 }] }],
|
|
107
|
+
})
|
|
108
|
+
expect(writeContract).toHaveBeenCalledTimes(1)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it("accepts string attribute values that look like non-integers", async () => {
|
|
112
|
+
const { client, writeContract } = makeClient()
|
|
113
|
+
await sendArkivTransaction(client, {
|
|
114
|
+
creates: [{ ...validCreate, attributes: [{ key: "n", value: "1.5" }] }],
|
|
115
|
+
})
|
|
116
|
+
expect(writeContract).toHaveBeenCalledTimes(1)
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe("encodeAttribute", () => {
|
|
121
|
+
it("encodes a plain string as STRING (valueType 2) left-aligned across 128 bytes", async () => {
|
|
122
|
+
const [attr] = await captureAttributes([{ key: "name", value: "hello world" }])
|
|
123
|
+
|
|
124
|
+
expect(attr.name).toBe(toHex("name", { size: 32 }))
|
|
125
|
+
expect(attr.valueType).toBe(2)
|
|
126
|
+
expect(attr.value).toEqual(encodeStringTo128("hello world"))
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it("encodes an empty string as STRING with all-zero value", async () => {
|
|
130
|
+
const [attr] = await captureAttributes([{ key: "tag", value: "" }])
|
|
131
|
+
|
|
132
|
+
expect(attr.valueType).toBe(2)
|
|
133
|
+
expect(attr.value).toEqual([ZERO_32, ZERO_32, ZERO_32, ZERO_32])
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it("encodes a hex string as ENTITY_KEY (valueType 3) in first slot, rest zero", async () => {
|
|
137
|
+
const entityKey = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
|
|
138
|
+
const [attr] = await captureAttributes([{ key: "ref", value: entityKey }])
|
|
139
|
+
|
|
140
|
+
expect(attr.name).toBe(toHex("ref", { size: 32 }))
|
|
141
|
+
expect(attr.valueType).toBe(3)
|
|
142
|
+
expect(attr.value).toEqual([entityKey, ZERO_32, ZERO_32, ZERO_32])
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it("treats hex strings that are not 32 bytes as STRING, not ENTITY_KEY", async () => {
|
|
146
|
+
// only exact entity keys get the hex type — everything else must round-trip
|
|
147
|
+
// as a string and stay matchable by quoted string queries
|
|
148
|
+
for (const value of ["0x", "0xab", "0x6186b0dba9652262942d5a465d49686eb560834c"]) {
|
|
149
|
+
const [attr] = await captureAttributes([{ key: "x", value }])
|
|
150
|
+
expect(attr.valueType).toBe(2)
|
|
151
|
+
expect(attr.value).toEqual(encodeStringTo128(value))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// the encoded bytes are the UTF-8 text, not a hex-decode: "0xab" is the
|
|
155
|
+
// four characters 0, x, a, b
|
|
156
|
+
const [attr] = await captureAttributes([{ key: "x", value: "0xab" }])
|
|
157
|
+
expect(attr.value[0].startsWith("0x30786162")).toBe(true)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it("encodes a number as UINT (valueType 1) big-endian in first slot, rest zero", async () => {
|
|
161
|
+
const [attr] = await captureAttributes([{ key: "count", value: 42 }])
|
|
162
|
+
|
|
163
|
+
expect(attr.name).toBe(toHex("count", { size: 32 }))
|
|
164
|
+
expect(attr.valueType).toBe(1)
|
|
165
|
+
expect(attr.value).toEqual([toHex(42n, { size: 32 }), ZERO_32, ZERO_32, ZERO_32])
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it("encodes zero as UINT", async () => {
|
|
169
|
+
const [attr] = await captureAttributes([{ key: "n", value: 0 }])
|
|
170
|
+
|
|
171
|
+
expect(attr.valueType).toBe(1)
|
|
172
|
+
expect(attr.value).toEqual([ZERO_32, ZERO_32, ZERO_32, ZERO_32])
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it("encodes a large number as UINT", async () => {
|
|
176
|
+
const large = Number.MAX_SAFE_INTEGER
|
|
177
|
+
const [attr] = await captureAttributes([{ key: "big", value: large }])
|
|
178
|
+
|
|
179
|
+
expect(attr.valueType).toBe(1)
|
|
180
|
+
expect(attr.value[0]).toBe(toHex(BigInt(large), { size: 32 }))
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it("encodes multiple mixed-type attributes preserving order", async () => {
|
|
184
|
+
const entityKey = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
|
|
185
|
+
const attrs = await captureAttributes([
|
|
186
|
+
{ key: "count", value: 7 },
|
|
187
|
+
{ key: "label", value: "test" },
|
|
188
|
+
{ key: "ref", value: entityKey },
|
|
189
|
+
])
|
|
190
|
+
|
|
191
|
+
expect(attrs).toHaveLength(3)
|
|
192
|
+
expect(attrs[0]).toMatchObject({ valueType: 1, name: toHex("count", { size: 32 }) })
|
|
193
|
+
expect(attrs[1]).toMatchObject({ valueType: 2, name: toHex("label", { size: 32 }) })
|
|
194
|
+
expect(attrs[2]).toMatchObject({ valueType: 3, name: toHex("ref", { size: 32 }) })
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it("encodes a UTF-8 string longer than 32 bytes spanning multiple chunks", async () => {
|
|
198
|
+
const long = "a".repeat(40)
|
|
199
|
+
const [attr] = await captureAttributes([{ key: "body", value: long }])
|
|
200
|
+
|
|
201
|
+
expect(attr.valueType).toBe(2)
|
|
202
|
+
const [chunk0, chunk1] = attr.value
|
|
203
|
+
expect(chunk0).toBe(toHex(new Uint8Array(32).fill(0x61))) // 32 × 'a'
|
|
204
|
+
expect(chunk1.startsWith("0x6161616161616161")).toBe(true) // next 8 × 'a', then zeros
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe("attribute sorting", () => {
|
|
209
|
+
it("sorts attributes ascending by their bytes32 name before encoding", async () => {
|
|
210
|
+
const attrs = await captureAttributes([
|
|
211
|
+
{ key: "tag", value: "zz" },
|
|
212
|
+
{ key: "status", value: "active" },
|
|
213
|
+
{ key: "score", value: 30 },
|
|
214
|
+
])
|
|
215
|
+
|
|
216
|
+
expect(attrs.map((a) => a.name)).toEqual([
|
|
217
|
+
toHex("score", { size: 32 }),
|
|
218
|
+
toHex("status", { size: 32 }),
|
|
219
|
+
toHex("tag", { size: 32 }),
|
|
220
|
+
])
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it("sorts a shorter key before a longer key sharing its prefix", async () => {
|
|
224
|
+
const attrs = await captureAttributes([
|
|
225
|
+
{ key: "abc", value: 1 },
|
|
226
|
+
{ key: "ab", value: 2 },
|
|
227
|
+
])
|
|
228
|
+
|
|
229
|
+
expect(attrs.map((a) => a.name)).toEqual([
|
|
230
|
+
toHex("ab", { size: 32 }),
|
|
231
|
+
toHex("abc", { size: 32 }),
|
|
232
|
+
])
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it("does not mutate the caller's attributes array", async () => {
|
|
236
|
+
const input = [
|
|
237
|
+
{ key: "b", value: 1 },
|
|
238
|
+
{ key: "a", value: 2 },
|
|
239
|
+
]
|
|
240
|
+
await captureAttributes(input)
|
|
241
|
+
expect(input.map((a) => a.key)).toEqual(["b", "a"])
|
|
53
242
|
})
|
|
54
243
|
|
|
55
|
-
it("
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
244
|
+
it("rejects duplicate attribute keys", async () => {
|
|
245
|
+
const { client } = makeClient()
|
|
246
|
+
await expect(
|
|
247
|
+
sendArkivTransaction(client, {
|
|
248
|
+
creates: [
|
|
249
|
+
{
|
|
250
|
+
...BASE_CREATE,
|
|
251
|
+
attributes: [
|
|
252
|
+
{ key: "tag", value: "one" },
|
|
253
|
+
{ key: "tag", value: "two" },
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
],
|
|
59
257
|
}),
|
|
60
|
-
).
|
|
258
|
+
).rejects.toThrow(DuplicateAttributeError)
|
|
61
259
|
})
|
|
62
260
|
})
|