@arkiv-network/sdk 0.8.1-dev.1 → 0.9.0-next.2

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 (184) hide show
  1. package/README.md +180 -61
  2. package/dist/chains/index.d.ts +17 -17
  3. package/dist/changeOwnership-Cy9gfyjN.d.cts +1160 -0
  4. package/dist/changeOwnership-Cy9gfyjN.d.cts.map +1 -0
  5. package/dist/changeOwnership-gXf-lBqI.d.ts +1160 -0
  6. package/dist/changeOwnership-gXf-lBqI.d.ts.map +1 -0
  7. package/dist/entity/index.cjs +4 -10
  8. package/dist/entity/index.d.cts +3 -3
  9. package/dist/entity/index.d.ts +3 -3
  10. package/dist/entity/index.js +3 -4
  11. package/dist/entity-BM1unrQf.cjs +41 -0
  12. package/dist/{entity-DJ0Gml9c.cjs → entity-BgaFJSaV.cjs} +4 -4
  13. package/dist/entity-CFAI1KyG.js +36 -0
  14. package/dist/entity-CFAI1KyG.js.map +1 -0
  15. package/dist/{entity-CniBgBSd.js → entity-CGqx_AU0.js} +4 -4
  16. package/dist/entity-CGqx_AU0.js.map +1 -0
  17. package/dist/{payload-AfUfSctb.d.ts → expirationTime-C7PZK49K.d.ts} +8 -37
  18. package/dist/expirationTime-C7PZK49K.d.ts.map +1 -0
  19. package/dist/expirationTime-CkIGNAg8.cjs +140 -0
  20. package/dist/{payload-D167BlJR.d.cts → expirationTime-DOB8V4B8.d.cts} +8 -37
  21. package/dist/expirationTime-DOB8V4B8.d.cts.map +1 -0
  22. package/dist/{expiry-Cgdb_dbq.js → expirationTime-UKPuA-LM.js} +62 -27
  23. package/dist/expirationTime-UKPuA-LM.js.map +1 -0
  24. package/dist/expiry-B48I-yTA.d.cts +40 -0
  25. package/dist/expiry-B48I-yTA.d.cts.map +1 -0
  26. package/dist/expiry-S2PUL8Hg.d.ts +40 -0
  27. package/dist/expiry-S2PUL8Hg.d.ts.map +1 -0
  28. package/dist/index-C-mMF5WZ.d.cts +735 -0
  29. package/dist/index-C-mMF5WZ.d.cts.map +1 -0
  30. package/dist/{index-BkqSnGJH.d.ts → index-C4zZqJ9S.d.ts} +5 -27
  31. package/dist/index-C4zZqJ9S.d.ts.map +1 -0
  32. package/dist/{index-CLhsFyVH.d.cts → index-CcVWYB6v.d.cts} +5 -27
  33. package/dist/index-CcVWYB6v.d.cts.map +1 -0
  34. package/dist/index-DCx2XC6S.d.ts +489 -0
  35. package/dist/index-DCx2XC6S.d.ts.map +1 -0
  36. package/dist/index-DLb7BfIw.d.ts +735 -0
  37. package/dist/index-DLb7BfIw.d.ts.map +1 -0
  38. package/dist/index-DzriOUm8.d.cts +489 -0
  39. package/dist/index-DzriOUm8.d.cts.map +1 -0
  40. package/dist/index.cjs +162 -146
  41. package/dist/index.d.cts +180 -133
  42. package/dist/index.d.cts.map +1 -1
  43. package/dist/index.d.ts +180 -133
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +93 -83
  46. package/dist/index.js.map +1 -1
  47. package/dist/params-BuIrJA9a.cjs +45 -0
  48. package/dist/params-DDJOnsNi.js +22 -0
  49. package/dist/params-DDJOnsNi.js.map +1 -0
  50. package/dist/query/index.cjs +9 -4
  51. package/dist/query/index.d.cts +6 -6
  52. package/dist/query/index.d.ts +6 -6
  53. package/dist/query/index.js +4 -5
  54. package/dist/query-D0KtuFh2.cjs +1625 -0
  55. package/dist/query-DpRDozmj.js +1417 -0
  56. package/dist/query-DpRDozmj.js.map +1 -0
  57. package/dist/schema/index.cjs +46 -0
  58. package/dist/schema/index.d.cts +2 -0
  59. package/dist/schema/index.d.ts +2 -0
  60. package/dist/schema/index.js +3 -0
  61. package/dist/{attr-BXTkpnoc.cjs → schema-B9AyXcgU.cjs} +513 -246
  62. package/dist/{attr-tmyREHtw.js → schema-DY4sqehl.js} +427 -244
  63. package/dist/schema-DY4sqehl.js.map +1 -0
  64. package/dist/types/index.cjs +1 -1
  65. package/dist/types/index.d.cts +5 -5
  66. package/dist/types/index.d.ts +5 -5
  67. package/dist/types/index.js +1 -1
  68. package/dist/utils/index.cjs +3 -5
  69. package/dist/utils/index.d.cts +3 -3
  70. package/dist/utils/index.d.ts +3 -3
  71. package/dist/utils/index.js +3 -3
  72. package/package.json +9 -12
  73. package/src/actions/public/getEntity.test.ts +69 -0
  74. package/src/actions/public/getEntity.ts +34 -20
  75. package/src/actions/public/predictEntityKeys.ts +8 -7
  76. package/src/actions/public/query.test.ts +58 -0
  77. package/src/actions/public/query.ts +41 -13
  78. package/src/actions/wallet/createEntity.ts +34 -63
  79. package/src/actions/wallet/executeBatch.test.ts +38 -0
  80. package/src/actions/wallet/executeBatch.ts +26 -9
  81. package/src/actions/wallet/extendEntity.ts +9 -8
  82. package/src/actions/wallet/patchEntity.ts +43 -52
  83. package/src/clients/createPublicClient.test.ts +10 -2
  84. package/src/clients/createWalletClient.ts +4 -1
  85. package/src/clients/decorators/arkivPublic.ts +123 -80
  86. package/src/clients/decorators/arkivWallet.ts +113 -65
  87. package/src/entity/create.test.ts +25 -21
  88. package/src/entity/expiry.ts +8 -6
  89. package/src/entity/flags.ts +1 -1
  90. package/src/entity/index.ts +2 -4
  91. package/src/entity/key.ts +3 -3
  92. package/src/entity/operations.ts +1 -1
  93. package/src/errors.ts +2 -11
  94. package/src/index.ts +18 -6
  95. package/src/query/builderBase.ts +166 -0
  96. package/src/query/countBuilder.test.ts +91 -0
  97. package/src/query/countBuilder.ts +67 -0
  98. package/src/query/engine.ts +9 -3
  99. package/src/query/errors.ts +24 -14
  100. package/src/query/expression.test.ts +129 -21
  101. package/src/query/expression.ts +323 -94
  102. package/src/query/index.ts +26 -4
  103. package/src/query/injection.test.ts +5 -11
  104. package/src/query/queryBuilder.test.ts +467 -58
  105. package/src/query/queryBuilder.ts +191 -98
  106. package/src/query/queryResult.ts +35 -9
  107. package/src/query/selection.test.ts +278 -24
  108. package/src/query/selection.ts +427 -86
  109. package/src/schema/attributes.test.ts +266 -0
  110. package/src/schema/attributes.ts +212 -0
  111. package/src/{attr → schema}/codec.test.ts +37 -5
  112. package/src/{attr → schema}/codec.ts +40 -11
  113. package/src/schema/column.ts +193 -0
  114. package/src/{attr → schema}/errors.ts +67 -10
  115. package/src/schema/index.ts +76 -0
  116. package/src/{attr → schema}/names.ts +4 -2
  117. package/src/schema/schema.test.ts +171 -0
  118. package/src/schema/schema.ts +250 -0
  119. package/src/{attr → schema}/types.ts +36 -30
  120. package/src/{attr → schema}/values.test.ts +62 -3
  121. package/src/{attr → schema}/values.ts +246 -65
  122. package/src/types/entity.test.ts +1 -44
  123. package/src/types/entity.ts +9 -55
  124. package/src/types/events.ts +1 -1
  125. package/src/types/index.ts +11 -4
  126. package/src/types/rpcSchema.ts +22 -16
  127. package/src/utils/arkivTransactions.test.ts +356 -116
  128. package/src/utils/arkivTransactions.ts +30 -38
  129. package/src/utils/entities.ts +10 -9
  130. package/src/utils/expirationTime.ts +5 -5
  131. package/src/utils/index.ts +0 -1
  132. package/src/utils/logger.test.ts +45 -0
  133. package/src/utils/logger.ts +5 -39
  134. package/src/utils/revert.test.ts +25 -0
  135. package/src/utils/revert.ts +28 -3
  136. package/dist/attr/index.cjs +0 -34
  137. package/dist/attr/index.d.cts +0 -2
  138. package/dist/attr/index.d.ts +0 -2
  139. package/dist/attr/index.js +0 -3
  140. package/dist/attr-tmyREHtw.js.map +0 -1
  141. package/dist/entity-B0t6sN7F.cjs +0 -71
  142. package/dist/entity-BIp_df9u.js +0 -67
  143. package/dist/entity-BIp_df9u.js.map +0 -1
  144. package/dist/entity-CniBgBSd.js.map +0 -1
  145. package/dist/expiry-BxOlvVWC.d.ts +0 -79
  146. package/dist/expiry-BxOlvVWC.d.ts.map +0 -1
  147. package/dist/expiry-CG9GhVXv.cjs +0 -135
  148. package/dist/expiry-Cgdb_dbq.js.map +0 -1
  149. package/dist/expiry-DRV29F1r.d.cts +0 -79
  150. package/dist/expiry-DRV29F1r.d.cts.map +0 -1
  151. package/dist/flags-DCD26uhe.cjs +0 -43
  152. package/dist/flags-DUT2gHEU.js +0 -33
  153. package/dist/flags-DUT2gHEU.js.map +0 -1
  154. package/dist/index-BkqSnGJH.d.ts.map +0 -1
  155. package/dist/index-C7fbakoI.d.cts +0 -278
  156. package/dist/index-C7fbakoI.d.cts.map +0 -1
  157. package/dist/index-CIcHPx2j.d.cts +0 -424
  158. package/dist/index-CIcHPx2j.d.cts.map +0 -1
  159. package/dist/index-CLhsFyVH.d.cts.map +0 -1
  160. package/dist/index-Cd2aaWme.d.ts +0 -424
  161. package/dist/index-Cd2aaWme.d.ts.map +0 -1
  162. package/dist/index-Cq6fa8AI.d.ts +0 -278
  163. package/dist/index-Cq6fa8AI.d.ts.map +0 -1
  164. package/dist/index-DaNeJUw8.d.ts +0 -955
  165. package/dist/index-DaNeJUw8.d.ts.map +0 -1
  166. package/dist/index-Dv16LO2s.d.cts +0 -955
  167. package/dist/index-Dv16LO2s.d.cts.map +0 -1
  168. package/dist/payload-AfUfSctb.d.ts.map +0 -1
  169. package/dist/payload-D167BlJR.d.cts.map +0 -1
  170. package/dist/payload-DE7wMztL.js +0 -92
  171. package/dist/payload-DE7wMztL.js.map +0 -1
  172. package/dist/payload-DqFweqiO.cjs +0 -108
  173. package/dist/query-9oojUdM7.cjs +0 -1128
  174. package/dist/query-Ber-lOJQ.js +0 -968
  175. package/dist/query-Ber-lOJQ.js.map +0 -1
  176. package/src/actions/public/getEntityCount.ts +0 -10
  177. package/src/attr/attributes.test.ts +0 -120
  178. package/src/attr/attributes.ts +0 -196
  179. package/src/attr/index.ts +0 -50
  180. package/src/types/mimeTypes.ts +0 -27
  181. package/src/utils/compression.test.ts +0 -32
  182. package/src/utils/compression.ts +0 -102
  183. package/src/utils/payload.ts +0 -36
  184. /package/src/{attr → schema}/decimal.ts +0 -0
