@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,23 +1,36 @@
1
1
  import type { Account, Address, Chain, Client, Hex, PublicActions, Transport } from "viem"
2
2
  import { type GetBlockTimingReturnType, getBlockTiming } from "../../actions/public/getBlockTiming"
3
3
  import { getEntity } from "../../actions/public/getEntity"
4
- import { getEntityCount } from "../../actions/public/getEntityCount"
5
4
  import { getEntityNonce } from "../../actions/public/getEntityNonce"
6
5
  import {
7
6
  type PredictEntityKeysParameters,
8
7
  type PredictEntityKeysReturnType,
9
8
  predictEntityKeys,
10
9
  } from "../../actions/public/predictEntityKeys"
11
- import { type QueryOptions, type QueryReturnType, query } from "../../actions/public/query"
10
+ import {
11
+ type QueryOptions,
12
+ type QueryReturnType,
13
+ type QueryRow,
14
+ query,
15
+ } from "../../actions/public/query"
12
16
  import {
13
17
  type WatchEntityEventsParameters,
14
18
  watchEntityEvents,
15
19
  } from "../../actions/public/watchEntityEvents"
16
20
  import type { SaltInput } from "../../entity/key"
21
+ import { CountQueryBuilder } from "../../query/countBuilder"
17
22
  import type { Expression } from "../../query/expression"
18
23
  import { SelectQueryBuilder } from "../../query/queryBuilder"
19
- import type { EntitySelection, FullEntity, ProjectedEntity, SelectArg } from "../../query/selection"
20
- import type { Entity } from "../../types/entity"
24
+ import type {
25
+ FullEntity,
26
+ FullRow,
27
+ KnownSystemFields,
28
+ NonEmptySelection,
29
+ PartialRow,
30
+ SelectArg,
31
+ SelectionOf,
32
+ } from "../../query/selection"
33
+ import type { Schema } from "../../schema"
21
34
 
22
35
  export type PublicArkivActions<
23
36
  transport extends Transport = Transport,
@@ -39,14 +52,21 @@ export type PublicArkivActions<
39
52
  /**
40
53
  * Returns the entity with the given key.
41
54
  *
42
- * - JSON-RPC Methods: `arkiv_query`
55
+ * Given a schema, it is read through it: the result is a flat row of every entity field and
56
+ * every column, each typed as the schema declares and checked against it.
57
+ *
58
+ * - JSON-RPC Methods: `arkiv_getEntity`
43
59
  *
44
60
  * @param key - The entity key (hex string)
45
- * @returns The entity with the given key, with every field populated. {@link FullEntity}
61
+ * @param schema - Optionally, the schema to read the entity through.
62
+ * @returns The entity with the given key, with every field populated — a {@link FullEntity}, or
63
+ * a {@link FullRow} through a schema.
46
64
  *
47
65
  * @throws {InvalidValueError} If the key is not 32 bytes.
48
66
  * @throws {NoEntityFoundError} If no live entity has that key — it never existed, or it was
49
67
  * deleted or has expired.
68
+ * @throws {SchemaMismatchError} If, given a schema, the entity does not fit it.
69
+ * @throws {SchemaDefinitionError} If the schema given was not built with `defineSchema`.
50
70
  *
51
71
  * @example
52
72
  * import { createPublicClient } from "@arkiv-network/sdk"
@@ -58,85 +78,120 @@ export type PublicArkivActions<
58
78
  * transport: http(),
59
79
  * })
60
80
  * const entity = await client.getEntity(entityKey)
61
- * // Entity {
62
- * // key: "0x9f2c…",
63
- * // owner: "0xabc…",
64
- * // contentType: "application/json",
65
- * // payload: Uint8Array, // entity.toJson() / entity.toText() decode it
66
- * // attributes: { category: { type: "str", value: "docs" } },
67
- * // expiresAt: 1_297_000n,
81
+ * // {
82
+ * // $key: "0x9f2c…",
83
+ * // $owner: "0xabc…",
84
+ * // $expiresAt: 1_297_000n,
85
+ * // $attributes: { category: { type: "str", value: "docs" } },
86
+ * // ...
68
87
  * // }
88
+ *
89
+ * const post = await client.getEntity(entityKey, Posts)
90
+ * // { $key: "0x9f2c…", $owner: "0xabc…", ..., category: { type: "str", value: "docs" }, ... }
69
91
  */
