@arkiv-network/sdk 0.7.0-dev.1 → 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.
Files changed (210) hide show
  1. package/README.md +29 -16
  2. package/dist/attr/index.cjs +34 -0
  3. package/dist/attr/index.d.cts +2 -0
  4. package/dist/attr/index.d.ts +2 -0
  5. package/dist/attr/index.js +3 -0
  6. package/dist/attr-BDZ-APg5.js +809 -0
  7. package/dist/attr-BDZ-APg5.js.map +1 -0
  8. package/dist/attr-BvxHN99W.cjs +1029 -0
  9. package/dist/chains/index.cjs +2 -3
  10. package/dist/chains/index.d.cts +22 -60
  11. package/dist/chains/index.d.cts.map +1 -1
  12. package/dist/chains/index.d.ts +22 -60
  13. package/dist/chains/index.d.ts.map +1 -1
  14. package/dist/chains/index.js +2 -2
  15. package/dist/chains-BJrZs6sG.js +57 -0
  16. package/dist/chains-BJrZs6sG.js.map +1 -0
  17. package/dist/chains-BYRTTipa.cjs +73 -0
  18. package/dist/changeOwnership-B7bEgla4.d.cts +509 -0
  19. package/dist/changeOwnership-B7bEgla4.d.cts.map +1 -0
  20. package/dist/changeOwnership-DwgtdSFc.d.ts +509 -0
  21. package/dist/changeOwnership-DwgtdSFc.d.ts.map +1 -0
  22. package/dist/entity/index.cjs +19 -0
  23. package/dist/entity/index.d.cts +4 -0
  24. package/dist/entity/index.d.ts +4 -0
  25. package/dist/entity/index.js +6 -0
  26. package/dist/entity-B0t6sN7F.cjs +71 -0
  27. package/dist/entity-BIp_df9u.js +67 -0
  28. package/dist/entity-BIp_df9u.js.map +1 -0
  29. package/dist/entity-CFhbAv0R.js +70 -0
  30. package/dist/entity-CFhbAv0R.js.map +1 -0
  31. package/dist/entity-wfBnZd91.cjs +98 -0
  32. package/dist/errors-D-eJkGVc.cjs +56 -0
  33. package/dist/errors-DGB8AgT2.js +33 -0
  34. package/dist/errors-DGB8AgT2.js.map +1 -0
  35. package/dist/expiry-BUYIXTGb.cjs +135 -0
  36. package/dist/expiry-BxOlvVWC.d.ts +79 -0
  37. package/dist/expiry-BxOlvVWC.d.ts.map +1 -0
  38. package/dist/expiry-DGRzrSan.js +95 -0
  39. package/dist/expiry-DGRzrSan.js.map +1 -0
  40. package/dist/expiry-DRV29F1r.d.cts +79 -0
  41. package/dist/expiry-DRV29F1r.d.cts.map +1 -0
  42. package/dist/flags-0Vl5rrHq.d.cts +41 -0
  43. package/dist/flags-0Vl5rrHq.d.cts.map +1 -0
  44. package/dist/flags-Ct8k0gpB.cjs +43 -0
  45. package/dist/flags-ZtlvOIIk.js +33 -0
  46. package/dist/flags-ZtlvOIIk.js.map +1 -0
  47. package/dist/flags-wmcMoAnN.d.ts +41 -0
  48. package/dist/flags-wmcMoAnN.d.ts.map +1 -0
  49. package/dist/index-Cgw1u5aY.d.cts +278 -0
  50. package/dist/index-Cgw1u5aY.d.cts.map +1 -0
  51. package/dist/index-ClgIuxJ8.d.ts +159 -0
  52. package/dist/index-ClgIuxJ8.d.ts.map +1 -0
  53. package/dist/index-D0CmtiWT.d.cts +159 -0
  54. package/dist/index-D0CmtiWT.d.cts.map +1 -0
  55. package/dist/index-D8LZt-3J.d.cts +417 -0
  56. package/dist/index-D8LZt-3J.d.cts.map +1 -0
  57. package/dist/index-DVYFaXNV.d.ts +417 -0
  58. package/dist/index-DVYFaXNV.d.ts.map +1 -0
  59. package/dist/index-VCqgn29D.d.ts +278 -0
  60. package/dist/index-VCqgn29D.d.ts.map +1 -0
  61. package/dist/index.cjs +676 -375
  62. package/dist/index.d.cts +247 -208
  63. package/dist/index.d.cts.map +1 -1
  64. package/dist/index.d.ts +247 -208
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +626 -368
  67. package/dist/index.js.map +1 -1
  68. package/dist/payload-Bnw1hrvb.cjs +83 -0
  69. package/dist/payload-CIxSn_DW.js +67 -0
  70. package/dist/payload-CIxSn_DW.js.map +1 -0
  71. package/dist/payload-Dgx3PQeK.d.ts +94 -0
  72. package/dist/payload-Dgx3PQeK.d.ts.map +1 -0
  73. package/dist/payload-P0nWyjLg.d.cts +94 -0
  74. package/dist/payload-P0nWyjLg.d.cts.map +1 -0
  75. package/dist/query/index.cjs +24 -150
  76. package/dist/query/index.d.cts +5 -3
  77. package/dist/query/index.d.ts +5 -3
  78. package/dist/query/index.js +6 -138
  79. package/dist/queryBuilder-BXDvX1K_.cjs +1123 -0
  80. package/dist/queryBuilder-CbLL4pG5.js +963 -0
  81. package/dist/queryBuilder-CbLL4pG5.js.map +1 -0
  82. package/dist/selection-BvK6yX0g.d.cts +433 -0
  83. package/dist/selection-BvK6yX0g.d.cts.map +1 -0
  84. package/dist/selection-fpmI3ADQ.d.ts +433 -0
  85. package/dist/selection-fpmI3ADQ.d.ts.map +1 -0
  86. package/dist/types/index.cjs +3 -3
  87. package/dist/types/index.d.cts +7 -2
  88. package/dist/types/index.d.ts +7 -2
  89. package/dist/types/index.js +3 -3
  90. package/dist/utils/index.cjs +4 -17
  91. package/dist/utils/index.d.cts +3 -17
  92. package/dist/utils/index.d.ts +3 -17
  93. package/dist/utils/index.js +4 -18
  94. package/package.json +26 -5
  95. package/src/actions/public/getEntity.ts +27 -43
  96. package/src/actions/public/getEntityNonce.ts +30 -0
  97. package/src/actions/public/predictEntityKeys.test.ts +143 -0
  98. package/src/actions/public/predictEntityKeys.ts +138 -0
  99. package/src/actions/public/query.ts +33 -69
  100. package/src/actions/public/watchEntityEvents.test.ts +414 -0
  101. package/src/actions/public/watchEntityEvents.ts +225 -0
  102. package/src/actions/wallet/changeOwnership.ts +2 -2
  103. package/src/actions/wallet/createEntity.ts +99 -24
  104. package/src/actions/wallet/extendEntity.ts +37 -13
  105. package/src/actions/wallet/mutateEntities.test.ts +76 -0
  106. package/src/actions/wallet/mutateEntities.ts +19 -21
  107. package/src/actions/wallet/patchEntity.ts +90 -0
  108. package/src/attr/attributes.test.ts +120 -0
  109. package/src/attr/attributes.ts +190 -0
  110. package/src/attr/codec.test.ts +162 -0
  111. package/src/attr/codec.ts +220 -0
  112. package/src/attr/decimal.ts +86 -0
  113. package/src/attr/errors.ts +145 -0
  114. package/src/attr/index.ts +50 -0
  115. package/src/attr/names.ts +106 -0
  116. package/src/attr/types.ts +150 -0
  117. package/src/attr/values.test.ts +271 -0
  118. package/src/attr/values.ts +502 -0
  119. package/src/chains/cheesecake.ts +22 -0
  120. package/src/chains/index.ts +1 -2
  121. package/src/clients/createPublicClient.test.ts +3 -5
  122. package/src/clients/createPublicClient.ts +5 -4
  123. package/src/clients/createWalletClient.test.ts +2 -2
  124. package/src/clients/createWalletClient.ts +13 -7
  125. package/src/clients/decorators/arkivPublic.ts +160 -139
  126. package/src/clients/decorators/arkivWallet.ts +86 -94
  127. package/src/entity/create.test.ts +184 -0
  128. package/src/entity/errors.ts +38 -0
  129. package/src/entity/events.ts +16 -0
  130. package/src/entity/expiry.test.ts +195 -0
  131. package/src/entity/expiry.ts +181 -0
  132. package/src/entity/flags.ts +65 -0
  133. package/src/entity/index.ts +14 -0
  134. package/src/entity/key.ts +68 -0
  135. package/src/entity/operations.ts +134 -0
  136. package/src/entity/params.ts +15 -0
  137. package/src/errors.ts +26 -49
  138. package/src/index.ts +6 -0
  139. package/src/query/engine.ts +59 -139
  140. package/src/query/errors.ts +151 -0
  141. package/src/query/expression.test.ts +286 -0
  142. package/src/query/expression.ts +493 -0
  143. package/src/query/index.ts +40 -4
  144. package/src/query/injection.test.ts +151 -0
  145. package/src/query/queryBuilder.test.ts +322 -637
  146. package/src/query/queryBuilder.ts +126 -359
  147. package/src/query/queryResult.ts +54 -51
  148. package/src/query/selection.test.ts +48 -98
  149. package/src/query/selection.ts +77 -65
  150. package/src/types/entity.test.ts +44 -155
  151. package/src/types/entity.ts +60 -52
  152. package/src/types/events.ts +59 -16
  153. package/src/types/index.ts +20 -17
  154. package/src/types/rpcSchema.ts +69 -37
  155. package/src/utils/arkivTransactions.test.ts +600 -155
  156. package/src/utils/arkivTransactions.ts +181 -190
  157. package/src/utils/chains.test.ts +21 -24
  158. package/src/utils/compression.ts +1 -1
  159. package/src/utils/entities.test.ts +62 -0
  160. package/src/utils/entities.ts +109 -37
  161. package/src/utils/expirationTime.ts +114 -10
  162. package/src/utils/logger.ts +2 -2
  163. package/src/utils/revert.test.ts +99 -0
  164. package/src/utils/revert.ts +145 -0
  165. package/dist/chains-BifaY04V.cjs +0 -108
  166. package/dist/chains-CrtkVbBh.js +0 -86
  167. package/dist/chains-CrtkVbBh.js.map +0 -1
  168. package/dist/consts-Bz0LZQZY.js +0 -7
  169. package/dist/consts-Bz0LZQZY.js.map +0 -1
  170. package/dist/consts-CxT1-RXU.cjs +0 -18
  171. package/dist/entity-BhSXaB23.cjs +0 -81
  172. package/dist/entity-CN41IOrq.d.cts +0 -401
  173. package/dist/entity-CN41IOrq.d.cts.map +0 -1
  174. package/dist/entity-DGfVyB-a.js +0 -71
  175. package/dist/entity-DGfVyB-a.js.map +0 -1
  176. package/dist/entity-NnEVlePN.d.ts +0 -401
  177. package/dist/entity-NnEVlePN.d.ts.map +0 -1
  178. package/dist/index-ClkpS6s_.d.ts +0 -481
  179. package/dist/index-ClkpS6s_.d.ts.map +0 -1
  180. package/dist/index-w4qop0Pe.d.cts +0 -481
  181. package/dist/index-w4qop0Pe.d.cts.map +0 -1
  182. package/dist/payload-BpNUPOnY.js +0 -13
  183. package/dist/payload-BpNUPOnY.js.map +0 -1
  184. package/dist/payload-C2KHOUoj.d.ts +0 -6
  185. package/dist/payload-C2KHOUoj.d.ts.map +0 -1
  186. package/dist/payload-CJN8fTvX.d.cts +0 -6
  187. package/dist/payload-CJN8fTvX.d.cts.map +0 -1
  188. package/dist/payload-D8YLKcJj.cjs +0 -23
  189. package/dist/query/index.js.map +0 -1
  190. package/dist/queryBuilder-BE_XhLLa.js +0 -735
  191. package/dist/queryBuilder-BE_XhLLa.js.map +0 -1
  192. package/dist/queryBuilder-J37buGCi.cjs +0 -865
  193. package/dist/utils/index.d.cts.map +0 -1
  194. package/dist/utils/index.d.ts.map +0 -1
  195. package/dist/utils/index.js.map +0 -1
  196. package/src/actions/public/buildQuery.ts +0 -10
  197. package/src/actions/public/subscribeEntityEvents.ts +0 -93
  198. package/src/actions/wallet/updateEntity.ts +0 -59
  199. package/src/chains/braga.ts +0 -26
  200. package/src/chains/kaolin.ts +0 -29
  201. package/src/query/engine.test.ts +0 -571
  202. package/src/query/predicate.test.ts +0 -461
  203. package/src/query/predicate.ts +0 -144
  204. package/src/query/queryResult.test.ts +0 -270
  205. package/src/query/selectQueryBuilder.test.ts +0 -213
  206. package/src/types/attributes.ts +0 -28
  207. package/src/utils/validation.test.ts +0 -106
  208. package/src/utils/validation.ts +0 -80
  209. /package/dist/{types-Ci4BRClw.js → types-Bx1EKe0v.js} +0 -0
  210. /package/dist/{types-a8gm_IaQ.cjs → types-CKPddlfS.cjs} +0 -0
