@arkiv-network/sdk 0.1.1-dev.0 → 0.1.3-dev.0

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.
Files changed (58) hide show
  1. package/README.md +22 -10
  2. package/dist/chains/index.d.ts +25 -25
  3. package/dist/chains/index.d.ts.map +1 -1
  4. package/dist/index.cjs +30 -9
  5. package/dist/index.js +30 -16
  6. package/dist/index.js.map +1 -1
  7. package/dist/utils/index.d.ts.map +1 -1
  8. package/package.json +22 -8
  9. package/src/accounts/index.ts +1 -0
  10. package/src/actions/public/buildQuery.ts +6 -0
  11. package/src/actions/public/getBlockTiming.ts +20 -0
  12. package/src/actions/public/getEntity.ts +18 -0
  13. package/src/actions/public/getEntityCount.ts +10 -0
  14. package/src/actions/public/query.ts +13 -0
  15. package/src/actions/public/subscribeEntityEvents.ts +105 -0
  16. package/src/actions/wallet/changeOwnership.ts +40 -0
  17. package/src/actions/wallet/createEntity.ts +45 -0
  18. package/src/actions/wallet/deleteEntity.ts +39 -0
  19. package/src/actions/wallet/extendEntity.ts +41 -0
  20. package/src/actions/wallet/mutateEntities.ts +100 -0
  21. package/src/actions/wallet/updateEntity.ts +47 -0
  22. package/src/chains/index.ts +3 -0
  23. package/src/chains/kaolin.ts +26 -0
  24. package/src/chains/localhost.ts +14 -0
  25. package/src/chains/marketplace.ts +26 -0
  26. package/src/clients/baseClient.ts +9 -0
  27. package/src/clients/createPublicClient.test.ts +22 -0
  28. package/src/clients/createPublicClient.ts +65 -0
  29. package/src/clients/createWalletClient.test.ts +23 -0
  30. package/src/clients/createWalletClient.ts +70 -0
  31. package/src/clients/decorators/arkivPublic.ts +203 -0
  32. package/src/clients/decorators/arkivWallet.ts +250 -0
  33. package/src/consts.ts +2 -0
  34. package/src/index.ts +21 -0
  35. package/src/query/engine.test.ts +288 -0
  36. package/src/query/engine.ts +112 -0
  37. package/src/query/errors.ts +23 -0
  38. package/src/query/index.ts +4 -0
  39. package/src/query/predicate.test.ts +418 -0
  40. package/src/query/predicate.ts +140 -0
  41. package/src/query/queryBuilder.test.ts +537 -0
  42. package/src/query/queryBuilder.ts +209 -0
  43. package/src/query/queryResult.test.ts +270 -0
  44. package/src/query/queryResult.ts +61 -0
  45. package/src/types/attributes.ts +4 -0
  46. package/src/types/entity.ts +36 -0
  47. package/src/types/events.ts +34 -0
  48. package/src/types/index.ts +4 -0
  49. package/src/types/mimeTypes.ts +27 -0
  50. package/src/types/rpcSchema.ts +61 -0
  51. package/src/types/txParams.ts +15 -0
  52. package/src/utils/arkivTransactions.ts +100 -0
  53. package/src/utils/compression.test.ts +32 -0
  54. package/src/utils/compression.ts +99 -0
  55. package/src/utils/entities.ts +23 -0
  56. package/src/utils/expirationTime.ts +13 -0
  57. package/src/utils/index.ts +3 -0
  58. package/src/utils/payload.ts +9 -0