70
- getEntity: (key: Hex) => Promise<FullEntity>
92
+ getEntity: {
93
+ (key: Hex): Promise<FullEntity>
94
+ <S extends Schema>(key: Hex, schema: S): Promise<FullRow<S>>
95
+ }
71
96
 
72
97
  /**
73
98
  * Returns a SelectQueryBuilder for building and executing queries — the recommended way to
74
99
  * read entities. You declare up front which parts of an entity you want returned, so results
75
100
  * always contain exactly the data you asked for.
76
101
  *
102
+ * The selection names entity fields and attributes side by side, and each result is a flat row.
103
+ * Chain `.from(schema)` to read through a schema: the selection keeps its meaning, but each
104
+ * attribute comes back typed and checked as the schema declares, and `where` may only filter on
105
+ * the schema's indexed columns.
106
+ *
77
107
  * - JSON-RPC Methods: `arkiv_query`
78
108
  *
79
109
  * @param selection - What to include in the results. Omit it (or pass `"*"`) to select everything,
80
110
  * or pass an object to select specific parts (at least one field is required). Every part is
81
- * opt-in, including the `key`. The selection is flat — each field maps to an entity field.
111
+ * opt-in, including the `$key`. The selection is flat — each field maps to a field of the result.
82
112
  * {@link SelectArg}
83
113
  * @returns A SelectQueryBuilder instance for building and executing queries. {@link SelectQueryBuilder}
84
114
  *
85
115
  * @example
86
116
  * import { createPublicClient } from "@arkiv-network/sdk"
87
117
  * import { tiramisu } from "@arkiv-network/sdk/chains"
88
- * import { eq } from "@arkiv-network/sdk/query"
118
+ * import { and, eq, gt } from "@arkiv-network/sdk/query"
119
+ * import { dec } from "@arkiv-network/sdk/schema"
89
120
  * import { http } from "viem"
90
121
  *
91
122
  * const client = createPublicClient({
92
123
  * chain: tiramisu,
93
124
  * transport: http(),
94
125
  * })
95
- * // select everything
96
- * await client.select().where(eq("category", "docs")).fetch()
97
- * await client.select("*").where(eq("category", "docs")).fetch()
98
- * // only the key
99
- * await client.select({ key: true }).where(eq("category", "docs")).fetch()
100
- * // select specific fields — result typed { owner: Hex; attributes: Attributes }
101
- * await client.select({ owner: true, attributes: true }).fetch()
102
- * // a single field — result typed { owner: Hex }
103
- * await client.select({ owner: true }).fetch()
126
+ * // through a schema — rows typed { $key: Hex; author: AddrValue; title: StrValue }
127
+ * await client
128
+ * .select({ $key: true, author: true, title: true })
129
+ * .from(Posts)
130
+ * .where(and(eq("category", "docs"), gt("score", dec("4.0"))))
131
+ * .fetch()
132
+ * // every field and column
133
+ * await client.select().from(Posts).where(eq("category", "docs")).fetch()
134
+ *
135
+ * // the same selection without a schema — rows typed
136
+ * // { $key: Hex; author: ArkivValue | undefined; title: ArkivValue | undefined }
137
+ * await client.select({ $key: true, author: true, title: true }).where(eq("category", "docs")).fetch()
104
138
  */