@@ -1,18 +1,17 @@
1
- import type { TypeTag } from "../attr"
1
+ import type { TypeTag } from "../schema"
2
2
 
3
3
  /**
4
4
  * A predicate asked for an operator the query language does not define for that type.
5
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.
6
+ * The operator × type matrix is part of the language, not a policy: a prefix test on a type other
7
+ * than `str` is a **type error** at the node (a {@link QueryError} of kind `"type"`), not
8
+ * an empty result. Catching it here turns a round trip and an RPC error code into a stack trace
9
+ * pointing at the call site.
9
10
  *
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 |
11
+ * | | `=` `!=` `<` `<=` `>` `>=` `BETWEEN` | `STARTSWITH` |
12
+ * | --- | --- | --- |
13
+ * | `str` | yes | yes |
14
+ * | every other indexable type | yes | no |
16
15
  */
17
16
  export class UnsupportedOperatorError extends Error {
18
17
  /** The attribute the predicate names. */
@@ -35,9 +34,20 @@ export class UnsupportedOperatorError extends Error {
35
34
  }
36
35
 
37
36
  /**
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.
37
+ * A predicate or query could not be expressed in the query language for a reason other than its
38
+ * operator. The main causes:
39
+ *
40
+ * - a `$` name that is not a queryable system attribute — one returned in results but never
41
+ * indexed (`$createdAt`, `$updatedAt`, `$creationFlags`), or none at all;
42
+ * - a system attribute compared against the wrong type, or tested with `exists()` / `hasType()`,
43
+ * which on a system attribute would be constants;
44
+ * - a comparison against a `bytes` value, which is never indexed;
45
+ * - a `BETWEEN` whose bounds have different types;
46
+ * - a `hasType()` tag that is not an indexable type;
47
+ * - a combinator with nothing to combine;
48
+ * - a query with no filter at all — a named schema's tag counts as one;
49
+ * - through `.from(schema)`, a filter that does not fit the schema: a name that is not a column, a
50
+ * column that is not indexed, or a value of another type than the column's.
41
51
  */
42
52
  export class InvalidPredicateError extends Error {
43
53
  /** The attribute the predicate names, where there is one. */
@@ -58,7 +68,7 @@ export class InvalidPredicateError extends Error {
58
68
  export type QueryErrorKind =
59
69
  /** The query string does not parse. `data` locates it: `{ position, expected, got }`. */
60
70
  | "parse"
61
- /** A predicate is well-formed but ill-typed — a range operator on an equality-only type, an unknown tag. */
71
+ /** A predicate is well-formed but ill-typed — `STARTSWITH` on a type other than `str`, `BETWEEN` bounds of different types, an unknown tag. */
62
72
  | "type"
63
73
  /** A literal failed validation — an `i32` out of range, a bad EIP-55 checksum, more than 18 decimal places. */
64
74
  | "literal"
@@ -1,9 +1,11 @@
1
1
  import { describe, expect, it } from "bun:test"
2
- import { addr, bytes32, dec, i32, key, str, u64, u256 } from "../attr"
3
- import { InvalidAttributeNameError } from "../attr/errors"
2
+ import { addr, bytes, bytes32, dec, defineSchema, i32, key, str, u64, u256 } from "../schema"
3
+ import { InvalidAttributeNameError } from "../schema/errors"
4
4
  import { InvalidPredicateError, UnsupportedOperatorError } from "./errors"
5
5
  import {
6
6
  and,
7
+ between,
8
+ checkAgainstSchema,
7
9
  eq,
8
10
  exists,
9
11
  gt,
@@ -72,30 +74,29 @@ describe("comparisons", () => {
72
74
  expect(String(lte("a", 1))).toBe("a <= i32(1)")
73
75
  })
74
76
 
75
- it("allows range operators only on the ordered types", () => {
77
+ it("allows range operators on every indexable type", () => {
76
78
  expect(String(gt("level", i32(1)))).toBe("level > i32(1)")
77
79
  expect(String(gt("balance", u256(1n)))).toBe("balance > u256(1)")
78
80
  expect(String(gt("score", dec("1.5")))).toBe("score > dec(1.5)")
79
-
80
- // The node treats a range operator on an equality-only type as a parse error rather than an
81
- // empty result, so catching it here saves a round trip for an error code.
82
- for (const value of [str("a"), addr(ACCOUNT), key(ENTITY_KEY), bytes32(ENTITY_KEY), true]) {
83
- expect(() => gt("a", value)).toThrow(UnsupportedOperatorError)
84
- }
85
- expect(() => lte("name", "Bob")).toThrow(/does not define <= for a str/)
81
+ // Ordered as the index orders them: false < true, and unsigned bytes for the rest.
82
+ expect(String(gte("flagged", true))).toBe("flagged >= true")
83
+ expect(String(lte("name", "Bob"))).toBe("name <= str('Bob')")
84
+ expect(String(lt("who", addr(ACCOUNT)))).toBe(`who < addr(${ACCOUNT})`)
85
+ expect(String(gt("parent", key(ENTITY_KEY)))).toBe(`parent > key(${ENTITY_KEY})`)
86
+ expect(String(gt("hash", bytes32(ENTITY_KEY)))).toBe(`hash > bytes32(${ENTITY_KEY})`)
86
87
  })
87
88
 
88
89
  it("carries the offending operator and type on the error", () => {
89
90
  try {
90
- gte("name", "Bob")
91
+ startsWith("level", i32(1) as never)
91
92
  expect.unreachable()
92
93
  } catch (error) {
93
94
  expect(error).toBeInstanceOf(UnsupportedOperatorError)
94
95
  const { attributeName, operator, tag } = error as UnsupportedOperatorError
95
96
  expect({ attributeName, operator, tag }).toEqual({
96
- attributeName: "name",
97
- operator: ">=",
98
- tag: "str",
97
+ attributeName: "level",
98
+ operator: "STARTSWITH",
99
+ tag: "i32",
99
100
  })
100
101
  }
101
102
  })
@@ -128,14 +129,16 @@ describe("system attributes", () => {
128
129
  expect(() => eq("$expiresAt", 1_200_000n)).toThrow(/write u64\(\.\.\.\) around the value/)
129
130
  })
130
131
 
131
- it("allows range comparisons only on $expiresAt", () => {
132
- expect(() => gt("$owner", addr(ACCOUNT))).toThrow(UnsupportedOperatorError)
133
- expect(() => gt("$key", key(ENTITY_KEY))).toThrow(UnsupportedOperatorError)
132
+ it("allows range comparisons on every one", () => {
133
+ expect(String(gt("$owner", addr(ACCOUNT)))).toBe(`$owner > addr(${ACCOUNT})`)
134
+ expect(String(lte("$key", key(ENTITY_KEY)))).toBe(`$key <= key(${ENTITY_KEY})`)
134
135
  })
135
136
 
136
137
  it("rejects the ones that are returned but not indexed", () => {
137
- for (const name of ["$createdAt", "$updatedAt", "$creationFlags", "$contentType", "$payload"]) {
138
+ for (const name of ["$createdAt", "$updatedAt", "$creationFlags"]) {
138
139
  expect(() => eq(name, 1)).toThrow(/carries no index, so it cannot be filtered on/)
140
+ // A selection names the entity field, which has no `$`.
141
+ expect(() => eq(name, 1)).toThrow(`Select it as \`${name.slice(1)}\``)
139
142
  }
140
143
  })
141
144
 
@@ -154,6 +157,48 @@ describe("system attributes", () => {
154
157
  })
155
158
  })
156
159
 
160
+ describe("between", () => {
161
+ it("renders an inclusive range with both bounds tagged", () => {
162
+ expect(String(between("score", dec("3.5"), dec("5")))).toBe("score BETWEEN dec(3.5) AND dec(5)")
163
+ expect(String(between("level", 1, 5))).toBe("level BETWEEN i32(1) AND i32(5)")
164
+ expect(String(between("name", "a", "m"))).toBe("name BETWEEN str('a') AND str('m')")
165
+ expect(String(between("$expiresAt", u64(100n), u64(200n)))).toBe(
166
+ "$expiresAt BETWEEN u64(100) AND u64(200)",
167
+ )
168
+ })
169
+
170
+ it("is a leaf: its own AND needs no parentheses", () => {
171
+ expect(String(and(between("a", 1, 5), eq("b", 2)))).toBe(
172
+ "a BETWEEN i32(1) AND i32(5) AND b = i32(2)",
173
+ )
174
+ expect(String(or(between("a", 1, 5), eq("b", 2)))).toBe(
175
+ "a BETWEEN i32(1) AND i32(5) OR b = i32(2)",
176
+ )
177
+ expect(String(not(between("a", 1, 5)))).toBe("NOT a BETWEEN i32(1) AND i32(5)")
178
+ })
179
+
180
+ it("rejects bounds of different types", () => {
181
+ // @ts-expect-error - both bounds must have one type
182
+ expect(() => between("score", 1, dec("5"))).toThrow(InvalidPredicateError)
183
+ expect(() => between("score", 1, dec("5") as never)).toThrow(/a i32 and a dec bound/)
184
+ })
185
+
186
+ it("applies the checks every comparison does", () => {
187
+ expect(() => between("$owner", ACCOUNT, ACCOUNT)).toThrow(/is a addr, but the value is a str/)
188
+ expect(() => between("blob", bytes("0x01") as never, bytes("0x02") as never)).toThrow(
189
+ /bytes are stored but never indexed/,
190
+ )
191
+ expect(() => between("not", 1, 2)).toThrow(InvalidAttributeNameError)
192
+ })
193
+
194
+ it("is checked against a schema like a comparison", () => {
195
+ const Posts = defineSchema({ score: dec().indexed(), title: str() })
196
+ expect(() => checkAgainstSchema(between("score", dec("1"), dec("2")), Posts)).not.toThrow()
197
+ expect(() => checkAgainstSchema(between("score", 1, 2), Posts)).toThrow(/asserts a i32/)
198
+ expect(() => checkAgainstSchema(between("title", "a", "b"), Posts)).toThrow(/not indexed/)
199
+ })
200
+ })
201
+
157
202
  describe("startsWith", () => {
158
203
  it("takes a bare string or a str value", () => {
159
204
  expect(String(startsWith("desc", "ab"))).toBe("desc STARTSWITH str('ab')")
@@ -177,8 +222,8 @@ describe("exists and hasType", () => {
177
222
  })
178
223
 
179
224
  it("rejects a type that cannot be set on an attribute", () => {
180
- // @ts-expect-error - bytes backs $payload and is never a user attribute
181
- expect(() => hasType("blob", "bytes")).toThrow(/is not a settable attribute type/)
225
+ // @ts-expect-error - bytes is never indexed, so TYPEOF can never see it
226
+ expect(() => hasType("blob", "bytes")).toThrow(/is not an indexable attribute type/)
182
227
  // @ts-expect-error - not a type at all
183
228
  expect(() => hasType("age", "int")).toThrow(InvalidPredicateError)
184
229
  })
@@ -188,7 +233,7 @@ describe("exists and hasType", () => {
188
233
  // `TYPEOF(a) = toString` — a query the node can only answer with a parse error.
189
234
  for (const inherited of ["constructor", "toString", "valueOf", "hasOwnProperty"]) {
190
235
  // @ts-expect-error - not a type tag
191
- expect(() => hasType("age", inherited)).toThrow(/is not a settable attribute type/)
236
+ expect(() => hasType("age", inherited)).toThrow(/is not an indexable attribute type/)
192
237
  }
193
238
  })
194
239
 
@@ -284,3 +329,66 @@ describe("the expression tree", () => {
284
329
  expect(`${expression}`).toBe(render(expression))
285
330
  })
286
331
  })
332
+
333
+ describe("bytes", () => {
334
+ it("cannot be filtered on, bare or tagged — it is never indexed", () => {
335
+ expect(() => eq("body", new Uint8Array([1]) as never)).toThrow(
336
+ /bytes are stored but never indexed/,
337
+ )
338
+ expect(() => ne("body", bytes("0x01") as never)).toThrow(InvalidPredicateError)
339
+ })
340
+ })
341
+
342
+ describe("checkAgainstSchema", () => {
343
+ const Posts = defineSchema({
344
+ author: addr().indexed(),
345
+ category: str().indexed(),
346
+ score: dec().indexed(),
347
+ title: str(),
348
+ body: bytes(),
349
+ })
350
+
351
+ it("accepts indexed columns compared against their own type, and system attributes", () => {
352
+ expect(() =>
353
+ checkAgainstSchema(
354
+ and(
355
+ eq("category", "docs"),
356
+ not(gt("score", dec("4"))),
357
+ or(exists("author"), startsWith("category", "d")),
358
+ hasType("score", "dec"),
359
+ eq("$owner", addr(ACCOUNT)),
360
+ ),
361
+ Posts,
362
+ ),
363
+ ).not.toThrow()
364
+ })
365
+
366
+ it("rejects an unindexed column, saying how to index it", () => {
367
+ expect(() => checkAgainstSchema(eq("title", "x"), Posts)).toThrow(
368
+ /"title" is not indexed.*str\(\)\.indexed\(\)/,
369
+ )
370
+ // Wherever it hides in the tree.
371
+ expect(() =>
372
+ checkAgainstSchema(and(eq("category", "docs"), not(exists("title"))), Posts),
373
+ ).toThrow(InvalidPredicateError)
374
+ })
375
+
376
+ it("rejects a bytes column without suggesting it be indexed, which bytes never can be", () => {
377
+ const check = () => checkAgainstSchema(exists("body"), Posts)
378
+ expect(check).toThrow(InvalidPredicateError)
379
+ expect(check).toThrow(/"body" is a bytes column, and bytes are stored but never indexed/)
380
+ expect(check).toThrow(/Index a str, bytes32 or key derived from it instead/)
381
+ expect(check).not.toThrow(/\.indexed\(\)/)
382
+ })
383
+
384
+ it("rejects a name that is not a column", () => {
385
+ expect(() => checkAgainstSchema(eq("nope", 1), Posts)).toThrow(/"nope" is not a column/)
386
+ })
387
+
388
+ it("rejects a comparison against a value of another type, which could never match", () => {
389
+ expect(() => checkAgainstSchema(eq("score", 5), Posts)).toThrow(
390
+ /"score" is a dec column, but the predicate asserts a i32/,
391
+ )
392
+ expect(() => checkAgainstSchema(hasType("score", "i32"), Posts)).toThrow(InvalidPredicateError)
393
+ })
394
+ })