@arkiv-network/sdk 0.6.8 → 0.7.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 (83) hide show
  1. package/README.md +52 -16
  2. package/dist/chains/index.d.cts +25 -25
  3. package/dist/consts-AUoL-v42.cjs +18 -0
  4. package/dist/consts-BV4PR-de.js +7 -0
  5. package/dist/consts-BV4PR-de.js.map +1 -0
  6. package/dist/{query-CO6f35AE.d.cts → entity-B6g_EPm1.d.cts} +78 -31
  7. package/dist/entity-B6g_EPm1.d.cts.map +1 -0
  8. package/dist/{query-BhdmAzYg.d.ts → entity-qoLMAuuk.d.ts} +78 -31
  9. package/dist/entity-qoLMAuuk.d.ts.map +1 -0
  10. package/dist/index-DdspxNAA.d.ts +481 -0
  11. package/dist/index-DdspxNAA.d.ts.map +1 -0
  12. package/dist/index-Ds9dwqb5.d.cts +481 -0
  13. package/dist/index-Ds9dwqb5.d.cts.map +1 -0
  14. package/dist/index.cjs +63 -28
  15. package/dist/index.d.cts +90 -5
  16. package/dist/index.d.cts.map +1 -1
  17. package/dist/index.d.ts +91 -6
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +58 -21
  20. package/dist/index.js.map +1 -1
  21. package/dist/{payload-Da9gJMG5.d.ts → payload-B2kFJyah.d.ts} +1 -1
  22. package/dist/{payload-Da9gJMG5.d.ts.map → payload-B2kFJyah.d.ts.map} +1 -1
  23. package/dist/payload-Claf2jUO.js +13 -0
  24. package/dist/payload-Claf2jUO.js.map +1 -0
  25. package/dist/{payload-vwU3Rc6P.cjs → payload-DpA5PA-w.cjs} +0 -17
  26. package/dist/query/index.cjs +10 -28
  27. package/dist/query/index.d.cts +3 -3
  28. package/dist/query/index.d.ts +3 -3
  29. package/dist/query/index.js +7 -28
  30. package/dist/query/index.js.map +1 -1
  31. package/dist/{queryBuilder-DonAg1aq.js → queryBuilder-D2uwP9jI.js} +209 -89
  32. package/dist/queryBuilder-D2uwP9jI.js.map +1 -0
  33. package/dist/{queryBuilder-DM-YO1Y7.cjs → queryBuilder-UW2MCaX4.cjs} +237 -87
  34. package/dist/types/index.d.cts +1 -1
  35. package/dist/types/index.d.ts +1 -1
  36. package/dist/utils/index.cjs +4 -10
  37. package/dist/utils/index.d.cts +0 -1
  38. package/dist/utils/index.d.cts.map +1 -1
  39. package/dist/utils/index.d.ts +1 -2
  40. package/dist/utils/index.d.ts.map +1 -1
  41. package/dist/utils/index.js +2 -3
  42. package/dist/utils/index.js.map +1 -1
  43. package/package.json +11 -15
  44. package/src/actions/public/buildQuery.ts +4 -0
  45. package/src/actions/public/query.ts +10 -0
  46. package/src/actions/wallet/createEntity.ts +12 -2
  47. package/src/actions/wallet/extendEntity.ts +6 -1
  48. package/src/actions/wallet/updateEntity.ts +12 -2
  49. package/src/clients/createPublicClient.test.ts +2 -0
  50. package/src/clients/decorators/arkivPublic.ts +67 -2
  51. package/src/clients/decorators/arkivWallet.ts +18 -0
  52. package/src/errors.ts +18 -0
  53. package/src/index.ts +0 -3
  54. package/src/query/engine.ts +22 -13
  55. package/src/query/index.ts +1 -0
  56. package/src/query/predicate.test.ts +43 -0
  57. package/src/query/predicate.ts +14 -10
  58. package/src/query/queryBuilder.test.ts +22 -0
  59. package/src/query/queryBuilder.ts +221 -106
  60. package/src/query/queryResult.ts +13 -7
  61. package/src/query/selectQueryBuilder.test.ts +210 -0
  62. package/src/query/selection.test.ts +120 -0
  63. package/src/query/selection.ts +116 -0
  64. package/src/types/attributes.ts +12 -0
  65. package/src/utils/arkivTransactions.test.ts +62 -0
  66. package/src/utils/arkivTransactions.ts +36 -27
  67. package/src/utils/index.ts +0 -1
  68. package/src/utils/validation.test.ts +60 -0
  69. package/src/utils/validation.ts +32 -0
  70. package/dist/accounts/index.cjs +0 -9
  71. package/dist/accounts/index.d.cts +0 -1
  72. package/dist/accounts/index.d.ts +0 -1
  73. package/dist/accounts/index.js +0 -1
  74. package/dist/payload-DKBYYjeA.js +0 -18
  75. package/dist/payload-DKBYYjeA.js.map +0 -1
  76. package/dist/query-BhdmAzYg.d.ts.map +0 -1
  77. package/dist/query-CO6f35AE.d.cts.map +0 -1
  78. package/dist/queryBuilder-BvoQXa_u.d.ts +0 -328
  79. package/dist/queryBuilder-BvoQXa_u.d.ts.map +0 -1
  80. package/dist/queryBuilder-DonAg1aq.js.map +0 -1
  81. package/dist/queryBuilder-zTsKXctT.d.cts +0 -328
  82. package/dist/queryBuilder-zTsKXctT.d.cts.map +0 -1
  83. package/src/accounts/index.ts +0 -1