105
139
  select: {
106
- /** Select every field. Pass nothing or `"*"`; the returned entities contain all fields. */
107
- (selection?: "*"): SelectQueryBuilder<FullEntity>
108
140
  /**
109
- * Pick the entity fields to return. Set the ones you want to `true` (at least one is required);
110
- * the result is typed to exactly those fields, so reading anything else is a compile error.
111
- *
112
- * Available fields: `key`, `owner`, `creator`, `createdAt`, `updatedAt`, `expiresAt`,
113
- * `creationFlags`, `contentType`, `payload`, `attributeSchema` and `attributes`.
141
+ * Select every field — and every column through a schema, or the `$attributes` map without
142
+ * one. Pass nothing or `"*"`. The `$attributeSchema` is not included: select it by name.
143
+ */
144
+ (selection?: "*"): SelectQueryBuilder<"*">
145
+ /**
146
+ * Pick the fields to return. Set the ones you want to `true` (at least one is required); the
147
+ * result is typed to exactly those fields, so reading anything else is a compile error.
114
148
  *
115
- * `attributes` also takes a map of names, to fetch only those:
116
- * `select({ key: true, attributes: { projectId: true } })`.
149
+ * Entity fields start with `$`: `$key`, `$owner`, `$creator`, `$createdAt`, `$updatedAt`,
150
+ * `$expiresAt` and `$creationFlags`. `$attributes` reads every attribute as a map, and
151
+ * `$attributeSchema` their names, types and indexing without the values — a cheap way to find
152
+ * out what entities hold before fetching values that may be too large for one response. Any
153
+ * other name is an attribute, read as a field
154
+ * of its own: untyped without a schema, and through one typed and checked as the column it
155
+ * must be. An attribute name cannot start with `$`, so the two never collide.
117
156
  *
118
157
  * Pass the selection inline so its fields stay literal `true`. A selection stored in a `let`/
119
- * `const` variable widens to `boolean` and the result type can no longer be narrowed — annotate
120
- * it `as const` (e.g. `const sel = { owner: true } as const`) in that case.
158
+ * `const` variable widens to `boolean`, and one typed {@link SelectArg} is only known at
159
+ * runtime: which fields come back is then decided at runtime, so every field of the result is
160
+ * optional — a {@link PartialRow}. Annotate a stored selection `as const` to keep it narrow.
121
161
  *
122
162
  * @example
123
- * client.select({ owner: true, attributes: true }) // entities typed { owner, attributes }
124
- * client.select({ key: true, payload: true }) // includes payload → toText()/toJson() too
125
- * client.select({ key: true, attributeSchema: true }) // what shape is the data?
126
- */
127
- <const S extends EntitySelection>(selection: S): SelectQueryBuilder<ProjectedEntity<S>>
128
- /**
129
- * Dynamic selection: accepts a value typed {@link SelectArg} (e.g. built at runtime). The
130
- * result cannot be narrowed in this case, so the entities are typed as the full entity.
163
+ * client.select({ $key: true, title: true }) // { $key: Hex; title: ArkivValue | undefined }
164
+ * client.select({ $key: true, title: true }).from(Posts) // { $key: Hex; title: StrValue }
165
+ * client.select({ $owner: true, $attributes: true }) // { $owner: Hex; $attributes: Attributes }
166
+ * client.select({ $key: true, $attributeSchema: true }) // what do they hold? no values
131
167
  */
132
- (selection: SelectArg): SelectQueryBuilder<FullEntity>
168
+ <const S extends SelectArg>(
169
+ selection: S & NonEmptySelection<S> & KnownSystemFields<S>,
170
+ ): SelectQueryBuilder<SelectionOf<S>>
133
171
  }
134
172
 
173
+ /**
174
+ * Returns a CountQueryBuilder: counts the entities a query matches, without fetching any of them.
175
+ *
176
+ * It filters exactly as {@link select} does — `.from(schema)`, `.where(...)`, `.ownedBy(...)`,
177
+ * `.createdBy(...)` and `.atBlock(...)` — and `.fetch()` resolves to a `bigint`. To show the total
178
+ * next to a page of rows, use `select(...).withCount()` instead.
179
+ *
180
+ * - JSON-RPC Methods: `arkiv_query`, with the `count` projection
181
+ *
182
+ * @example
183
+ * const total = await client.count().from(Posts).ownedBy(account.address).fetch()
184
+ * const news = await client.count().from(Posts).where(eq("category", "news")).fetch()
185
+ */
186
+ count: () => CountQueryBuilder
187
+
135
188
  /**
136
189
  * Runs one query and returns one page, with no builder in between.
137
190
  *
138
- * Use {@link PublicArkivActions.select} for anything typed — this returns full {@link Entity} objects whatever the
139
- * selection, and takes a raw string as an escape hatch for a query built elsewhere. A raw string
191
+ * The selection means what it does in {@link PublicArkivActions.select}, and the results are the
192
+ * same flat rows a query without a schema returns, typed from the selection. Use `select()` to
193
+ * read through a schema. This also takes a raw string as an escape hatch for a query built
194
+ * elsewhere. A raw string
140
195
  * goes to the node exactly as written, with none of the name, type or operator checks the
141
196
  * expression combinators apply.
142
197
  *
@@ -148,7 +203,7 @@ export type PublicArkivActions<
148
203
  * import { createPublicClient } from "@arkiv-network/sdk"
149
204
  * import { tiramisu } from "@arkiv-network/sdk/chains"
150
205
  * import { and, eq, gte } from "@arkiv-network/sdk/query"
151
- * import { i32 } from "@arkiv-network/sdk/attr"
206
+ * import { i32 } from "@arkiv-network/sdk/schema"
152
207
  * import { http } from "viem"
153
208
  *
154
209
  * const client = createPublicClient({
@@ -156,33 +211,18 @@ export type PublicArkivActions<
156
211
  * transport: http(),
157
212
  * })
158
213
  * const page = await client.query(and(eq("category", "docs"), gte("level", i32(10))), {
159
- * select: { key: true, attributes: true },
214
+ * select: { $key: true, title: true },
160
215
  * limit: 100,
161
216
  * })
162
- * // { entities: [Entity], cursor: "b64:…", blockNumber: 32223n }
217
+ * // { entities: [{ $key: Hex; title: ArkivValue | undefined }], cursor: "b64:…", blockNumber: 32223n }
163
218
  *
164
219
  * // The raw form, unchecked:
165
220
  * await client.query("category = str('docs') AND level >= i32(10)")
166
221
  */
