@arkiv-network/sdk 0.7.0 → 0.8.0-advanced.3

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 (240) hide show
  1. package/README.md +71 -16
  2. package/dist/actions/advanced/index.cjs +13 -0
  3. package/dist/actions/advanced/index.d.cts +8 -0
  4. package/dist/actions/advanced/index.d.ts +9 -0
  5. package/dist/actions/advanced/index.js +9 -0
  6. package/dist/advanced-Ds2XRflQ.cjs +656 -0
  7. package/dist/advanced-FZy7WoYR.js +598 -0
  8. package/dist/advanced-FZy7WoYR.js.map +1 -0
  9. package/dist/arkivTransactions-BpARZOsH.d.cts +584 -0
  10. package/dist/arkivTransactions-BpARZOsH.d.cts.map +1 -0
  11. package/dist/arkivTransactions-DfZ_yVDD.d.ts +584 -0
  12. package/dist/arkivTransactions-DfZ_yVDD.d.ts.map +1 -0
  13. package/dist/attr/index.cjs +35 -0
  14. package/dist/attr/index.d.cts +2 -0
  15. package/dist/attr/index.d.ts +3 -0
  16. package/dist/attr/index.js +4 -0
  17. package/dist/attributes-BJGuhsqp.js +819 -0
  18. package/dist/attributes-BJGuhsqp.js.map +1 -0
  19. package/dist/attributes-CMJlp1wn.cjs +1039 -0
  20. package/dist/chains/index.cjs +2 -3
  21. package/dist/chains/index.d.cts +41 -76
  22. package/dist/chains/index.d.cts.map +1 -1
  23. package/dist/chains/index.d.ts +24 -59
  24. package/dist/chains/index.d.ts.map +1 -1
  25. package/dist/chains/index.js +2 -2
  26. package/dist/chains-BfNQliHU.cjs +76 -0
  27. package/dist/chains-Du8tql7I.js +60 -0
  28. package/dist/chains-Du8tql7I.js.map +1 -0
  29. package/dist/codec-M74UR1_0.d.ts +101 -0
  30. package/dist/codec-M74UR1_0.d.ts.map +1 -0
  31. package/dist/entity/index.cjs +21 -0
  32. package/dist/entity/index.d.cts +4 -0
  33. package/dist/entity/index.d.ts +5 -0
  34. package/dist/entity/index.js +7 -0
  35. package/dist/entity-B00Li04j.cjs +71 -0
  36. package/dist/entity-B7G8q_Eo.cjs +0 -0
  37. package/dist/entity-BYQR1uoe.js +67 -0
  38. package/dist/entity-BYQR1uoe.js.map +1 -0
  39. package/dist/entity-CD_qFF2M.js +0 -0
  40. package/dist/errors-51znTSB1.cjs +56 -0
  41. package/dist/errors-CwPJ16bn.js +33 -0
  42. package/dist/errors-CwPJ16bn.js.map +1 -0
  43. package/dist/expiry-CZErShX6.js +95 -0
  44. package/dist/expiry-CZErShX6.js.map +1 -0
  45. package/dist/expiry-DRV29F1r.d.cts +79 -0
  46. package/dist/expiry-DRV29F1r.d.cts.map +1 -0
  47. package/dist/expiry-nDbCgZX4.d.ts +79 -0
  48. package/dist/expiry-nDbCgZX4.d.ts.map +1 -0
  49. package/dist/expiry-xr6mx4f7.cjs +135 -0
  50. package/dist/flags-5dTNRnow.d.ts +41 -0
  51. package/dist/flags-5dTNRnow.d.ts.map +1 -0
  52. package/dist/flags-Bh0HJldY.cjs +43 -0
  53. package/dist/flags-CRjPpoh3.d.cts +41 -0
  54. package/dist/flags-CRjPpoh3.d.cts.map +1 -0
  55. package/dist/flags-Cr76ldwF.js +33 -0
  56. package/dist/flags-Cr76ldwF.js.map +1 -0
  57. package/dist/index-CMeD8uEq.d.cts +280 -0
  58. package/dist/index-CMeD8uEq.d.cts.map +1 -0
  59. package/dist/index-CTDI5pXZ.d.ts +184 -0
  60. package/dist/index-CTDI5pXZ.d.ts.map +1 -0
  61. package/dist/index-CekGjkRx.d.cts +184 -0
  62. package/dist/index-CekGjkRx.d.cts.map +1 -0
  63. package/dist/index-D0IzFwMX.d.ts +328 -0
  64. package/dist/index-D0IzFwMX.d.ts.map +1 -0
  65. package/dist/index-DZxwz2Ky.d.cts +177 -0
  66. package/dist/index-DZxwz2Ky.d.cts.map +1 -0
  67. package/dist/index-Dz4SkQWg.d.ts +280 -0
  68. package/dist/index-Dz4SkQWg.d.ts.map +1 -0
  69. package/dist/index-JP31fjyM.d.ts +177 -0
  70. package/dist/index-JP31fjyM.d.ts.map +1 -0
  71. package/dist/index-NbfnXxgr.d.cts +424 -0
  72. package/dist/index-NbfnXxgr.d.cts.map +1 -0
  73. package/dist/index.cjs +434 -412
  74. package/dist/index.d.cts +401 -235
  75. package/dist/index.d.cts.map +1 -1
  76. package/dist/index.d.ts +402 -235
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +370 -404
  79. package/dist/index.js.map +1 -1
  80. package/dist/key-E7mv0BYf.cjs +116 -0
  81. package/dist/key-XQkpw2xh.js +82 -0
  82. package/dist/key-XQkpw2xh.js.map +1 -0
  83. package/dist/logger-CVpvU8ik.js +153 -0
  84. package/dist/logger-CVpvU8ik.js.map +1 -0
  85. package/dist/logger-Cr15lPAQ.cjs +188 -0
  86. package/dist/payload-Cvrir3Q-.cjs +108 -0
  87. package/dist/payload-D53ms-Mg.d.ts +153 -0
  88. package/dist/payload-D53ms-Mg.d.ts.map +1 -0
  89. package/dist/payload-DaPzUvEh.js +92 -0
  90. package/dist/payload-DaPzUvEh.js.map +1 -0
  91. package/dist/payload-hluX1rc0.d.cts +153 -0
  92. package/dist/payload-hluX1rc0.d.cts.map +1 -0
  93. package/dist/query/index.cjs +26 -149
  94. package/dist/query/index.d.cts +5 -3
  95. package/dist/query/index.d.ts +6 -3
  96. package/dist/query/index.js +8 -137
  97. package/dist/queryBuilder-BXR-HxD9.cjs +947 -0
  98. package/dist/queryBuilder-BwauPaGO.js +823 -0
  99. package/dist/queryBuilder-BwauPaGO.js.map +1 -0
  100. package/dist/selection-C6KeWJ7g.d.cts +441 -0
  101. package/dist/selection-C6KeWJ7g.d.cts.map +1 -0
  102. package/dist/selection-mjiiAGDO.d.ts +442 -0
  103. package/dist/selection-mjiiAGDO.d.ts.map +1 -0
  104. package/dist/types/index.cjs +4 -1
  105. package/dist/types/index.d.cts +7 -2
  106. package/dist/types/index.d.ts +8 -2
  107. package/dist/types/index.js +4 -1
  108. package/dist/types-Bu_ZwMbZ.js +0 -0
  109. package/dist/types-NRqymi6S.cjs +0 -0
  110. package/dist/utils/index.cjs +4 -17
  111. package/dist/utils/index.d.cts +3 -17
  112. package/dist/utils/index.d.ts +3 -17
  113. package/dist/utils/index.js +4 -18
  114. package/package.json +26 -9
  115. package/src/actions/advanced/advanced.test.ts +258 -0
  116. package/src/actions/advanced/buildMutation.ts +104 -0
  117. package/src/actions/advanced/getMutationResult.ts +112 -0
  118. package/src/actions/advanced/index.ts +21 -0
  119. package/src/actions/advanced/pingTransaction.ts +43 -0
  120. package/src/actions/advanced/sendMutation.ts +59 -0
  121. package/src/actions/public/getEntity.ts +28 -43
  122. package/src/actions/public/getEntityNonce.ts +30 -0
  123. package/src/actions/public/predictEntityKeys.test.ts +159 -0
  124. package/src/actions/public/predictEntityKeys.ts +152 -0
  125. package/src/actions/public/query.ts +35 -67
  126. package/src/actions/public/watchEntityEvents.test.ts +414 -0
  127. package/src/actions/public/watchEntityEvents.ts +225 -0
  128. package/src/actions/wallet/changeOwnership.ts +8 -8
  129. package/src/actions/wallet/createEntity.ts +105 -26
  130. package/src/actions/wallet/deleteEntity.ts +2 -3
  131. package/src/actions/wallet/executeBatch.test.ts +76 -0
  132. package/src/actions/wallet/executeBatch.ts +51 -0
  133. package/src/actions/wallet/extendEntity.ts +40 -16
  134. package/src/actions/wallet/patchEntity.ts +90 -0
  135. package/src/attr/attributes.test.ts +120 -0
  136. package/src/attr/attributes.ts +196 -0
  137. package/src/attr/codec.test.ts +162 -0
  138. package/src/attr/codec.ts +220 -0
  139. package/src/attr/decimal.ts +86 -0
  140. package/src/attr/errors.ts +145 -0
  141. package/src/attr/index.ts +50 -0
  142. package/src/attr/names.ts +106 -0
  143. package/src/attr/types.ts +150 -0
  144. package/src/attr/values.test.ts +271 -0
  145. package/src/attr/values.ts +506 -0
  146. package/src/chains/cheesecake.ts +22 -0
  147. package/src/chains/index.ts +1 -2
  148. package/src/chains/localhost.ts +3 -0
  149. package/src/clients/createPublicClient.test.ts +4 -6
  150. package/src/clients/createPublicClient.ts +9 -8
  151. package/src/clients/createWalletClient.test.ts +4 -4
  152. package/src/clients/createWalletClient.ts +13 -9
  153. package/src/clients/decorators/arkivAdvanced.ts +144 -0
  154. package/src/clients/decorators/arkivPublic.ts +181 -147
  155. package/src/clients/decorators/arkivWallet.ts +141 -122
  156. package/src/consts.ts +2 -1
  157. package/src/entity/create.test.ts +203 -0
  158. package/src/entity/errors.ts +39 -0
  159. package/src/entity/events.ts +16 -0
  160. package/src/entity/expiry.test.ts +195 -0
  161. package/src/entity/expiry.ts +182 -0
  162. package/src/entity/flags.ts +65 -0
  163. package/src/entity/index.ts +16 -0
  164. package/src/entity/key.ts +88 -0
  165. package/src/entity/operations.ts +135 -0
  166. package/src/entity/params.ts +15 -0
  167. package/src/errors.ts +33 -28
  168. package/src/index.ts +19 -0
  169. package/src/query/engine.ts +68 -132
  170. package/src/query/errors.ts +151 -0
  171. package/src/query/expression.test.ts +286 -0
  172. package/src/query/expression.ts +500 -0
  173. package/src/query/index.ts +40 -4
  174. package/src/query/injection.test.ts +151 -0
  175. package/src/query/queryBuilder.test.ts +342 -637
  176. package/src/query/queryBuilder.ts +133 -359
  177. package/src/query/queryResult.ts +54 -51
  178. package/src/query/selection.test.ts +48 -98
  179. package/src/query/selection.ts +77 -65
  180. package/src/types/entity.test.ts +44 -155
  181. package/src/types/entity.ts +62 -44
  182. package/src/types/events.ts +64 -23
  183. package/src/types/index.ts +24 -20
  184. package/src/types/rpcSchema.ts +71 -33
  185. package/src/types/txParams.ts +8 -0
  186. package/src/utils/arkivTransactions.test.ts +687 -38
  187. package/src/utils/arkivTransactions.ts +400 -115
  188. package/src/utils/chains.test.ts +21 -24
  189. package/src/utils/compression.ts +1 -1
  190. package/src/utils/entities.test.ts +62 -0
  191. package/src/utils/entities.ts +111 -29
  192. package/src/utils/expirationTime.ts +153 -10
  193. package/src/utils/logger.ts +2 -2
  194. package/src/utils/payload.ts +29 -2
  195. package/src/utils/revert.test.ts +99 -0
  196. package/src/utils/revert.ts +145 -0
  197. package/dist/chains-BifaY04V.cjs +0 -108
  198. package/dist/chains-CrtkVbBh.js +0 -86
  199. package/dist/chains-CrtkVbBh.js.map +0 -1
  200. package/dist/consts-AUoL-v42.cjs +0 -18
  201. package/dist/consts-BV4PR-de.js +0 -7
  202. package/dist/consts-BV4PR-de.js.map +0 -1
  203. package/dist/entity-B6g_EPm1.d.cts +0 -382
  204. package/dist/entity-B6g_EPm1.d.cts.map +0 -1
  205. package/dist/entity-qoLMAuuk.d.ts +0 -382
  206. package/dist/entity-qoLMAuuk.d.ts.map +0 -1
  207. package/dist/index-DdspxNAA.d.ts +0 -481
  208. package/dist/index-DdspxNAA.d.ts.map +0 -1
  209. package/dist/index-Ds9dwqb5.d.cts +0 -481
  210. package/dist/index-Ds9dwqb5.d.cts.map +0 -1
  211. package/dist/payload-B2kFJyah.d.ts +0 -6
  212. package/dist/payload-B2kFJyah.d.ts.map +0 -1
  213. package/dist/payload-CJN8fTvX.d.cts +0 -6
  214. package/dist/payload-CJN8fTvX.d.cts.map +0 -1
  215. package/dist/payload-Claf2jUO.js +0 -13
  216. package/dist/payload-Claf2jUO.js.map +0 -1
  217. package/dist/payload-DpA5PA-w.cjs +0 -23
  218. package/dist/query/index.js.map +0 -1
  219. package/dist/queryBuilder-D2uwP9jI.js +0 -704
  220. package/dist/queryBuilder-D2uwP9jI.js.map +0 -1
  221. package/dist/queryBuilder-UW2MCaX4.cjs +0 -792
  222. package/dist/utils/index.d.cts.map +0 -1
  223. package/dist/utils/index.d.ts.map +0 -1
  224. package/dist/utils/index.js.map +0 -1
  225. package/src/actions/public/buildQuery.ts +0 -10
  226. package/src/actions/public/subscribeEntityEvents.ts +0 -119
  227. package/src/actions/wallet/mutateEntities.ts +0 -111
  228. package/src/actions/wallet/updateEntity.ts +0 -60
  229. package/src/chains/braga.ts +0 -26
  230. package/src/chains/kaolin.ts +0 -29
  231. package/src/query/engine.test.ts +0 -526
  232. package/src/query/predicate.test.ts +0 -461
  233. package/src/query/predicate.ts +0 -144
  234. package/src/query/queryResult.test.ts +0 -270
  235. package/src/query/selectQueryBuilder.test.ts +0 -210
  236. package/src/types/attributes.ts +0 -16
  237. package/src/utils/validation.test.ts +0 -60
  238. package/src/utils/validation.ts +0 -32
  239. /package/dist/{types-H-TSmRpm.js → attr-DlmErkvR.cjs} +0 -0
  240. /package/dist/{types-o3zWarRp.cjs → attr-GcQKatub.js} +0 -0
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Max uint64 value
3
+ */
4
+ export const MAX_EXPIRES_AT = 2n ** 64n - 1n
5
+
6
+ /** The operation tags of the `execute` ABI's tagged union. */
7
+ export const OperationType = {
8
+ Create: 1,
9
+ Patch: 2,
10
+ ExtendExpiry: 3,
11
+ TransferOwnership: 4,
12
+ Delete: 5,
13
+ } as const
14
+
15
+ export type OperationType = (typeof OperationType)[keyof typeof OperationType]
package/src/errors.ts CHANGED
@@ -1,45 +1,50 @@
1
- import { BLOCK_TIME } from "./consts"
2
-
3
1
  export class EntityMutationError extends Error {
4
- constructor(message: string) {
5
- super(message)
2
+ constructor(message: string, options?: { cause?: unknown }) {
3
+ super(message, options)
6
4
  this.name = "EntityMutationError"
7
5
  }
8
6
  }
9
- export class InvalidExpirationError extends Error {
10
- constructor(expiresIn: unknown) {
11
- super(
12
- `Invalid expiresIn: ${String(expiresIn)}. expiresIn must be a positive integer and a multiple of the Arkiv block time (${BLOCK_TIME} seconds), because expiration is measured in whole blocks (1 block = ${BLOCK_TIME} seconds).`,
13
- )
14
- this.name = "InvalidExpirationError"
7
+ export class NoMoreResultsError extends Error {
8
+ constructor() {
9
+ super("No more results — the last page carried no cursor. Check hasNextPage() before next().")
10
+ this.name = "NoMoreResultsError"
15
11
  }
16
12
  }
17
- export class InvalidAttributeError extends Error {
18
- constructor(key: string, value: number) {
13
+
14
+ export class NoEntityFoundError extends Error {
15
+ /** The key that matched nothing, when the lookup was by key. */
16
+ readonly entityKey: string | undefined
17
+
18
+ constructor(entityKey?: string) {
19
19
  super(
20
- `Invalid numeric value for attribute "${key}": ${String(value)}. Numeric attribute values must be integers. To keep numeric ordering/range queries, scale it to an integer (e.g. Math.round(${String(value)} * 1000), dividing by 1000 on read); otherwise store it as a string (e.g. "${String(value)}").`,
20
+ entityKey === undefined
21
+ ? "No entity found"
22
+ : `No live entity with key ${entityKey}. It was never created, or it has been deleted or ` +
23
+ "has expired.",
21
24
  )
22
- this.name = "InvalidAttributeError"
25
+ this.name = "NoEntityFoundError"
26
+ this.entityKey = entityKey
23
27
  }
24
28
  }
25
29
 
26
- export class NoMoreResultsError extends Error {
27
- constructor() {
28
- super("No more results")
29
- this.name = "NoMoreResultsError"
30
- }
31
- }
30
+ export class EmptyPatchError extends Error {
31
+ readonly entityKey: string
32
32
 
33
- export class NoCursorOrLimitError extends Error {
34
- constructor() {
35
- super("Cursor and limit must be defined to fetch next")
36
- this.name = "NoCursorOrLimitError"
33
+ constructor(entityKey: string) {
34
+ super(
35
+ `The patch for entity ${entityKey} has nothing to apply. Pass "set" to write attributes, ` +
36
+ '"unset" to remove them, or "payload"/"contentType" to replace the contents.',
37
+ )
38
+ this.name = "EmptyPatchError"
39
+ this.entityKey = entityKey
37
40
  }
38
41
  }
39
42
 
40
- export class NoEntityFoundError extends Error {
41
- constructor() {
42
- super("No entity found")
43
- this.name = "NoEntityFoundError"
43
+ export class InvalidContentTypeError extends Error {
44
+ constructor(contentType: string) {
45
+ super(
46
+ `Invalid content type "${contentType}". Must follow RFC 2045 MIME grammar and be lowercase only (e.g. "text/plain").`,
47
+ )
48
+ this.name = "InvalidContentTypeError"
44
49
  }
45
50
  }
package/src/index.ts CHANGED
@@ -2,18 +2,37 @@
2
2
  * @module main
3
3
  */
4
4
 
5
+ // the advanced path: build, send, ping and read mutation results as separate minimal-RPC steps
6
+ export * from "./actions/advanced"
7
+ // the attribute type system
8
+ export * from "./attr"
5
9
  // export main arkiv stuff
6
10
  export type { ArkivClient } from "./clients/baseClient"
7
11
  export type { PublicArkivClient } from "./clients/createPublicClient"
8
12
  export { createPublicClient } from "./clients/createPublicClient"
9
13
  export type { WalletArkivClient } from "./clients/createWalletClient"
10
14
  export { createWalletClient } from "./clients/createWalletClient"
15
+ export type {
16
+ PublicAdvancedActions,
17
+ WalletAdvancedActions,
18
+ } from "./clients/decorators/arkivAdvanced"
11
19
  export type { PublicArkivActions } from "./clients/decorators/arkivPublic"
12
20
  export type { WalletArkivActions } from "./clients/decorators/arkivWallet"
21
+ // entity lifetimes, flags, keys and the protocol parameters behind them
22
+ export * from "./entity"
13
23
  // re-export errors
14
24
  export * from "./errors"
15
25
  // re-export arkiv types in main index file
16
26
  export * from "./types"
27
+ // the building blocks behind the advanced path, for callers that go even lower
28
+ export type { EntityMutationOps, MutationEvents } from "./utils/arkivTransactions"
29
+ export {
30
+ buildEntityOperations,
31
+ collectMutationEvents,
32
+ mutationNeedsBlockNumber,
33
+ } from "./utils/arkivTransactions"
17
34
  // re-export chosen utils
18
35
  export { chainFromName } from "./utils/chains"
36
+ // every create needs an expiry, and this is the only way to build one
37
+ export { type DeadlineOptions, ExpirationTime } from "./utils/expirationTime"
19
38
  export { jsonToPayload, stringToPayload } from "./utils/payload"
@@ -1,153 +1,89 @@
1
- import type { Hex } from "viem"
2
- import { hexToNumber, numberToHex } from "viem"
1
+ import { numberToHex } from "viem"
3
2
  import type { ArkivClient } from "../clients/baseClient"
4
- import type { RpcIncludeData, RpcOrderByAttribute, RpcQueryOptions } from "../types/rpcSchema"
3
+ import type { Entity } from "../types/entity"
4
+ import type { RpcQueryOptions, RpcSelect } from "../types/rpcSchema"
5
+ import { entityFromRpcResult } from "../utils/entities"
5
6
  import { getLogger } from "../utils/logger"
6
- import type { Predicate } from "./predicate"
7
+ import { asQueryError } from "./errors"
7
8
 
8
9
  const logger = getLogger("query:engine")
9
10
 
10
- function processPredicates(predicates: Predicate[]): string {
11
- const processValue = (value: string | number) => {
12
- if (typeof value === "string") {
13
- return `"${value}"`
14
- }
15
- return value
16
- }
17
- return predicates
18
- .map((predicate) => {
19
- switch (predicate.type) {
20
- case "eq":
21
- return `${predicate.key} = ${processValue(predicate.value)}`
22
- case "neq":
23
- return `${predicate.key} != ${processValue(predicate.value)}`
24
- case "gt":
25
- return `${predicate.key} > ${processValue(predicate.value)}`
26
- case "gte":
27
- return `${predicate.key} >= ${processValue(predicate.value)}`
28
- case "lt":
29
- return `${predicate.key} < ${processValue(predicate.value)}`
30
- case "lte":
31
- return `${predicate.key} <= ${processValue(predicate.value)}`
32
- case "not":
33
- return `!${predicate.key}`
34
- case "or":
35
- return `(${predicate.predicates.map((predicate) => processPredicates([predicate])).join(" || ")})`
36
- case "and":
37
- return `(${predicate.predicates.map((predicate) => processPredicates([predicate])).join(" && ")})`
38
- default:
39
- return ""
40
- }
41
- })
42
- .join(" && ")
11
+ export const MAX_LIMIT = 200
12
+
13
+ /** One `arkiv_query` call. */
14
+ export type QueryRequest = {
15
+ /** The query expression, already rendered. */
16
+ query: string
17
+ /** What to return. */
18
+ select: RpcSelect
19
+ /** Page size, up to {@link MAX_LIMIT}. Defaults to the node's own page size. */
20
+ limit?: number | undefined
21
+ /** Cursor from a previous page. */
22
+ cursor?: string | undefined
23
+ /** Block height to read at. Defaults to the head. */
24
+ atBlock?: bigint | undefined
43
25
  }
44
26
 
45
- export async function processQuery(
46
- client: ArkivClient,
47
- queryParams: {
48
- predicates: Predicate[]
49
- limit: number | undefined
50
- cursor: string | undefined
51
- ownedBy: Hex | undefined
52
- createdBy: Hex | undefined
53
- orderBy: RpcOrderByAttribute[] | undefined
54
- validAtBlock?: bigint | undefined
55
- withAttributes?: boolean | undefined
56
- withMetadata?: boolean | undefined
57
- withPayload?: boolean | undefined
58
- /**
59
- * Fully-resolved include-data for fine-grained selection. When provided it takes
60
- * precedence over the `withAttributes`/`withMetadata`/`withPayload` booleans.
61
- */
62
- includeData?: RpcIncludeData | undefined
63
- },
64
- ) {
65
- const {
66
- predicates,
67
- limit,
68
- cursor,
69
- ownedBy,
70
- createdBy,
71
- orderBy,
72
- validAtBlock,
73
- withAttributes,
74
- withMetadata,
75
- withPayload,
76
- includeData,
77
- } = queryParams
27
+ /** One page of results. */
28
+ export type QueryResponse = {
29
+ entities: Entity[]
30
+ /** The block the page was read at — the same for every page of a paginated query. */
31
+ blockNumber: bigint
32
+ /** The cursor for the next page, or `undefined` when this was the last one. */
33
+ cursor: string | undefined
34
+ }
78
35
 
79
- logger("Processing query with params %o", {
80
- predicates,
81
- cursor,
82
- limit,
83
- ownedBy,
84
- createdBy,
85
- orderBy,
86
- validAtBlock,
87
- withAttributes,
88
- withMetadata,
89
- withPayload,
90
- includeData,
91
- })
36
+ /**
37
+ * Runs one query and decodes the page it returns.
38
+ *
39
+ * @throws {QueryError} If the node rejects the query.
40
+ */
41
+ export async function runQuery(client: ArkivClient, request: QueryRequest): Promise<QueryResponse> {
42
+ const { query, select, limit, cursor, atBlock } = request
92
43
 
93
- let query = processPredicates(predicates)
94
- if (ownedBy) {
95
- query += ` && $owner=${ownedBy}`
96
- }
97
- if (createdBy) {
98
- query += ` && $creator=${createdBy}`
44
+ // A cursor is bound to the block it was issued at; without a block the node reads at the head,
45
+ // and the resume breaks whenever a block has landed in between — an intermittent failure this
46
+ // turns into a deterministic one. (Pages chained through `QueryResult.next` inherit the block
47
+ // automatically; this catches a cursor resumed by hand.)
48
+ if (cursor !== undefined && atBlock === undefined) {
49
+ throw new Error(
50
+ "a cursor must be resumed with `atBlock` set to the `blockNumber` of the page it came " +
51
+ "from — persist the two together.",
52
+ )
99
53
  }
100
54
 
101
- // remove leading and trailing spaces and leading &&
102
- query = query.trim()
103
- if (query.startsWith("&& ")) {
104
- query = query.slice(3)
55
+ if (limit !== undefined) {
56
+ if (!Number.isInteger(limit) || limit < 1) {
57
+ throw new Error(`limit must be a positive integer, got ${limit}.`)
58
+ }
59
+ if (limit > MAX_LIMIT) {
60
+ throw new Error(
61
+ `limit ${limit} exceeds the node maximum of ${MAX_LIMIT}. Page through the results with ` +
62
+ "the cursor instead.",
63
+ )
64
+ }
105
65
  }
106
66
 
107
67
  const queryOptions: RpcQueryOptions = {
108
- includeData:
109
- includeData ??
110
- ({
111
- key: true,
112
- attributes: withAttributes ?? false,
113
- payload: withPayload ?? false,
114
- contentType: withMetadata ?? false,
115
- expiration: withMetadata ?? false,
116
- owner: withMetadata ?? false,
117
- creator: withMetadata ?? false,
118
- createdAtBlock: withMetadata ?? false,
119
- lastModifiedAtBlock: withMetadata ?? false,
120
- transactionIndexInBlock: withMetadata ?? false,
121
- operationIndexInTransaction: withMetadata ?? false,
122
- } as RpcIncludeData),
68
+ select,
69
+ ...(atBlock !== undefined && { atBlock: numberToHex(atBlock) }),
70
+ ...(limit !== undefined && { limit: numberToHex(limit) }),
71
+ ...(cursor !== undefined && { cursor }),
123
72
  }
124
73
 
125
- if (validAtBlock !== undefined) {
126
- queryOptions.atBlock = numberToHex(validAtBlock)
127
- }
128
- if (limit !== undefined) {
129
- queryOptions.resultsPerPage = numberToHex(limit)
130
- }
131
- if (cursor !== undefined) {
132
- queryOptions.cursor = cursor
133
- }
134
- if (orderBy !== undefined) {
135
- queryOptions.orderBy = orderBy
136
- }
74
+ logger("arkiv_query %s %o", query, queryOptions)
137
75
 
138
- logger("Built query to send %s %o", query, {
139
- includeData: queryOptions.includeData,
140
- atBlock: queryOptions.atBlock ? hexToNumber(queryOptions.atBlock) : undefined,
141
- orderBy: queryOptions.orderBy,
142
- resultsPerPage: queryOptions.resultsPerPage,
143
- cursor: queryOptions.cursor,
144
- })
76
+ const result = await client
77
+ .request({ method: "arkiv_query", params: [query, queryOptions] })
78
+ .catch((error: unknown) => {
79
+ throw asQueryError(error, query) ?? error
80
+ })
145
81
 
146
- const result = await client.request({
147
- method: "arkiv_query",
148
- params: [query, queryOptions],
149
- })
150
82
  logger("Raw result from query %o", result)
151
83
 
152
- return result
84
+ return {
85
+ entities: result.data.map((rpcEntity) => entityFromRpcResult(rpcEntity)),
86
+ blockNumber: BigInt(result.blockNumber),
87
+ cursor: result.cursor,
88
+ }
153
89
  }
@@ -0,0 +1,151 @@
1
+ import type { TypeTag } from "../attr"
2
+
3
+ /**
4
+ * A predicate asked for an operator the query language does not define for that type.
5
+ *
6
+ * The operator × type matrix is part of the language, not a policy: a range comparison on an
7
+ * equality-only type is a **parse error** at the node, not an empty result. Catching it here turns
8
+ * a round trip and an RPC error code into a stack trace pointing at the call site.
9
+ *
10
+ * | | `=` `!=` | `<` `<=` `>` `>=` | `STARTSWITH` |
11
+ * | --- | --- | --- | --- |
12
+ * | `bool` | yes | no | no |
13
+ * | `i32` `u64` `u256` `dec` | yes | yes | no |
14
+ * | `str` | yes | no | yes |
15
+ * | `addr` `key` `bytes32` | yes | no | no |
16
+ */
17
+ export class UnsupportedOperatorError extends Error {
18
+ /** The attribute the predicate names. */
19
+ readonly attributeName: string
20
+ /** The operator, as it would have been written in the query. */
21
+ readonly operator: string
22
+ /** The type of the value it was applied to. */
23
+ readonly tag: TypeTag
24
+
25
+ constructor(attributeName: string, operator: string, tag: TypeTag, hint?: string) {
26
+ super(
27
+ `The query language does not define ${operator} for a ${tag}, so "${attributeName} ` +
28
+ `${operator} ..." cannot be expressed.${hint ? ` ${hint}` : ""}`,
29
+ )
30
+ this.name = "UnsupportedOperatorError"
31
+ this.attributeName = attributeName
32
+ this.operator = operator
33
+ this.tag = tag
34
+ }
35
+ }
36
+
37
+ /**
38
+ * A predicate could not be expressed in the query language for a reason other than its operator —
39
+ * a system attribute that is returned in results but cannot be filtered on, a system attribute
40
+ * compared against the wrong type, or a combinator with nothing to combine.
41
+ */
42
+ export class InvalidPredicateError extends Error {
43
+ /** The attribute the predicate names, where there is one. */
44
+ readonly attributeName: string | undefined
45
+
46
+ constructor(message: string, attributeName?: string) {
47
+ super(message)
48
+ this.name = "InvalidPredicateError"
49
+ this.attributeName = attributeName
50
+ }
51
+ }
52
+
53
+ /**
54
+ * What a node rejected a query for. Part of the frozen RPC surface, so it is safe to branch on —
55
+ * a `"cursor"` failure means start the query again, a `"block"` failure means the node no longer
56
+ * retains that height, and the rest mean the query itself needs fixing.
57
+ */
58
+ export type QueryErrorKind =
59
+ /** The query string does not parse. `data` locates it: `{ position, expected, got }`. */
60
+ | "parse"
61
+ /** A predicate is well-formed but ill-typed — a range operator on an equality-only type, an unknown tag. */
62
+ | "type"
63
+ /** A literal failed validation — an `i32` out of range, a bad EIP-55 checksum, more than 18 decimal places. */
64
+ | "literal"
65
+ /** A node budget was exceeded — query length, predicate count, nesting depth, bitmap operations. */
66
+ | "limits"
67
+ /** The cursor expired, is malformed, or belongs to a different query, block or selection. */
68
+ | "cursor"
69
+ /** `atBlock` is outside the range the node retains. */
70
+ | "block"
71
+
72
+ /** The JSON-RPC error codes `arkiv_query` answers with, and what each one means. */
73
+ const QUERY_ERROR_KINDS: Readonly<Record<number, QueryErrorKind>> = {
74
+ [-32001]: "parse",
75
+ [-32002]: "type",
76
+ [-32003]: "literal",
77
+ [-32004]: "limits",
78
+ [-32005]: "cursor",
79
+ [-32006]: "block",
80
+ }
81
+
82
+ /**
83
+ * The node rejected a query.
84
+ *
85
+ * These codes are part of the frozen `arkiv_query` surface, so {@link kind} and {@link data} are
86
+ * stable enough to branch on — which the underlying JSON-RPC error is not, because the codes
87
+ * overlap Ethereum's own EIP-1474 numbering and get wrapped accordingly on the way up.
88
+ *
89
+ * @example
90
+ * try {
91
+ * await result.next()
92
+ * } catch (error) {
93
+ * if (error instanceof QueryError && error.kind === "cursor") {
94
+ * // The page is gone — start the query over.
95
+ * }
96
+ * }
97
+ */
98
+ export class QueryError extends Error {
99
+ /** Which failure this is. */
100
+ readonly kind: QueryErrorKind
101
+ /** The JSON-RPC error code. */
102
+ readonly code: number
103
+ /** The machine-readable detail the node sent, if any — e.g. `{ position, expected, got }`. */
104
+ readonly data: unknown
105
+ /** The query string that was rejected. */
106
+ readonly query: string
107
+
108
+ constructor(kind: QueryErrorKind, code: number, query: string, data: unknown, detail?: string) {
109
+ super(`Query rejected (${kind}, ${code})${detail ? `: ${detail}` : ""}. Query: ${query}`, {
110
+ cause: data,
111
+ })
112
+ this.name = "QueryError"
113
+ this.kind = kind
114
+ this.code = code
115
+ this.data = data
116
+ this.query = query
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Recognises an `arkiv_query` failure in whatever the transport threw.
122
+ *
123
+ * The codes are dug out of the error chain rather than read off the top: viem maps this range onto
124
+ * its own EIP-1474 error classes (`-32001` is its "resource not found"), so by the time an error
125
+ * surfaces the Arkiv meaning is one or more `cause` links down.
126
+ *
127
+ * @returns The typed error, or `undefined` if this was not a query rejection.
128
+ */
129
+ export function asQueryError(error: unknown, query: string): QueryError | undefined {
130
+ let code: number | undefined
131
+ let data: unknown
132
+ let detail: string | undefined
133
+
134
+ let current: unknown = error
135
+ for (let depth = 0; current != null && depth < 8; depth++) {
136
+ const link = current as {
137
+ code?: unknown
138
+ data?: unknown
139
+ shortMessage?: unknown
140
+ cause?: unknown
141
+ }
142
+ if (code === undefined && typeof link.code === "number") code = link.code
143
+ if (data === undefined && link.data !== undefined) data = link.data
144
+ if (detail === undefined && typeof link.shortMessage === "string") detail = link.shortMessage
145
+ current = link.cause
146
+ }
147
+
148
+ const kind = code === undefined ? undefined : QUERY_ERROR_KINDS[code]
149
+ if (kind === undefined || code === undefined) return undefined
150
+ return new QueryError(kind, code, query, data, detail)
151
+ }