@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
package/src/errors.ts CHANGED
@@ -1,65 +1,42 @@
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"
15
- }
16
- }
17
- export class InvalidAttributeError extends Error {
18
- constructor(key: string, value: number) {
19
- const remedy = Number.isInteger(value)
20
- ? `Offset it into the non-negative range (e.g. store ${String(value)} + OFFSET, subtracting OFFSET on read) to keep numeric ordering/range queries; otherwise store it as a string (e.g. "${String(value)}")`
21
- : `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)}")`
22
- super(
23
- `Invalid numeric value for attribute "${key}": ${String(value)}. Numeric attribute values must be non-negative integers (stored on-chain as unsigned 256-bit). ${remedy}.`,
24
- )
25
- this.name = "InvalidAttributeError"
26
- }
27
- }
28
-
29
- export class InvalidAttributeKeyError extends Error {
30
- constructor(key: string, reason: string) {
31
- super(
32
- `Invalid attribute key "${key}": ${reason}. Keys must be 1-32 bytes, start with a lowercase letter (a-z), and contain only a-z, 0-9, ".", "-" or "_".`,
33
- )
34
- this.name = "InvalidAttributeKeyError"
35
- }
36
- }
37
-
38
- export class DuplicateAttributeError extends Error {
39
- constructor(key: string) {
40
- super(`Duplicate attribute key "${key}". Attribute keys must be unique per entity.`)
41
- this.name = "DuplicateAttributeError"
42
- }
43
- }
44
-
45
7
  export class NoMoreResultsError extends Error {
46
8
  constructor() {
47
- super("No more results")
9
+ super("No more results — the last page carried no cursor. Check hasNextPage() before next().")
48
10
  this.name = "NoMoreResultsError"
49
11
  }
50
12
  }
51
13
 
52
- export class NoCursorOrLimitError extends Error {
53
- constructor() {
54
- super("Cursor and limit must be defined to fetch next")
55
- this.name = "NoCursorOrLimitError"
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
+ super(
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.",
24
+ )
25
+ this.name = "NoEntityFoundError"
26
+ this.entityKey = entityKey
56
27
  }
57
28
  }
58
29
 
59
- export class NoEntityFoundError extends Error {
60
- constructor() {
61
- super("No entity found")
62
- this.name = "NoEntityFoundError"
30
+ export class EmptyPatchError extends Error {
31
+ readonly entityKey: string
32
+
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
63
40
  }
64
41
  }
65
42
 
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,158 +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 { isEntityKey } from "../utils/validation"
7
- import type { Predicate } from "./predicate"
7
+ import { asQueryError } from "./errors"
8
8
 
9
9
  const logger = getLogger("query:engine")
10
10
 
11
- function processPredicates(predicates: Predicate[]): string {
12
- const processValue = (value: string | number) => {
13
- if (typeof value === "string") {
14
- // 32-byte hex strings are stored as the EntityKey attribute type (see
15
- // encodeAttribute), which the query language only matches against unquoted
16
- // hex literals. All other strings — including shorter hex — are stored and
17
- // compared as quoted strings.
18
- return isEntityKey(value) ? value : `"${value}"`
19
- }
20
- return value
21
- }
22
- return predicates
23
- .map((predicate) => {
24
- switch (predicate.type) {
25
- case "eq":
26
- return `${predicate.key} = ${processValue(predicate.value)}`
27
- case "neq":
28
- return `${predicate.key} != ${processValue(predicate.value)}`
29
- case "gt":
30
- return `${predicate.key} > ${processValue(predicate.value)}`
31
- case "gte":
32
- return `${predicate.key} >= ${processValue(predicate.value)}`
33
- case "lt":
34
- return `${predicate.key} < ${processValue(predicate.value)}`
35
- case "lte":
36
- return `${predicate.key} <= ${processValue(predicate.value)}`
37
- case "not":
38
- return `!${predicate.key}`
39
- case "or":
40
- return `(${predicate.predicates.map((predicate) => processPredicates([predicate])).join(" || ")})`
41
- case "and":
42
- return `(${predicate.predicates.map((predicate) => processPredicates([predicate])).join(" && ")})`
43
- default:
44
- return ""
45
- }
46
- })
47
- .join(" && ")
48
- }
11
+ export const MAX_LIMIT = 200
49
12
 
50
- export async function processQuery(
51
- client: ArkivClient,
52
- queryParams: {
53
- predicates: Predicate[]
54
- limit: number | undefined
55
- cursor: string | undefined
56
- ownedBy: Hex | undefined
57
- createdBy: Hex | undefined
58
- orderBy: RpcOrderByAttribute[] | undefined
59
- validAtBlock?: bigint | undefined
60
- withAttributes?: boolean | undefined
61
- withMetadata?: boolean | undefined
62
- withPayload?: boolean | undefined
63
- /**
64
- * Fully-resolved include-data for fine-grained selection. When provided it takes
65
- * precedence over the `withAttributes`/`withMetadata`/`withPayload` booleans.
66
- */
67
- includeData?: RpcIncludeData | undefined
68
- },
69
- ) {
70
- const {
71
- predicates,
72
- limit,
73
- cursor,
74
- ownedBy,
75
- createdBy,
76
- orderBy,
77
- validAtBlock,
78
- withAttributes,
79
- withMetadata,
80
- withPayload,
81
- includeData,
82
- } = 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
+ }
83
26
 
84
- logger("Processing query with params %o", {
85
- predicates,
86
- cursor,
87
- limit,
88
- ownedBy,
89
- createdBy,
90
- orderBy,
91
- validAtBlock,
92
- withAttributes,
93
- withMetadata,
94
- withPayload,
95
- includeData,
96
- })
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
+ }
97
35
 