167
- query: (query: Expression | string, queryOptions?: QueryOptions) => Promise<QueryReturnType>
168
-
169
- /**
170
- * Returns the total number of entities on the chain.
171
- * @returns The number of entities currently stored
172
- *
173
- * @example
174
- * import { createPublicClient } from "@arkiv-network/sdk"
175
- * import { tiramisu } from "@arkiv-network/sdk/chains"
176
- * import { http } from "viem"
177
- *
178
- * const client = createPublicClient({
179
- * chain: tiramisu,
180
- * transport: http(),
181
- * })
182
- * const entityCount = await client.getEntityCount()
183
- * // entityCount = 0
184
- */
185
- getEntityCount: () => Promise<number>
222
+ query: <const S extends SelectArg = "*">(
223
+ query: Expression | string,
224
+ queryOptions?: QueryOptions<S>,
225
+ ) => Promise<QueryReturnType<QueryRow<S>>>
186
226
 
187
227
  /**
188
228
  * Returns how many entities an account has created — its entity-minting nonce.
@@ -226,18 +266,19 @@ export type PublicArkivActions<
226
266
  * {@link PredictEntityKeysReturnType}
227
267
  *
228
268
  * @example A batch whose second entity points at its first.
229
- * import { key } from "@arkiv-network/sdk/attr"
269
+ * import { defineSchema, key, str } from "@arkiv-network/sdk/schema"
270
+ *
271
+ * const Nodes = defineSchema("nodes", { name: str(), parent: key().indexed().optional() })
230
272
  *
231
273
  * const [parent, child] = await client.predictEntityKeys({ owner: account.address, count: 2 })
232
274
  * await wallet.executeBatch({
233
275
  * creates: [
234
- * { payload, contentType, expires, salt: parent.salt },
276
+ * { schema: Nodes, values: { name: "root" }, expiration, salt: parent.salt },
235
277
  * {
236
- * payload,
237
- * contentType,
238
- * expires,
278
+ * schema: Nodes,
279
+ * values: { name: "leaf", parent: key(parent.key) },
280
+ * expiration,
239
281
  * salt: child.salt,
240
- * attributes: { parent: key(parent.key) },
241
282
  * },
242
283
  * ],
243
284
  * })
@@ -306,12 +347,14 @@ export function publicArkivActions<
306
347
  account extends Account | undefined = Account | undefined,
307
348
  >(client: Client<transport, chain, account>) {
308
349
  return {
309
- getEntity: (key: Hex) => getEntity(client, key),
310
- query: (rawQuery: Expression | string, queryOptions?: QueryOptions) =>
311
- query(client, rawQuery, queryOptions),
312
- select: (selection?: SelectArg) => new SelectQueryBuilder(client, selection),
350
+ getEntity: ((key: Hex, schema?: Schema) =>
351
+ getEntity(client, key, schema as Schema)) as PublicArkivActions["getEntity"],
352
+ query: ((rawQuery: Expression | string, queryOptions?: QueryOptions) =>
353
+ query(client, rawQuery, queryOptions)) as PublicArkivActions["query"],
354
+ select: ((selection?: SelectArg) =>
355
+ new SelectQueryBuilder(client, selection)) as PublicArkivActions["select"],
356
+ count: () => new CountQueryBuilder(client),
313
357
  getBlockTiming: () => getBlockTiming(client),
314
- getEntityCount: () => getEntityCount(client),
315
358
  getEntityNonce: (owner: Address) => getEntityNonce(client, owner),
316
359
  predictEntityKeys: <
317
360
  const TCount extends number = number,
@@ -1,11 +1,11 @@
1
- import type { Account, Chain, Client, PublicActions, Transport, WalletActions } from "viem"
1
+ import type { Account, Chain, Client, Hex, PublicActions, Transport, WalletActions } from "viem"
2
2
  import type {
3
3
  ChangeOwnershipParameters,
4
4
  ChangeOwnershipReturnType,
5
5
  } from "../../actions/wallet/changeOwnership"
6
6
  import { changeOwnership } from "../../actions/wallet/changeOwnership"
7
7
  import {
8
- type CreateEntityParameters,
8
+ type CreateEntityOptions,
9
9
  type CreateEntityReturnType,
10
10
  createEntity,
11
11
  } from "../../actions/wallet/createEntity"
@@ -24,8 +24,12 @@ import type {
24
24
  ExtendEntityReturnType,
25
25
  } from "../../actions/wallet/extendEntity"
26
26
  import { extendEntity } from "../../actions/wallet/extendEntity"
27
- import type { PatchEntityParameters, PatchEntityReturnType } from "../../actions/wallet/patchEntity"
28
- import { patchEntity } from "../../actions/wallet/patchEntity"
27
+ import {
28
+ type PatchEntityChanges,
29
+ type PatchEntityReturnType,
30
+ patchEntity,
31
+ } from "../../actions/wallet/patchEntity"
32
+ import type { CreateEntityValues, Schema } from "../../schema"
29
33
  import type { TxParams } from "../../types"
30
34
 
31
35
  export type WalletArkivActions<
@@ -48,81 +52,99 @@ export type WalletArkivActions<
48
52
  | "writeContract"
49
53
  > & {
50
54
  /**
51
- * Creates a new entity.
55
+ * Creates a new entity through a schema: each column becomes an attribute, written with the
56
+ * type and indexed flag the schema declares.
52
57
  *
53
58
  * - JSON-RPC Methods: `eth_sendRawTransaction`
54
59
  *
55
- * @param data - The entity creation parameters
60
+ * @param schema - The schema the entity is written through, built with `defineSchema`.
61
+ * @param values - The entity's values, keyed by column. Required columns must be given;
62
+ * defaulted ones left out are written with their default. {@link CreateEntityValues}
63
+ * @param options - The entity's lifetime, plus its flags and salt. {@link CreateEntityOptions}
56
64
  * @param txParams - Optional transaction parameters
57
- * @returns The new entity's key, the transaction hash, and the block it is expected to
58
- * expire at. {@link CreateEntityReturnType}
65
+ * @returns The new entity's key, the transaction hash, and the block it expires at, as the
66
+ * engine recorded it. {@link CreateEntityReturnType}
59
67
  *
60
68
  * @throws {InvalidExpiryError} If the expiry exceeds a protocol bound or would leave the
61
69
  * entity dead on arrival.
62
- * @throws {InvalidValueError} If an attribute value does not fit the type it names.
63
- * @throws {InvalidAttributeNameError} If a name violates the attribute-name grammar.
70
+ * @throws {SchemaMismatchError} If the values do not fit the schema.
71
+ * @throws {InvalidValueError} If a value does not fit its type.
72
+ * @throws {EntityTooLargeError} If the `bytes` values add up to more than `MAX_ENTITY_BYTES`.
73
+ * @throws {EntityMutationError} If the transaction fails or reverts.
64
74
  *
65
75
  * @example
66
- * import { createWalletClient, ExpirationTime, jsonToPayload } from "@arkiv-network/sdk"
67
- * import { i32 } from "@arkiv-network/sdk/attr"
76
+ * import { createWalletClient, ExpirationTime } from "@arkiv-network/sdk"
77
+ * import { addr, bytes, dec, defineSchema, str } from "@arkiv-network/sdk/schema"
68
78
  * import { tiramisu } from "@arkiv-network/sdk/chains"
69
79
  * import { http } from "viem"
70
80
  * import { privateKeyToAccount } from "viem/accounts"
71
81
  *
82
+ * const Posts = defineSchema("posts", {
83
+ * author: addr().indexed(),
84
+ * category: str().indexed(),
85
+ * score: dec().indexed(),
86
+ * title: str(),
87
+ * body: bytes(),
88
+ * thumbnail: bytes().optional(),
89
+ * mimeType: str().default("text/markdown"),
90
+ * })
91
+ *
72
92
  * const client = createWalletClient({
73
93
  * account: privateKeyToAccount("0x..."),
74
94
  * chain: tiramisu,
75
95
  * transport: http(),
76
96
  * })
77
- * const { entityKey, txHash, expiresAt } = await client.createEntity({
78
- * payload: jsonToPayload({ entityType: "testType", entityId: "testId" }),
79
- * contentType: "application/json",
80
- * attributes: { testKey: "testValue", level: i32(3) },
81
- * expires: ExpirationTime.fromDays(30),
82
- * })
97
+ * const { entityKey, txHash, expiresAt } = await client.createEntity(
98
+ * Posts,
99
+ * {
100
+ * author: addr(client.account.address),
101
+ * category: "docs",
102
+ * score: dec("4.1"),
103
+ * title: "Cells, not entities",
104
+ * body: new TextEncoder().encode("# Hello"),
105
+ * },
106
+ * { expiration: ExpirationTime.fromDays(30) },
107
+ * )
83
108
  */