@@ -1,209 +1,104 @@
1
1
  import type { Hex } from "viem"
2
+ import { addr } from "../attr"
2
3
  import type { ArkivClient } from "../clients/baseClient"
3
4
  import type { Entity } from "../types/entity"
4
- import type { RpcEntity, RpcIncludeData, RpcOrderByAttribute } from "../types/rpcSchema"
5
- import { entityFromRpcResult } from "../utils/entities"
6
- import { processQuery } from "./engine"
7
- import type { Predicate } from "./predicate"
5
+ import type { RpcSelect } from "../types/rpcSchema"
6
+ import { type QueryRequest, runQuery } from "./engine"
7
+ import { InvalidPredicateError } from "./errors"
8
+ import { and, type Expression, eq } from "./expression"
8
9
  import { QueryResult } from "./queryResult"
9
- import { type SelectArg, selectionToIncludeData } from "./selection"
10
+ import { type SelectArg, toRpcSelect } from "./selection"
10
11
 
11
12
  /**
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
- */
16
- export type OrderByAttribute = {
17
- name: string
18
- type: "string" | "number"
19
- order: "asc" | "desc"
20
- }
21
-
22
- /**
23
- * Helper function to create an ascending order by attribute
24
- * @param attributeName - The name of the attribute to order by
25
- * @param attributeType - The type of the attribute to order by (string or number)
26
- * @returns Input for orderBy method
13
+ * Builds and runs a query.
27
14
  *
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.
15
+ * The selection is fixed when the builder is created and the filter is added by chaining, so the
16
+ * result type is known before a single predicate is written. Create one with `client.select(...)`
17
+ * rather than constructing it directly — that is what infers the entity shape from the selection.
31
18
  *
32
- * @example
33
- * const ascAttribute = asc("name", "string")
34
- */
35
- export function asc(attributeName: string, attributeType: "string" | "number"): OrderByAttribute {
36
- return {
37
- name: attributeName,
38
- type: attributeType,
39
- order: "asc",
40
- }
41
- }
42
-
43
- /**
44
- * Helper function to create a descending order by attribute
45
- * @param attributeName - The name of the attribute to order by
46
- * @param attributeType - The type of the attribute to order by (string or number)
47
- * @returns Input for orderBy method
19
+ * Every filter added with {@link where}, {@link ownedBy} and {@link createdBy} must hold; use
20
+ * {@link or} for alternatives inside one call.
48
21
  *
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.
22
+ * @typeParam TEntity - The projected entity shape, inferred from the selection by `client.select()`.
52
23
  *
53
24
  * @example
54
- * const descAttribute = desc("name", "string")
55
- */
56
- export function desc(attributeName: string, attributeType: "string" | "number"): OrderByAttribute {
57
- return {
58
- name: attributeName,
59
- type: attributeType,
60
- order: "desc",
61
- }
62
- }
63
-
64
- /**
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.
67
- */
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.
25
+ * import { createPublicClient } from "@arkiv-network/sdk"
26
+ * import { i32 } from "@arkiv-network/sdk/attr"
27
+ * import { cheesecake } from "@arkiv-network/sdk/chains"
28
+ * import { eq, gte, or } from "@arkiv-network/sdk/query"
29
+ * import { http } from "viem"
30
+ *
31
+ * const client = createPublicClient({ chain: cheesecake, transport: http() })
80
32
  *