98
- let query = processPredicates(predicates)
99
- if (ownedBy) {
100
- query += ` && $owner=${ownedBy}`
101
- }
102
- if (createdBy) {
103
- query += ` && $creator=${createdBy}`
104
- }
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
105
43
 
106
- // remove leading and trailing spaces and leading &&
107
- query = query.trim()
108
- if (query.startsWith("&& ")) {
109
- 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
+ }
110
54
  }
111
55
 
112
56
  const queryOptions: RpcQueryOptions = {
113
- includeData:
114
- includeData ??
115
- ({
116
- key: true,
117
- attributes: withAttributes ?? false,
118
- payload: withPayload ?? false,
119
- contentType: withMetadata ?? false,
120
- expiration: withMetadata ?? false,
121
- owner: withMetadata ?? false,
122
- creator: withMetadata ?? false,
123
- createdAtBlock: withMetadata ?? false,
124
- lastModifiedAtBlock: withMetadata ?? false,
125
- transactionIndexInBlock: withMetadata ?? false,
126
- operationIndexInTransaction: withMetadata ?? false,
127
- } as RpcIncludeData),
57
+ select,
58
+ ...(atBlock !== undefined && { atBlock: numberToHex(atBlock) }),
59
+ ...(limit !== undefined && { limit: numberToHex(limit) }),
60
+ ...(cursor !== undefined && { cursor }),
128
61
  }
129
62
 
130
- if (validAtBlock !== undefined) {
131
- queryOptions.atBlock = numberToHex(validAtBlock)
132
- }
133
- if (limit !== undefined) {
134
- queryOptions.resultsPerPage = numberToHex(limit)
135
- }
136
- if (cursor !== undefined) {
137
- queryOptions.cursor = cursor
138
- }
139
- if (orderBy !== undefined) {
140
- queryOptions.orderBy = orderBy
141
- }
63
+ logger("arkiv_query %s %o", query, queryOptions)
142
64
 
143
- logger("Built query to send %s %o", query, {
144
- includeData: queryOptions.includeData,
145
- atBlock: queryOptions.atBlock ? hexToNumber(queryOptions.atBlock) : undefined,
146
- orderBy: queryOptions.orderBy,
147
- resultsPerPage: queryOptions.resultsPerPage,
148
- cursor: queryOptions.cursor,
149
- })
65
+ const result = await client
66
+ .request({ method: "arkiv_query", params: [query, queryOptions] })
67
+ .catch((error: unknown) => {
68
+ throw asQueryError(error, query) ?? error
69
+ })
150
70
 
151
- const result = await client.request({
152
- method: "arkiv_query",
153
- params: [query, queryOptions],
154
- })
155
71
  logger("Raw result from query %o", result)
156
72
 
157
- return result
73
+ return {
74
+ entities: result.data.map((rpcEntity) => entityFromRpcResult(rpcEntity)),
75
+ blockNumber: BigInt(result.blockNumber),
76
+ cursor: result.cursor,
77
+ }
158
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
+ }