@arkiv-network/sdk 0.7.0 → 0.8.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +39 -77
  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 +675 -357
  62. package/dist/index.d.cts +251 -203
  63. package/dist/index.d.cts.map +1 -1
  64. package/dist/index.d.ts +251 -203
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +626 -355
  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 -149
  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 -137
  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 +4 -1
  87. package/dist/types/index.d.cts +7 -2
  88. package/dist/types/index.d.ts +7 -2
  89. package/dist/types/index.js +4 -1
  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 +8 -8
  103. package/src/actions/wallet/createEntity.ts +104 -26
  104. package/src/actions/wallet/deleteEntity.ts +2 -3
  105. package/src/actions/wallet/extendEntity.ts +39 -16
  106. package/src/actions/wallet/mutateEntities.test.ts +76 -0
  107. package/src/actions/wallet/mutateEntities.ts +29 -74
  108. package/src/actions/wallet/patchEntity.ts +90 -0
  109. package/src/attr/attributes.test.ts +120 -0
  110. package/src/attr/attributes.ts +190 -0
  111. package/src/attr/codec.test.ts +162 -0
  112. package/src/attr/codec.ts +220 -0
  113. package/src/attr/decimal.ts +86 -0
  114. package/src/attr/errors.ts +145 -0
  115. package/src/attr/index.ts +50 -0
  116. package/src/attr/names.ts +106 -0
  117. package/src/attr/types.ts +150 -0
  118. package/src/attr/values.test.ts +271 -0
  119. package/src/attr/values.ts +502 -0
  120. package/src/chains/cheesecake.ts +22 -0
  121. package/src/chains/index.ts +1 -2
  122. package/src/clients/createPublicClient.test.ts +3 -5
  123. package/src/clients/createPublicClient.ts +5 -4
  124. package/src/clients/createWalletClient.test.ts +2 -2
  125. package/src/clients/createWalletClient.ts +13 -7
  126. package/src/clients/decorators/arkivPublic.ts +160 -139
  127. package/src/clients/decorators/arkivWallet.ts +91 -95
  128. package/src/consts.ts +2 -1
  129. package/src/entity/create.test.ts +184 -0
  130. package/src/entity/errors.ts +38 -0
  131. package/src/entity/events.ts +16 -0
  132. package/src/entity/expiry.test.ts +195 -0
  133. package/src/entity/expiry.ts +181 -0
  134. package/src/entity/flags.ts +65 -0
  135. package/src/entity/index.ts +14 -0
  136. package/src/entity/key.ts +68 -0
  137. package/src/entity/operations.ts +134 -0
  138. package/src/entity/params.ts +15 -0
  139. package/src/errors.ts +33 -28
  140. package/src/index.ts +6 -0
  141. package/src/query/engine.ts +59 -134
  142. package/src/query/errors.ts +151 -0
  143. package/src/query/expression.test.ts +286 -0
  144. package/src/query/expression.ts +493 -0
  145. package/src/query/index.ts +40 -4
  146. package/src/query/injection.test.ts +151 -0
  147. package/src/query/queryBuilder.test.ts +322 -637
  148. package/src/query/queryBuilder.ts +126 -359
  149. package/src/query/queryResult.ts +54 -51
  150. package/src/query/selection.test.ts +48 -98
  151. package/src/query/selection.ts +77 -65
  152. package/src/types/entity.test.ts +44 -155
  153. package/src/types/entity.ts +62 -44
  154. package/src/types/events.ts +64 -23
  155. package/src/types/index.ts +20 -16
  156. package/src/types/rpcSchema.ts +70 -33
  157. package/src/utils/arkivTransactions.test.ts +681 -38
  158. package/src/utils/arkivTransactions.ts +249 -104
  159. package/src/utils/chains.test.ts +21 -24
  160. package/src/utils/compression.ts +1 -1
  161. package/src/utils/entities.test.ts +62 -0
  162. package/src/utils/entities.ts +111 -29
  163. package/src/utils/expirationTime.ts +114 -10
  164. package/src/utils/logger.ts +2 -2
  165. package/src/utils/payload.ts +4 -2
  166. package/src/utils/revert.test.ts +99 -0
  167. package/src/utils/revert.ts +145 -0
  168. package/dist/chains-BifaY04V.cjs +0 -108
  169. package/dist/chains-CrtkVbBh.js +0 -86
  170. package/dist/chains-CrtkVbBh.js.map +0 -1
  171. package/dist/consts-AUoL-v42.cjs +0 -18
  172. package/dist/consts-BV4PR-de.js +0 -7
  173. package/dist/consts-BV4PR-de.js.map +0 -1
  174. package/dist/entity-B6g_EPm1.d.cts +0 -382
  175. package/dist/entity-B6g_EPm1.d.cts.map +0 -1
  176. package/dist/entity-qoLMAuuk.d.ts +0 -382
  177. package/dist/entity-qoLMAuuk.d.ts.map +0 -1
  178. package/dist/index-DdspxNAA.d.ts +0 -481
  179. package/dist/index-DdspxNAA.d.ts.map +0 -1
  180. package/dist/index-Ds9dwqb5.d.cts +0 -481
  181. package/dist/index-Ds9dwqb5.d.cts.map +0 -1
  182. package/dist/payload-B2kFJyah.d.ts +0 -6
  183. package/dist/payload-B2kFJyah.d.ts.map +0 -1
  184. package/dist/payload-CJN8fTvX.d.cts +0 -6
  185. package/dist/payload-CJN8fTvX.d.cts.map +0 -1
  186. package/dist/payload-Claf2jUO.js +0 -13
  187. package/dist/payload-Claf2jUO.js.map +0 -1
  188. package/dist/payload-DpA5PA-w.cjs +0 -23
  189. package/dist/query/index.js.map +0 -1
  190. package/dist/queryBuilder-D2uwP9jI.js +0 -704
  191. package/dist/queryBuilder-D2uwP9jI.js.map +0 -1
  192. package/dist/queryBuilder-UW2MCaX4.cjs +0 -792
  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 -119
  198. package/src/actions/wallet/updateEntity.ts +0 -60
  199. package/src/chains/braga.ts +0 -26
  200. package/src/chains/kaolin.ts +0 -29
  201. package/src/query/engine.test.ts +0 -526
  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 -210
  206. package/src/types/attributes.ts +0 -16
  207. package/src/utils/validation.test.ts +0 -60
  208. package/src/utils/validation.ts +0 -32
  209. /package/dist/{types-H-TSmRpm.js → types-Bx1EKe0v.js} +0 -0
  210. /package/dist/{types-o3zWarRp.cjs → types-CKPddlfS.cjs} +0 -0
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,6 +2,8 @@
2
2
  * @module main