84
- createEntity: (
85
- data: CreateEntityParameters,
109
+ createEntity: <S extends Schema>(
110
+ schema: S,
111
+ values: CreateEntityValues<S>,
112
+ options: CreateEntityOptions,
86
113
  txParams?: TxParams,
87
114
  ) => Promise<CreateEntityReturnType>
88
115
 
89
116
  /**
90
- * Applies a patch to the entity with the given key: sets some fields, unsets others, and leaves
91
- * everything it does not name alone.
117
+ * Patches the entity with the given key through a schema: sets some columns, unsets optional
118
+ * ones, and leaves everything it does not name alone.
92
119
  *
93
120
  * - JSON-RPC Methods: `eth_sendRawTransaction`
94
121
  *
95
- * @param data - The entity key and the mutations to apply
122
+ * @param schema - The schema the changes are typed by.
123
+ * @param entityKey - The entity to patch.
124
+ * @param changes - The columns to set and the optional columns to unset. {@link PatchEntityChanges}
96
125
  * @param txParams - Optional transaction parameters
97
126
  * @returns The patched entity's key and the transaction hash. {@link PatchEntityReturnType}
98
127
  *
99
128
  * @throws {EmptyPatchError} If the patch has nothing to apply.
100
129
  * @throws {ConflictingMutationError} If a name appears in both `set` and `unset`.
101
- * @throws {InvalidValueError} If an attribute value does not fit the type it names.
102
- * @throws {InvalidAttributeNameError} If a name violates the attribute-name grammar.
130
+ * @throws {SchemaMismatchError} If the changes do not fit the schema.
131
+ * @throws {InvalidValueError} If a value does not fit its type.
132
+ * @throws {EntityTooLargeError} If the `bytes` values in `set` add up to more than
133
+ * `MAX_ENTITY_BYTES`.
134
+ * @throws {EntityMutationError} If the transaction fails or reverts.
103
135
  *
104
136
  * @example
105
- * import { createWalletClient, jsonToPayload } from "@arkiv-network/sdk"
106
- * import { i32 } from "@arkiv-network/sdk/attr"
107
- * import { tiramisu } from "@arkiv-network/sdk/chains"
108
- * import { http } from "viem"
109
- * import { privateKeyToAccount } from "viem/accounts"
110
- *
111
- * const client = createWalletClient({
112
- * account: privateKeyToAccount("0x..."),
113
- * chain: tiramisu,
114
- * transport: http(),
115
- * })
116
- * // Publish the entity: one attribute changes, one goes away, the payload is replaced.
117
- * const { txHash } = await client.patchEntity({
118
- * entityKey,
119
- * set: { status: "published", revision: i32(2) },
120
- * unset: ["draft"],
121
- * payload: jsonToPayload({ title: "Hello" }),
137
+ * // Posts is the schema from the createEntity example.
138
+ * // One column changes, an optional one goes away.
139
+ * const { txHash } = await client.patchEntity(Posts, entityKey, {
140
+ * set: { score: dec("4.5") },
141
+ * unset: ["thumbnail"],
122
142
  * })
123
143
  */
124
- patchEntity: (
125
- data: PatchEntityParameters,
144
+ patchEntity: <S extends Schema>(
145
+ schema: S,
146
+ entityKey: Hex,
147
+ changes: PatchEntityChanges<S>,
126
148
  txParams?: TxParams,
127
149
  ) => Promise<PatchEntityReturnType>
128
150
 
@@ -159,14 +181,15 @@ export type WalletArkivActions<
159
181
  *
160
182
  * The new lifetime is resolved the same way a create's is: a duration counts from now rather
161
183
  * than adding to what the entity has left, and `atBlock` / `atDate` pin an absolute deadline.
162
- * The engine rejects an extension that would not move the expiry later.
184
+ * The engine rejects an extension that would move the expiry earlier; one that resolves to the
185
+ * current expiry changes nothing.
163
186
  *
164
187
  * - JSON-RPC Methods: `eth_sendRawTransaction`
165
188
  *
166
189
  * @param data - The entity key and its new lifetime
167
190
  * @param txParams - Optional transaction parameters
168
- * @returns The entity's key, the transaction hash, and the block it is now expected to expire
169
- * at. {@link ExtendEntityReturnType}
191
+ * @returns The entity's key, the transaction hash, and the block it now expires at, as the
192
+ * engine recorded it. {@link ExtendEntityReturnType}
170
193
  *
171
194
  * @throws {InvalidExpiryError} If the expiry is malformed, exceeds a protocol bound, or would
172
195
  * leave the entity dead on arrival.
@@ -184,7 +207,7 @@ export type WalletArkivActions<
184
207
  * })
