@arkiv-network/sdk 0.7.0 → 0.8.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/README.md +29 -16
- package/dist/attr/index.cjs +34 -0
- package/dist/attr/index.d.cts +2 -0
- package/dist/attr/index.d.ts +2 -0
- package/dist/attr/index.js +3 -0
- package/dist/attr-BDZ-APg5.js +809 -0
- package/dist/attr-BDZ-APg5.js.map +1 -0
- package/dist/attr-BvxHN99W.cjs +1029 -0
- package/dist/chains/index.cjs +2 -3
- package/dist/chains/index.d.cts +39 -77
- 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/changeOwnership-B7bEgla4.d.cts +509 -0
- package/dist/changeOwnership-B7bEgla4.d.cts.map +1 -0
- package/dist/changeOwnership-DwgtdSFc.d.ts +509 -0
- package/dist/changeOwnership-DwgtdSFc.d.ts.map +1 -0
- package/dist/entity/index.cjs +19 -0
- package/dist/entity/index.d.cts +4 -0
- package/dist/entity/index.d.ts +4 -0
- package/dist/entity/index.js +6 -0
- package/dist/entity-B0t6sN7F.cjs +71 -0
- package/dist/entity-BIp_df9u.js +67 -0
- package/dist/entity-BIp_df9u.js.map +1 -0
- package/dist/entity-CFhbAv0R.js +70 -0
- package/dist/entity-CFhbAv0R.js.map +1 -0
- package/dist/entity-wfBnZd91.cjs +98 -0
- package/dist/errors-D-eJkGVc.cjs +56 -0
- package/dist/errors-DGB8AgT2.js +33 -0
- package/dist/errors-DGB8AgT2.js.map +1 -0
- package/dist/expiry-BUYIXTGb.cjs +135 -0
- package/dist/expiry-BxOlvVWC.d.ts +79 -0
- package/dist/expiry-BxOlvVWC.d.ts.map +1 -0
- package/dist/expiry-DGRzrSan.js +95 -0
- package/dist/expiry-DGRzrSan.js.map +1 -0
- package/dist/expiry-DRV29F1r.d.cts +79 -0
- package/dist/expiry-DRV29F1r.d.cts.map +1 -0
- package/dist/flags-0Vl5rrHq.d.cts +41 -0
- package/dist/flags-0Vl5rrHq.d.cts.map +1 -0
- package/dist/flags-Ct8k0gpB.cjs +43 -0
- package/dist/flags-ZtlvOIIk.js +33 -0
- package/dist/flags-ZtlvOIIk.js.map +1 -0
- package/dist/flags-wmcMoAnN.d.ts +41 -0
- package/dist/flags-wmcMoAnN.d.ts.map +1 -0
- package/dist/index-Cgw1u5aY.d.cts +278 -0
- package/dist/index-Cgw1u5aY.d.cts.map +1 -0
- package/dist/index-ClgIuxJ8.d.ts +159 -0
- package/dist/index-ClgIuxJ8.d.ts.map +1 -0
- package/dist/index-D0CmtiWT.d.cts +159 -0
- package/dist/index-D0CmtiWT.d.cts.map +1 -0
- package/dist/index-D8LZt-3J.d.cts +417 -0
- package/dist/index-D8LZt-3J.d.cts.map +1 -0
- package/dist/index-DVYFaXNV.d.ts +417 -0
- package/dist/index-DVYFaXNV.d.ts.map +1 -0
- package/dist/index-VCqgn29D.d.ts +278 -0
- package/dist/index-VCqgn29D.d.ts.map +1 -0
- package/dist/index.cjs +675 -357
- package/dist/index.d.cts +251 -203
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +251 -203
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +626 -355
- package/dist/index.js.map +1 -1
- package/dist/payload-Bnw1hrvb.cjs +83 -0
- package/dist/payload-CIxSn_DW.js +67 -0
- package/dist/payload-CIxSn_DW.js.map +1 -0
- package/dist/payload-Dgx3PQeK.d.ts +94 -0
- package/dist/payload-Dgx3PQeK.d.ts.map +1 -0
- package/dist/payload-P0nWyjLg.d.cts +94 -0
- package/dist/payload-P0nWyjLg.d.cts.map +1 -0
- package/dist/query/index.cjs +24 -149
- package/dist/query/index.d.cts +5 -3
- package/dist/query/index.d.ts +5 -3
- package/dist/query/index.js +6 -137
- package/dist/queryBuilder-BXDvX1K_.cjs +1123 -0
- package/dist/queryBuilder-CbLL4pG5.js +963 -0
- package/dist/queryBuilder-CbLL4pG5.js.map +1 -0
- package/dist/selection-BvK6yX0g.d.cts +433 -0
- package/dist/selection-BvK6yX0g.d.cts.map +1 -0
- package/dist/selection-fpmI3ADQ.d.ts +433 -0
- package/dist/selection-fpmI3ADQ.d.ts.map +1 -0
- package/dist/types/index.cjs +4 -1
- package/dist/types/index.d.cts +7 -2
- package/dist/types/index.d.ts +7 -2
- package/dist/types/index.js +4 -1
- 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/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 +33 -69
- package/src/actions/public/watchEntityEvents.test.ts +414 -0
- package/src/actions/public/watchEntityEvents.ts +225 -0
- package/src/actions/wallet/changeOwnership.ts +8 -8
- package/src/actions/wallet/createEntity.ts +104 -26
- package/src/actions/wallet/deleteEntity.ts +2 -3
- package/src/actions/wallet/extendEntity.ts +39 -16
- package/src/actions/wallet/mutateEntities.test.ts +76 -0
- package/src/actions/wallet/mutateEntities.ts +29 -74
- 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/arkivPublic.ts +160 -139
- package/src/clients/decorators/arkivWallet.ts +91 -95
- package/src/consts.ts +2 -1
- 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 +14 -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 +33 -28
- package/src/index.ts +6 -0
- package/src/query/engine.ts +59 -134
- 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 +322 -637
- package/src/query/queryBuilder.ts +126 -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 +62 -44
- package/src/types/events.ts +64 -23
- package/src/types/index.ts +20 -16
- package/src/types/rpcSchema.ts +70 -33
- package/src/utils/arkivTransactions.test.ts +681 -38
- package/src/utils/arkivTransactions.ts +249 -104
- 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 +111 -29
- package/src/utils/expirationTime.ts +114 -10
- package/src/utils/logger.ts +2 -2
- package/src/utils/payload.ts +4 -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-AUoL-v42.cjs +0 -18
- package/dist/consts-BV4PR-de.js +0 -7
- package/dist/consts-BV4PR-de.js.map +0 -1
- package/dist/entity-B6g_EPm1.d.cts +0 -382
- package/dist/entity-B6g_EPm1.d.cts.map +0 -1
- package/dist/entity-qoLMAuuk.d.ts +0 -382
- package/dist/entity-qoLMAuuk.d.ts.map +0 -1
- package/dist/index-DdspxNAA.d.ts +0 -481
- package/dist/index-DdspxNAA.d.ts.map +0 -1
- package/dist/index-Ds9dwqb5.d.cts +0 -481
- package/dist/index-Ds9dwqb5.d.cts.map +0 -1
- package/dist/payload-B2kFJyah.d.ts +0 -6
- package/dist/payload-B2kFJyah.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-Claf2jUO.js +0 -13
- package/dist/payload-Claf2jUO.js.map +0 -1
- package/dist/payload-DpA5PA-w.cjs +0 -23
- package/dist/query/index.js.map +0 -1
- package/dist/queryBuilder-D2uwP9jI.js +0 -704
- package/dist/queryBuilder-D2uwP9jI.js.map +0 -1
- package/dist/queryBuilder-UW2MCaX4.cjs +0 -792
- 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 -119
- package/src/actions/wallet/updateEntity.ts +0 -60
- package/src/chains/braga.ts +0 -26
- package/src/chains/kaolin.ts +0 -29
- package/src/query/engine.test.ts +0 -526
- 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 -210
- package/src/types/attributes.ts +0 -16
- package/src/utils/validation.test.ts +0 -60
- package/src/utils/validation.ts +0 -32
- /package/dist/{types-H-TSmRpm.js → types-Bx1EKe0v.js} +0 -0
- /package/dist/{types-o3zWarRp.cjs → types-CKPddlfS.cjs} +0 -0
|
@@ -1,685 +1,370 @@
|
|
|
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
|
-
})
|
|
80
|
-
|
|
81
|
-
test("adds multiple predicates as varargs", async () => {
|
|
82
|
-
mockProcessQuery.mockResolvedValue({
|
|
83
|
-
data: [],
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
const builder = new QueryBuilder(mockClient)
|
|
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
|
-
})
|
|
102
|
-
|
|
103
|
-
test("chains multiple where() calls", async () => {
|
|
104
|
-
mockProcessQuery.mockResolvedValue({
|
|
105
|
-
data: [],
|
|
106
|
-
})
|
|
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
|
+
})
|
|
107
75
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
})
|
|
114
82
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
})
|
|
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
|
+
})
|
|
133
91
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const builder = new QueryBuilder(mockClient)
|
|
140
|
-
const complexPredicate = or([eq("type", "admin"), eq("type", "moderator")])
|
|
141
|
-
await builder.where(complexPredicate).fetch()
|
|
142
|
-
|
|
143
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
144
|
-
predicates: [complexPredicate],
|
|
145
|
-
limit: undefined,
|
|
146
|
-
offset: undefined,
|
|
147
|
-
ownedBy: undefined,
|
|
148
|
-
createdBy: undefined,
|
|
149
|
-
orderBy: undefined,
|
|
150
|
-
validBeforeBlock: undefined,
|
|
151
|
-
withAttributes: undefined,
|
|
152
|
-
withMetadata: undefined,
|
|
153
|
-
withPayload: undefined,
|
|
154
|
-
})
|
|
155
|
-
})
|
|
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()
|
|
156
96
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const builder = new QueryBuilder(mockClient)
|
|
163
|
-
const nestedPredicate = and([
|
|
164
|
-
eq("active", 1),
|
|
165
|
-
or([eq("role", "admin"), eq("role", "moderator")]),
|
|
166
|
-
])
|
|
167
|
-
await builder.where(nestedPredicate).fetch()
|
|
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
|
-
})
|
|
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"])
|
|
182
101
|
})
|
|
183
102
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
ownedBy: owner,
|
|
199
|
-
createdBy: undefined,
|
|
200
|
-
orderBy: undefined,
|
|
201
|
-
validBeforeBlock: undefined,
|
|
202
|
-
withAttributes: undefined,
|
|
203
|
-
withMetadata: undefined,
|
|
204
|
-
withPayload: undefined,
|
|
205
|
-
})
|
|
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",
|
|
206
117
|
})
|
|
118
|
+
})
|
|
207
119
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
createdBy: creator,
|
|
223
|
-
orderBy: undefined,
|
|
224
|
-
validBeforeBlock: undefined,
|
|
225
|
-
withAttributes: undefined,
|
|
226
|
-
withMetadata: undefined,
|
|
227
|
-
withPayload: undefined,
|
|
228
|
-
})
|
|
229
|
-
})
|
|
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()
|
|
132
|
+
})
|
|
133
|
+
})
|
|
230
134
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
+
],
|
|
251
156
|
})
|
|
252
157
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
data: [],
|
|
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
|
-
})
|
|
273
|
-
})
|
|
158
|
+
const page = await new SelectQueryBuilder(client, "*").where(eq("a", 1)).fetch()
|
|
159
|
+
const entity = page.entities[0]
|
|
274
160
|
|
|
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
|
-
})
|
|
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,
|
|
295
167
|
})
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
})
|
|
317
|
-
|
|
318
|
-
test("cursor() sets cursor", async () => {
|
|
319
|
-
mockProcessQuery.mockResolvedValue({
|
|
320
|
-
data: [],
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
const builder = new QueryBuilder(mockClient)
|
|
324
|
-
await builder.cursor("0xABC123").fetch()
|
|
325
|
-
|
|
326
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
327
|
-
predicates: [],
|
|
328
|
-
limit: undefined,
|
|
329
|
-
cursor: "0xABC123",
|
|
330
|
-
ownedBy: undefined,
|
|
331
|
-
createdBy: undefined,
|
|
332
|
-
orderBy: undefined,
|
|
333
|
-
validAtBlock: undefined,
|
|
334
|
-
withAttributes: undefined,
|
|
335
|
-
withMetadata: undefined,
|
|
336
|
-
withPayload: undefined,
|
|
337
|
-
})
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
test("withAttributes() sets withAttributes to true by default", async () => {
|
|
341
|
-
mockProcessQuery.mockResolvedValue({
|
|
342
|
-
data: [],
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
const builder = new QueryBuilder(mockClient)
|
|
346
|
-
await builder.withAttributes().fetch()
|
|
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
|
-
})
|
|
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" },
|
|
360
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
|
+
})
|
|
361
182
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
await builder.withAttributes(false).fetch()
|
|
369
|
-
|
|
370
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
371
|
-
predicates: [],
|
|
372
|
-
limit: undefined,
|
|
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
|
-
})
|
|
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
|
+
})
|
|
383
189
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
})
|
|
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
|
+
})
|
|
405
210
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
415
|
-
predicates: [],
|
|
416
|
-
limit: undefined,
|
|
417
|
-
cursor: undefined,
|
|
418
|
-
ownedBy: undefined,
|
|
419
|
-
createdBy: undefined,
|
|
420
|
-
orderBy: undefined,
|
|
421
|
-
validAtBlock: undefined,
|
|
422
|
-
withAttributes: undefined,
|
|
423
|
-
withMetadata: false,
|
|
424
|
-
withPayload: undefined,
|
|
425
|
-
})
|
|
426
|
-
})
|
|
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
|
+
})
|
|
427
219
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
})
|
|
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
|
+
})
|
|
449
231
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
const builder = new QueryBuilder(mockClient)
|
|
456
|
-
await builder.withPayload(false).fetch()
|
|
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
|
-
})
|
|
469
|
-
})
|
|
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))
|
|
470
235
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const owner = "0x1234567890123456789012345678901234567890" as const
|
|
478
|
-
await builder
|
|
479
|
-
.where(eq("name", "test"))
|
|
480
|
-
.where(gte("age", 18))
|
|
481
|
-
.ownedBy(owner)
|
|
482
|
-
.limit(10)
|
|
483
|
-
.cursor("0xABC123")
|
|
484
|
-
.withAttributes(true)
|
|
485
|
-
.withMetadata(true)
|
|
486
|
-
.withPayload(true)
|
|
487
|
-
.fetch()
|
|
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()
|
|
488
242
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
cursor: "0xABC123",
|
|
493
|
-
ownedBy: owner,
|
|
494
|
-
createdBy: undefined,
|
|
495
|
-
validAtBlock: undefined,
|
|
496
|
-
withAttributes: true,
|
|
497
|
-
withMetadata: true,
|
|
498
|
-
withPayload: true,
|
|
499
|
-
})
|
|
500
|
-
})
|
|
243
|
+
expect(sent(request, 1).query).toBe("a = i32(1)")
|
|
244
|
+
expect(sent(request, 1).options).not.toHaveProperty("limit")
|
|
245
|
+
expect(sent(request, 1).options).not.toHaveProperty("atBlock")
|
|
501
246
|
})
|
|
502
247
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
})
|
|
555
|
-
|
|
556
|
-
const builder = new QueryBuilder(mockClient)
|
|
557
|
-
const result = await builder.limit(10).cursor("0xABC123").fetch()
|
|
248
|
+
it("walks every page through the async iterator", async () => {
|
|
249
|
+
const { client } = makeClient(page("b64:1", "0x1", "0x2"), page(undefined, "0x3"))
|
|
250
|
+
const keys: unknown[] = []
|
|
251
|
+
for await (const entity of new SelectQueryBuilder(client, { key: true }).where(eq("a", 1))) {
|
|
252
|
+
keys.push((entity as { key: string }).key)
|
|
253
|
+
}
|
|
254
|
+
expect(keys).toEqual(["0x1", "0x2", "0x3"])
|
|
255
|
+
})
|
|
256
|
+
})
|
|
558
257
|
|
|
559
|
-
|
|
560
|
-
|
|
258
|
+
describe("node rejections", () => {
|
|
259
|
+
/** viem wraps the arkiv codes in its own EIP-1474 classes, so the code sits down the chain. */
|
|
260
|
+
function rpcFailure(code: number, data?: unknown) {
|
|
261
|
+
const inner = Object.assign(new Error("RPC Request failed."), { code, data })
|
|
262
|
+
return Object.assign(new Error("Something went wrong."), {
|
|
263
|
+
shortMessage: "Something went wrong.",
|
|
264
|
+
cause: inner,
|
|
561
265
|
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
it("names the failure and keeps the node's detail", async () => {
|
|
269
|
+
const { client } = makeClient()
|
|
270
|
+
const data = { position: 14, expected: "typed literal", got: "3.5" }
|
|
271
|
+
;(client.request as ReturnType<typeof vi.fn>).mockRejectedValue(rpcFailure(-32001, data))
|
|
272
|
+
|
|
273
|
+
const promise = new SelectQueryBuilder(client, { key: true }).where(eq("a", 1)).fetch()
|
|
274
|
+
await expect(promise).rejects.toThrow(QueryError)
|
|
275
|
+
|
|
276
|
+
const error = (await promise.catch((e) => e)) as QueryError
|
|
277
|
+
expect(error.kind).toBe("parse")
|
|
278
|
+
expect(error.code).toBe(-32001)
|
|
279
|
+
expect(error.data).toEqual(data)
|
|
280
|
+
expect(error.query).toBe("a = i32(1)")
|
|
281
|
+
expect(error.message).toContain("Query: a = i32(1)")
|
|
562
282
|
})
|
|
563
283
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
-
})
|
|
284
|
+
it("maps each code in the frozen range", async () => {
|
|
285
|
+
const kinds = {
|
|
286
|
+
[-32001]: "parse",
|
|
287
|
+
[-32002]: "type",
|
|
288
|
+
[-32003]: "literal",
|
|
289
|
+
[-32004]: "limits",
|
|
290
|
+
[-32005]: "cursor",
|
|
291
|
+
[-32006]: "block",
|
|
292
|
+
}
|
|
293
|
+
for (const [code, kind] of Object.entries(kinds)) {
|
|
294
|
+
const { client } = makeClient()
|
|
295
|
+
;(client.request as ReturnType<typeof vi.fn>).mockRejectedValue(rpcFailure(Number(code)))
|
|
296
|
+
const error = await new SelectQueryBuilder(client, { key: true })
|
|
297
|
+
.where(eq("a", 1))
|
|
298
|
+
.fetch()
|
|
299
|
+
.catch((e) => e)
|
|
300
|
+
expect((error as QueryError).kind).toBe(kind as QueryError["kind"])
|
|
301
|
+
}
|
|
302
|
+
})
|
|
594
303
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
304
|
+
it("leaves anything else alone", async () => {
|
|
305
|
+
const { client } = makeClient()
|
|
306
|
+
const transportFailure = new Error("fetch failed")
|
|
307
|
+
;(client.request as ReturnType<typeof vi.fn>).mockRejectedValue(transportFailure)
|
|
599
308
|
|
|
600
|
-
|
|
601
|
-
|
|
309
|
+
const error = await new SelectQueryBuilder(client, { key: true })
|
|
310
|
+
.where(eq("a", 1))
|
|
311
|
+
.fetch()
|
|
312
|
+
.catch((e) => e)
|
|
313
|
+
expect(error).toBe(transportFailure)
|
|
314
|
+
})
|
|
315
|
+
})
|
|
602
316
|
|
|
603
|
-
|
|
317
|
+
describe("the projected type", () => {
|
|
318
|
+
// Compile-time only: these assertions fail the build, not the run.
|
|
319
|
+
it("narrows the entity to exactly the selected fields", async () => {
|
|
320
|
+
const { client } = makeClient({
|
|
321
|
+
blockNumber: "0x1",
|
|
322
|
+
data: [{ key: KEY, owner: ACCOUNT, payload: "0x7b7d" }],
|
|
604
323
|
})
|
|
605
324
|
|
|
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
|
-
})
|
|
325
|
+
const page = await new SelectQueryBuilder<ProjectedEntity<{ key: true; owner: true }>>(client, {
|
|
326
|
+
key: true,
|
|
327
|
+
owner: true,
|
|
632
328
|
})
|
|
329
|
+
.where(eq("a", 1))
|
|
330
|
+
.fetch()
|
|
331
|
+
|
|
332
|
+
const entity = page.entities[0]
|
|
333
|
+
expectType<Hex>(entity.key)
|
|
334
|
+
expectType<Hex>(entity.owner)
|
|
335
|
+
// @ts-expect-error - creator was not selected
|
|
336
|
+
entity.creator
|
|
337
|
+
// @ts-expect-error - toJson comes with the payload, which was not selected
|
|
338
|
+
entity.toJson()
|
|
633
339
|
})
|
|
634
340
|
|
|
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
|
-
)
|
|
341
|
+
it("keeps the payload helpers when the payload is selected", async () => {
|
|
342
|
+
const { client } = makeClient({ blockNumber: "0x1", data: [{ payload: "0x7b7d" }] })
|
|
343
|
+
const page = await new SelectQueryBuilder<ProjectedEntity<{ payload: true }>>(client, {
|
|
344
|
+
payload: true,
|
|
650
345
|
})
|
|
346
|
+
.where(eq("a", 1))
|
|
347
|
+
.fetch()
|
|
651
348
|
|
|
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
|
-
})
|
|
349
|
+
expect(page.entities[0].toJson()).toEqual({})
|
|
350
|
+
expectType<Uint8Array>(page.entities[0].payload)
|
|
351
|
+
})
|
|
667
352
|
|
|
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
|
-
)
|
|
353
|
+
it("treats a named attribute subset as a selection of attributes", async () => {
|
|
354
|
+
const { client } = makeClient({
|
|
355
|
+
blockNumber: "0x1",
|
|
356
|
+
data: [{ key: KEY, attributes: [{ name: "version", type: "i32", value: 4 }] }],
|
|
683
357
|
})
|
|
358
|
+
const page = await new SelectQueryBuilder<
|
|
359
|
+
ProjectedEntity<{ key: true; attributes: { version: true } }>
|
|
360
|
+
>(client, { key: true, attributes: { version: true } })
|
|
361
|
+
.where(eq("a", 1))
|
|
362
|
+
.fetch()
|
|
363
|
+
|
|
364
|
+
expectType<Attributes>(page.entities[0].attributes)
|
|
365
|
+
expect(page.entities[0].attributes).toEqual({ version: { type: "i32", value: 4 } })
|
|
684
366
|
})
|
|
685
367
|
})
|
|
368
|
+
|
|
369
|
+
/** Asserts the static type of an expression without touching it at runtime. */
|
|
370
|
+
function expectType<T>(_value: T): void {}
|