@@ -0,0 +1,209 @@
1
+ import type { Hex } from "viem"
2
+ import type { ArkivClient } from "../clients/baseClient"
3
+ import { entityFromRpcResult } from "../utils/entities"
4
+ import { processQuery } from "./engine"
5
+ import type { Predicate } from "./predicate"
6
+ import { QueryResult } from "./queryResult"
7
+
8
+ /**
9
+ * QueryBuilder is a helper class to build queries to the Arkiv DBChains.
10
+ * It can be used to fetch entities from the Arkiv DBChains. It follows the Builder pattern allowing chaining of methods.
11
+ * @param client - The Arkiv client
12
+ * @returns The QueryBuilder instance {@link QueryBuilder}
13
+ */
14
+ export class QueryBuilder {
15
+ private _client: ArkivClient
16
+ private _ownedBy: Hex | undefined
17
+ private _validAtBlock: bigint | undefined
18
+ private _withAttributes: boolean | undefined
19
+ private _withMetadata: boolean | undefined
20
+ private _withPayload: boolean | undefined
21
+ private _limit: number | undefined
22
+ private _cursor: string | undefined
23
+ private _predicates: Predicate[]
24
+
25
+ constructor(client: ArkivClient) {
26
+ this._client = client
27
+ this._predicates = []
28
+ }
29
+
30
+ /**
31
+ * Sets the ownedBy filter
32
+ * @param ownedBy - The address of the owner
33
+ * @returns The QueryBuilder instance
34
+ *
35
+ * @example
36
+ * const builder = new QueryBuilder(client)
37
+ * builder.ownedBy("0x1234567890123456789012345678901234567890")
38
+ */
39
+ ownedBy(ownedBy: Hex) {
40
+ this._ownedBy = ownedBy
41
+ return this
42
+ }
43
+
44
+ /**
45
+ * Sets the withAttributes flag which will return the attributes for the entities if true
46
+ * @param withAttributes - The boolean value to set
47
+ * @returns The QueryBuilder instance
48
+ *
49
+ * @example
50
+ * const builder = new QueryBuilder(client)
51
+ * builder.withAttributes(true)
52
+ */
53
+ withAttributes(withAttributes: boolean = true) {
54
+ this._withAttributes = withAttributes
55
+ return this
56
+ }
57
+
58
+ /**
59
+ * Sets the withMetadata flag which will return the metadata (like owner, expiredAt, etc.) for the entities if true
60
+ * @param withMetadata - The boolean value to set
61
+ * @returns The QueryBuilder instance
62
+ *
63
+ * @example
64
+ * const builder = new QueryBuilder(client)
65
+ * builder.withMetadata(true)
66
+ */
67
+ withMetadata(withMetadata: boolean = true) {
68
+ this._withMetadata = withMetadata
69
+ return this
70
+ }
71
+
72
+ /**
73
+ * Sets the withPayload flag which will return the payload for the entities if true
74
+ * @param withPayload - The boolean value to set
75
+ * @returns The QueryBuilder instance
76
+ *
77
+ * @example
78
+ * const builder = new QueryBuilder(client)
79
+ * builder.withPayload(true)
80
+ */
81
+ withPayload(withPayload: boolean = true) {
82
+ this._withPayload = withPayload
83
+ return this
84
+ }
85
+
86
+ /**
87
+ * Sets the limit for the query
88
+ * @param limit - The number of entities to return
89
+ * @returns The QueryBuilder instance
90
+ *
91
+ * @example
92
+ * const builder = new QueryBuilder(client)
93
+ * builder.limit(10)
94
+ */
95
+ limit(limit: number) {
96
+ this._limit = limit
97
+ return this
98
+ }
99
+
100
+ /**
101
+ * Sets the cursor for the query - it is advances setting which rather shouldn't be used manually but it is provided from query result if limit is used (pagination).
102
+ * @param cursor - The cursor to set which tells to RPC Query server where to start or continue the query.
103
+ * @returns The QueryBuilder instance
104
+ *
105
+ * @example
106
+ * const builder = new QueryBuilder(client)
107
+ * builder.offset(10)
108
+ */
109
+ cursor(cursor: string) {
110
+ this._cursor = cursor
111
+ return this
112
+ }
113
+
114
+ /**
115
+ * Sets the validAtBlock for the query which tells at which block height the state we are intested.
116
+ * If not set, the latest block is used.
117
+ * @param validAtBlock - The block number to set
118
+ * @returns The QueryBuilder instance
119
+ *
120
+ * @example
121
+ * const builder = new QueryBuilder(client)
122
+ * builder.validAtBlock(10000)
123
+ */
124
+ validAtBlock(validAtBlock: bigint) {
125
+ this._validAtBlock = validAtBlock
126
+ return this
127
+ }
128
+
129
+ /**
130
+ * Sets the predicates for the query limiting the results. It can be a single predicate or an array of predicates combined with 'and'.
131
+ * Predicates can be nested using 'or' and 'and' predicates.
132
+ * @param predicates - The predicates to set
133
+ * @returns The QueryBuilder instance
134
+ *
135
+ * @example
136
+ * const builder = new QueryBuilder(client)
137
+ * builder.where(eq("name", "John"))
138
+ * builder.where([eq("name", "John"), eq("age", 30)])
139
+ * builder.where([eq("name", "John"), or([eq("age", 30), eq("age", 31)])])
140
+ * builder.where([eq("name", "John"), and([eq("age", 30), eq("age", 31)])])
141
+ * builder.where([eq("name", "John"), or([eq("age", 30), and([eq("age", 31), eq("age", 32)])])])
142
+ * builder.where([eq("name", "John"), and([eq("age", 30), or([eq("age", 31), eq("age", 32)])])])
143
+ * builder.where([eq("name", "John"), and([eq("age", 30), or([eq("age", 31), and([eq("age", 32), eq("age", 33)])])])])
144
+ */
145
+ where(predicates: Predicate[] | Predicate) {
146
+ if (Array.isArray(predicates)) {
147
+ this._predicates.push(...predicates)
148
+ } else {
149
+ this._predicates.push(predicates)
150
+ }
151
+ return this
152
+ }
153
+
154
+ /**
155
+ * Fetches the entities from the query. Re
156
+ * It will return a QueryResult instance which can be used to fetch the next and previous pages.
157
+ * @returns The QueryResult instance {@link QueryResult}
158
+ *
159
+ * @example
160
+ * const builder = new QueryBuilder(client)
161
+ * const result = await builder.where(eq("name", "John")).fetch()
162
+ * // result = { entities: [Entity, Entity, Entity], next: async () => QueryResult, previous: async () => QueryResult }
163
+ */
164
+ async fetch() {
165
+ const queryResult = await processQuery(this._client, {
166
+ predicates: this._predicates,
167
+ limit: this._limit,
168
+ cursor: this._cursor,
169
+ ownedBy: this._ownedBy,
170
+ validAtBlock: this._validAtBlock,
171
+ withAttributes: this._withAttributes,
172
+ withMetadata: this._withMetadata,
173
+ withPayload: this._withPayload,
174
+ })
175
+
176
+ const entities = await Promise.all(
177
+ queryResult.data.map((entity) => entityFromRpcResult(entity)),
178
+ )
179
+
180
+ this.cursor(queryResult.cursor)
181
+ this.validAtBlock(queryResult.blockNumber)
182
+
183
+ return new QueryResult(entities, this, this._cursor, this._limit, this._validAtBlock)
184
+ }
185
+
186
+ /**
187
+ * Counts the entities from the query.
188
+ * @returns The number of entities
189
+ *
190
+ * @example
191
+ * const builder = new QueryBuilder(client)
192
+ * const result = await builder.where(eq("name", "John")).count()
193
+ * // result = 10
194
+ */
195
+ async count() {
196
+ const queryResult = await processQuery(this._client, {
197
+ predicates: this._predicates,
198
+ limit: this._limit,
199
+ cursor: this._cursor,
200
+ ownedBy: this._ownedBy,
201
+ validAtBlock: this._validAtBlock,
202
+ withAttributes: false,
203
+ withMetadata: false,
204
+ withPayload: false,
205
+ })
206
+
207
+ return queryResult.data.length ?? 0
208
+ }
209
+ }
@@ -0,0 +1,270 @@
1
+ import { beforeEach, describe, expect, jest, test } from "bun:test"
2
+ import { Entity } from "../types/entity"
3
+ import { NoCursorOrLimitError, NoMoreResultsError } from "./errors"
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
+ })
@@ -0,0 +1,61 @@
1
+ import type { Entity } from "../types/entity"
2
+ import { NoCursorOrLimitError, NoMoreResultsError } from "./errors"
3
+ import type { QueryBuilder } from "./queryBuilder"
4
+
5
+ export class QueryResult {
6
+ entities: Entity[]
7
+ private _endOfIteration: boolean
8
+ private _cursor: string | undefined
9
+ private _limit: number | undefined
10
+ private _validAtBlock: bigint | undefined
11
+ private _queryBuilder: QueryBuilder
12
+
13
+ // Public getters for internal state
14
+ get queryBuilder(): QueryBuilder {
15
+ return this._queryBuilder
16
+ }
17
+
18
+ get cursor(): string | undefined {
19
+ return this._cursor
20
+ }
21
+
22
+ constructor(
23
+ entities: Entity[],
24
+ queryBuilder: QueryBuilder,
25
+ cursor: string | undefined,
26
+ limit: number | undefined,
27
+ validAtBlock: bigint | undefined,
28
+ ) {
29
+ this.entities = entities
30
+ this._queryBuilder = queryBuilder
31
+ this._endOfIteration = !limit || entities.length < limit
32
+ this._cursor = cursor
33
+ this._limit = limit
34
+ this._validAtBlock = validAtBlock
35
+ }
36
+
37
+ async next() {
38
+ if (this._cursor === undefined || this._limit === undefined) {
39
+ throw new NoCursorOrLimitError()
40
+ }
41
+ if (this._endOfIteration) {
42
+ throw new NoMoreResultsError()
43
+ }
44
+ this._queryBuilder.cursor(this._cursor)
45
+ const result = await this._queryBuilder.fetch()
46
+ this.entities = result.entities
47
+ // Update the query builder reference
48
+ this._queryBuilder = result.queryBuilder
49
+ // Check if we've reached the end (no more cursor or we got fewer entities than limit)
50
+ this._endOfIteration = !result.cursor || result.entities.length < this._limit
51
+ // Update the cursor
52
+ this._cursor = result.cursor
53
+
54
+ // TODO check current block height and if it is not too old
55
+ console.debug("Current block height for next page: ", this._validAtBlock)
56
+ }
57
+
58
+ hasNextPage() {
59
+ return !this._endOfIteration
60
+ }
61
+ }
@@ -0,0 +1,4 @@
1
+ export type Attribute = {
2
+ key: string
3
+ value: string | number
4
+ }
@@ -0,0 +1,36 @@
1
+ import { bytesToString, type Hex } from "viem"
2
+ import type { MimeType } from "../types"
3
+ import type { Attribute } from "./attributes"
4
+
5
+ export class Entity {
6
+ key: Hex
7
+ owner: Hex
8
+ expiresAtBlock: bigint
9
+ payload: Uint8Array
10
+ attributes: Attribute[]
11
+ contentType: MimeType
12
+
13
+ constructor(
14
+ key: Hex,
15
+ contentType: MimeType,
16
+ owner: Hex,
17
+ expiresAtBlock: bigint,
18
+ payload: Uint8Array,
19
+ attributes: Attribute[],
20
+ ) {
21
+ this.key = key
22
+ this.owner = owner
23
+ this.expiresAtBlock = expiresAtBlock
24
+ this.payload = payload
25
+ this.attributes = attributes
26
+ this.contentType = contentType
27
+ }
28
+
29
+ toText(): string {
30
+ return bytesToString(this.payload)
31
+ }
32
+
33
+ toJson(): any {
34
+ return JSON.parse(bytesToString(this.payload))
35
+ }
36
+ }
@@ -0,0 +1,34 @@
1
+ import type { Hex } from "viem"
2
+
3
+ export type OnEntityCreatedEvent = {
4
+ entityKey: Hex
5
+ owner: Hex
6
+ expirationBlock: number
7
+ cost: bigint
8
+ }
9
+
10
+ export type OnEntityUpdatedEvent = {
11
+ entityKey: Hex
12
+ owner: Hex
13
+ oldExpirationBlock: number
14
+ newExpirationBlock: number
15
+ cost: bigint
16
+ }
17
+
18
+ export type OnEntityDeletedEvent = {
19
+ entityKey: Hex
20
+ owner: Hex
21
+ }
22
+
23
+ export type OnEntityExpiredEvent = {
24
+ entityKey: Hex
25
+ owner: Hex
26
+ }
27
+
28
+ export type OnEntityExpiresInExtendedEvent = {
29
+ entityKey: Hex
30
+ owner: Hex
31
+ oldExpirationBlock: number
32
+ newExpirationBlock: number
33
+ cost: bigint
34
+ }
@@ -0,0 +1,4 @@
1
+ export type { Attribute } from "./attributes"
2
+ export type { Entity } from "./entity"
3
+ export type { MimeType } from "./mimeTypes"
4
+ export type { TxParams } from "./txParams"
@@ -0,0 +1,27 @@
1
+ export type MimeType =
2
+ | "text/plain"
3
+ | "text/html"
4
+ | "text/css"
5
+ | "text/csv"
6
+ | "text/xml"
7
+ | "text/javascript"
8
+ | "application/json"
9
+ | "application/xml"
10
+ | "application/pdf"
11
+ | "application/zip"
12
+ | "application/gzip"
13
+ | "application/octet-stream"
14
+ | "application/javascript"
15
+ | "application/x-www-form-urlencoded"
16
+ | "image/png"
17
+ | "image/jpeg"
18
+ | "image/webp"
19
+ | "image/gif"
20
+ | "image/svg+xml"
21
+ | "audio/mpeg"
22
+ | "audio/wav"
23
+ | "audio/ogg"
24
+ | "video/mp4"
25
+ | "video/webm"
26
+ | "video/ogg"
27
+ | "multipart/form-data"