@@ -1,11 +1,18 @@
1
1
  import type { Hex } from "viem"
2
2
  import type { ArkivClient } from "../clients/baseClient"
3
- import type { RpcOrderByAttribute } from "../types/rpcSchema"
3
+ import type { Entity } from "../types/entity"
4
+ import type { RpcEntity, RpcIncludeData, RpcOrderByAttribute } from "../types/rpcSchema"
4
5
  import { entityFromRpcResult } from "../utils/entities"
5
6
  import { processQuery } from "./engine"
6
7
  import type { Predicate } from "./predicate"
7
8
  import { QueryResult } from "./queryResult"
9
+ import { type SelectArg, selectionToIncludeData } from "./selection"
8
10
 
11
+ /**
12
+ * @deprecated Server-side ordering is not supported by the network. Sort the fetched
13
+ * entities in JavaScript instead (e.g. `entities.sort(...)`). This type will be removed
14
+ * in a future release.
15
+ */
9
16
  export type OrderByAttribute = {
10
17
  name: string
11
18
  type: "string" | "number"
@@ -18,6 +25,10 @@ export type OrderByAttribute = {
18
25
  * @param attributeType - The type of the attribute to order by (string or number)
19
26
  * @returns Input for orderBy method
20
27
  *
28
+ * @deprecated Server-side ordering is not supported by the network, so `orderBy` (and this
29
+ * helper) have no effect on the returned order. Sort the fetched entities in JavaScript
30
+ * instead (e.g. `entities.sort(...)`). This function will be removed in a future release.
31
+ *
21
32
  * @example
22
33
  * const ascAttribute = asc("name", "string")
23
34
  */
@@ -35,6 +46,10 @@ export function asc(attributeName: string, attributeType: "string" | "number"):
35
46
  * @param attributeType - The type of the attribute to order by (string or number)
36
47
  * @returns Input for orderBy method
37
48
  *
49
+ * @deprecated Server-side ordering is not supported by the network, so `orderBy` (and this
50
+ * helper) have no effect on the returned order. Sort the fetched entities in JavaScript
51
+ * instead (e.g. `entities.sort(...)`). This function will be removed in a future release.
52
+ *
38
53
  * @example
39
54
  * const descAttribute = desc("name", "string")
40
55
  */
@@ -45,24 +60,37 @@ export function desc(attributeName: string, attributeType: "string" | "number"):
45
60
  order: "desc",
46
61
  }
47
62
  }
63
+
48
64
  /**
49
- * QueryBuilder is a helper class to build queries to the Arkiv DBChains.
50
- * It can be used to fetch entities from the Arkiv DBChains. It follows the Builder pattern allowing chaining of methods.
51
- * @param client - The Arkiv client
52
- * @returns The QueryBuilder instance {@link QueryBuilder}
65
+ * Data-selection parameters forwarded to the query engine on each fetch.
66
+ * Subclasses of {@link BaseQueryBuilder} decide which parts of an entity are fetched.
53
67
  */
