@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,270 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, jest, test } from "bun:test"
|
|
2
|
-
import { NoCursorOrLimitError, NoMoreResultsError } from "../errors"
|
|
3
|
-
import { Entity } from "../types/entity"
|
|
4
|
-
import type { QueryBuilder } from "./queryBuilder"
|
|
5
|
-
import { QueryResult } from "./queryResult"
|
|
6
|
-
|
|
7
|
-
describe("QueryResult", () => {
|
|
8
|
-
let mockQueryBuilder: QueryBuilder
|
|
9
|
-
let mockEntities: Entity[]
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
// Create mock entities
|
|
13
|
-
mockEntities = [
|
|
14
|
-
new Entity("0x1" as const, "0xowner1" as const, 1000n, new Uint8Array([1]), []),
|
|
15
|
-
new Entity("0x2" as const, "0xowner2" as const, 2000n, new Uint8Array([2]), []),
|
|
16
|
-
new Entity("0x3" as const, "0xowner3" as const, 3000n, new Uint8Array([3]), []),
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
// Create mock query builder
|
|
20
|
-
mockQueryBuilder = {
|
|
21
|
-
cursor: jest.fn().mockReturnThis(),
|
|
22
|
-
fetch: jest.fn(),
|
|
23
|
-
} as unknown as QueryBuilder
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
describe("constructor", () => {
|
|
27
|
-
test("creates QueryResult with entities and query builder", () => {
|
|
28
|
-
const result = new QueryResult(
|
|
29
|
-
mockEntities,
|
|
30
|
-
mockQueryBuilder,
|
|
31
|
-
undefined,
|
|
32
|
-
undefined,
|
|
33
|
-
undefined,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
expect(result.entities).toEqual(mockEntities)
|
|
37
|
-
expect(result.entities).toHaveLength(3)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test("creates QueryResult with limit and cursor", () => {
|
|
41
|
-
const result = new QueryResult(mockEntities, mockQueryBuilder, "0xABC123", 10, undefined)
|
|
42
|
-
|
|
43
|
-
expect(result.entities).toEqual(mockEntities)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
test("creates QueryResult with undefined limit and cursor", () => {
|
|
47
|
-
const result = new QueryResult(
|
|
48
|
-
mockEntities,
|
|
49
|
-
mockQueryBuilder,
|
|
50
|
-
undefined,
|
|
51
|
-
undefined,
|
|
52
|
-
undefined,
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
expect(result.entities).toEqual(mockEntities)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test("creates QueryResult with empty entities array", () => {
|
|
59
|
-
const result = new QueryResult([], mockQueryBuilder, "0xABC123", 10, undefined)
|
|
60
|
-
|
|
61
|
-
expect(result.entities).toEqual([])
|
|
62
|
-
expect(result.entities).toHaveLength(0)
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
describe("next()", () => {
|
|
67
|
-
test("throws NoCursorOrLimitError when cursor is undefined", async () => {
|
|
68
|
-
const result = new QueryResult(mockEntities, mockQueryBuilder, undefined, 10, undefined)
|
|
69
|
-
|
|
70
|
-
await expect(result.next()).rejects.toThrow(NoCursorOrLimitError)
|
|
71
|
-
await expect(result.next()).rejects.toThrow("Cursor and limit must be defined to fetch next")
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
test("throws NoCursorOrLimitError when limit is undefined", async () => {
|
|
75
|
-
const result = new QueryResult(
|
|
76
|
-
mockEntities,
|
|
77
|
-
mockQueryBuilder,
|
|
78
|
-
"0xABC123",
|
|
79
|
-
undefined,
|
|
80
|
-
undefined,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
await expect(result.next()).rejects.toThrow(NoCursorOrLimitError)
|
|
84
|
-
await expect(result.next()).rejects.toThrow("Cursor and limit must be defined to fetch next")
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
test("throws NoCursorOrLimitError when both cursor and limit are undefined", async () => {
|
|
88
|
-
const result = new QueryResult(
|
|
89
|
-
mockEntities,
|
|
90
|
-
mockQueryBuilder,
|
|
91
|
-
undefined,
|
|
92
|
-
undefined,
|
|
93
|
-
undefined,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
await expect(result.next()).rejects.toThrow(NoCursorOrLimitError)
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
test("throws NoMoreResultsError when limit > entities.length", async () => {
|
|
100
|
-
// limit is 4 but we have 3 entities, so limit > entities.length triggers error
|
|
101
|
-
const result = new QueryResult(mockEntities, mockQueryBuilder, "0xABC123", 4, 123n)
|
|
102
|
-
|
|
103
|
-
await expect(result.next()).rejects.toThrow(NoMoreResultsError)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
test("fetches next page when limit equals entities length", async () => {
|
|
107
|
-
const newEntities = [
|
|
108
|
-
new Entity("0x4" as const, "0xowner4" as const, 4000n, new Uint8Array([4]), []),
|
|
109
|
-
new Entity("0x5" as const, "0xowner5" as const, 5000n, new Uint8Array([5]), []),
|
|
110
|
-
new Entity("0x6" as const, "0xowner6" as const, 6000n, new Uint8Array([6]), []),
|
|
111
|
-
]
|
|
112
|
-
|
|
113
|
-
const mockFetch = jest.fn().mockResolvedValue({
|
|
114
|
-
entities: newEntities,
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
mockQueryBuilder.fetch = mockFetch
|
|
118
|
-
mockQueryBuilder.cursor = jest.fn().mockReturnValue(mockQueryBuilder)
|
|
119
|
-
|
|
120
|
-
// limit is 3 and we have 3 entities, so there might be more
|
|
121
|
-
const result = new QueryResult(mockEntities, mockQueryBuilder, "0xABC123", 3, undefined)
|
|
122
|
-
|
|
123
|
-
await result.next()
|
|
124
|
-
|
|
125
|
-
expect(mockQueryBuilder.cursor).toHaveBeenCalledWith("0xABC123")
|
|
126
|
-
expect(mockFetch).toHaveBeenCalled()
|
|
127
|
-
expect(result.entities).toEqual(newEntities)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
test("updates offset correctly for multiple next calls", async () => {
|
|
131
|
-
const initialEntities = [
|
|
132
|
-
new Entity("0x1" as const, "0xowner1" as const, 1000n, new Uint8Array([1]), []),
|
|
133
|
-
new Entity("0x2" as const, "0xowner2" as const, 2000n, new Uint8Array([2]), []),
|
|
134
|
-
new Entity("0x3" as const, "0xowner3" as const, 3000n, new Uint8Array([3]), []),
|
|
135
|
-
]
|
|
136
|
-
|
|
137
|
-
const newEntities1 = [
|
|
138
|
-
new Entity("0x4" as const, "0xowner4" as const, 4000n, new Uint8Array([4]), []),
|
|
139
|
-
new Entity("0x5" as const, "0xowner5" as const, 5000n, new Uint8Array([5]), []),
|
|
140
|
-
new Entity("0x6" as const, "0xowner6" as const, 6000n, new Uint8Array([6]), []),
|
|
141
|
-
]
|
|
142
|
-
|
|
143
|
-
const newEntities2 = [
|
|
144
|
-
new Entity("0x7" as const, "0xowner7" as const, 7000n, new Uint8Array([7]), []),
|
|
145
|
-
new Entity("0x8" as const, "0xowner8" as const, 8000n, new Uint8Array([8]), []),
|
|
146
|
-
new Entity("0x9" as const, "0xowner9" as const, 9000n, new Uint8Array([9]), []),
|
|
147
|
-
]
|
|
148
|
-
|
|
149
|
-
const mockCursor = jest.fn().mockReturnValue(mockQueryBuilder)
|
|
150
|
-
const mockFetch = jest
|
|
151
|
-
.fn()
|
|
152
|
-
.mockResolvedValueOnce({
|
|
153
|
-
entities: newEntities1,
|
|
154
|
-
queryBuilder: mockQueryBuilder,
|
|
155
|
-
cursor: "0xABC123",
|
|
156
|
-
})
|
|
157
|
-
.mockResolvedValueOnce({
|
|
158
|
-
entities: newEntities2,
|
|
159
|
-
queryBuilder: mockQueryBuilder,
|
|
160
|
-
cursor: "0xABC123",
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
mockQueryBuilder.cursor = mockCursor
|
|
164
|
-
mockQueryBuilder.fetch = mockFetch
|
|
165
|
-
|
|
166
|
-
// limit = 3, entities.length = 3, so 3 < 3 is false (won't throw)
|
|
167
|
-
const result = new QueryResult(initialEntities, mockQueryBuilder, "0xABC123", 3, undefined)
|
|
168
|
-
|
|
169
|
-
// First next call - offset should be 0 + 3 = 3
|
|
170
|
-
await result.next()
|
|
171
|
-
expect(mockCursor).toHaveBeenCalledWith("0xABC123")
|
|
172
|
-
expect(result.entities).toEqual(newEntities1)
|
|
173
|
-
|
|
174
|
-
// Second next call - offset should be 3 + 3 = 6
|
|
175
|
-
await result.next()
|
|
176
|
-
expect(mockCursor).toHaveBeenCalledWith("0xABC123")
|
|
177
|
-
expect(result.entities).toEqual(newEntities2)
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
test("fetches next page when limit is greater than entities length", async () => {
|
|
181
|
-
// limit is 5 but only 3 entities (5 > 3) so there is no more results
|
|
182
|
-
// so NoMoreResultsError is thrown
|
|
183
|
-
const result = new QueryResult(mockEntities, mockQueryBuilder, "0xABC123", 5, undefined)
|
|
184
|
-
|
|
185
|
-
await expect(result.next()).rejects.toThrow(NoMoreResultsError)
|
|
186
|
-
})
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
describe("pagination flow", () => {
|
|
190
|
-
test("simulates complete pagination: next -> next -> previous -> previous", async () => {
|
|
191
|
-
const page1 = [
|
|
192
|
-
new Entity("0x1" as const, "0xowner1" as const, 1000n, new Uint8Array([1]), []),
|
|
193
|
-
]
|
|
194
|
-
const page2 = [
|
|
195
|
-
new Entity("0x2" as const, "0xowner2" as const, 2000n, new Uint8Array([2]), []),
|
|
196
|
-
]
|
|
197
|
-
const page3 = [
|
|
198
|
-
new Entity("0x3" as const, "0xowner3" as const, 3000n, new Uint8Array([3]), []),
|
|
199
|
-
]
|
|
200
|
-
|
|
201
|
-
const mockCursor = jest.fn().mockReturnValue(mockQueryBuilder)
|
|
202
|
-
const mockFetch = jest
|
|
203
|
-
.fn()
|
|
204
|
-
.mockResolvedValueOnce({
|
|
205
|
-
entities: page2,
|
|
206
|
-
queryBuilder: mockQueryBuilder,
|
|
207
|
-
cursor: "0xABC123",
|
|
208
|
-
}) // first next
|
|
209
|
-
.mockResolvedValueOnce({
|
|
210
|
-
entities: page3,
|
|
211
|
-
queryBuilder: mockQueryBuilder,
|
|
212
|
-
cursor: "0xABC123",
|
|
213
|
-
}) // second next
|
|
214
|
-
.mockResolvedValueOnce({
|
|
215
|
-
entities: page2,
|
|
216
|
-
queryBuilder: mockQueryBuilder,
|
|
217
|
-
cursor: "0xABC123",
|
|
218
|
-
}) // first previous
|
|
219
|
-
.mockResolvedValueOnce({
|
|
220
|
-
entities: page1,
|
|
221
|
-
queryBuilder: mockQueryBuilder,
|
|
222
|
-
cursor: "0xABC123",
|
|
223
|
-
}) // second previous
|
|
224
|
-
|
|
225
|
-
mockQueryBuilder.cursor = mockCursor
|
|
226
|
-
mockQueryBuilder.fetch = mockFetch
|
|
227
|
-
|
|
228
|
-
const result = new QueryResult(page1, mockQueryBuilder, "0xABC123", 1, undefined)
|
|
229
|
-
|
|
230
|
-
// Start at page 1 (offset 0)
|
|
231
|
-
expect(result.entities).toEqual(page1)
|
|
232
|
-
|
|
233
|
-
// Next to page 2 (offset 1)
|
|
234
|
-
await result.next()
|
|
235
|
-
expect(mockCursor).toHaveBeenCalledWith("0xABC123")
|
|
236
|
-
expect(result.entities).toEqual(page2)
|
|
237
|
-
|
|
238
|
-
// Next to page 3 (offset 2)
|
|
239
|
-
await result.next()
|
|
240
|
-
expect(mockCursor).toHaveBeenCalledWith("0xABC123")
|
|
241
|
-
expect(result.entities).toEqual(page3)
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
test("There is another page to fetch when limit < entities.length", async () => {
|
|
245
|
-
// limit is 2 but 3 entities, so 2 < 3 there is another page to fetch
|
|
246
|
-
const mockCursor = jest.fn().mockReturnValue(mockQueryBuilder)
|
|
247
|
-
const mockFetch = jest
|
|
248
|
-
.fn()
|
|
249
|
-
.mockResolvedValueOnce({
|
|
250
|
-
entities: mockEntities,
|
|
251
|
-
queryBuilder: mockQueryBuilder,
|
|
252
|
-
cursor: "0xABC123",
|
|
253
|
-
})
|
|
254
|
-
.mockResolvedValueOnce({
|
|
255
|
-
entities: mockEntities,
|
|
256
|
-
queryBuilder: mockQueryBuilder,
|
|
257
|
-
cursor: "0xABC123",
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
mockQueryBuilder.cursor = mockCursor
|
|
261
|
-
mockQueryBuilder.fetch = mockFetch
|
|
262
|
-
|
|
263
|
-
const result = new QueryResult(mockEntities, mockQueryBuilder, "0xABC123", 2, undefined)
|
|
264
|
-
|
|
265
|
-
await result.next()
|
|
266
|
-
expect(mockCursor).toHaveBeenCalledWith("0xABC123")
|
|
267
|
-
expect(result.entities).toEqual(mockEntities)
|
|
268
|
-
})
|
|
269
|
-
})
|
|
270
|
-
})
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, jest, test } from "bun:test"
|
|
2
|
-
import type { ArkivClient } from "../clients/baseClient"
|
|
3
|
-
import type { Entity } from "../types/entity"
|
|
4
|
-
import * as entitiesUtils from "../utils/entities"
|
|
5
|
-
import * as engine from "./engine"
|
|
6
|
-
import { eq, gte } from "./predicate"
|
|
7
|
-
import { SelectQueryBuilder } from "./queryBuilder"
|
|
8
|
-
import { selectionToIncludeData } from "./selection"
|
|
9
|
-
|
|
10
|
-
describe("SelectQueryBuilder", () => {
|
|
11
|
-
let mockClient: ArkivClient
|
|
12
|
-
let mockProcessQuery: any
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
mockClient = {
|
|
16
|
-
request: jest.fn(),
|
|
17
|
-
} as unknown as ArkivClient
|
|
18
|
-
|
|
19
|
-
mockProcessQuery = jest.spyOn(engine, "processQuery")
|
|
20
|
-
jest.spyOn(entitiesUtils, "entityFromRpcResult")
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
afterEach(() => {
|
|
24
|
-
jest.restoreAllMocks()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test("select() with no argument forwards include-data for everything", async () => {
|
|
28
|
-
mockProcessQuery.mockResolvedValue({ data: [] })
|
|
29
|
-
|
|
30
|
-
await new SelectQueryBuilder(mockClient).where(eq("name", "test")).fetch()
|
|
31
|
-
|
|
32
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(
|
|
33
|
-
mockClient,
|
|
34
|
-
expect.objectContaining({
|
|
35
|
-
predicates: [eq("name", "test")],
|
|
36
|
-
includeData: selectionToIncludeData(),
|
|
37
|
-
}),
|
|
38
|
-
)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
test('select("*") forwards include-data for everything', async () => {
|
|
42
|
-
mockProcessQuery.mockResolvedValue({ data: [] })
|
|
43
|
-
|
|
44
|
-
await new SelectQueryBuilder(mockClient, "*").fetch()
|
|
45
|
-
|
|
46
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(
|
|
47
|
-
mockClient,
|
|
48
|
-
expect.objectContaining({
|
|
49
|
-
includeData: selectionToIncludeData("*"),
|
|
50
|
-
}),
|
|
51
|
-
)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test("select({ owner: true, attributes: true }) forwards the resolved include-data", async () => {
|
|
55
|
-
mockProcessQuery.mockResolvedValue({ data: [] })
|
|
56
|
-
|
|
57
|
-
await new SelectQueryBuilder(mockClient, { owner: true, attributes: true }).fetch()
|
|
58
|
-
|
|
59
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(
|
|
60
|
-
mockClient,
|
|
61
|
-
expect.objectContaining({
|
|
62
|
-
includeData: selectionToIncludeData({ owner: true, attributes: true }),
|
|
63
|
-
}),
|
|
64
|
-
)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test("select({ owner: true }) forwards narrowed include-data", async () => {
|
|
68
|
-
mockProcessQuery.mockResolvedValue({ data: [] })
|
|
69
|
-
|
|
70
|
-
await new SelectQueryBuilder(mockClient, { owner: true }).fetch()
|
|
71
|
-
|
|
72
|
-
const call = mockProcessQuery.mock.calls[0][1]
|
|
73
|
-
expect(call.includeData).toEqual(selectionToIncludeData({ owner: true }))
|
|
74
|
-
expect(call.includeData.owner).toBe(true)
|
|
75
|
-
expect(call.includeData.creator).toBe(false)
|
|
76
|
-
// key is opt-in: not selected here, so it is excluded
|
|
77
|
-
expect(call.includeData.key).toBe(false)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
test("does not forward the with* boolean flags", async () => {
|
|
81
|
-
mockProcessQuery.mockResolvedValue({ data: [] })
|
|
82
|
-
|
|
83
|
-
await new SelectQueryBuilder(mockClient, { attributes: true }).fetch()
|
|
84
|
-
|
|
85
|
-
const call = mockProcessQuery.mock.calls[0][1]
|
|
86
|
-
expect(call.withAttributes).toBeUndefined()
|
|
87
|
-
expect(call.withMetadata).toBeUndefined()
|
|
88
|
-
expect(call.withPayload).toBeUndefined()
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test("shares the filtering/ordering/pagination chain methods", async () => {
|
|
92
|
-
mockProcessQuery.mockResolvedValue({ data: [] })
|
|
93
|
-
|
|
94
|
-
const owner = "0x1234567890123456789012345678901234567890" as const
|
|
95
|
-
await new SelectQueryBuilder(mockClient, { attributes: true })
|
|
96
|
-
.where(eq("name", "test"))
|
|
97
|
-
.where(gte("age", 18))
|
|
98
|
-
.ownedBy(owner)
|
|
99
|
-
.createdBy(owner)
|
|
100
|
-
.orderBy("name", "string", "desc")
|
|
101
|
-
.limit(10)
|
|
102
|
-
.cursor("0xABC123")
|
|
103
|
-
.validAtBlock(123n)
|
|
104
|
-
.fetch()
|
|
105
|
-
|
|
106
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
107
|
-
predicates: [eq("name", "test"), gte("age", 18)],
|
|
108
|
-
limit: 10,
|
|
109
|
-
cursor: "0xABC123",
|
|
110
|
-
ownedBy: owner,
|
|
111
|
-
createdBy: owner,
|
|
112
|
-
orderBy: [{ name: "name", type: "string", desc: true }],
|
|
113
|
-
validAtBlock: 123n,
|
|
114
|
-
includeData: selectionToIncludeData({ attributes: true }),
|
|
115
|
-
})
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
test("count() ignores the selection and requests no data", async () => {
|
|
119
|
-
mockProcessQuery.mockResolvedValue({
|
|
120
|
-
data: [
|
|
121
|
-
{ key: "0xabc", value: "data1" },
|
|
122
|
-
{ key: "0xdef", value: "data2" },
|
|
123
|
-
],
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
const count = await new SelectQueryBuilder(mockClient, "*")
|
|
127
|
-
.where(eq("status", "active"))
|
|
128
|
-
.count()
|
|
129
|
-
|
|
130
|
-
expect(count).toBe(2)
|
|
131
|
-
expect(mockProcessQuery).toHaveBeenCalledWith(mockClient, {
|
|
132
|
-
predicates: [eq("status", "active")],
|
|
133
|
-
limit: undefined,
|
|
134
|
-
cursor: undefined,
|
|
135
|
-
ownedBy: undefined,
|
|
136
|
-
createdBy: undefined,
|
|
137
|
-
orderBy: undefined,
|
|
138
|
-
validAtBlock: undefined,
|
|
139
|
-
withAttributes: false,
|
|
140
|
-
withMetadata: false,
|
|
141
|
-
withPayload: false,
|
|
142
|
-
})
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
describe("result entities", () => {
|
|
146
|
-
test("fetch returns flat Entity instances with the selected data and methods", async () => {
|
|
147
|
-
mockProcessQuery.mockResolvedValue({
|
|
148
|
-
data: [
|
|
149
|
-
{
|
|
150
|
-
key: "0xabc",
|
|
151
|
-
owner: "0xowner",
|
|
152
|
-
value: "0x68656c6c6f", // "hello"
|
|
153
|
-
stringAttributes: [{ key: "category", value: "docs" }],
|
|
154
|
-
numericAttributes: [{ key: "score", value: "0x2a" }],
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
cursor: "",
|
|
158
|
-
blockNumber: "0x1",
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
const result = await new SelectQueryBuilder(mockClient, {
|
|
162
|
-
key: true,
|
|
163
|
-
owner: true,
|
|
164
|
-
attributes: true,
|
|
165
|
-
payload: true,
|
|
166
|
-
}).fetch()
|
|
167
|
-
|
|
168
|
-
expect(result.entities).toHaveLength(1)
|
|
169
|
-
const entity = result.entities[0] as unknown as Entity
|
|
170
|
-
// flat, backwards-compatible field access
|
|
171
|
-
expect(entity.key).toBe("0xabc")
|
|
172
|
-
expect(entity.owner).toBe("0xowner")
|
|
173
|
-
expect(entity.attributes).toEqual([
|
|
174
|
-
{ key: "category", value: "docs" },
|
|
175
|
-
{ key: "score", value: 42 },
|
|
176
|
-
])
|
|
177
|
-
// entity methods are preserved on the result
|
|
178
|
-
expect(typeof entity.toText).toBe("function")
|
|
179
|
-
expect(entity.toText()).toBe("hello")
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
test("toJson decodes a JSON payload", async () => {
|
|
183
|
-
mockProcessQuery.mockResolvedValue({
|
|
184
|
-
data: [{ key: "0xabc", value: "0x7b2261223a317d" }], // {"a":1}
|
|
185
|
-
cursor: "",
|
|
186
|
-
blockNumber: "0x1",
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
const result = await new SelectQueryBuilder(mockClient, { payload: true }).fetch()
|
|
190
|
-
const entity = result.entities[0] as unknown as Entity
|
|
191
|
-
expect(entity.toJson()).toEqual({ a: 1 })
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
test("fields the RPC omits (because they were not selected) are undefined at runtime", async () => {
|
|
195
|
-
// The RPC honours includeData and returns only the key.
|
|
196
|
-
mockProcessQuery.mockResolvedValue({
|
|
197
|
-
data: [{ key: "0xabc" }],
|
|
198
|
-
cursor: "",
|
|
199
|
-
blockNumber: "0x1",
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
const result = await new SelectQueryBuilder(mockClient, { key: true }).fetch()
|
|
203
|
-
const entity = result.entities[0] as unknown as Entity
|
|
204
|
-
expect(entity.key).toBe("0xabc")
|
|
205
|
-
expect(entity.owner).toBeUndefined()
|
|
206
|
-
expect(entity.payload).toBeUndefined()
|
|
207
|
-
expect(entity.attributes).toEqual([])
|
|
208
|
-
})
|
|
209
|
-
})
|
|
210
|
-
})
|
package/src/types/attributes.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* An entity attribute (key/value pair).
|
|
3
|
-
*
|
|
4
|
-
* Arkiv supports both string and number values. Numeric values **must be
|
|
5
|
-
* integers** — passing a non-integer number (e.g. `1.5`) throws an
|
|
6
|
-
* {@link InvalidAttributeError} at mutation time. To store a non-integer value:
|
|
7
|
-
* - Scale it to an integer (e.g. `1.5` -> `1500`, dividing by the same factor on
|
|
8
|
-
* read) to keep numeric ordering and range queries working.
|
|
9
|
-
* - Or pass it as a string (e.g. `"1.5"`), which sorts lexicographically and so
|
|
10
|
-
* does not support numeric comparisons.
|
|
11
|
-
*/
|
|
12
|
-
export type Attribute = {
|
|
13
|
-
key: string
|
|
14
|
-
/** Attribute value. A `number` must be an integer (scale non-integers, e.g. `1.5` -> `1500`, or use a string). Throws {@link InvalidAttributeError} otherwise. */
|
|
15
|
-
value: string | number
|
|
16
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test"
|
|
2
|
-
import { BLOCK_TIME } from "../consts"
|
|
3
|
-
import { InvalidAttributeError, InvalidExpirationError } from "../errors"
|
|
4
|
-
import { validateAttribute, validateExpiresIn } from "./validation"
|
|
5
|
-
|
|
6
|
-
describe("validateExpiresIn", () => {
|
|
7
|
-
it("accepts positive integers that are a multiple of the block time", () => {
|
|
8
|
-
expect(() => validateExpiresIn(BLOCK_TIME)).not.toThrow()
|
|
9
|
-
expect(() => validateExpiresIn(1000)).not.toThrow()
|
|
10
|
-
expect(() => validateExpiresIn(BLOCK_TIME * 12345)).not.toThrow()
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
it("throws for non-integer values", () => {
|
|
14
|
-
expect(() => validateExpiresIn(51.5)).toThrow(InvalidExpirationError)
|
|
15
|
-
expect(() => validateExpiresIn(1.5)).toThrow(InvalidExpirationError)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it("throws for values that are not a multiple of the block time", () => {
|
|
19
|
-
expect(() => validateExpiresIn(51)).toThrow(InvalidExpirationError)
|
|
20
|
-
expect(() => validateExpiresIn(999)).toThrow(InvalidExpirationError)
|
|
21
|
-
expect(() => validateExpiresIn(1)).toThrow(InvalidExpirationError)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it("throws for zero and negative values", () => {
|
|
25
|
-
expect(() => validateExpiresIn(0)).toThrow(InvalidExpirationError)
|
|
26
|
-
expect(() => validateExpiresIn(-1000)).toThrow(InvalidExpirationError)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it("throws for NaN and non-finite values", () => {
|
|
30
|
-
expect(() => validateExpiresIn(Number.NaN)).toThrow(InvalidExpirationError)
|
|
31
|
-
expect(() => validateExpiresIn(Number.POSITIVE_INFINITY)).toThrow(InvalidExpirationError)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it("includes the offending value in the error message", () => {
|
|
35
|
-
expect(() => validateExpiresIn(51)).toThrow(/51/)
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
describe("validateAttribute", () => {
|
|
40
|
-
it("accepts string values", () => {
|
|
41
|
-
expect(() => validateAttribute({ key: "k", value: "1.5" })).not.toThrow()
|
|
42
|
-
expect(() => validateAttribute({ key: "k", value: "anything" })).not.toThrow()
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it("accepts integer numeric values", () => {
|
|
46
|
-
expect(() => validateAttribute({ key: "k", value: 0 })).not.toThrow()
|
|
47
|
-
expect(() => validateAttribute({ key: "k", value: 123 })).not.toThrow()
|
|
48
|
-
expect(() => validateAttribute({ key: "k", value: -5 })).not.toThrow()
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it("throws for non-integer numeric values", () => {
|
|
52
|
-
expect(() => validateAttribute({ key: "k", value: 1.5 })).toThrow(InvalidAttributeError)
|
|
53
|
-
expect(() => validateAttribute({ key: "k", value: Number.NaN })).toThrow(InvalidAttributeError)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it("includes the attribute key and value in the error message", () => {
|
|
57
|
-
expect(() => validateAttribute({ key: "myKey", value: 1.5 })).toThrow(/myKey/)
|
|
58
|
-
expect(() => validateAttribute({ key: "myKey", value: 1.5 })).toThrow(/1\.5/)
|
|
59
|
-
})
|
|
60
|
-
})
|
package/src/utils/validation.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { BLOCK_TIME } from "../consts"
|
|
2
|
-
import { InvalidAttributeError, InvalidExpirationError } from "../errors"
|
|
3
|
-
import type { Attribute } from "../types"
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Expiration on Arkiv is measured in blocks (1 block =
|
|
7
|
-
* 2 seconds), so the value must be a positive integer that is
|
|
8
|
-
* a multiple of the block time.
|
|
9
|
-
*
|
|
10
|
-
* @param expiresIn - The expiration in seconds.
|
|
11
|
-
* @throws {InvalidExpirationError} If the value is not a positive integer that
|
|
12
|
-
* is a multiple of the block time.
|
|
13
|
-
*/
|
|
14
|
-
export function validateExpiresIn(expiresIn: number): void {
|
|
15
|
-
if (!Number.isInteger(expiresIn) || expiresIn <= 0 || expiresIn % BLOCK_TIME !== 0) {
|
|
16
|
-
throw new InvalidExpirationError(expiresIn)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Validates a single attribute. Arkiv supports string and number attribute
|
|
22
|
-
* values, but numeric values must be integers.
|
|
23
|
-
*
|
|
24
|
-
* @param attribute - The attribute to validate.
|
|
25
|
-
* @throws {InvalidAttributeError} If the attribute has a non-integer numeric
|
|
26
|
-
* value.
|
|
27
|
-
*/
|
|
28
|
-
export function validateAttribute(attribute: Attribute): void {
|
|
29
|
-
if (typeof attribute.value === "number" && !Number.isInteger(attribute.value)) {
|
|
30
|
-
throw new InvalidAttributeError(attribute.key, attribute.value)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
File without changes
|
|
File without changes
|