185
208
  * const { txHash, expiresAt } = await client.extendEntity({
186
209
  * entityKey,
187
- * expires: ExpirationTime.fromDays(30),
210
+ * expiration: ExpirationTime.fromDays(30),
188
211
  * })
189
212
  */
190
213
  extendEntity: (
@@ -213,19 +236,28 @@ export type WalletArkivActions<
213
236
  * - JSON-RPC Methods: `eth_sendRawTransaction`
214
237
  *
215
238
  * Every operation lands in one transaction, so the whole batch applies or none of it does.
216
- * At least one operation is required.
239
+ * At least one operation is required. Each create and patch names its own schema and is typed
240
+ * by it, so one batch can write entities of several schemas.
217
241
  *
218
242
  * @param data - The batch parameters (creates, patches, deletes, extensions, ownershipChanges)
219
243
  * @param txParams - Optional transaction parameters
220
244
  * @returns The transaction hash, plus the keys touched by each kind of operation.
221
245
  * {@link ExecuteBatchReturnType}
222
246
  *
247
+ * @throws {Error} If the batch has no operations.
223
248
  * @throws {InvalidExpiryError} If an expiry exceeds a protocol bound or would leave the entity
224
249
  * dead on arrival.
225
- * @throws {InvalidValueError} If an attribute value does not fit the type it names.
250
+ * @throws {SchemaMismatchError} If a create's or patch's values do not fit its schema.
251
+ * @throws {InvalidValueError} If a value does not fit its type.
252
+ * @throws {EmptyPatchError} If a patch has nothing to apply.
253
+ * @throws {ConflictingMutationError} If a patch names a column in both `set` and `unset`.
254
+ * @throws {EntityTooLargeError} If the `bytes` values of one create or patch add up to more
255
+ * than `MAX_ENTITY_BYTES`.
256
+ * @throws {EntityMutationError} If the transaction fails or reverts.
226
257
  *
227
258
  * @example
228
- * import { createWalletClient, ExpirationTime, jsonToPayload } from "@arkiv-network/sdk"
259
+ * import { createWalletClient, ExpirationTime } from "@arkiv-network/sdk"
260
+ * import { dec } from "@arkiv-network/sdk/schema"
229
261
  * import { tiramisu } from "@arkiv-network/sdk/chains"
230
262
  * import { http } from "viem"
231
263
  * import { privateKeyToAccount } from "viem/accounts"
@@ -235,24 +267,27 @@ export type WalletArkivActions<
235
267
  * chain: tiramisu,
236
268
  * transport: http(),
237
269
  * })