81
- * Use {@link SelectQueryBuilder} via `client.select()` to build and execute queries.
33
+ * const page = await client
34
+ * .select({ key: true, attributes: true })
35
+ * .where(gte("level", i32(10)), or(eq("status", "open"), eq("status", "review")))
36
+ * .ownedBy(owner)
37
+ * .limit(100)
38
+ * .fetch()
82
39
  *
83
- * @typeParam TEntity - The shape of each entity produced by {@link BaseQueryBuilder.fetch}.
40
+ * for (const entity of page.entities) console.log(entity.key, entity.attributes)
84
41
  */
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[]
42
+ export class SelectQueryBuilder<TEntity = Entity> {
43
+ private readonly _client: ArkivClient
44
+ private readonly _select: RpcSelect
45
+ private readonly _filters: Expression[] = []
46
+ private _ownedBy: Expression | undefined
47
+ private _createdBy: Expression | undefined
48
+ private _limit: number | undefined
49
+ private _cursor: string | undefined
50
+ private _atBlock: bigint | undefined
94
51
 
95
- constructor(client: ArkivClient) {
52
+ constructor(client: ArkivClient, selection?: SelectArg) {
96
53
  this._client = client
97
- this._predicates = []
54
+ this._select = toRpcSelect(selection)
98
55
  }
99
56
 
100
57
  /**
101
- * Sets the ownedBy filter
102
- * @param ownedBy - The address of the owner
103
- * @returns The query builder instance
58
+ * Adds filters. Everything passed here, and across repeated calls, must hold.
59
+ *
60
+ * @param expressions - The expressions, as separate arguments or one array.
104
61
  *
105
62
  * @example
106
- * const builder = client.select()
107
- * builder.ownedBy("0x1234567890123456789012345678901234567890")
63
+ * builder.where(eq("category", "docs"))
64
+ * builder.where(gte("level", i32(10)), lt("level", i32(20)))
65
+ * builder.where(or(eq("status", "open"), not(exists("closedAt"))))
108
66
  */
109
- ownedBy(ownedBy: Hex): this {
110
- this._ownedBy = ownedBy
67
+ where(expressions: readonly Expression[]): this
68
+ where(...expressions: Expression[]): this
69
+ where(...expressions: (Expression | readonly Expression[])[]): this {
70
+ this._filters.push(...expressions.flat())
111
71
  return this
112
72
  }
113
73
 
114
74
  /**
115
- * Sets the createdBy filter
116
- * @param createdBy - The address of the creator
117
- * @returns The query builder instance
75
+ * Restricts the results to entities this account owns — shorthand for
76
+ * `where(eq("$owner", addr(owner)))`. Calling it again replaces the filter.
118
77
  *
119
78
  * @example
120
- * const builder = client.select()
121
- * builder.createdBy("0x1234567890123456789012345678901234567890")
79
+ * builder.ownedBy("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
122
80
  */
123
- createdBy(createdBy: Hex): this {
124
- this._createdBy = createdBy
81
+ ownedBy(owner: Hex): this {
82
+ this._ownedBy = eq("$owner", addr(owner))
125
83
  return this
126
84
  }
127
85
 
128
86
  /**
129
- * Sets the orderBy for the query.
130
- * It can be called multiple times to order by multiple attributes.
131
- * The order of the attributes is important. The first attribute is the primary order by attribute.
132
- * You can use the helper functions asc() and desc() as input for this method.
133
- * @param attributeName - The name of the attribute to order by
134
- * @param attributeType - The type of the attribute to order by (string or number)
135
- * @param order - The order to set the order by (asc or desc)
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.
141
- *
142
- * @example
143
- * const builder = client.select()
144
- * builder.orderBy("name", "string", "desc")
145
- * builder.orderBy(asc("name", "string"))
146
- * builder.orderBy(desc("name", "string"))
147
- */
148
- orderBy(attributeName: string, attributeType: "string" | "number", order?: "asc" | "desc"): this
149
- /**
150
- * Sets the orderBy for the query.
151
- * This method takes the OrderByAttribute object as an argument and is mainly
152
- * used to use the helper functions asc() and desc() to create the OrderByAttribute instances.
153
- * @param orderByAttribute - The OrderByAttribute instance to set
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.
87
+ * Restricts the results to entities this account created — shorthand for
88
+ * `where(eq("$creator", addr(creator)))`. Calling it again replaces the filter.
159
89
  *
160
- * @example
161
- * const builder = client.select()
162
- * builder.orderBy(asc("name", "string"))
163
- * builder.orderBy(desc("name", "string"))
90
+ * The creator never changes; the owner does, so these differ after a transfer.
164
91
  */
165
- orderBy(orderByAttribute: OrderByAttribute): this
166
- orderBy(
167
- attributeNameOrOrderByAttribute: string | OrderByAttribute,
168
- attributeType?: "string" | "number",
169
- order: "asc" | "desc" = "asc",
170
- ): this {
171
- if (!this._orderBy) {
172
- this._orderBy = []
173
- }
174
-
175
- const pushOrderByAttribute = ({ name, type, order }: OrderByAttribute) => {
176
- this._orderBy?.push({
177
- name,
178
- type: type === "number" ? "numeric" : type,
179
- desc: order === "desc",
180
- })
181
- }
182
-
183
- if (typeof attributeNameOrOrderByAttribute === "string") {
184
- if (!attributeType) {
185
- throw new Error("attributeType is required when using positional orderBy arguments")
186
- }
187
- pushOrderByAttribute({
188
- name: attributeNameOrOrderByAttribute,
189
- type: attributeType,
190
- order,
191
- })
192
- } else {
193
- pushOrderByAttribute(attributeNameOrOrderByAttribute)
194
- }
195
-
92
+ createdBy(creator: Hex): this {
93
+ this._createdBy = eq("$creator", addr(creator))
196
94
  return this
197
95
  }
198
96
 
199
97
  /**
200
- * Sets the limit for the query
201
- * @param limit - The number of entities to return
202
- * @returns The query builder instance
98
+ * Sets the page size, up to the node maximum of 200. Without it the node picks the page size.
203
99
  *
204
100
  * @example
205
- * const builder = client.select()
206
- * builder.limit(10)
101
+ * builder.limit(100)
207
102
  */
208
103
  limit(limit: number): this {
209
104
  this._limit = limit
@@ -211,13 +106,11 @@ export abstract class BaseQueryBuilder<TEntity> {
211
106
  }
212
107
 
213
108
  /**
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).
215
- * @param cursor - The cursor to set which tells to RPC Query server where to start or continue the query.
216
- * @returns The query builder instance
109
+ * Starts from a cursor returned by an earlier page.
217
110
  *
218
- * @example
219
- * const builder = client.select()
220
- * builder.cursor("0xABC123")
111
+ * Pagination normally goes through {@link QueryResult.next}, which carries the cursor for you;
112
+ * this is for resuming a walk in a later process. A cursor is bound to the query, block and
113
+ * selection it came from, so it must be used with an identically-built query.
221
114
  */
222
115
  cursor(cursor: string): this {
223
116
  this._cursor = cursor
@@ -225,223 +118,97 @@ export abstract class BaseQueryBuilder<TEntity> {
225
118
  }
226
119
 
227
120
  /**
228
- * Sets the validAtBlock for the query which tells at which block height the state we are intested.
229
- * If not set, the latest block is used.
230
- * @param validAtBlock - The block number to set
231
- * @returns The query builder instance
121
+ * Reads the state as of a given block rather than the head. The block must be within the range
122
+ * the node retains.
232
123
  *
233
124
  * @example
234
- * const builder = client.select()
235
- * builder.validAtBlock(10000)
125
+ * builder.atBlock(1_297_000n)
236
126
  */
237
- validAtBlock(validAtBlock: bigint): this {
238
- this._validAtBlock = validAtBlock
127
+ atBlock(block: bigint): this {
128
+ this._atBlock = block
239
129
  return this
240
130
  }
241
131
 
242
132
  /**
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'.
245
- * Predicates can be nested using 'or' and 'and' predicates.
246
- * @param predicates - The predicates to set, either as a single array or as separate arguments
247
- * @returns The query builder instance
133
+ * The query string this builder will send — the same text the node parses.
248
134
  *
249
- * @example
250
- * const builder = client.select()
251
- * builder.where(eq("name", "John"))
252
- * builder.where(eq("name", "John"), eq("age", 30))
253
- * builder.where([eq("name", "John"), eq("age", 30)])
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))))
135
+ * @throws {InvalidPredicateError} If no filter has been added.
258
136
  */
259
- where(predicates: Predicate[]): this
260
- where(...predicates: Predicate[]): this
261
- where(...predicates: (Predicate | Predicate[])[]) {
262
- this._predicates.push(...predicates.flat())
263
- return this
137
+ toString(): string {
138
+ const filters = [...this._filters]
139
+ if (this._ownedBy) filters.push(this._ownedBy)
140
+ if (this._createdBy) filters.push(this._createdBy)
141
+ if (filters.length === 0) {
142
+ throw new InvalidPredicateError(
143
+ "A query needs at least one filter — the language has no spelling for 'match every " +
144
+ 'entity". Add a where(...) predicate, or narrow by ownedBy(...) / createdBy(...).',
145
+ )
146
+ }
147
+ return and(filters).toString()
264
148
  }
265
149
 
266
150
  /**
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.
280
- * It will return a QueryResult instance which can be used to fetch the next and previous pages.
281
- * @returns The QueryResult instance {@link QueryResult}
151
+ * Runs the query and returns the first page.
282
152
  *
283
- * @example
284
- * const builder = client.select()
285
- * const result = await builder.where(eq("name", "John")).fetch()
286
- * // result = { entities: [Entity, Entity, Entity], next: async () => QueryResult, previous: async () => QueryResult }
287
- */
288
- async fetch(): Promise<QueryResult<TEntity>> {
289
- const queryResult = await processQuery(this._client, {
290
- predicates: this._predicates,
291
- limit: this._limit,
292
- cursor: this._cursor,
293
- ownedBy: this._ownedBy,
294
- createdBy: this._createdBy,
295
- orderBy: this._orderBy,
296
- validAtBlock: this._validAtBlock,
297
- ...this.selectionParams(),
298
- })
299
-
300
- const entities = await Promise.all(queryResult.data.map((entity) => this.projectEntity(entity)))
301
-
302
- this.cursor(queryResult.cursor)
303
- this.validAtBlock(BigInt(queryResult.blockNumber ?? 0))
304
-
305
- return new QueryResult<TEntity>(entities, this, this._cursor, this._limit, this._validAtBlock)
306
- }
307
-
308
- /**
309
- * Counts the entities from the query.
310
- * @returns The number of entities
153
+ * @throws {InvalidPredicateError} If no filter has been added.
154
+ * @throws {QueryError} If the node rejects the query.
311
155
  *
312
156
  * @example
313
- * const builder = client.select()
314
- * const result = await builder.where(eq("name", "John")).count()
315
- * // result = 10
157
+ * const page = await client.select({ key: true }).where(eq("category", "docs")).fetch()
158
+ * page.entities // this page
159
+ * await page.next() // the next one
316
160
  */
317
- async count() {
318
- const queryResult = await processQuery(this._client, {
319
- predicates: this._predicates,
161
+ // Async so that a builder with no filter rejects like every other failure here, rather than
162
+ // throwing synchronously out of a call the caller is awaiting.
163
+ async fetch(): Promise<QueryResult<TEntity>> {
164
+ // Snapshot the request here, so the pages of one walk are all pages of the *same* query. The
165
+ // builder stays mutable and reusable; a cursor is bound to the query, block and selection it
166
+ // was issued for, so re-reading a since-edited builder would page with a mismatched cursor.
167
+ const request = Object.freeze({
168
+ query: this.toString(),
169
+ select: this._select,
320
170
  limit: this._limit,
321
- cursor: this._cursor,
322
- ownedBy: this._ownedBy,
323
- createdBy: this._createdBy,
324
- orderBy: undefined,
325
- validAtBlock: this._validAtBlock,
326
- withAttributes: false,
327
- withMetadata: false,
328
- withPayload: false,
171
+ atBlock: this._atBlock,
329
172
  })
330
-
331
- return queryResult.data.length ?? 0
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
173
+ return fetchPage<TEntity>(this._client, request, this._cursor)
381
174
  }
382
175
 
383
176
  /**
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
177
+ * Walks every page, yielding one entity at a time.
387
178
  *
388
179
  * @example
389
- * const builder = client.buildQuery()
390
- * builder.withPayload(true)
180
+ * for await (const entity of client.select({ key: true }).where(eq("category", "docs"))) {
181
+ * console.log(entity.key)
182
+ * }
391
183
  */
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,
184
+ async *[Symbol.asyncIterator](): AsyncGenerator<TEntity> {
185
+ let page: QueryResult<TEntity> | undefined = await this.fetch()
186
+ while (page) {
187
+ yield* page.entities
188
+ page = page.hasNextPage() ? await page.next() : undefined
402
189
  }
403
190
  }
404
-
405
- protected projectEntity(rpcEntity: RpcEntity): Promise<Entity> {
406
- return entityFromRpcResult(rpcEntity)
407
- }
408
191
  }
409
192
 
410
193
  /**
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()`.
194
+ * Fetches one page and wires up the next.
420
195
  *
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()
196
+ * Free-standing rather than a method: a page holds this closure for its lifetime, and closing over
197
+ * a frozen request keeps the builder — and everything it references — out of that reference.
429
198
  */
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
- }
199
+ async function fetchPage<TEntity>(
200
+ client: ArkivClient,
201
+ request: Readonly<Omit<QueryRequest, "cursor">>,
202
+ cursor: string | undefined,
203
+ ): Promise<QueryResult<TEntity>> {
204
+ const response = await runQuery(client, { ...request, cursor })
205
+
206
+ return new QueryResult<TEntity>(
207
+ // Every row decodes to a full Entity; the static narrowing to the selected fields comes from
208
+ // `client.select()`'s inference and nothing at runtime depends on it.
209
+ response.entities as unknown as TEntity[],
210
+ response.blockNumber,
211
+ response.cursor,
212
+ (next) => fetchPage<TEntity>(client, request, next),
213
+ )
447
214
  }