3
3
  */
4
4
 
5
+ // the attribute type system
6
+ export * from "./attr"
5
7
  // export main arkiv stuff
6
8
  export type { ArkivClient } from "./clients/baseClient"
7
9
  export type { PublicArkivClient } from "./clients/createPublicClient"
@@ -10,10 +12,14 @@ export type { WalletArkivClient } from "./clients/createWalletClient"
10
12
  export { createWalletClient } from "./clients/createWalletClient"
11
13
  export type { PublicArkivActions } from "./clients/decorators/arkivPublic"
12
14
  export type { WalletArkivActions } from "./clients/decorators/arkivWallet"
15
+ // entity lifetimes, flags, keys and the protocol parameters behind them
16
+ export * from "./entity"
13
17
  // re-export errors
14
18
  export * from "./errors"
15
19
  // re-export arkiv types in main index file
16
20
  export * from "./types"
17
21
  // re-export chosen utils
18
22
  export { chainFromName } from "./utils/chains"
23
+ // every create needs an expiry, and this is the only way to build one
24
+ export { type DeadlineOptions, ExpirationTime } from "./utils/expirationTime"
19
25
  export { jsonToPayload, stringToPayload } from "./utils/payload"
@@ -1,153 +1,78 @@
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(" && ")
43
- }
11
+ export const MAX_LIMIT = 200
44
12
 
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
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
25
+ }
78
26
 
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
- })
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
+ }
92
35
 
93
- let query = processPredicates(predicates)
94
- if (ownedBy) {
95
- query += ` && $owner=${ownedBy}`
96
- }
97
- if (createdBy) {
98
- query += ` && $creator=${createdBy}`
99
- }
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
100
43
 
101
- // remove leading and trailing spaces and leading &&
102
- query = query.trim()
103
- if (query.startsWith("&& ")) {
104
- query = query.slice(3)
44
+ if (limit !== undefined) {
45
+ if (!Number.isInteger(limit) || limit < 1) {
46
+ throw new Error(`limit must be a positive integer, got ${limit}.`)
47
+ }
48
+ if (limit > MAX_LIMIT) {
49
+ throw new Error(
50
+ `limit ${limit} exceeds the node maximum of ${MAX_LIMIT}. Page through the results with ` +
51
+ "the cursor instead.",
52
+ )
53
+ }
105
54
  }
106
55
 
107
56
  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),
57
+ select,
58
+ ...(atBlock !== undefined && { atBlock: numberToHex(atBlock) }),
59
+ ...(limit !== undefined && { limit: numberToHex(limit) }),
60
+ ...(cursor !== undefined && { cursor }),
123
61
  }
124
62
 
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
- }
63
+ logger("arkiv_query %s %o", query, queryOptions)
137
64
 
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
- })
65
+ const result = await client
66
+ .request({ method: "arkiv_query", params: [query, queryOptions] })
67
+ .catch((error: unknown) => {
68
+ throw asQueryError(error, query) ?? error
69
+ })
145
70
 
146
- const result = await client.request({
147
- method: "arkiv_query",
148
- params: [query, queryOptions],
149
- })
150
71
  logger("Raw result from query %o", result)
151
72
 
152
- return result
73
+ return {
74
+ entities: result.data.map((rpcEntity) => entityFromRpcResult(rpcEntity)),
75
+ blockNumber: BigInt(result.blockNumber),
76
+ cursor: result.cursor,
77
+ }
153
78
  }
@@ -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` `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
+ }