270
+ * // Posts is the schema from the createEntity example.
238
271
  * const { txHash, createdEntities } = await client.executeBatch({
239
272
  * creates: [{
240
- * payload: jsonToPayload({ entityType: "testType", entityId: "testId" }),
241
- * contentType: "application/json",
242
- * attributes: { testKey: "testValue" },
243
- * expires: ExpirationTime.fromDays(30),
273
+ * schema: Posts,
274
+ * values: { author, category: "docs", score: dec("4.1"), title: "Hi", body: new Uint8Array() },
275
+ * expiration: ExpirationTime.fromDays(30),
244
276
  * }],
245
- * patches: [{ entityKey: keyToRevise, set: { status: "archived" }, unset: ["draft"] }],
277
+ * patches: [{ schema: Posts, entityKey: keyToRevise, set: { category: "archive" } }],
246
278
  * deletes: [{ entityKey: staleKey }],
247
279
  * extensions: [{
248
280
  * entityKey: keyToKeepAlive,
249
- * expires: ExpirationTime.atBlock(1_200_000n),
281
+ * expiration: ExpirationTime.atBlock(1_200_000n),
250
282
  * }],
251
283
  * ownershipChanges: [{ entityKey: keyToHandOver, newOwner }],
252
284
  * })
253
285
  */