54
- export class QueryBuilder {
55
- private _client: ArkivClient
56
- private _ownedBy: Hex | undefined
57
- private _createdBy: Hex | undefined
58
- private _orderBy: RpcOrderByAttribute[] | undefined
59
- private _validAtBlock: bigint | undefined
60
- private _withAttributes: boolean | undefined
61
- private _withMetadata: boolean | undefined
62
- private _withPayload: boolean | undefined
63
- private _limit: number | undefined
64
- private _cursor: string | undefined
65
- private _predicates: Predicate[]
68
+ type SelectionParams = {
69
+ withAttributes?: boolean | undefined
70
+ withMetadata?: boolean | undefined
71
+ withPayload?: boolean | undefined
72
+ includeData?: RpcIncludeData | undefined
73
+ }
74
+
75
+ /**
76
+ * BaseQueryBuilder holds the query-building logic shared by every query builder
77
+ * (filtering, ordering, pagination, execution). It follows the Builder pattern,
78
+ * allowing methods to be chained. Subclasses decide how data selection is expressed
79
+ * by implementing the protected `selectionParams` method.
80
+ *
81
+ * Use {@link SelectQueryBuilder} via `client.select()` to build and execute queries.
82
+ *
83
+ * @typeParam TEntity - The shape of each entity produced by {@link BaseQueryBuilder.fetch}.
84
+ */
85
+ export abstract class BaseQueryBuilder<TEntity> {
86
+ protected _client: ArkivClient
87
+ protected _ownedBy: Hex | undefined
88
+ protected _createdBy: Hex | undefined
89
+ protected _orderBy: RpcOrderByAttribute[] | undefined
90
+ protected _validAtBlock: bigint | undefined
91
+ protected _limit: number | undefined
92
+ protected _cursor: string | undefined
93
+ protected _predicates: Predicate[]
66
94
 
67
95
  constructor(client: ArkivClient) {
68
96
  this._client = client
@@ -72,13 +100,13 @@ export class QueryBuilder {
72
100
  /**
73
101
  * Sets the ownedBy filter
74
102
  * @param ownedBy - The address of the owner
75
- * @returns The QueryBuilder instance
103
+ * @returns The query builder instance
76
104
  *
77
105
  * @example
78
- * const builder = new QueryBuilder(client)
106
+ * const builder = client.select()
79
107
  * builder.ownedBy("0x1234567890123456789012345678901234567890")
80
108
  */
81
- ownedBy(ownedBy: Hex) {
109
+ ownedBy(ownedBy: Hex): this {
82
110
  this._ownedBy = ownedBy
83
111
  return this
84
112
  }
@@ -86,13 +114,13 @@ export class QueryBuilder {
86
114
  /**
87
115
  * Sets the createdBy filter
88
116
  * @param createdBy - The address of the creator
89
- * @returns The QueryBuilder instance
117
+ * @returns The query builder instance
90
118
  *
91
119
  * @example
92
- * const builder = new QueryBuilder(client)
120
+ * const builder = client.select()
93
121
  * builder.createdBy("0x1234567890123456789012345678901234567890")
94
122
  */
95
- createdBy(createdBy: Hex) {
123
+ createdBy(createdBy: Hex): this {
96
124
  this._createdBy = createdBy
97
125
  return this
98
126
  }
@@ -105,10 +133,14 @@ export class QueryBuilder {
105
133
  * @param attributeName - The name of the attribute to order by
106
134
  * @param attributeType - The type of the attribute to order by (string or number)
107
135
  * @param order - The order to set the order by (asc or desc)
108
- * @returns The QueryBuilder instance
136
+ * @returns The query builder instance
137
+ *
138
+ * @deprecated Server-side ordering is not supported by the network, so this method has no
139
+ * effect on the returned order. Sort the fetched entities in JavaScript instead, e.g.
140
+ * `result.entities.sort((a, b) => ...)`. This method will be removed in a future release.
109
141
  *
110
142
  * @example
111
- * const builder = client.buildQuery()
143
+ * const builder = client.select()
112
144
  * builder.orderBy("name", "string", "desc")
113
145
  * builder.orderBy(asc("name", "string"))
114
146
  * builder.orderBy(desc("name", "string"))
@@ -119,10 +151,14 @@ export class QueryBuilder {
119
151
  * This method takes the OrderByAttribute object as an argument and is mainly
120
152
  * used to use the helper functions asc() and desc() to create the OrderByAttribute instances.
121
153
  * @param orderByAttribute - The OrderByAttribute instance to set
122
- * @returns The QueryBuilder instance
154
+ * @returns The query builder instance
155
+ *
156
+ * @deprecated Server-side ordering is not supported by the network, so this method has no
157
+ * effect on the returned order. Sort the fetched entities in JavaScript instead, e.g.
158
+ * `result.entities.sort((a, b) => ...)`. This method will be removed in a future release.
123
159
  *
124
160
  * @example
125
- * const builder = new QueryBuilder(client)
161
+ * const builder = client.select()
126
162
  * builder.orderBy(asc("name", "string"))
127
163
  * builder.orderBy(desc("name", "string"))
128
164
  */
@@ -131,7 +167,7 @@ export class QueryBuilder {
131
167
  attributeNameOrOrderByAttribute: string | OrderByAttribute,
132
168
  attributeType?: "string" | "number",
133
169
  order: "asc" | "desc" = "asc",
134
- ) {
170
+ ): this {
135
171
  if (!this._orderBy) {
136
172
  this._orderBy = []
137
173
  }
@@ -160,58 +196,16 @@ export class QueryBuilder {
160
196
  return this
161
197
  }
162
198
 
163
- /**
164
- * Sets the withAttributes flag which will return the attributes for the entities if true
165
- * @param withAttributes - The boolean value to set
166
- * @returns The QueryBuilder instance
167
- *
168
- * @example
169
- * const builder = new QueryBuilder(client)
170
- * builder.withAttributes(true)
171
- */
172
- withAttributes(withAttributes: boolean = true) {
173
- this._withAttributes = withAttributes
174
- return this
175
- }
176
-
177
- /**
178
- * Sets the withMetadata flag which will return the metadata (like owner, expiredAt, etc.) for the entities if true
179
- * @param withMetadata - The boolean value to set
180
- * @returns The QueryBuilder instance
181
- *
182
- * @example
183
- * const builder = new QueryBuilder(client)
184
- * builder.withMetadata(true)
185
- */
186
- withMetadata(withMetadata: boolean = true) {
187
- this._withMetadata = withMetadata
188
- return this
189
- }
190
-
191
- /**
192
- * Sets the withPayload flag which will return the payload for the entities if true
193
- * @param withPayload - The boolean value to set
194
- * @returns The QueryBuilder instance
195
- *
196
- * @example
197
- * const builder = new QueryBuilder(client)
198
- * builder.withPayload(true)
199
- */
200
- withPayload(withPayload: boolean = true) {
201
- this._withPayload = withPayload
202
- return this
203
- }
204
-
205
199
  /**
206
200
  * Sets the limit for the query
207
201
  * @param limit - The number of entities to return
208
- * @returns The QueryBuilder instance
202
+ * @returns The query builder instance
209
203
  *
210
204
  * @example
211
- * const builder = new QueryBuilder(client)
205
+ * const builder = client.select()
212
206
  * builder.limit(10)
213
207
  */
214
- limit(limit: number) {
208
+ limit(limit: number): this {
215
209
  this._limit = limit
216
210
  return this
217
211
  }
@@ -219,13 +213,13 @@ export class QueryBuilder {
219
213
  /**
220
214
  * 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).
221
215
  * @param cursor - The cursor to set which tells to RPC Query server where to start or continue the query.
222
- * @returns The QueryBuilder instance
216
+ * @returns The query builder instance
223
217
  *
224
218
  * @example
225
- * const builder = new QueryBuilder(client)
226
- * builder.offset(10)
219
+ * const builder = client.select()
220
+ * builder.cursor("0xABC123")
227
221
  */
228
- cursor(cursor: string) {
222
+ cursor(cursor: string): this {
229
223
  this._cursor = cursor
230
224
  return this
231
225
  }
@@ -234,53 +228,64 @@ export class QueryBuilder {
234
228
  * Sets the validAtBlock for the query which tells at which block height the state we are intested.
235
229
  * If not set, the latest block is used.
236
230
  * @param validAtBlock - The block number to set
237
- * @returns The QueryBuilder instance
231
+ * @returns The query builder instance
238
232
  *
239
233
  * @example
240
- * const builder = new QueryBuilder(client)
234
+ * const builder = client.select()
241
235
  * builder.validAtBlock(10000)
242
236
  */
243
- validAtBlock(validAtBlock: bigint) {
237
+ validAtBlock(validAtBlock: bigint): this {
244
238
  this._validAtBlock = validAtBlock
245
239
  return this
246
240
  }
247
241
 
248
242
  /**
249
- * Sets the predicates for the query limiting the results. It can be a single predicate or an array of predicates combined with 'and'.
243
+ * Sets the predicates for the query limiting the results. It can be a single predicate,
244
+ * multiple predicates passed as separate arguments, or an array of predicates - all combined with 'and'.
250
245
  * Predicates can be nested using 'or' and 'and' predicates.
251
- * @param predicates - The predicates to set
252
- * @returns The QueryBuilder instance
246
+ * @param predicates - The predicates to set, either as a single array or as separate arguments
247
+ * @returns The query builder instance
253
248
  *
254
249
  * @example
255
- * const builder = new QueryBuilder(client)
250
+ * const builder = client.select()
256
251
  * builder.where(eq("name", "John"))
252
+ * builder.where(eq("name", "John"), eq("age", 30))
257
253
  * builder.where([eq("name", "John"), eq("age", 30)])
258
- * builder.where([eq("name", "John"), or([eq("age", 30), eq("age", 31)])])
259
- * builder.where([eq("name", "John"), and([eq("age", 30), eq("age", 31)])])
260
- * builder.where([eq("name", "John"), or([eq("age", 30), and([eq("age", 31), eq("age", 32)])])])
261
- * builder.where([eq("name", "John"), and([eq("age", 30), or([eq("age", 31), eq("age", 32)])])])
262
- * builder.where([eq("name", "John"), and([eq("age", 30), or([eq("age", 31), and([eq("age", 32), eq("age", 33)])])])])
254
+ * builder.where(eq("name", "John"), or(eq("age", 30), eq("age", 31)))
255
+ * builder.where(eq("name", "John"), and(eq("age", 30), eq("age", 31)))
256
+ * builder.where(eq("name", "John"), or(eq("age", 30), and(eq("age", 31), eq("age", 32))))
257
+ * builder.where(eq("name", "John"), and(eq("age", 30), or(eq("age", 31), eq("age", 32))))
263
258
  */
264
- where(predicates: Predicate[] | Predicate) {
265
- if (Array.isArray(predicates)) {
266
- this._predicates.push(...predicates)
267
- } else {
268
- this._predicates.push(predicates)
269
- }
259
+ where(predicates: Predicate[]): this
260
+ where(...predicates: Predicate[]): this
261
+ where(...predicates: (Predicate | Predicate[])[]) {
262
+ this._predicates.push(...predicates.flat())
270
263
  return this
271
264
  }
272
265
 
273
266
  /**
274
- * Fetches the entities from the query. Re
267
+ * Returns the data-selection parameters forwarded to the query engine.
268
+ * Subclasses decide which parts of an entity are fetched.
269
+ */
270
+ protected abstract selectionParams(): SelectionParams
271
+
272
+ /**
273
+ * Builds a single result entity from a raw RPC entity. Subclasses decide the produced shape
274
+ * (a full {@link Entity}, or a projected object inferred from the selection).
275
+ */
276
+ protected abstract projectEntity(rpcEntity: RpcEntity): TEntity | Promise<TEntity>
277
+
278
+ /**
279
+ * Fetches the entities from the query.
275
280
  * It will return a QueryResult instance which can be used to fetch the next and previous pages.
276
281
  * @returns The QueryResult instance {@link QueryResult}
277
282
  *
278
283
  * @example
279
- * const builder = new QueryBuilder(client)
284
+ * const builder = client.select()
280
285
  * const result = await builder.where(eq("name", "John")).fetch()
281
286
  * // result = { entities: [Entity, Entity, Entity], next: async () => QueryResult, previous: async () => QueryResult }
282
287
  */
283
- async fetch() {
288
+ async fetch(): Promise<QueryResult<TEntity>> {
284
289
  const queryResult = await processQuery(this._client, {
285
290
  predicates: this._predicates,
286
291
  limit: this._limit,
@@ -289,19 +294,15 @@ export class QueryBuilder {
289
294
  createdBy: this._createdBy,
290
295
  orderBy: this._orderBy,
291
296
  validAtBlock: this._validAtBlock,
292
- withAttributes: this._withAttributes,
293
- withMetadata: this._withMetadata,
294
- withPayload: this._withPayload,
297
+ ...this.selectionParams(),
295
298
  })
296
299
 
297
- const entities = await Promise.all(
298
- queryResult.data.map((entity) => entityFromRpcResult(entity)),
299
- )
300
+ const entities = await Promise.all(queryResult.data.map((entity) => this.projectEntity(entity)))
300
301
 
301
302
  this.cursor(queryResult.cursor)
302
303
  this.validAtBlock(BigInt(queryResult.blockNumber ?? 0))
303
304
 
304
- return new QueryResult(entities, this, this._cursor, this._limit, this._validAtBlock)
305
+ return new QueryResult<TEntity>(entities, this, this._cursor, this._limit, this._validAtBlock)
305
306
  }
306
307
 
307
308
  /**
@@ -309,7 +310,7 @@ export class QueryBuilder {
309
310
  * @returns The number of entities
310
311
  *
311
312
  * @example
312
- * const builder = new QueryBuilder(client)
313
+ * const builder = client.select()
313
314
  * const result = await builder.where(eq("name", "John")).count()
314
315
  * // result = 10
315
316
  */
@@ -330,3 +331,117 @@ export class QueryBuilder {
330
331
  return queryResult.data.length ?? 0
331
332
  }
332
333
  }
334
+
335
+ /**
336
+ * QueryBuilder is a helper class to build queries to the Arkiv DBChains.
337
+ * It can be used to fetch entities from the Arkiv DBChains. It follows the Builder pattern allowing chaining of methods.
338
+ *
339
+ * By default the result includes only the entity `key`. Additional data is opt-in through
340
+ * `withAttributes()`, `withMetadata()` and `withPayload()`.
341
+ *
342
+ * @deprecated Use {@link SelectQueryBuilder} via `client.select()` instead. Declaring the
343
+ * selection up front avoids the common mistake of forgetting to opt in to data and getting back
344
+ * entities with only their `key` populated. This class remains for backwards compatibility and
345
+ * will be removed in a future release.
346
+ *
347
+ * @param client - The Arkiv client
348
+ * @returns The QueryBuilder instance {@link QueryBuilder}
349
+ */
350
+ export class QueryBuilder extends BaseQueryBuilder<Entity> {
351
+ private _withAttributes: boolean | undefined
352
+ private _withMetadata: boolean | undefined
353
+ private _withPayload: boolean | undefined
354
+
355
+ /**
356
+ * Sets the withAttributes flag which will return the attributes for the entities if true
357
+ * @param withAttributes - The boolean value to set
358
+ * @returns The QueryBuilder instance
359
+ *
360
+ * @example
361
+ * const builder = client.buildQuery()
362
+ * builder.withAttributes(true)
363
+ */
364
+ withAttributes(withAttributes: boolean = true): this {
365
+ this._withAttributes = withAttributes
366
+ return this
367
+ }
368
+
369
+ /**
370
+ * Sets the withMetadata flag which will return the metadata (like owner, expiredAt, etc.) for the entities if true
371
+ * @param withMetadata - The boolean value to set
372
+ * @returns The QueryBuilder instance
373
+ *
374
+ * @example
375
+ * const builder = client.buildQuery()
376
+ * builder.withMetadata(true)
377
+ */
378
+ withMetadata(withMetadata: boolean = true): this {
379
+ this._withMetadata = withMetadata
380
+ return this
381
+ }
382
+
383
+ /**
384
+ * Sets the withPayload flag which will return the payload for the entities if true
385
+ * @param withPayload - The boolean value to set
386
+ * @returns The QueryBuilder instance
387
+ *
388
+ * @example
389
+ * const builder = client.buildQuery()
390
+ * builder.withPayload(true)
391
+ */
392
+ withPayload(withPayload: boolean = true): this {
393
+ this._withPayload = withPayload
394
+ return this
395
+ }
396
+
397
+ protected selectionParams(): SelectionParams {
398
+ return {
399
+ withAttributes: this._withAttributes,
400
+ withMetadata: this._withMetadata,
401
+ withPayload: this._withPayload,
402
+ }
403
+ }
404
+
405
+ protected projectEntity(rpcEntity: RpcEntity): Promise<Entity> {
406
+ return entityFromRpcResult(rpcEntity)
407
+ }
408
+ }
409
+
410
+ /**
411
+ * SelectQueryBuilder is the recommended query builder. It requires the selection to be declared
412
+ * up front, so results always contain exactly the data you asked for — and the type of each
413
+ * returned entity is narrowed to exactly the selected fields.
414
+ *
415
+ * The selection is fixed at construction time and is flat — each field maps to an entity field.
416
+ * Create one via `client.select(...)` rather than constructing it directly, so the result type is
417
+ * inferred from the selection.
418
+ *
419
+ * @typeParam TEntity - The projected entity shape, inferred from the selection by `client.select()`.
420
+ *
421
+ * @example
422
+ * // everything
423
+ * await client.select().where(eq("name", "John")).fetch()
424
+ * await client.select("*").where(eq("name", "John")).fetch()
425
+ * // specific fields
426
+ * await client.select({ owner: true, attributes: true }).fetch()
427
+ * // a single field — result is typed (flat) { owner: Hex }
428
+ * await client.select({ owner: true }).fetch()
429
+ */
430
+ export class SelectQueryBuilder<TEntity> extends BaseQueryBuilder<TEntity> {
431
+ private _includeData: RpcIncludeData
432
+
433
+ constructor(client: ArkivClient, selection?: SelectArg) {
434
+ super(client)
435
+ this._includeData = selectionToIncludeData(selection)
436
+ }
437
+
438
+ protected selectionParams(): SelectionParams {
439
+ return { includeData: this._includeData }
440
+ }
441
+
442
+ protected async projectEntity(rpcEntity: RpcEntity): Promise<TEntity> {
443
+ // The runtime value is a full Entity (with methods); the static type is narrowed to the
444
+ // selected fields via `client.select()`'s inference.
445
+ return (await entityFromRpcResult(rpcEntity)) as unknown as TEntity
446
+ }
447
+ }
@@ -1,20 +1,26 @@
1
1
  import { NoCursorOrLimitError, NoMoreResultsError } from "../errors"
2
2
  import type { Entity } from "../types/entity"
3
3
  import { getLogger } from "../utils/logger"
4
- import type { QueryBuilder } from "./queryBuilder"
4
+ import type { BaseQueryBuilder } from "./queryBuilder"
5
5
 
6
6
  const logger = getLogger("query:result")
7
7
 
8
- export class QueryResult {
9
- entities: Entity[]
8
+ /**
9
+ * The result of a query. Holds the fetched entities and supports cursor-based pagination.
10
+ *
11
+ * @typeParam TEntity - The shape of each entity, inferred from the query builder
12
+ * (a full {@link Entity}, or a projected object inferred from a `select()` selection).
13
+ */
14
+ export class QueryResult<TEntity = Entity> {
15
+ entities: TEntity[]
10
16
  private _endOfIteration: boolean
11
17
  private _cursor: string | undefined
12
18
  private _limit: number | undefined
13
19
  private _validAtBlock: bigint | undefined
14
- private _queryBuilder: QueryBuilder
20
+ private _queryBuilder: BaseQueryBuilder<TEntity>
15
21
 
16
22
  // Public getters for internal state
17
- get queryBuilder(): QueryBuilder {
23
+ get queryBuilder(): BaseQueryBuilder<TEntity> {
18
24
  return this._queryBuilder
19
25
  }
20
26
 
@@ -23,8 +29,8 @@ export class QueryResult {
23
29
  }
24
30
 
25
31
  constructor(
26
- entities: Entity[],
27
- queryBuilder: QueryBuilder,
32
+ entities: TEntity[],
33
+ queryBuilder: BaseQueryBuilder<TEntity>,
28
34
  cursor: string | undefined,
29
35
  limit: number | undefined,
30
36
  validAtBlock: bigint | undefined,