@arkiv-network/sdk 0.7.0-dev.1 → 0.8.0-advanced.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 +71 -16
- package/dist/actions/advanced/index.cjs +13 -0
- package/dist/actions/advanced/index.d.cts +8 -0
- package/dist/actions/advanced/index.d.ts +9 -0
- package/dist/actions/advanced/index.js +9 -0
- package/dist/advanced-8ZPTmmaN.js +598 -0
- package/dist/advanced-8ZPTmmaN.js.map +1 -0
- package/dist/advanced-fPImL6lW.cjs +656 -0
- package/dist/arkivTransactions-D74gERHG.d.cts +571 -0
- package/dist/arkivTransactions-D74gERHG.d.cts.map +1 -0
- package/dist/arkivTransactions-NtGD6uk5.d.ts +571 -0
- package/dist/arkivTransactions-NtGD6uk5.d.ts.map +1 -0
- package/dist/attr/index.cjs +35 -0
- package/dist/attr/index.d.cts +2 -0
- package/dist/attr/index.d.ts +3 -0
- package/dist/attr/index.js +4 -0
- package/dist/attributes-BhY5DwJp.js +809 -0
- package/dist/attributes-BhY5DwJp.js.map +1 -0
- package/dist/attributes-Df9IsCVs.cjs +1029 -0
- package/dist/chains/index.cjs +2 -3
- package/dist/chains/index.d.cts +22 -60
- package/dist/chains/index.d.cts.map +1 -1
- package/dist/chains/index.d.ts +22 -60
- package/dist/chains/index.d.ts.map +1 -1
- package/dist/chains/index.js +2 -2
- package/dist/chains-BJrZs6sG.js +57 -0
- package/dist/chains-BJrZs6sG.js.map +1 -0
- package/dist/chains-BYRTTipa.cjs +73 -0
- package/dist/codec-Car3KkFF.d.ts +102 -0
- package/dist/codec-Car3KkFF.d.ts.map +1 -0
- package/dist/entity/index.cjs +20 -0
- package/dist/entity/index.d.cts +4 -0
- package/dist/entity/index.d.ts +5 -0
- package/dist/entity/index.js +7 -0
- package/dist/entity-B00Li04j.cjs +71 -0
- package/dist/entity-B7G8q_Eo.cjs +0 -0
- package/dist/entity-BYQR1uoe.js +67 -0
- package/dist/entity-BYQR1uoe.js.map +1 -0
- package/dist/entity-CD_qFF2M.js +0 -0
- package/dist/errors-BVbjKVMN.cjs +56 -0
- package/dist/errors-LTe7bOYS.js +33 -0
- package/dist/errors-LTe7bOYS.js.map +1 -0
- package/dist/expiry-BELY2Kpw.cjs +135 -0
- package/dist/expiry-BjpxZZQU.js +95 -0
- package/dist/expiry-BjpxZZQU.js.map +1 -0
- package/dist/expiry-DRV29F1r.d.cts +79 -0
- package/dist/expiry-DRV29F1r.d.cts.map +1 -0
- package/dist/expiry-nDbCgZX4.d.ts +79 -0
- package/dist/expiry-nDbCgZX4.d.ts.map +1 -0
- package/dist/flags-5dTNRnow.d.ts +41 -0
- package/dist/flags-5dTNRnow.d.ts.map +1 -0
- package/dist/flags-CRjPpoh3.d.cts +41 -0
- package/dist/flags-CRjPpoh3.d.cts.map +1 -0
- package/dist/flags-DFBs2J94.js +33 -0
- package/dist/flags-DFBs2J94.js.map +1 -0
- package/dist/flags-kr276q6i.cjs +43 -0
- package/dist/index-B5j76711.d.ts +184 -0
- package/dist/index-B5j76711.d.ts.map +1 -0
- package/dist/index-BF7Mq9Is.d.cts +280 -0
- package/dist/index-BF7Mq9Is.d.cts.map +1 -0
- package/dist/index-CA4V7Pdh.d.cts +167 -0
- package/dist/index-CA4V7Pdh.d.cts.map +1 -0
- package/dist/index-CV5zCdpa.d.cts +417 -0
- package/dist/index-CV5zCdpa.d.cts.map +1 -0
- package/dist/index-DHDud2hC.d.ts +280 -0
- package/dist/index-DHDud2hC.d.ts.map +1 -0
- package/dist/index-DvYvrkdk.d.ts +320 -0
- package/dist/index-DvYvrkdk.d.ts.map +1 -0
- package/dist/index-EeUO0nnJ.d.cts +184 -0
- package/dist/index-EeUO0nnJ.d.cts.map +1 -0
- package/dist/index-fX_XyqLp.d.ts +167 -0
- package/dist/index-fX_XyqLp.d.ts.map +1 -0
- package/dist/index.cjs +411 -405
- package/dist/index.d.cts +360 -209
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +361 -209
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +342 -387
- package/dist/index.js.map +1 -1
- package/dist/key-D1Bh7MF0.js +70 -0
- package/dist/key-D1Bh7MF0.js.map +1 -0
- package/dist/key-WKhXnXuU.cjs +98 -0
- package/dist/logger-CVpvU8ik.js +153 -0
- package/dist/logger-CVpvU8ik.js.map +1 -0
- package/dist/logger-Cr15lPAQ.cjs +188 -0
- package/dist/payload-BNoud2KZ.d.cts +94 -0
- package/dist/payload-BNoud2KZ.d.cts.map +1 -0
- package/dist/payload-Bz6651ax.js +67 -0
- package/dist/payload-Bz6651ax.js.map +1 -0
- package/dist/payload-CcUj85ys.d.ts +94 -0
- package/dist/payload-CcUj85ys.d.ts.map +1 -0
- package/dist/payload-KC14SDlN.cjs +83 -0
- package/dist/query/index.cjs +26 -150
- package/dist/query/index.d.cts +5 -3
- package/dist/query/index.d.ts +6 -3
- package/dist/query/index.js +8 -138
- package/dist/queryBuilder-DM6AWCBa.js +820 -0
- package/dist/queryBuilder-DM6AWCBa.js.map +1 -0
- package/dist/queryBuilder-DbUMEZln.cjs +944 -0
- package/dist/selection-C4UhsMOO.d.ts +434 -0
- package/dist/selection-C4UhsMOO.d.ts.map +1 -0
- package/dist/selection-DPg0uyJf.d.cts +433 -0
- package/dist/selection-DPg0uyJf.d.cts.map +1 -0
- package/dist/types/index.cjs +3 -3
- package/dist/types/index.d.cts +7 -2
- package/dist/types/index.d.ts +8 -2
- package/dist/types/index.js +3 -3
- package/dist/types-Bu_ZwMbZ.js +0 -0
- package/dist/types-NRqymi6S.cjs +0 -0
- package/dist/utils/index.cjs +4 -17
- package/dist/utils/index.d.cts +3 -17
- package/dist/utils/index.d.ts +3 -17
- package/dist/utils/index.js +4 -18
- package/package.json +26 -5
- package/src/actions/advanced/advanced.test.ts +258 -0
- package/src/actions/advanced/buildMutation.ts +104 -0
- package/src/actions/advanced/getMutationResult.ts +112 -0
- package/src/actions/advanced/index.ts +21 -0
- package/src/actions/advanced/pingTransaction.ts +43 -0
- package/src/actions/advanced/sendMutation.ts +59 -0
- package/src/actions/public/getEntity.ts +27 -43
- package/src/actions/public/getEntityNonce.ts +30 -0
- package/src/actions/public/predictEntityKeys.test.ts +143 -0
- package/src/actions/public/predictEntityKeys.ts +138 -0
- package/src/actions/public/query.ts +35 -67
- package/src/actions/public/watchEntityEvents.test.ts +414 -0
- package/src/actions/public/watchEntityEvents.ts +225 -0
- package/src/actions/wallet/changeOwnership.ts +2 -2
- package/src/actions/wallet/createEntity.ts +99 -24
- package/src/actions/wallet/extendEntity.ts +37 -13
- package/src/actions/wallet/mutateEntities.test.ts +76 -0
- package/src/actions/wallet/mutateEntities.ts +16 -33
- package/src/actions/wallet/patchEntity.ts +90 -0
- package/src/attr/attributes.test.ts +120 -0
- package/src/attr/attributes.ts +190 -0
- package/src/attr/codec.test.ts +162 -0
- package/src/attr/codec.ts +220 -0
- package/src/attr/decimal.ts +86 -0
- package/src/attr/errors.ts +145 -0
- package/src/attr/index.ts +50 -0
- package/src/attr/names.ts +106 -0
- package/src/attr/types.ts +150 -0
- package/src/attr/values.test.ts +271 -0
- package/src/attr/values.ts +502 -0
- package/src/chains/cheesecake.ts +22 -0
- package/src/chains/index.ts +1 -2
- package/src/clients/createPublicClient.test.ts +3 -5
- package/src/clients/createPublicClient.ts +5 -4
- package/src/clients/createWalletClient.test.ts +2 -2
- package/src/clients/createWalletClient.ts +13 -7
- package/src/clients/decorators/arkivAdvanced.ts +144 -0
- package/src/clients/decorators/arkivPublic.ts +172 -139
- package/src/clients/decorators/arkivWallet.ts +101 -94
- package/src/entity/create.test.ts +184 -0
- package/src/entity/errors.ts +38 -0
- package/src/entity/events.ts +16 -0
- package/src/entity/expiry.test.ts +195 -0
- package/src/entity/expiry.ts +181 -0
- package/src/entity/flags.ts +65 -0
- package/src/entity/index.ts +15 -0
- package/src/entity/key.ts +68 -0
- package/src/entity/operations.ts +134 -0
- package/src/entity/params.ts +15 -0
- package/src/errors.ts +26 -49
- package/src/index.ts +19 -0
- package/src/query/engine.ts +68 -137
- package/src/query/errors.ts +151 -0
- package/src/query/expression.test.ts +286 -0
- package/src/query/expression.ts +493 -0
- package/src/query/index.ts +40 -4
- package/src/query/injection.test.ts +151 -0
- package/src/query/queryBuilder.test.ts +342 -637
- package/src/query/queryBuilder.ts +133 -359
- package/src/query/queryResult.ts +54 -51
- package/src/query/selection.test.ts +48 -98
- package/src/query/selection.ts +77 -65
- package/src/types/entity.test.ts +44 -155
- package/src/types/entity.ts +60 -52
- package/src/types/events.ts +59 -16
- package/src/types/index.ts +20 -17
- package/src/types/rpcSchema.ts +69 -37
- package/src/utils/arkivTransactions.test.ts +600 -155
- package/src/utils/arkivTransactions.ts +350 -219
- package/src/utils/chains.test.ts +21 -24
- package/src/utils/compression.ts +1 -1
- package/src/utils/entities.test.ts +62 -0
- package/src/utils/entities.ts +109 -37
- package/src/utils/expirationTime.ts +114 -10
- package/src/utils/logger.ts +2 -2
- package/src/utils/revert.test.ts +99 -0
- package/src/utils/revert.ts +145 -0
- package/dist/chains-BifaY04V.cjs +0 -108
- package/dist/chains-CrtkVbBh.js +0 -86
- package/dist/chains-CrtkVbBh.js.map +0 -1
- package/dist/consts-Bz0LZQZY.js +0 -7
- package/dist/consts-Bz0LZQZY.js.map +0 -1
- package/dist/consts-CxT1-RXU.cjs +0 -18
- package/dist/entity-BhSXaB23.cjs +0 -81
- package/dist/entity-CN41IOrq.d.cts +0 -401
- package/dist/entity-CN41IOrq.d.cts.map +0 -1
- package/dist/entity-DGfVyB-a.js +0 -71
- package/dist/entity-DGfVyB-a.js.map +0 -1
- package/dist/entity-NnEVlePN.d.ts +0 -401
- package/dist/entity-NnEVlePN.d.ts.map +0 -1
- package/dist/index-ClkpS6s_.d.ts +0 -481
- package/dist/index-ClkpS6s_.d.ts.map +0 -1
- package/dist/index-w4qop0Pe.d.cts +0 -481
- package/dist/index-w4qop0Pe.d.cts.map +0 -1
- package/dist/payload-BpNUPOnY.js +0 -13
- package/dist/payload-BpNUPOnY.js.map +0 -1
- package/dist/payload-C2KHOUoj.d.ts +0 -6
- package/dist/payload-C2KHOUoj.d.ts.map +0 -1
- package/dist/payload-CJN8fTvX.d.cts +0 -6
- package/dist/payload-CJN8fTvX.d.cts.map +0 -1
- package/dist/payload-D8YLKcJj.cjs +0 -23
- package/dist/query/index.js.map +0 -1
- package/dist/queryBuilder-BE_XhLLa.js +0 -735
- package/dist/queryBuilder-BE_XhLLa.js.map +0 -1
- package/dist/queryBuilder-J37buGCi.cjs +0 -865
- package/dist/utils/index.d.cts.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/src/actions/public/buildQuery.ts +0 -10
- package/src/actions/public/subscribeEntityEvents.ts +0 -93
- package/src/actions/wallet/updateEntity.ts +0 -59
- package/src/chains/braga.ts +0 -26
- package/src/chains/kaolin.ts +0 -29
- package/src/query/engine.test.ts +0 -571
- package/src/query/predicate.test.ts +0 -461
- package/src/query/predicate.ts +0 -144
- package/src/query/queryResult.test.ts +0 -270
- package/src/query/selectQueryBuilder.test.ts +0 -213
- package/src/types/attributes.ts +0 -28
- package/src/utils/validation.test.ts +0 -106
- package/src/utils/validation.ts +0 -80
- /package/dist/{types-Ci4BRClw.js → attr-DlmErkvR.cjs} +0 -0
- /package/dist/{types-a8gm_IaQ.cjs → attr-GcQKatub.js} +0 -0
|
@@ -1,685 +1,390 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describe, expect, it, vi } from "bun:test"
|
|
2
|
+
import type { Hex } from "viem"
|
|
3
|
+
import { type Attributes, i32 } from "../attr"
|
|
2
4
|
import type { ArkivClient } from "../clients/baseClient"
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
import { NoMoreResultsError } from "../errors"
|
|
6
|
+
import type { RpcEntity } from "../types/rpcSchema"
|
|
7
|
+
import { MAX_LIMIT } from "./engine"
|
|
8
|
+
import { InvalidPredicateError, QueryError } from "./errors"
|
|
9
|
+
import { eq, gte, or } from "./expression"
|
|
10
|
+
import { SelectQueryBuilder } from "./queryBuilder"
|
|
11
|
+
import type { ProjectedEntity } from "./selection"
|
|
12
|
+
|
|
13
|
+
const ACCOUNT = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
|
|
14
|
+
const KEY = `0x${"ab".repeat(32)}` as const
|
|
15
|
+
|
|
16
|
+
type Page = { data: RpcEntity[]; blockNumber: string; cursor?: string }
|
|
17
|
+
|
|
18
|
+
/** A client whose `arkiv_query` serves the given pages in order. */
|
|
19
|
+
function makeClient(...pages: Page[]) {
|
|
20
|
+
const served = pages.length > 0 ? pages : [{ data: [], blockNumber: "0x1" }]
|
|
21
|
+
let index = 0
|
|
22
|
+
const request = vi.fn(async () => served[Math.min(index++, served.length - 1)])
|
|
23
|
+
return { client: { request } as unknown as ArkivClient, request }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The `[queryString, options]` of the nth `arkiv_query` call. */
|
|
27
|
+
function sent(request: ReturnType<typeof makeClient>["request"], call = 0) {
|
|
28
|
+
const params = request.mock.calls[call][0] as unknown as {
|
|
29
|
+
method: string
|
|
30
|
+
params: [string, Record<string, unknown>]
|
|
31
|
+
}
|
|
32
|
+
return { method: params.method, query: params.params[0], options: params.params[1] }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("the query it sends", () => {
|
|
36
|
+
it("ANDs everything across repeated where() calls", async () => {
|
|
37
|
+
const { client, request } = makeClient()
|
|
38
|
+
await new SelectQueryBuilder(client, { key: true })
|
|
39
|
+
.where(gte("level", i32(10)))
|
|
40
|
+
.where(eq("category", "docs"), or(eq("status", "open"), eq("status", "review")))
|
|
41
|
+
.fetch()
|
|
42
|
+
|
|
43
|
+
expect(sent(request).method).toBe("arkiv_query")
|
|
44
|
+
expect(sent(request).query).toBe(
|
|
45
|
+
"level >= i32(10) AND category = str('docs') AND " +
|
|
46
|
+
"(status = str('open') OR status = str('review'))",
|
|
47
|
+
)
|
|
23
48
|
})
|
|
24
49
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
50
|
+
it("takes an array of expressions", async () => {
|
|
51
|
+
const { client, request } = makeClient()
|
|
52
|
+
await new SelectQueryBuilder(client, { key: true }).where([eq("a", 1), eq("b", 2)]).fetch()
|
|
53
|
+
expect(sent(request).query).toBe("a = i32(1) AND b = i32(2)")
|
|
28
54
|
})
|
|
29
55
|
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
56
|
+
it("turns ownedBy and createdBy into typed system predicates", async () => {
|
|
57
|
+
const { client, request } = makeClient()
|
|
58
|
+
await new SelectQueryBuilder(client, { key: true })
|
|
59
|
+
.where(eq("category", "docs"))
|
|
60
|
+
.ownedBy(ACCOUNT)
|
|
61
|
+
.createdBy(ACCOUNT)
|
|
62
|
+
.fetch()
|
|
63
|
+
|
|
64
|
+
expect(sent(request).query).toBe(
|
|
65
|
+
`category = str('docs') AND $owner = addr(${ACCOUNT}) AND $creator = addr(${ACCOUNT})`,
|
|
66
|
+
)
|
|
33
67
|
})
|
|
34
68
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const builder = new QueryBuilder(mockClient)
|
|
42
|
-
await builder.where(eq("name", "test")).fetch()
|
|
43
|
-
|
|
44
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
45
|
-
predicates: [eq("name", "test")],
|
|
46
|
-
limit: undefined,
|
|
47
|
-
offset: undefined,
|
|
48
|
-
ownedBy: undefined,
|
|
49
|
-
createdBy: undefined,
|
|
50
|
-
orderBy: undefined,
|
|
51
|
-
validBeforeBlock: undefined,
|
|
52
|
-
withAttributes: undefined,
|
|
53
|
-
withMetadata: undefined,
|
|
54
|
-
withPayload: undefined,
|
|
55
|
-
})
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test("adds multiple predicates as array", async () => {
|
|
59
|
-
mockProcessQuery.mockResolvedValue({
|
|
60
|
-
data: [],
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
const builder = new QueryBuilder(mockClient)
|
|
64
|
-
const predicates = [eq("name", "test"), gt("age", 18)]
|
|
65
|
-
await builder.where(predicates).fetch()
|
|
66
|
-
|
|
67
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
68
|
-
predicates: predicates,
|
|
69
|
-
limit: undefined,
|
|
70
|
-
offset: undefined,
|
|
71
|
-
ownedBy: undefined,
|
|
72
|
-
createdBy: undefined,
|
|
73
|
-
orderBy: undefined,
|
|
74
|
-
validBeforeBlock: undefined,
|
|
75
|
-
withAttributes: undefined,
|
|
76
|
-
withMetadata: undefined,
|
|
77
|
-
withPayload: undefined,
|
|
78
|
-
})
|
|
79
|
-
})
|
|
69
|
+
it("replaces rather than ANDs a second ownedBy — two owners would match nothing", async () => {
|
|
70
|
+
const other = "0x1111111111111111111111111111111111111111"
|
|
71
|
+
const { client, request } = makeClient()
|
|
72
|
+
await new SelectQueryBuilder(client, { key: true }).ownedBy(ACCOUNT).ownedBy(other).fetch()
|
|
73
|
+
expect(sent(request).query).toBe(`$owner = addr(${other})`)
|
|
74
|
+
})
|
|
80
75
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
await builder.where(eq("name", "test"), gt("age", 18), neq("status", "inactive")).fetch()
|
|
88
|
-
|
|
89
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
90
|
-
predicates: [eq("name", "test"), gt("age", 18), neq("status", "inactive")],
|
|
91
|
-
limit: undefined,
|
|
92
|
-
offset: undefined,
|
|
93
|
-
ownedBy: undefined,
|
|
94
|
-
createdBy: undefined,
|
|
95
|
-
orderBy: undefined,
|
|
96
|
-
validBeforeBlock: undefined,
|
|
97
|
-
withAttributes: undefined,
|
|
98
|
-
withMetadata: undefined,
|
|
99
|
-
withPayload: undefined,
|
|
100
|
-
})
|
|
101
|
-
})
|
|
76
|
+
it("is readable without running anything", () => {
|
|
77
|
+
const { client } = makeClient()
|
|
78
|
+
const builder = new SelectQueryBuilder(client, { key: true }).where(eq("a", 1))
|
|
79
|
+
expect(builder.toString()).toBe("a = i32(1)")
|
|
80
|
+
expect(`${builder}`).toBe("a = i32(1)")
|
|
81
|
+
})
|
|
102
82
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
83
|
+
it("refuses a query with no filter, which the language cannot spell", async () => {
|
|
84
|
+
const { client, request } = makeClient()
|
|
85
|
+
const builder = new SelectQueryBuilder(client, { key: true })
|
|
86
|
+
expect(() => builder.toString()).toThrow(InvalidPredicateError)
|
|
87
|
+
await expect(builder.fetch()).rejects.toThrow(/at least one filter/)
|
|
88
|
+
expect(request).not.toHaveBeenCalled()
|
|
89
|
+
})
|
|
90
|
+
})
|
|
107
91
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
.where([neq("status", "inactive"), eq("verified", 1)])
|
|
113
|
-
.fetch()
|
|
92
|
+
describe("the options it sends", () => {
|
|
93
|
+
it("sends the resolved selection, and nothing else by default", async () => {
|
|
94
|
+
const { client, request } = makeClient()
|
|
95
|
+
await new SelectQueryBuilder(client, { key: true, attributes: true }).where(eq("a", 1)).fetch()
|
|
114
96
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
eq("verified", 1),
|
|
121
|
-
],
|
|
122
|
-
limit: undefined,
|
|
123
|
-
offset: undefined,
|
|
124
|
-
ownedBy: undefined,
|
|
125
|
-
createdBy: undefined,
|
|
126
|
-
orderBy: undefined,
|
|
127
|
-
validBeforeBlock: undefined,
|
|
128
|
-
withAttributes: undefined,
|
|
129
|
-
withMetadata: undefined,
|
|
130
|
-
withPayload: undefined,
|
|
131
|
-
})
|
|
132
|
-
})
|
|
97
|
+
const { options } = sent(request)
|
|
98
|
+
expect(options).toEqual({ select: expect.objectContaining({ key: true, attributes: true }) })
|
|
99
|
+
// No limit, cursor or atBlock unless asked for — the node picks its own page size.
|
|
100
|
+
expect(Object.keys(options).sort()).toEqual(["select"])
|
|
101
|
+
})
|
|
133
102
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
createdBy: undefined,
|
|
149
|
-
orderBy: undefined,
|
|
150
|
-
validBeforeBlock: undefined,
|
|
151
|
-
withAttributes: undefined,
|
|
152
|
-
withMetadata: undefined,
|
|
153
|
-
withPayload: undefined,
|
|
154
|
-
})
|
|
103
|
+
it("sends limit, cursor and atBlock as the RPC spells them", async () => {
|
|
104
|
+
const { client, request } = makeClient()
|
|
105
|
+
await new SelectQueryBuilder(client, { key: true })
|
|
106
|
+
.where(eq("a", 1))
|
|
107
|
+
.limit(100)
|
|
108
|
+
.cursor("b64:abc")
|
|
109
|
+
.atBlock(1_297_000n)
|
|
110
|
+
.fetch()
|
|
111
|
+
|
|
112
|
+
const { options } = sent(request)
|
|
113
|
+
expect(options).toMatchObject({
|
|
114
|
+
limit: "0x64",
|
|
115
|
+
cursor: "b64:abc",
|
|
116
|
+
atBlock: "0x13ca68",
|
|
155
117
|
})
|
|
118
|
+
})
|
|
156
119
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
170
|
-
predicates: [nestedPredicate],
|
|
171
|
-
limit: undefined,
|
|
172
|
-
offset: undefined,
|
|
173
|
-
ownedBy: undefined,
|
|
174
|
-
createdBy: undefined,
|
|
175
|
-
orderBy: undefined,
|
|
176
|
-
validBeforeBlock: undefined,
|
|
177
|
-
withAttributes: undefined,
|
|
178
|
-
withMetadata: undefined,
|
|
179
|
-
withPayload: undefined,
|
|
180
|
-
})
|
|
181
|
-
})
|
|
120
|
+
it("rejects a page size the node would reject", async () => {
|
|
121
|
+
const { client, request } = makeClient()
|
|
122
|
+
const query = () => new SelectQueryBuilder(client, { key: true }).where(eq("a", 1))
|
|
123
|
+
await expect(
|
|
124
|
+
query()
|
|
125
|
+
.limit(MAX_LIMIT + 1)
|
|
126
|
+
.fetch(),
|
|
127
|
+
).rejects.toThrow(/exceeds the node maximum/)
|
|
128
|
+
await expect(query().limit(0).fetch()).rejects.toThrow(/positive integer/)
|
|
129
|
+
await expect(query().limit(1.5).fetch()).rejects.toThrow(/positive integer/)
|
|
130
|
+
expect(query().limit(MAX_LIMIT)).toBeDefined()
|
|
131
|
+
expect(request).not.toHaveBeenCalled()
|
|
182
132
|
})
|
|
133
|
+
})
|
|
183
134
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
})
|
|
135
|
+
describe("decoding", () => {
|
|
136
|
+
it("builds entities from exactly the fields the row carries", async () => {
|
|
137
|
+
const { client } = makeClient({
|
|
138
|
+
blockNumber: "0x8e1ff",
|
|
139
|
+
data: [
|
|
140
|
+
{
|
|
141
|
+
key: KEY,
|
|
142
|
+
owner: ACCOUNT,
|
|
143
|
+
expiresAt: "0x92e21",
|
|
144
|
+
creationFlags: { readonly: false, permissionlessExtension: true, raw: 2 },
|
|
145
|
+
contentType: "application/json",
|
|
146
|
+
payload: "0x7b7d",
|
|
147
|
+
attributeSchema: [{ name: "version", type: "i32" }],
|
|
148
|
+
attributes: [
|
|
149
|
+
{ name: "projectId", type: "str", value: "alice-123" },
|
|
150
|
+
{ name: "version", type: "i32", value: 4 },
|
|
151
|
+
{ name: "balance", type: "u256", value: "0xf4240" },
|
|
152
|
+
{ name: "score", type: "dec", value: "3.5" },
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
],
|
|
206
156
|
})
|
|
207
157
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
data: [],
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
const builder = new QueryBuilder(mockClient)
|
|
214
|
-
const creator = "0x1234567890123456789012345678901234567890" as const
|
|
215
|
-
await builder.createdBy(creator).fetch()
|
|
216
|
-
|
|
217
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
218
|
-
predicates: [],
|
|
219
|
-
limit: undefined,
|
|
220
|
-
offset: undefined,
|
|
221
|
-
ownedBy: undefined,
|
|
222
|
-
createdBy: creator,
|
|
223
|
-
orderBy: undefined,
|
|
224
|
-
validBeforeBlock: undefined,
|
|
225
|
-
withAttributes: undefined,
|
|
226
|
-
withMetadata: undefined,
|
|
227
|
-
withPayload: undefined,
|
|
228
|
-
})
|
|
229
|
-
})
|
|
158
|
+
const page = await new SelectQueryBuilder(client, "*").where(eq("a", 1)).fetch()
|
|
159
|
+
const entity = page.entities[0]
|
|
230
160
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
await builder.orderBy("name", "string", "asc").fetch()
|
|
238
|
-
|
|
239
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
240
|
-
predicates: [],
|
|
241
|
-
limit: undefined,
|
|
242
|
-
offset: undefined,
|
|
243
|
-
ownedBy: undefined,
|
|
244
|
-
createdBy: undefined,
|
|
245
|
-
orderBy: [{ name: "name", type: "string", desc: false }],
|
|
246
|
-
validAtBlock: undefined,
|
|
247
|
-
withAttributes: undefined,
|
|
248
|
-
withMetadata: undefined,
|
|
249
|
-
withPayload: undefined,
|
|
250
|
-
})
|
|
161
|
+
expect(entity.key).toBe(KEY)
|
|
162
|
+
expect(entity.expiresAt).toBe(0x92e21n)
|
|
163
|
+
expect(entity.creationFlags).toEqual({
|
|
164
|
+
readonly: false,
|
|
165
|
+
permissionlessExtension: true,
|
|
166
|
+
raw: 2,
|
|
251
167
|
})
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const builder = new QueryBuilder(mockClient)
|
|
259
|
-
await builder.orderBy("name", "string", "desc").fetch()
|
|
260
|
-
|
|
261
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
262
|
-
predicates: [],
|
|
263
|
-
limit: undefined,
|
|
264
|
-
offset: undefined,
|
|
265
|
-
ownedBy: undefined,
|
|
266
|
-
createdBy: undefined,
|
|
267
|
-
orderBy: [{ name: "name", type: "string", desc: true }],
|
|
268
|
-
validAtBlock: undefined,
|
|
269
|
-
withAttributes: undefined,
|
|
270
|
-
withMetadata: undefined,
|
|
271
|
-
withPayload: undefined,
|
|
272
|
-
})
|
|
168
|
+
expect(entity.attributes).toEqual({
|
|
169
|
+
projectId: { type: "str", value: "alice-123" },
|
|
170
|
+
version: { type: "i32", value: 4 },
|
|
171
|
+
balance: { type: "u256", value: 1_000_000n },
|
|
172
|
+
score: { type: "dec", value: "3.5" },
|
|
273
173
|
})
|
|
174
|
+
expect(entity.attributeSchema).toEqual({ version: "i32" })
|
|
175
|
+
expect(entity.toJson()).toEqual({})
|
|
176
|
+
// Unselected stays undefined rather than becoming an empty value.
|
|
177
|
+
expect(entity.creator).toBeUndefined()
|
|
178
|
+
expect(entity.createdAt).toBeUndefined()
|
|
179
|
+
expect(page.blockNumber).toBe(0x8e1ffn)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
274
182
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
await builder.orderBy(asc("name", "string")).fetch()
|
|
282
|
-
|
|
283
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
284
|
-
predicates: [],
|
|
285
|
-
limit: undefined,
|
|
286
|
-
offset: undefined,
|
|
287
|
-
ownedBy: undefined,
|
|
288
|
-
createdBy: undefined,
|
|
289
|
-
orderBy: [{ name: "name", type: "string", desc: false }],
|
|
290
|
-
validAtBlock: undefined,
|
|
291
|
-
withAttributes: undefined,
|
|
292
|
-
withMetadata: undefined,
|
|
293
|
-
withPayload: undefined,
|
|
294
|
-
})
|
|
295
|
-
})
|
|
296
|
-
test("limit() sets limit", async () => {
|
|
297
|
-
mockProcessQuery.mockResolvedValue({
|
|
298
|
-
data: [],
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
const builder = new QueryBuilder(mockClient)
|
|
302
|
-
await builder.limit(10).fetch()
|
|
303
|
-
|
|
304
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
305
|
-
predicates: [],
|
|
306
|
-
limit: 10,
|
|
307
|
-
offset: undefined,
|
|
308
|
-
ownedBy: undefined,
|
|
309
|
-
createdBy: undefined,
|
|
310
|
-
orderBy: undefined,
|
|
311
|
-
validBeforeBlock: undefined,
|
|
312
|
-
withAttributes: undefined,
|
|
313
|
-
withMetadata: undefined,
|
|
314
|
-
withPayload: undefined,
|
|
315
|
-
})
|
|
316
|
-
})
|
|
183
|
+
describe("pagination", () => {
|
|
184
|
+
const page = (cursor: string | undefined, ...keys: string[]): Page => ({
|
|
185
|
+
blockNumber: "0x8e1ff",
|
|
186
|
+
data: keys.map((k) => ({ key: k as `0x${string}` })),
|
|
187
|
+
...(cursor !== undefined && { cursor }),
|
|
188
|
+
})
|
|
317
189
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
})
|
|
190
|
+
it("follows the cursor and stops when the node omits it", async () => {
|
|
191
|
+
const { client, request } = makeClient(
|
|
192
|
+
page("b64:1", "0x1", "0x2"),
|
|
193
|
+
page("b64:2", "0x3"),
|
|
194
|
+
page(undefined, "0x4"),
|
|
195
|
+
)
|
|
196
|
+
const builder = new SelectQueryBuilder(client, { key: true }).where(eq("a", 1)).limit(2)
|
|
197
|
+
|
|
198
|
+
const first = await builder.fetch()
|
|
199
|
+
expect(first.entities).toHaveLength(2)
|
|
200
|
+
expect(first.hasNextPage()).toBe(true)
|
|
201
|
+
|
|
202
|
+
const second = await first.next()
|
|
203
|
+
expect(sent(request, 1).options).toMatchObject({ cursor: "b64:1" })
|
|
204
|
+
|
|
205
|
+
const third = await second.next()
|
|
206
|
+
expect(sent(request, 2).options).toMatchObject({ cursor: "b64:2" })
|
|
207
|
+
expect(third.hasNextPage()).toBe(false)
|
|
208
|
+
await expect(third.next()).rejects.toThrow(NoMoreResultsError)
|
|
209
|
+
})
|
|
339
210
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
349
|
-
predicates: [],
|
|
350
|
-
limit: undefined,
|
|
351
|
-
offset: undefined,
|
|
352
|
-
ownedBy: undefined,
|
|
353
|
-
createdBy: undefined,
|
|
354
|
-
orderBy: undefined,
|
|
355
|
-
validBeforeBlock: undefined,
|
|
356
|
-
withAttributes: true,
|
|
357
|
-
withMetadata: undefined,
|
|
358
|
-
withPayload: undefined,
|
|
359
|
-
})
|
|
360
|
-
})
|
|
211
|
+
it("leaves an already-read page readable", async () => {
|
|
212
|
+
const { client } = makeClient(page("b64:1", "0x1"), page(undefined, "0x2"))
|
|
213
|
+
const first = await new SelectQueryBuilder(client, { key: true }).where(eq("a", 1)).fetch()
|
|
214
|
+
await first.next()
|
|
215
|
+
// next() returns the following page rather than mutating this one.
|
|
216
|
+
expect(first.entities).toHaveLength(1)
|
|
217
|
+
expect(first.cursor).toBe("b64:1")
|
|
218
|
+
})
|
|
361
219
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
cursor: undefined,
|
|
374
|
-
ownedBy: undefined,
|
|
375
|
-
createdBy: undefined,
|
|
376
|
-
orderBy: undefined,
|
|
377
|
-
validBeforeBlock: undefined,
|
|
378
|
-
withAttributes: false,
|
|
379
|
-
withMetadata: undefined,
|
|
380
|
-
withPayload: undefined,
|
|
381
|
-
})
|
|
382
|
-
})
|
|
220
|
+
it("reports a full last page correctly", async () => {
|
|
221
|
+
// The old heuristic was "fewer rows than the limit means the end", which mistakes a final page
|
|
222
|
+
// that happens to be exactly full for a middle one. The node omits the cursor instead.
|
|
223
|
+
const { client } = makeClient(page(undefined, "0x1", "0x2"))
|
|
224
|
+
const result = await new SelectQueryBuilder(client, { key: true })
|
|
225
|
+
.where(eq("a", 1))
|
|
226
|
+
.limit(2)
|
|
227
|
+
.fetch()
|
|
228
|
+
expect(result.entities).toHaveLength(2)
|
|
229
|
+
expect(result.hasNextPage()).toBe(false)
|
|
230
|
+
})
|
|
383
231
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
withAttributes: undefined,
|
|
401
|
-
withMetadata: true,
|
|
402
|
-
withPayload: undefined,
|
|
403
|
-
})
|
|
404
|
-
})
|
|
232
|
+
it("pages the query it started with, not whatever the builder says later", async () => {
|
|
233
|
+
const { client, request } = makeClient(page("b64:1", "0x1"), page(undefined, "0x2"))
|
|
234
|
+
const builder = new SelectQueryBuilder(client, { key: true }).where(eq("a", 1))
|
|
235
|
+
|
|
236
|
+
const first = await builder.fetch()
|
|
237
|
+
// The builder stays mutable and reusable. Re-reading it per page would send page 2 under a
|
|
238
|
+
// different query than the cursor was issued for, which the node answers with a "cursor" error
|
|
239
|
+
// — or, worse, with rows from another result set.
|
|
240
|
+
builder.where(eq("b", 2)).limit(50).atBlock(9n)
|
|
241
|
+
await first.next()
|
|
242
|
+
|
|
243
|
+
expect(sent(request, 1).query).toBe("a = i32(1)")
|
|
244
|
+
expect(sent(request, 1).options).not.toHaveProperty("limit")
|
|
245
|
+
// The block page 2 reads at is the one page 1 was served from, not the builder's later edit.
|
|
246
|
+
expect(sent(request, 1).options).toMatchObject({ atBlock: "0x8e1ff" })
|
|
247
|
+
})
|
|
405
248
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
withPayload: undefined,
|
|
425
|
-
})
|
|
426
|
-
})
|
|
249
|
+
it("pins the rest of the walk to the block the first page was read at", async () => {
|
|
250
|
+
const { client, request } = makeClient(
|
|
251
|
+
page("b64:1", "0x1"),
|
|
252
|
+
page("b64:2", "0x2"),
|
|
253
|
+
page(undefined, "0x3"),
|
|
254
|
+
)
|
|
255
|
+
// No atBlock on the builder: the first page reads at the head, and every page after it reads
|
|
256
|
+
// at the block that page came back with. Otherwise a block mined mid-walk moves the head on
|
|
257
|
+
// and the node rejects a cursor bound to the earlier one.
|
|
258
|
+
const first = await new SelectQueryBuilder(client, { key: true }).where(eq("a", 1)).fetch()
|
|
259
|
+
expect(sent(request, 0).options).not.toHaveProperty("atBlock")
|
|
260
|
+
|
|
261
|
+
const second = await first.next()
|
|
262
|
+
expect(sent(request, 1).options).toMatchObject({ cursor: "b64:1", atBlock: "0x8e1ff" })
|
|
263
|
+
|
|
264
|
+
await second.next()
|
|
265
|
+
expect(sent(request, 2).options).toMatchObject({ cursor: "b64:2", atBlock: "0x8e1ff" })
|
|
266
|
+
})
|
|
427
267
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
predicates: [],
|
|
438
|
-
limit: undefined,
|
|
439
|
-
cursor: undefined,
|
|
440
|
-
ownedBy: undefined,
|
|
441
|
-
createdBy: undefined,
|
|
442
|
-
orderBy: undefined,
|
|
443
|
-
validAtBlock: undefined,
|
|
444
|
-
withAttributes: undefined,
|
|
445
|
-
withMetadata: undefined,
|
|
446
|
-
withPayload: true,
|
|
447
|
-
})
|
|
448
|
-
})
|
|
268
|
+
it("walks every page through the async iterator", async () => {
|
|
269
|
+
const { client } = makeClient(page("b64:1", "0x1", "0x2"), page(undefined, "0x3"))
|
|
270
|
+
const keys: unknown[] = []
|
|
271
|
+
for await (const entity of new SelectQueryBuilder(client, { key: true }).where(eq("a", 1))) {
|
|
272
|
+
keys.push((entity as { key: string }).key)
|
|
273
|
+
}
|
|
274
|
+
expect(keys).toEqual(["0x1", "0x2", "0x3"])
|
|
275
|
+
})
|
|
276
|
+
})
|
|
449
277
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
459
|
-
predicates: [],
|
|
460
|
-
limit: undefined,
|
|
461
|
-
cursor: undefined,
|
|
462
|
-
ownedBy: undefined,
|
|
463
|
-
createdBy: undefined,
|
|
464
|
-
validAtBlock: undefined,
|
|
465
|
-
withAttributes: undefined,
|
|
466
|
-
withMetadata: undefined,
|
|
467
|
-
withPayload: false,
|
|
468
|
-
})
|
|
278
|
+
describe("node rejections", () => {
|
|
279
|
+
/** viem wraps the arkiv codes in its own EIP-1474 classes, so the code sits down the chain. */
|
|
280
|
+
function rpcFailure(code: number, data?: unknown) {
|
|
281
|
+
const inner = Object.assign(new Error("RPC Request failed."), { code, data })
|
|
282
|
+
return Object.assign(new Error("Something went wrong."), {
|
|
283
|
+
shortMessage: "Something went wrong.",
|
|
284
|
+
cause: inner,
|
|
469
285
|
})
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
it("names the failure and keeps the node's detail", async () => {
|
|
289
|
+
const { client } = makeClient()
|
|
290
|
+
const data = { position: 14, expected: "typed literal", got: "3.5" }
|
|
291
|
+
;(client.request as ReturnType<typeof vi.fn>).mockRejectedValue(rpcFailure(-32001, data))
|
|
292
|
+
|
|
293
|
+
const promise = new SelectQueryBuilder(client, { key: true }).where(eq("a", 1)).fetch()
|
|
294
|
+
await expect(promise).rejects.toThrow(QueryError)
|
|
295
|
+
|
|
296
|
+
const error = (await promise.catch((e) => e)) as QueryError
|
|
297
|
+
expect(error.kind).toBe("parse")
|
|
298
|
+
expect(error.code).toBe(-32001)
|
|
299
|
+
expect(error.data).toEqual(data)
|
|
300
|
+
expect(error.query).toBe("a = i32(1)")
|
|
301
|
+
expect(error.message).toContain("Query: a = i32(1)")
|
|
302
|
+
})
|
|
470
303
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
.
|
|
485
|
-
.withMetadata(true)
|
|
486
|
-
.withPayload(true)
|
|
304
|
+
it("maps each code in the frozen range", async () => {
|
|
305
|
+
const kinds = {
|
|
306
|
+
[-32001]: "parse",
|
|
307
|
+
[-32002]: "type",
|
|
308
|
+
[-32003]: "literal",
|
|
309
|
+
[-32004]: "limits",
|
|
310
|
+
[-32005]: "cursor",
|
|
311
|
+
[-32006]: "block",
|
|
312
|
+
}
|
|
313
|
+
for (const [code, kind] of Object.entries(kinds)) {
|
|
314
|
+
const { client } = makeClient()
|
|
315
|
+
;(client.request as ReturnType<typeof vi.fn>).mockRejectedValue(rpcFailure(Number(code)))
|
|
316
|
+
const error = await new SelectQueryBuilder(client, { key: true })
|
|
317
|
+
.where(eq("a", 1))
|
|
487
318
|
.fetch()
|
|
488
|
-
|
|
489
|
-
expect(
|
|
490
|
-
|
|
491
|
-
limit: 10,
|
|
492
|
-
cursor: "0xABC123",
|
|
493
|
-
ownedBy: owner,
|
|
494
|
-
createdBy: undefined,
|
|
495
|
-
validAtBlock: undefined,
|
|
496
|
-
withAttributes: true,
|
|
497
|
-
withMetadata: true,
|
|
498
|
-
withPayload: true,
|
|
499
|
-
})
|
|
500
|
-
})
|
|
319
|
+
.catch((e) => e)
|
|
320
|
+
expect((error as QueryError).kind).toBe(kind as QueryError["kind"])
|
|
321
|
+
}
|
|
501
322
|
})
|
|
502
323
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
{ key: "0xabc" as const, value: "encodedData1" },
|
|
508
|
-
{ key: "0xdef" as const, value: "encodedData2" },
|
|
509
|
-
],
|
|
510
|
-
}
|
|
511
|
-
const mockEntity1 = new Entity(
|
|
512
|
-
"0xabc" as const,
|
|
513
|
-
"application/json" as const,
|
|
514
|
-
"0x123" as const,
|
|
515
|
-
1000n,
|
|
516
|
-
1000n,
|
|
517
|
-
1000n,
|
|
518
|
-
1000n,
|
|
519
|
-
1000n,
|
|
520
|
-
new Uint8Array(),
|
|
521
|
-
[],
|
|
522
|
-
)
|
|
523
|
-
const mockEntity2 = new Entity(
|
|
524
|
-
"0xdef" as const,
|
|
525
|
-
"application/json" as const,
|
|
526
|
-
"0x456" as const,
|
|
527
|
-
2000n,
|
|
528
|
-
2000n,
|
|
529
|
-
2000n,
|
|
530
|
-
2000n,
|
|
531
|
-
2000n,
|
|
532
|
-
new Uint8Array(),
|
|
533
|
-
[],
|
|
534
|
-
)
|
|
535
|
-
|
|
536
|
-
mockProcessQuery.mockResolvedValue(mockQueryResult)
|
|
537
|
-
mockEntityFromRpcResult.mockResolvedValueOnce(mockEntity1).mockResolvedValueOnce(mockEntity2)
|
|
538
|
-
|
|
539
|
-
const builder = new QueryBuilder(mockClient)
|
|
540
|
-
const result = await builder.where(eq("name", "test")).fetch()
|
|
541
|
-
|
|
542
|
-
expect(mockProcessQuery).toHaveBeenCalled()
|
|
543
|
-
expect(mockEntityFromRpcResult).toHaveBeenCalled()
|
|
544
|
-
expect(mockEntityFromRpcResult).toHaveBeenCalledWith({ key: "0xabc", value: "encodedData1" })
|
|
545
|
-
expect(mockEntityFromRpcResult).toHaveBeenCalledWith({ key: "0xdef", value: "encodedData2" })
|
|
546
|
-
expect(result.entities).toHaveLength(2)
|
|
547
|
-
expect(result.entities[0]).toBe(mockEntity1)
|
|
548
|
-
expect(result.entities[1]).toBe(mockEntity2)
|
|
549
|
-
})
|
|
550
|
-
|
|
551
|
-
test("returns QueryResult with correct properties", async () => {
|
|
552
|
-
mockProcessQuery.mockResolvedValue({
|
|
553
|
-
data: [],
|
|
554
|
-
})
|
|
324
|
+
it("leaves anything else alone", async () => {
|
|
325
|
+
const { client } = makeClient()
|
|
326
|
+
const transportFailure = new Error("fetch failed")
|
|
327
|
+
;(client.request as ReturnType<typeof vi.fn>).mockRejectedValue(transportFailure)
|
|
555
328
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
})
|
|
329
|
+
const error = await new SelectQueryBuilder(client, { key: true })
|
|
330
|
+
.where(eq("a", 1))
|
|
331
|
+
.fetch()
|
|
332
|
+
.catch((e) => e)
|
|
333
|
+
expect(error).toBe(transportFailure)
|
|
562
334
|
})
|
|
335
|
+
})
|
|
563
336
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
{ key: "0xghi", value: "data3" },
|
|
571
|
-
],
|
|
572
|
-
}
|
|
573
|
-
mockProcessQuery.mockResolvedValue(mockQueryResult)
|
|
574
|
-
|
|
575
|
-
const builder = new QueryBuilder(mockClient)
|
|
576
|
-
const count = await builder.where(eq("status", "active")).count()
|
|
577
|
-
|
|
578
|
-
expect(mockProcessQuery).toHaveBeenCalled()
|
|
579
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
580
|
-
predicates: [eq("status", "active")],
|
|
581
|
-
limit: undefined,
|
|
582
|
-
cursor: undefined,
|
|
583
|
-
ownedBy: undefined,
|
|
584
|
-
createdBy: undefined,
|
|
585
|
-
validAtBlock: undefined,
|
|
586
|
-
withAttributes: false,
|
|
587
|
-
withMetadata: false,
|
|
588
|
-
withPayload: false,
|
|
589
|
-
})
|
|
590
|
-
expect(count as number).toEqual(3 as number)
|
|
591
|
-
// Verify entityFromRpcResult is NOT called for count()
|
|
592
|
-
expect(mockEntityFromRpcResult).not.toHaveBeenCalled()
|
|
593
|
-
})
|
|
594
|
-
|
|
595
|
-
test("returns 0 for empty results", async () => {
|
|
596
|
-
mockProcessQuery.mockResolvedValue({
|
|
597
|
-
data: [],
|
|
598
|
-
})
|
|
599
|
-
|
|
600
|
-
const builder = new QueryBuilder(mockClient)
|
|
601
|
-
const count = await builder.count()
|
|
602
|
-
|
|
603
|
-
expect(count as number).toEqual(0 as number)
|
|
337
|
+
describe("the projected type", () => {
|
|
338
|
+
// Compile-time only: these assertions fail the build, not the run.
|
|
339
|
+
it("narrows the entity to exactly the selected fields", async () => {
|
|
340
|
+
const { client } = makeClient({
|
|
341
|
+
blockNumber: "0x1",
|
|
342
|
+
data: [{ key: KEY, owner: ACCOUNT, payload: "0x7b7d" }],
|
|
604
343
|
})
|
|
605
344
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
})
|
|
610
|
-
|
|
611
|
-
const builder = new QueryBuilder(mockClient)
|
|
612
|
-
const owner = "0x1234567890123456789012345678901234567890" as const
|
|
613
|
-
await builder
|
|
614
|
-
.where([eq("active", 1), gte("score", 100)])
|
|
615
|
-
.ownedBy(owner)
|
|
616
|
-
.limit(50)
|
|
617
|
-
.cursor("0xABC123")
|
|
618
|
-
.validAtBlock(123n)
|
|
619
|
-
.count()
|
|
620
|
-
|
|
621
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
622
|
-
predicates: [eq("active", 1), gte("score", 100)],
|
|
623
|
-
limit: 50,
|
|
624
|
-
cursor: "0xABC123",
|
|
625
|
-
ownedBy: owner,
|
|
626
|
-
createdBy: undefined,
|
|
627
|
-
validAtBlock: 123n,
|
|
628
|
-
withAttributes: false,
|
|
629
|
-
withMetadata: false,
|
|
630
|
-
withPayload: false,
|
|
631
|
-
})
|
|
345
|
+
const page = await new SelectQueryBuilder<ProjectedEntity<{ key: true; owner: true }>>(client, {
|
|
346
|
+
key: true,
|
|
347
|
+
owner: true,
|
|
632
348
|
})
|
|
349
|
+
.where(eq("a", 1))
|
|
350
|
+
.fetch()
|
|
351
|
+
|
|
352
|
+
const entity = page.entities[0]
|
|
353
|
+
expectType<Hex>(entity.key)
|
|
354
|
+
expectType<Hex>(entity.owner)
|
|
355
|
+
// @ts-expect-error - creator was not selected
|
|
356
|
+
entity.creator
|
|
357
|
+
// @ts-expect-error - toJson comes with the payload, which was not selected
|
|
358
|
+
entity.toJson()
|
|
633
359
|
})
|
|
634
360
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
})
|
|
640
|
-
|
|
641
|
-
const builder = new QueryBuilder(mockClient)
|
|
642
|
-
await builder.where(eq("name", "john")).fetch()
|
|
643
|
-
|
|
644
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(
|
|
645
|
-
mockClient,
|
|
646
|
-
expect.objectContaining({
|
|
647
|
-
predicates: [eq("name", "john")],
|
|
648
|
-
}),
|
|
649
|
-
)
|
|
361
|
+
it("keeps the payload helpers when the payload is selected", async () => {
|
|
362
|
+
const { client } = makeClient({ blockNumber: "0x1", data: [{ payload: "0x7b7d" }] })
|
|
363
|
+
const page = await new SelectQueryBuilder<ProjectedEntity<{ payload: true }>>(client, {
|
|
364
|
+
payload: true,
|
|
650
365
|
})
|
|
366
|
+
.where(eq("a", 1))
|
|
367
|
+
.fetch()
|
|
651
368
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
})
|
|
656
|
-
|
|
657
|
-
const builder = new QueryBuilder(mockClient)
|
|
658
|
-
await builder.where(gt("age", 25)).fetch()
|
|
659
|
-
|
|
660
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(
|
|
661
|
-
mockClient,
|
|
662
|
-
expect.objectContaining({
|
|
663
|
-
predicates: [gt("age", 25)],
|
|
664
|
-
}),
|
|
665
|
-
)
|
|
666
|
-
})
|
|
369
|
+
expect(page.entities[0].toJson()).toEqual({})
|
|
370
|
+
expectType<Uint8Array>(page.entities[0].payload)
|
|
371
|
+
})
|
|
667
372
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const builder = new QueryBuilder(mockClient)
|
|
674
|
-
const predicates = [eq("name", "john"), gt("age", 25), neq("status", "inactive")]
|
|
675
|
-
await builder.where(predicates).fetch()
|
|
676
|
-
|
|
677
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(
|
|
678
|
-
mockClient,
|
|
679
|
-
expect.objectContaining({
|
|
680
|
-
predicates: predicates,
|
|
681
|
-
}),
|
|
682
|
-
)
|
|
373
|
+
it("treats a named attribute subset as a selection of attributes", async () => {
|
|
374
|
+
const { client } = makeClient({
|
|
375
|
+
blockNumber: "0x1",
|
|
376
|
+
data: [{ key: KEY, attributes: [{ name: "version", type: "i32", value: 4 }] }],
|
|
683
377
|
})
|
|
378
|
+
const page = await new SelectQueryBuilder<
|
|
379
|
+
ProjectedEntity<{ key: true; attributes: { version: true } }>
|
|
380
|
+
>(client, { key: true, attributes: { version: true } })
|
|
381
|
+
.where(eq("a", 1))
|
|
382
|
+
.fetch()
|
|
383
|
+
|
|
384
|
+
expectType<Attributes>(page.entities[0].attributes)
|
|
385
|
+
expect(page.entities[0].attributes).toEqual({ version: { type: "i32", value: 4 } })
|
|
684
386
|
})
|
|
685
387
|
})
|
|
388
|
+
|
|
389
|
+
/** Asserts the static type of an expression without touching it at runtime. */
|
|
390
|
+
function expectType<T>(_value: T): void {}
|