254
- executeBatch: (
255
- data: ExecuteBatchParameters,
286
+ executeBatch: <
287
+ const TCreates extends readonly Schema[] = [],
288
+ const TPatches extends readonly Schema[] = [],
289
+ >(
290
+ data: ExecuteBatchParameters<TCreates, TPatches>,
256
291
  txParams?: TxParams,
257
292
  ) => Promise<ExecuteBatchReturnType>
258
293
  }
@@ -263,17 +298,30 @@ export function walletArkivActions<
263
298
  account extends Account | undefined = Account | undefined,
264
299
  >(client: Client<transport, chain, account>) {
265
300
  return {
266
- createEntity: (data: CreateEntityParameters, txParams?: TxParams) =>
267
- createEntity(client, data, txParams),
268
- patchEntity: (data: PatchEntityParameters, txParams?: TxParams) =>
269
- patchEntity(client, data, txParams),
301
+ createEntity: <S extends Schema>(
302
+ schema: S,
303
+ values: CreateEntityValues<S>,
304
+ options: CreateEntityOptions,
305
+ txParams?: TxParams,
306
+ ) => createEntity(client, schema, values, options, txParams),
307
+ patchEntity: <S extends Schema>(
308
+ schema: S,
309
+ entityKey: Hex,
310
+ changes: PatchEntityChanges<S>,
311
+ txParams?: TxParams,
312
+ ) => patchEntity(client, schema, entityKey, changes, txParams),
270
313
  deleteEntity: (data: DeleteEntityParameters, txParams?: TxParams) =>
271
314
  deleteEntity(client, data, txParams),
272
315
  extendEntity: (data: ExtendEntityParameters, txParams?: TxParams) =>
273
316
  extendEntity(client, data, txParams),
274
317
  changeOwnership: (data: ChangeOwnershipParameters, txParams?: TxParams) =>
275
318
  changeOwnership(client, data, txParams),
276
- executeBatch: (data: ExecuteBatchParameters, txParams?: TxParams) =>
277
- executeBatch(client, data, txParams),
319
+ executeBatch: <
320
+ const TCreates extends readonly Schema[] = [],
321
+ const TPatches extends readonly Schema[] = [],
322
+ >(
323
+ data: ExecuteBatchParameters<TCreates, TPatches>,
324
+ txParams?: TxParams,
325
+ ) => executeBatch(client, data, txParams),
278
326
  }
279
327
  }