@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,6 +1,6 @@
1
1
  let viem = require("viem");
2
2
 
3
- //#region src/attr/errors.ts
3
+ //#region src/schema/errors.ts
4
4
  /**
5
5
  * A value was rejected by its tagged constructor — out of range, wrong length, bad checksum, too
6
6
  * much precision, and so on.
@@ -22,13 +22,13 @@ var InvalidValueError = class extends Error {
22
22
  };
23
23
  /**
24
24
  * A bare JavaScript value could not be given a type. Raised when an attribute or predicate is
25
- * handed something that is neither a tagged value nor one of the four bare forms
26
- * (`boolean`, `number`, `bigint`, `string`).
25
+ * handed something that is neither a tagged value nor one of the five bare forms
26
+ * (`boolean`, `number`, `bigint`, `string`, `Uint8Array`).
27
27
  */
28
28
  var UntypedValueError = class extends Error {
29
29
  input;
30
30
  constructor(input) {
31
- super(`Cannot infer an Arkiv type for ${describe(input)}. Attribute values must be a tagged value (i32, u64, u256, dec, str, addr, key, bytes32, bool) or a bare boolean, number, bigint or string.`);
31
+ super(`Cannot infer an Arkiv type for ${describe(input)}. Attribute values must be a tagged value (i32, u64, u256, dec, str, addr, key, bytes32, bytes, bool) or a bare boolean, number, bigint, string or Uint8Array.`);
32
32
  this.name = "UntypedValueError";
33
33
  this.input = input;
34
34
  }
@@ -37,15 +37,19 @@ var UntypedValueError = class extends Error {
37
37
  * An attribute was given `undefined` or `null` as its value.
38
38
  *
39
39
  * There is no such thing as an attribute with no value: an attribute either exists with a type, or
40
- * is not set. The fix is always to leave the name out of the map rather than to write an empty
41
- * value, so this is called out separately from {@link UntypedValueError} — the remedy is different.
40
+ * is not set. The fix is always to leave the name out rather than to write an empty value — or, to
41
+ * remove a column from an existing entity, to `unset` it in a patch — so this is called out
42
+ * separately from {@link UntypedValueError}: the remedy is different.
43
+ *
44
+ * `createEntity` and `patchEntity` already treat a column given as `undefined` as left out, so
45
+ * through them only `null` raises this.
42
46
  */
43
47
  var MissingValueError = class extends Error {
44
48
  /** The attribute whose value was missing, when raised from an attribute map. */
45
49
  attributeName;
46
50
  constructor(input, attributeName) {
47
51
  const subject = attributeName ? `attribute "${attributeName}"` : "attribute value";
48
- super(`The ${subject} is ${input === null ? "null" : "undefined"}. An attribute is either set with a value and a type or not set at all, so there is nothing to write here. ` + (attributeName ? `Omit "${attributeName}" from the attributes instead — e.g. build the map conditionally, or drop the key when the value is absent.` : "Omit the attribute instead of giving it an empty value."));
52
+ super(`The ${subject} is ${input === null ? "null" : "undefined"}. An attribute is either set with a value and a type or not set at all, so there is nothing to write here. ` + (attributeName ? `Leave "${attributeName}" out of the values (or a patch's "set") instead — e.g. build the object conditionally, or drop the key when the value is absent. To remove the column from an existing entity, list it in a patch's "unset".` : "Omit the attribute instead of giving it an empty value."));
49
53
  this.name = "MissingValueError";
50
54
  this.attributeName = attributeName;
51
55
  }
@@ -70,13 +74,51 @@ var ConflictingMutationError = class extends Error {
70
74
  this.attributeName = attributeName;
71
75
  }
72
76
  };
73
- /** More attributes were supplied than an entity operation can carry. */
77
+ /**
78
+ * More attributes were supplied than an entity operation can carry — raised by `defineSchema` for a
79
+ * schema with more columns than one create could write.
80
+ */
74
81
  var TooManyAttributesError = class extends Error {
75
- constructor(count, max) {
76
- super(`An entity may carry at most ${max} attributes, got ${count}.`);
82
+ /**
83
+ * @param tagged - Whether `count` includes a named schema's tag, which the message then calls out:
84
+ * the tag is not among the declared columns, so the count would otherwise look off by one.
85
+ */
86
+ constructor(count, max, tagged = false) {
87
+ super(`An entity may carry at most ${max} attributes, got ${count}.` + (tagged ? ` A named schema's tag counts as one of them, so it may declare at most ${max - 1} columns.` : ""));
77
88
  this.name = "TooManyAttributesError";
78
89
  }
79
90
  };
91
+ /** The `bytes` values of one create or patch add up to more than `MAX_ENTITY_BYTES`. */
92
+ var EntityTooLargeError = class extends Error {
93
+ constructor(total, max) {
94
+ super(`The bytes columns of one create or patch may hold at most ${max} bytes in total, got ${total}. Split the data across entities, or store it elsewhere and keep a reference`);
95
+ this.name = "EntityTooLargeError";
96
+ }
97
+ };
98
+ /** A schema, or one of its columns, was defined in a way the SDK or the protocol cannot honour. */
99
+ var SchemaDefinitionError = class extends Error {
100
+ /** The column at fault, when there is one. */
101
+ columnName;
102
+ constructor(reason, columnName) {
103
+ super(`Invalid schema${columnName === void 0 ? "" : ` column "${columnName}"`}: ${reason}.`);
104
+ this.name = "SchemaDefinitionError";
105
+ this.columnName = columnName;
106
+ }
107
+ };
108
+ /**
109
+ * Data did not fit its schema: a write named a column the schema does not have, left out one it
110
+ * requires, or gave one a value of the wrong type — or an entity read back through a schema lacks a
111
+ * required column or holds one with a different type.
112
+ */
113
+ var SchemaMismatchError = class extends Error {
114
+ /** The column at fault. */
115
+ columnName;
116
+ constructor(columnName, reason) {
117
+ super(`Column "${columnName}": ${reason}.`);
118
+ this.name = "SchemaMismatchError";
119
+ this.columnName = columnName;
120
+ }
121
+ };
80
122
  /** The node sent back an attribute this SDK cannot decode. */
81
123
  var UnknownAttributeTypeError = class extends Error {
82
124
  /** The type as the node named it — a tag over JSON-RPC, a typeId on the wire. */
@@ -106,7 +148,7 @@ function safeJson(input) {
106
148
  }
107
149
 
108
150
  //#endregion
109
- //#region src/attr/decimal.ts
151
+ //#region src/schema/decimal.ts
110
152
  /**
111
153
  * Decimal places a `dec` value is scaled by. Fixed by the protocol, so any two decimals are
112
154
  * directly comparable — and range-indexable — as plain integers.
@@ -165,7 +207,7 @@ function unitsToDecimal(units) {
165
207
  }
166
208
 
167
209
  //#endregion
168
- //#region src/attr/types.ts
210
+ //#region src/schema/types.ts
169
211
  /**
170
212
  * The protocol's attribute type vocabulary, mapping each short **tag** (the name used by the
171
213
  * client, the query language and this SDK) to its **typeId** (the byte carried on the wire and in
@@ -174,18 +216,20 @@ function unitsToDecimal(units) {
174
216
  * The typeIds are consensus-critical: they are written into entity records and mixed into index
175
217
  * keys, so this table must never drift from the node's `AttributeType`.
176
218
  *
177
- * | tag | typeId | ABI wire type | indexing |
178
- * | --------- | ------ | ------------------- | ----------------- |
179
- * | `bool` | 1 | `bool` | equality |
180
- * | `i32` | 2 | `int32` | equality + range |
181
- * | `u64` | 3 | `uint64` | equality + range |
182
- * | `u256` | 4 | `uint256` | equality + range |
183
- * | `dec` | 5 | `int256` (18 dp) | equality + range |
184
- * | `bytes32` | 6 | `bytes32` | equality |
185
- * | `bytes` | 7 | `bytes` | none — system-only |
186
- * | `str` | 8 | `string` (<= 128 B) | equality + prefix |
187
- * | `addr` | 9 | `address` | equality |
188
- * | `key` | 10 | `bytes32` | equality |
219
+ * | tag | typeId | ABI wire type | when indexed |
220
+ * | --------- | ------ | -------------------- | ----------------- |
221
+ * | `bool` | 1 | `bool` | equality |
222
+ * | `i32` | 2 | `int32` | equality + range |
223
+ * | `u64` | 3 | `uint64` | equality + range |
224
+ * | `u256` | 4 | `uint256` | equality + range |
225
+ * | `dec` | 5 | `int256` (18 dp) | equality + range |
226
+ * | `bytes32` | 6 | `bytes32` | equality |
227
+ * | `bytes` | 7 | `bytes` (<= 128 KiB) | never indexed |
228
+ * | `str` | 8 | `string` (<= 128 B) | equality + prefix |
229
+ * | `addr` | 9 | `address` | equality |
230
+ * | `key` | 10 | `bytes32` | equality |
231
+ *
232
+ * On the wire the typeId shares its byte with the indexed flag — see {@link INDEXED_FLAG}.
189
233
  */
190
234
  const TYPE_IDS = {
191
235
  bool: 1,
@@ -200,6 +244,15 @@ const TYPE_IDS = {
200
244
  key: 10
201
245
  };
202
246
  /**
247
+ * Bit 7 of the `typeId` byte: set when the node should index the attribute. The low seven bits are
248
+ * the type itself, so `str` travels as `8` unindexed and `0x88` indexed.
249
+ */
250
+ const INDEXED_FLAG = 128;
251
+ /** Whether attributes of this type may be indexed — everything except `bytes`. */
252
+ function isIndexable(tag) {
253
+ return tag !== "bytes";
254
+ }
255
+ /**
203
256
  * Builds a validated value. Module-internal: the constructors in `values.ts` call this **after**
204
257
  * validating, which is what makes the {@link validated} brand meaningful.
205
258
  */
@@ -217,7 +270,57 @@ function isArkivValue(input) {
217
270
  }
218
271
 
219
272
  //#endregion
220
- //#region src/attr/values.ts
273
+ //#region src/schema/column.ts
274
+ /**
275
+ * Marks a column. A registry symbol rather than `instanceof`, so a column made by one copy of the
276
+ * SDK — its ESM build, say — is still recognised by another, such as the CJS build a dependency
277
+ * loaded.
278
+ */
279
+ const COLUMN_BRAND = Symbol.for("@arkiv-network/sdk/column");
280
+ var ColumnDefinition = class ColumnDefinition {
281
+ type;
282
+ isIndexed;
283
+ isOptional;
284
+ defaultValue;
285
+ #coerce;
286
+ constructor(type, isIndexed, isOptional, defaultValue, coerce) {
287
+ this.type = type;
288
+ this.isIndexed = isIndexed;
289
+ this.isOptional = isOptional;
290
+ this.defaultValue = defaultValue;
291
+ this.#coerce = coerce;
292
+ Object.defineProperty(this, COLUMN_BRAND, { value: true });
293
+ Object.freeze(this);
294
+ }
295
+ indexed() {
296
+ if (this.type === "bytes") throw new SchemaDefinitionError("a bytes column cannot be indexed. Bytes are stored, never indexed, so they can be selected but not filtered on — index a str, bytes32 or key derived from them instead");
297
+ return this.#with({ isIndexed: true });
298
+ }
299
+ optional() {
300
+ return this.#with({ isOptional: true });
301
+ }
302
+ default(...args) {
303
+ if (args.length === 0) throw new SchemaDefinitionError(`.default() needs a value — e.g. ${this.type}().default(...). Leave .default() off for a column the create must always provide`);
304
+ return this.#with({ defaultValue: this.#coerce(this.type, args[0]) });
305
+ }
306
+ #with(changes) {
307
+ return new ColumnDefinition(this.type, changes.isIndexed ?? this.isIndexed, changes.isOptional ?? this.isOptional, changes.defaultValue ?? this.defaultValue, this.#coerce);
308
+ }
309
+ };
310
+ /**
311
+ * A fresh column of type `tag`: unindexed, required, no default. Internal seam for the type
312
+ * constructors, which return one when called with no arguments.
313
+ */
314
+ function createColumn(tag, coerce) {
315
+ return new ColumnDefinition(tag, false, false, void 0, coerce);
316
+ }
317
+ /** Whether `input` is a column, as opposed to a value or anything else. */
318
+ function isColumn(input) {
319
+ return typeof input === "object" && input !== null && COLUMN_BRAND in input;
320
+ }
321
+
322
+ //#endregion
323
+ //#region src/schema/values.ts
221
324
  /** The widest `i32`. */
222
325
  const I32_MAX = 2147483647;
223
326
  /** The narrowest `i32`. */
@@ -231,6 +334,11 @@ const U256_MAX = 2n ** 256n - 1n;
231
334
  */
232
335
  const MAX_STRING_BYTES = 128;
233
336
  /**
337
+ * The longest `bytes` value, in bytes. The cap applies to each `bytes` attribute separately, but
338
+ * all the `bytes` values of one create or patch also share the `MAX_ENTITY_BYTES` budget.
339
+ */
340
+ const MAX_BYTES_LENGTH = 128 * 1024;
341
+ /**
234
342
  * Finds the first C0 control or DEL — the characters a `str` may not contain.
235
343
  */
236
344
  function findControlChar(value) {
@@ -242,31 +350,15 @@ function findControlChar(value) {
242
350
  };
243
351
  }
244
352
  }
245
- /**
246
- * A boolean.
247
- *
248
- * `bool` is the one type that may also be written bare — `{ flagged: true }` and
249
- * `{ flagged: bool(true) }` are the same attribute.
250
- *
251
- * @example
252
- * bool(true) // { type: "bool", value: true }
253
- */
254
- function bool(value) {
353
+ function bool(...args) {
354
+ if (args.length === 0) return createColumn("bool", toTypedValue);
355
+ const [value] = args;
255
356
  if (typeof value !== "boolean") throw new InvalidValueError("bool", value, "not a boolean");
256
357
  return makeValue("bool", value);
257
358
  }
258
- /**
259
- * A 32-bit signed integer, indexed for equality and range queries. This is the default type for a
260
- * bare number, so `{ level: 10 }` and `{ level: i32(10) }` are the same attribute.
261
- *
262
- * @param value - An integer in [-2147483648, 2147483647].
263
- * @throws {InvalidValueError} If the value is not an integer, or does not fit an `i32`.
264
- *
265
- * @example
266
- * i32(10) // { type: "i32", value: 10 }
267
- * i32(-42) // negative values are fine — i32 is the signed integer
268
- */
269
- function i32(value) {
359
+ function i32(...args) {
360
+ if (args.length === 0) return createColumn("i32", toTypedValue);
361
+ const [value] = args;
270
362
  if (typeof value === "bigint") {
271
363
  if (value > BigInt(I32_MAX) || value < BigInt(I32_MIN)) throw new InvalidValueError("i32", value, outOfI32Range(value >= 0n));
272
364
  return makeValue("i32", Number(value));
@@ -280,41 +372,13 @@ function outOfI32Range(nonNegative) {
280
372
  const range = `outside the i32 range [${I32_MIN}, ${I32_MAX}]`;
281
373
  return nonNegative ? `${range} — use u64(...) for large counters, timestamps and block heights (Date.now() does not fit an i32), or u256(...) beyond 2**64 - 1` : `${range} — use dec(...) for values that need more range than an i32`;
282
374
  }
283
- /**
284
- * A 256-bit unsigned integer, indexed for equality and range queries. The type for token amounts
285
- * and anything else that can outgrow 2⁶⁴ — reach for {@link u64} first for counters, timestamps
286
- * and block heights.
287
- *
288
- * @param value - A non-negative integer, as a `bigint`, a safe `number`, or a decimal or `0x` hex
289
- * string.
290
- * @throws {InvalidValueError} If the value is negative, non-integral, or wider than 256 bits.
291
- *
292
- * @example
293
- * u256(1_000_000n)
294
- * u256(Date.now()) // safe integers are accepted
295
- * u256("0xf4240") // hex, as it comes back over JSON-RPC
296
- */
297
- function u256(value) {
298
- return makeValue("u256", toUnsigned("u256", value, U256_MAX));
375
+ function u256(...args) {
376
+ if (args.length === 0) return createColumn("u256", toTypedValue);
377
+ return makeValue("u256", toUnsigned("u256", args[0], U256_MAX));
299
378
  }
300
- /**
301
- * A 64-bit unsigned integer, indexed for equality and range queries.
302
- *
303
- * This is the width the protocol counts in: block heights, `$expiresAt` and the entity-nonce are
304
- * all `u64`, so it is the type to reach for when a value has to line up with one of them. Use
305
- * {@link u256} for token amounts and anything else that can exceed 2⁶⁴.
306
- *
307
- * @param value - A non-negative integer, as a `bigint`, a safe `number`, or a decimal or `0x` hex
308
- * string.
309
- * @throws {InvalidValueError} If the value is negative, non-integral, or wider than 64 bits.
310
- *
311
- * @example
312
- * u64(1_200_000n) // a block height
313
- * u64(Date.now()) // safe integers are accepted
314
- * u64("0x124f80") // hex, as it comes back over JSON-RPC
315
- */
316
- function u64(value) {
317
- return makeValue("u64", toUnsigned("u64", value, U64_MAX));
379
+ function u64(...args) {
380
+ if (args.length === 0) return createColumn("u64", toTypedValue);
381
+ return makeValue("u64", toUnsigned("u64", args[0], U64_MAX));
318
382
  }
319
383
  /**
320
384
  * Parses and range-checks an unsigned integer for `tag`, whose widest value is `max`.
@@ -341,25 +405,9 @@ function toUnsignedBigint(tag, value) {
341
405
  }
342
406
  throw new InvalidValueError(tag, value, "not a bigint, number or string");
343
407
  }
344
- /**
345
- * A signed fixed-point decimal with exactly 18 decimal places, indexed for equality and range
346
- * queries.
347
- *
348
- * Pass fractional values as **strings**. A JavaScript number cannot represent most decimals
349
- * exactly, so accepting `dec(0.1 + 0.2)` would quietly store `0.30000000000000004`; requiring the
350
- * string keeps what you write and what is stored the same thing. Excess precision is likewise an
351
- * error rather than a rounding.
352
- *
353
- * @param value - A decimal string, or an integral `number` / `bigint`.
354
- * @throws {InvalidValueError} If the value is malformed, has more than 18 fractional digits, or
355
- * falls outside the `int256` range.
356
- *
357
- * @example
358
- * dec("3.5")
359
- * dec("-0.000000000000000001") // the smallest step
360
- * dec(5) // integers may be passed bare
361
- */
362
- function dec(value) {
408
+ function dec(...args) {
409
+ if (args.length === 0) return createColumn("dec", toTypedValue);
410
+ const [value] = args;
363
411
  if (typeof value === "string") return makeValue("dec", unitsToDecimal(decimalToUnits(value)));
364
412
  if (typeof value === "bigint") return decFromUnits(value * DECIMAL_UNIT);
365
413
  if (typeof value !== "number" || !Number.isFinite(value)) throw new InvalidValueError("dec", value, "not a number");
@@ -386,75 +434,39 @@ function decUnits(value) {
386
434
  function decFromUnits(units) {
387
435
  return makeValue("dec", unitsToDecimal(units));
388
436
  }
389
- /**
390
- * A UTF-8 string of at most 128 bytes, indexed for equality and prefix (`STARTSWITH`) matching.
391
- *
392
- * The limit is on **bytes**, not characters: a string of 128 emoji does not fit.
393
- *
394
- * C0 control characters and DEL are rejected: the query language writes a `str` as `str('...')`,
395
- * whose only escape is a doubled quote, so a control character could be stored but never queried.
396
- *
397
- * @throws {InvalidValueError} If the value is not a string, holds a control character, or exceeds
398
- * 128 UTF-8 bytes.
399
- *
400
- * @example
401
- * str("Bob")
402
- */
403
- function str(value) {
437
+ function str(...args) {
438
+ if (args.length === 0) return createColumn("str", toTypedValue);
439
+ const [value] = args;
404
440
  if (typeof value !== "string") throw new InvalidValueError("str", value, "not a string");
405
441
  const control = findControlChar(value);
406
- if (control !== void 0) throw new InvalidValueError("str", value, `contains the control character U+${control.code.toString(16).padStart(4, "0").toUpperCase()} at index ${control.index}`, "A str is written into a query as str('...'), whose only escape is a doubled quote — there is no spelling for a control character, so this value could be stored but never queried. Free-form text belongs in the entity payload; keep a short, printable key here.");
407
- const bytes = (0, viem.stringToBytes)(value).length;
408
- if (bytes > MAX_STRING_BYTES) throw new InvalidValueError("str", value, `${bytes} UTF-8 bytes exceeds the ${MAX_STRING_BYTES}-byte limit on a str`, "Store the long form in the entity payload and keep a short, queryable key here.");
442
+ if (control !== void 0) throw new InvalidValueError("str", value, `contains the control character U+${control.code.toString(16).padStart(4, "0").toUpperCase()} at index ${control.index}`, "A str is written into a query as str('...'), whose only escape is a doubled quote — there is no spelling for a control character, so this value could be stored but never queried. Free-form text belongs in a bytes column; keep a short, printable key here.");
443
+ const bytes$1 = (0, viem.stringToBytes)(value).length;
444
+ if (bytes$1 > MAX_STRING_BYTES) throw new InvalidValueError("str", value, `${bytes$1} UTF-8 bytes exceeds the ${MAX_STRING_BYTES}-byte limit on a str`, "Store the long form in a bytes column and keep a short, queryable key here.");
409
445
  return makeValue("str", value);
410
446
  }
411
- /**
412
- * A 20-byte Ethereum address, indexed for equality.
413
- *
414
- * All-lowercase and all-uppercase input carries no checksum and is accepted as-is; mixed-case
415
- * input is checked against its EIP-55 checksum, so a mistyped address is caught here rather than
416
- * being written to a wrong account. The stored value is always EIP-55 checksummed.
417
- *
418
- * @throws {InvalidValueError} If the value is not 20 hex bytes, or fails its EIP-55 checksum.
419
- *
420
- * @example
421
- * addr("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
422
- */
423
- function addr(value) {
447
+ function addr(...args) {
448
+ if (args.length === 0) return createColumn("addr", toTypedValue);
449
+ const [value] = args;
424
450
  if (typeof value !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(value)) throw new InvalidValueError("addr", value, "not a 20-byte 0x-prefixed hex address");
425
451
  const body = value.slice(2);
426
452
  if (body !== body.toLowerCase() && body !== body.toUpperCase() && !(0, viem.isAddress)(value, { strict: true })) throw new InvalidValueError("addr", value, "fails its EIP-55 checksum, so a digit is probably mistyped", `If those hex digits are right, the correctly checksummed form is ${(0, viem.checksumAddress)(value)}.`);
427
453
  return makeValue("addr", (0, viem.checksumAddress)(value));
428
454
  }
429
- /**
430
- * A reference to another entity, indexed for equality.
431
- *
432
- * References are **weak**: nothing checks that the target exists when you write it, and a
433
- * reference to a deleted or expired entity is allowed to dangle.
434
- *
435
- * @param value - A 32-byte entity key, as `0x` hex or raw bytes.
436
- * @throws {InvalidValueError} If the value is not exactly 32 bytes.
437
- *
438
- * @example
439
- * key(parentEntityKey)
440
- */
441
- function key(value) {
442
- return makeValue("key", asFixedHex("key", value, 32));
455
+ function key(...args) {
456
+ if (args.length === 0) return createColumn("key", toTypedValue);
457
+ return makeValue("key", asFixedHex("key", args[0], 32));
443
458
  }
444
- /**
445
- * 32 opaque bytes — a hash, a digest, an identifier from another system — indexed for equality.
446
- *
447
- * Use {@link key} instead when the bytes are an Arkiv entity key: same wire width, but the
448
- * distinct type makes the reference visible to readers and to the query language.
449
- *
450
- * @param value - 32 bytes, as `0x` hex or raw bytes.
451
- * @throws {InvalidValueError} If the value is not exactly 32 bytes.
452
- *
453
- * @example
454
- * bytes32(keccak256(toBytes("hello")))
455
- */
456
- function bytes32(value) {
457
- return makeValue("bytes32", asFixedHex("bytes32", value, 32));
459
+ function bytes32(...args) {
460
+ if (args.length === 0) return createColumn("bytes32", toTypedValue);
461
+ return makeValue("bytes32", asFixedHex("bytes32", args[0], 32));
462
+ }
463
+ function bytes(...args) {
464
+ if (args.length === 0) return createColumn("bytes", toTypedValue);
465
+ const [value] = args;
466
+ const hex = asHex("bytes", value);
467
+ const length = (hex.length - 2) / 2;
468
+ if (length > MAX_BYTES_LENGTH) throw new InvalidValueError("bytes", `<${length} bytes>`, `${length} bytes exceeds the ${MAX_BYTES_LENGTH}-byte limit on a bytes value`, "Split it across several entities, or store the bulk elsewhere and keep a reference.");
469
+ return makeValue("bytes", hex);
458
470
  }
459
471
  /** Normalizes hex-or-bytes input of a known width into lowercase `0x` hex. */
460
472
  function asFixedHex(tag, value, size$1) {
@@ -462,8 +474,15 @@ function asFixedHex(tag, value, size$1) {
462
474
  if (value.length !== size$1) throw new InvalidValueError(tag, `<${value.length} bytes>`, `not exactly ${size$1} bytes`);
463
475
  return (0, viem.toHex)(value);
464
476
  }
477
+ const hex = asHex(tag, value);
478
+ if (hex.length !== 2 + size$1 * 2) throw new InvalidValueError(tag, value, `${(hex.length - 2) / 2} bytes, not exactly ${size$1} bytes`);
479
+ return hex;
480
+ }
481
+ /** Normalizes hex-or-bytes input into lowercase `0x` hex of a whole number of bytes. */
482
+ function asHex(tag, value) {
483
+ if (value instanceof Uint8Array) return (0, viem.toHex)(value);
465
484
  if (typeof value !== "string" || !(0, viem.isHex)(value, { strict: true })) throw new InvalidValueError(tag, value, "not 0x-prefixed hex");
466
- if (value.length !== 2 + size$1 * 2) throw new InvalidValueError(tag, value, `${(value.length - 2) / 2} bytes, not exactly ${size$1} bytes`);
485
+ if (value.length % 2 !== 0) throw new InvalidValueError(tag, value, "an odd number of hex digits, so not whole bytes");
467
486
  return value.toLowerCase();
468
487
  }
469
488
  /**
@@ -475,8 +494,8 @@ function asFixedHex(tag, value, size$1) {
475
494
  *
476
495
  * @throws {MissingValueError} If the input is `undefined` or `null` — an attribute is either set
477
496
  * with a value and a type, or not set at all.
478
- * @throws {UntypedValueError} If the input is not a tagged value or a bare boolean, number, bigint
479
- * or string.
497
+ * @throws {UntypedValueError} If the input is not a tagged value or a bare boolean, number, bigint,
498
+ * string or `Uint8Array`.
480
499
  * @throws {InvalidValueError} If a bare value does not fit the type it defaults to.
481
500
  *
482
501
  * @example
@@ -493,16 +512,47 @@ function toValue(input, attributeName) {
493
512
  case "bigint": return u256(input);
494
513
  case "string": return str(input);
495
514
  default: {
515
+ if (input instanceof Uint8Array) return bytes(input);
516
+ if (isArkivValue(input)) return revalidate(input);
496
517
  const candidate = input;
497
- if (isArkivValue(candidate)) {
498
- if (candidate.type === "bytes") throw new InvalidValueError("bytes", candidate.value, "system-only — bytes backs the entity payload and cannot be set as an attribute", "Pass the bytes as the entity payload instead.");
499
- return revalidate(candidate);
500
- }
518
+ if (isColumn(candidate)) throw columnGivenAsValue(candidate);
501
519
  throw new UntypedValueError(input);
502
520
  }
503
521
  }
504
522
  }
505
523
  /**
524
+ * Resolves `input` to a value of type `tag` — a tagged value of that type, or whatever `tag`'s
525
+ * constructor accepts. What a column's `.default(...)` runs its argument through.
526
+ *
527
+ * @throws {InvalidValueError} If the input does not make a valid value of type `tag`.
528
+ */
529
+ function toTypedValue(tag, input) {
530
+ if (isArkivValue(input)) {
531
+ const value = revalidate(input);
532
+ if (value.type !== tag) throw new InvalidValueError(tag, input, `a ${value.type} value, not a ${tag}`);
533
+ return value;
534
+ }
535
+ if (isColumn(input)) throw columnGivenAsValue(input);
536
+ const construct = CONSTRUCTORS[tag];
537
+ return construct(input);
538
+ }
539
+ function columnGivenAsValue(column) {
540
+ return new InvalidValueError(column.type, `<${column.type} column>`, "a column, not a value", `Pass the value itself, e.g. ${column.type}(...) with an argument — ${column.type}() with none declares a schema column.`);
541
+ }
542
+ /** Each tag's constructor, called with a value. */
543
+ const CONSTRUCTORS = {
544
+ bool,
545
+ i32,
546
+ u64,
547
+ u256,
548
+ dec,
549
+ bytes32,
550
+ bytes,
551
+ str,
552
+ addr,
553
+ key
554
+ };
555
+ /**
506
556
  * Re-runs a tagged value through its own constructor.
507
557
  *
508
558
  * {@link isArkivValue} can only check shape — the `validated` brand is a type-level construct with
@@ -523,6 +573,7 @@ function revalidate(value) {
523
573
  case "u256": return u256(value.value);
524
574
  case "dec": return dec(value.value);
525
575
  case "bytes32": return bytes32(value.value);
576
+ case "bytes": return bytes(value.value);
526
577
  case "str": return str(value.value);
527
578
  case "addr": return addr(value.value);
528
579
  case "key": return key(value.value);
@@ -530,7 +581,7 @@ function revalidate(value) {
530
581
  }
531
582
 
532
583
  //#endregion
533
- //#region src/attr/codec.ts
584
+ //#region src/schema/codec.ts
534
585
  function strFromChain(value) {
535
586
  return makeValue("str", value);
536
587
  }
@@ -565,16 +616,33 @@ function encodeValueBytes(value) {
565
616
  /**
566
617
  * Encodes a named, typed attribute into its ABI form.
567
618
  *
568
- * @param name - The attribute name. Assumed already validated, or a system (`$`) name the SDK sets
569
- * itself.
619
+ * @param name - The attribute name. Assumed already validated.
620
+ * @param indexed - Whether the node should index it. Never true for `bytes`, which the engine
621
+ * rejects with the flag set.
622
+ * @throws {InvalidValueError} If asked to index a `bytes` value.
570
623
  */
571
- function encodeAbiAttribute(name, value) {
624
+ function encodeAbiAttribute(name, value, indexed) {
625
+ if (indexed && value.type === "bytes") throw new InvalidValueError("bytes", `<${name}>`, "bytes are stored, never indexed");
572
626
  return {
573
627
  name: (0, viem.toHex)(name, { size: 32 }),
574
- typeId: TYPE_IDS[value.type],
628
+ typeId: TYPE_IDS[value.type] | (indexed ? INDEXED_FLAG : 0),
575
629
  value: encodeValueBytes(value)
576
630
  };
577
631
  }
632
+ /**
633
+ * Splits a wire `typeId` into its type and indexed flag.
634
+ *
635
+ * @throws {UnknownAttributeTypeError} If the low seven bits name no known type.
636
+ */
637
+ function decodeTypeId(typeId) {
638
+ const base = typeId & ~INDEXED_FLAG;
639
+ const tag = Object.keys(TYPE_IDS).find((candidate) => TYPE_IDS[candidate] === base);
640
+ if (tag === void 0) throw new UnknownAttributeTypeError(typeId);
641
+ return {
642
+ tag,
643
+ indexed: (typeId & INDEXED_FLAG) !== 0
644
+ };
645
+ }
578
646
  /** Encodes a tombstone — the mutation that unsets `name`. Legal only in a patch. */
579
647
  function encodeTombstone(name) {
580
648
  return {
@@ -644,7 +712,7 @@ function asIntegerish(tag, value) {
644
712
  }
645
713
 
646
714
  //#endregion
647
- //#region src/attr/names.ts
715
+ //#region src/schema/names.ts
648
716
  /** The longest attribute name, in bytes — the width of the on-chain `Ident32` name cell. */
649
717
  const MAX_NAME_BYTES = 32;
650
718
  /**
@@ -670,6 +738,7 @@ const RESERVED = new Set([
670
738
  "not",
671
739
  "true",
672
740
  "false",
741
+ "between",
673
742
  "startswith",
674
743
  "exists",
675
744
  "typeof",
@@ -689,8 +758,9 @@ const RESERVED = new Set([
689
758
  * reserved words.
690
759
  *
691
760
  * @param name - The attribute name.
692
- * @throws {InvalidAttributeNameError} If the name is empty, too long, uses characters outside the
693
- * grammar, or collides with a reserved word.
761
+ * @throws {InvalidAttributeNameError} If the name is empty, starts with `$` (reserved for system
762
+ * attributes), too long, uses characters outside the grammar, contains `--` (which opens a query
763
+ * comment), or collides with a reserved word.
694
764
  */
695
765
  function validateAttributeName(name) {
696
766
  if (typeof name !== "string" || name.length === 0) throw new InvalidAttributeNameError(String(name), "the name is empty");
@@ -715,89 +785,208 @@ function isValidAttributeName(name) {
715
785
  }
716
786
 
717
787
  //#endregion
718
- //#region src/attr/attributes.ts
788
+ //#region src/schema/schema.ts
719
789
  /**
720
- * The most attribute cells one operation may carry.
790
+ * The most attributes one entity operation may carry, and so the most columns a schema may have —
791
+ * one fewer for a named schema, whose tag is one more attribute on every create.
721
792
  */
722
793
  const MAX_ATTRIBUTES = 32;
723
794
  /**
724
- * The largest payload an entity may carry, in bytes. The payload travels as the `$payload` system
725
- * cell, so this is a bound on that one attribute rather than on the operation as a whole.
795
+ * The attribute a named schema tags its entities with: an indexed `str` holding the schema's name.
796
+ * Creates write it, and `.from(schema)` filters on it, so a query through a schema only ever sees
797
+ * entities written through it.
798
+ */
799
+ const SCHEMA_ATTRIBUTE = "arkiv.schema";
800
+ /**
801
+ * Marks a schema. A registry symbol rather than `instanceof`, so a schema defined through one copy
802
+ * of the SDK — its CJS build, say — is still recognised by another, such as the ESM build an app
803
+ * loaded.
726
804
  */
727
- const MAX_PAYLOAD_BYTES = 128 * 1024;
728
- /** The system attribute holding an entity's opaque payload. */
729
- const PAYLOAD_CELL = "$payload";
730
- /** The system attribute holding an entity's content type. */
731
- const CONTENT_TYPE_CELL = "$contentType";
805
+ const SCHEMA_BRAND = Symbol.for("@arkiv-network/sdk/schema");
732
806
  /**
733
- * The typed form of an attribute map: names validated, bare values resolved to their default type.
807
+ * A schema: the named, typed attributes an entity of one kind carries, and which of them the node
808
+ * indexes. Built with {@link defineSchema}.
734
809
  *
735
- * @throws {InvalidAttributeNameError} If a name violates the attribute-name grammar.
736
- * @throws {MissingValueError} If a value is `undefined` or `null` — omit the name instead.
737
- * @throws {InvalidValueError} If a value does not fit the type it names or defaults to.
738
- * @throws {TooManyAttributesError} If there are more than {@link MAX_ATTRIBUTES} attributes.
810
+ * A schema is an SDK-side convention. The node sees only attributes, each with a type and an
811
+ * indexed flag; the schema is what types the SDK's writes and reads, and checks them at runtime.
739
812
  *
740
- * @example
741
- * resolveAttributes({ level: 10, name: "Bob" })
742
- * // { level: { type: "i32", value: 10 }, name: { type: "str", value: "Bob" } }
813
+ * A named schema also tags every entity it creates with its name, in the indexed
814
+ * {@link SCHEMA_ATTRIBUTE} attribute, so that a query through it sees only its own entities.
815
+ *
816
+ * @typeParam C - The columns, keyed by attribute name.
743
817
  */
744
- function resolveAttributes(attributes) {
745
- const entries = Object.entries(attributes);
746
- if (entries.length > MAX_ATTRIBUTES) throw new TooManyAttributesError(entries.length, MAX_ATTRIBUTES);
747
- const resolved = {};
748
- for (const [name, input] of entries) {
818
+ var Schema = class {
819
+ /** The name entities are tagged with, or `undefined` for an untagged schema. */
820
+ name;
821
+ /** The columns, keyed by attribute name, in the order they were declared. */
822
+ columns;
823
+ /** The tag every create writes and every query filters on, if the schema is named. */
824
+ tag;
825
+ /** Use {@link defineSchema}. */
826
+ constructor(name, columns) {
827
+ this.tag = name === void 0 ? void 0 : validateName(name);
828
+ validateColumns(columns, name !== void 0);
829
+ this.name = name;
830
+ this.columns = Object.freeze({ ...columns });
831
+ Object.defineProperty(this, SCHEMA_BRAND, { value: true });
832
+ Object.freeze(this);
833
+ }
834
+ };
835
+ function defineSchema(...args) {
836
+ return args.length === 2 ? new Schema(args[0], args[1]) : new Schema(void 0, args[0]);
837
+ }
838
+ function validateName(name) {
839
+ if (typeof name !== "string" || name.length === 0) throw new SchemaDefinitionError("a schema name must be a non-empty string — leave it out for an untagged schema");
840
+ try {
841
+ return str(name);
842
+ } catch (error) {
843
+ if (!(error instanceof InvalidValueError)) throw error;
844
+ throw new SchemaDefinitionError(`the name is not a valid str tag. ${error.message}`);
845
+ }
846
+ }
847
+ function validateColumns(columns, tagged) {
848
+ if (typeof columns !== "object" || columns === null || Array.isArray(columns)) throw new SchemaDefinitionError("defineSchema takes an object of columns keyed by name");
849
+ const entries = Object.entries(columns);
850
+ const attributes = entries.length + (tagged ? 1 : 0);
851
+ if (attributes > MAX_ATTRIBUTES) throw new TooManyAttributesError(attributes, MAX_ATTRIBUTES, tagged);
852
+ for (const [name, column] of entries) {
749
853
  validateAttributeName(name);
750
- resolved[name] = toValue(input, name);
854
+ if (name === SCHEMA_ATTRIBUTE) throw new SchemaDefinitionError(`"${name}" is the attribute a named schema tags its entities with, so a column cannot use it. Pick another name`, name);
855
+ if (!isColumn(column)) throw notAColumn(name, column);
751
856
  }
752
- return resolved;
857
+ }
858
+ /** Whether `input` is a schema built with {@link defineSchema}. */
859
+ function isSchema(input) {
860
+ return typeof input === "object" && input !== null && SCHEMA_BRAND in input;
753
861
  }
754
862
  /**
755
- * Encodes an attribute map, plus the payload and content-type system cells, into the ABI attribute
756
- * array an operation carries.
863
+ * The column `name` of `schema`, or `undefined` if it has none. Only the schema's own columns
864
+ * count, so an inherited key such as `toString` or `__proto__` is not mistaken for one.
865
+ */
866
+ function lookupColumn(schema, name) {
867
+ return Object.hasOwn(schema.columns, name) ? schema.columns[name] : void 0;
868
+ }
869
+ /** The schema's column names, for an error message that has to say what would have worked. */
870
+ function describeColumns(schema) {
871
+ return Object.keys(schema.columns).join(", ") || "(none)";
872
+ }
873
+ function notAColumn(name, input) {
874
+ if (isArkivValue(input)) return new SchemaDefinitionError(`got a ${input.type} value where a column belongs. Did you mean ${input.type}().default(${defaultArgument(input)})? Called with a value, ${input.type}(...) makes a value; called with none, ${input.type}() declares a column`, name);
875
+ return new SchemaDefinitionError("not a column. Declare it with a type constructor called with no arguments, e.g. str() or dec().indexed()", name);
876
+ }
877
+ /** A value spelled as the argument its constructor would take, for an error message. */
878
+ function defaultArgument(value) {
879
+ switch (value.type) {
880
+ case "bool":
881
+ case "i32": return String(value.value);
882
+ case "u64":
883
+ case "u256": return `${value.value}n`;
884
+ default: return JSON.stringify(value.value);
885
+ }
886
+ }
887
+
888
+ //#endregion
889
+ //#region src/schema/attributes.ts
890
+ /**
891
+ * The most data the `bytes` columns of one create or patch may hold together, in bytes. Each
892
+ * `bytes` value is also capped on its own, at `MAX_BYTES_LENGTH`.
757
893
  *
758
- * @throws {InvalidAttributeNameError} If a name violates the attribute-name grammar.
759
- * @throws {MissingValueError} If a value is `undefined` or `null`.
894
+ * Provisional: the protocol has not settled a per-entity cap yet, so this keeps the budget the
895
+ * single payload had — and an operation much larger than it would not fit in a transaction the
896
+ * node accepts anyway.
897
+ */
898
+ const MAX_ENTITY_BYTES = 128 * 1024;
899
+ /**
900
+ * Encodes a create's values into the ABI attribute array it carries: every column the
901
+ * values name, plus the default of every defaulted column they leave out, each flagged indexed or
902
+ * not as its column says — and, for a named schema, the indexed tag that marks the entity as its.
903
+ *
904
+ * @throws {SchemaDefinitionError} If `schema` is not a schema built with `defineSchema`.
905
+ * @throws {TypeError} If `values` is not an object.
906
+ * @throws {SchemaMismatchError} If a value names a column the schema lacks, a required column is
907
+ * left out, or a value's type is not its column's.
908
+ * @throws {MissingValueError} If a value is `null`.
909
+ * @throws {UntypedValueError} If a value is neither a tagged value nor a bare form.
760
910
  * @throws {InvalidValueError} If a value does not fit its type.
761
- * @throws {TooManyAttributesError} If there are more than {@link MAX_ATTRIBUTES} attributes.
911
+ * @throws {EntityTooLargeError} If the `bytes` values add up to more than {@link MAX_ENTITY_BYTES}.
762
912
  */
763
- function encodeAttributes(attributes = {}, system = {}) {
764
- const cells = Object.entries(resolveAttributes(attributes));
765
- if (system.payload !== void 0) {
766
- if (system.payload.length > MAX_PAYLOAD_BYTES) throw new InvalidValueError("bytes", `<${system.payload.length} bytes>`, `${system.payload.length} bytes exceeds the ${MAX_PAYLOAD_BYTES}-byte payload limit`, "Store the bulk elsewhere and keep a reference to it on the entity.");
767
- cells.push([PAYLOAD_CELL, makeValue("bytes", toHexBytes(system.payload))]);
913
+ function encodeCreate(schema, values) {
914
+ assertSchema(schema);
915
+ for (const name of keysOf(values, "create")) columnFor(schema, name, "create");
916
+ const attributes = [];
917
+ if (schema.tag !== void 0) attributes.push(encodeAbiAttribute(SCHEMA_ATTRIBUTE, schema.tag, true));
918
+ for (const [name, column] of Object.entries(schema.columns)) {
919
+ const input = Object.hasOwn(values, name) ? values[name] : void 0;
920
+ if (input === void 0) {
921
+ if (column.defaultValue !== void 0) attributes.push(encodeAbiAttribute(name, column.defaultValue, column.isIndexed));
922
+ else if (!column.isOptional) throw new SchemaMismatchError(name, `the create leaves out a required ${column.type} column. Give it a value, or declare it ${column.type}().optional() or with a .default(...)`);
923
+ continue;
924
+ }
925
+ attributes.push(encodeColumn(name, column, input));
768
926
  }
769
- if (system.contentType !== void 0) cells.push([CONTENT_TYPE_CELL, str(system.contentType)]);
770
- if (cells.length > MAX_ATTRIBUTES) throw new TooManyAttributesError(cells.length, MAX_ATTRIBUTES);
771
- return cells.map(([name, value]) => encodeAbiAttribute(name, value)).sort(byName);
927
+ assertBytesFit(attributes);
928
+ return attributes.sort(byName);
772
929
  }
773
930
  /**
774
- * Encodes a patch's mutations into the ABI attribute array the operation carries.
931
+ * Encodes a patch into the ABI attribute array it carries: a write for each column in `set`, with
932
+ * the indexed flag its column declares, and a tombstone for each name in `unset`. A column `set`
933
+ * to `undefined` is left out, as it is from a create.
775
934
  *
935
+ * Writing a column with a different indexed flag than the entity holds flips its indexing, so a
936
+ * schema that marks a column `.indexed()` indexes it from the next patch that writes it.
937
+ *
938
+ * @throws {SchemaDefinitionError} If `schema` is not a schema built with `defineSchema`.
939
+ * @throws {SchemaMismatchError} If a name is not a column, a value's type is not its column's, or
940
+ * `unset` names a required column.
776
941
  * @throws {ConflictingMutationError} If a name appears in both `set` and `unset`.
777
- * @throws {InvalidAttributeNameError} If a name violates the attribute-name grammar.
778
- * @throws {MissingValueError} If a value is `undefined` or `null`.
942
+ * @throws {MissingValueError} If a value in `set` is `null`.
943
+ * @throws {UntypedValueError} If a value in `set` is neither a tagged value nor a bare form.
779
944
  * @throws {InvalidValueError} If a value does not fit its type.
780
- * @throws {TooManyAttributesError} If the mutations exceed {@link MAX_ATTRIBUTES}.
781
- * @throws {TypeError} If `unset` is not an array.
945
+ * @throws {EntityTooLargeError} If the `bytes` values in `set` add up to more than
946
+ * {@link MAX_ENTITY_BYTES}.
947
+ * @throws {TypeError} If `set` is not an object, or `unset` is not an array.
782
948
  */
783
- function encodeMutations({ set, unset, payload, contentType }) {
784
- const written = encodeAttributes(set, {
785
- payload,
786
- contentType
787
- });
788
- const tombstones = resolveUnset(unset, set).map(encodeTombstone);
789
- const mutations = [...written, ...tombstones];
790
- if (mutations.length > MAX_ATTRIBUTES) throw new TooManyAttributesError(mutations.length, MAX_ATTRIBUTES);
949
+ function encodePatch(schema, { set = {}, unset = [] }) {
950
+ assertSchema(schema);
951
+ const mutations = [];
952
+ for (const name of keysOf(set, "patch")) {
953
+ const column = columnFor(schema, name, "patch");
954
+ const input = set[name];
955
+ if (input !== void 0) mutations.push(encodeColumn(name, column, input));
956
+ }
957
+ if (!Array.isArray(unset)) throw new TypeError(`"unset" must be an array of column names`);
958
+ for (const name of new Set(unset)) {
959
+ const column = columnFor(schema, name, "patch");
960
+ if (!column.isOptional) throw new SchemaMismatchError(name, `a required column, so unsetting it would leave the entity without a value its schema requires. Declare it ${column.type}().optional() to allow that`);
961
+ if (Object.hasOwn(set, name) && set[name] !== void 0) throw new ConflictingMutationError(name);
962
+ mutations.push(encodeTombstone(name));
963
+ }
964
+ assertBytesFit(mutations);
791
965
  return mutations.sort(byName);
792
966
  }
793
- function resolveUnset(unset = [], set = {}) {
794
- if (!Array.isArray(unset)) throw new TypeError(`"unset" must be an array of attribute names`);
795
- const names = new Set(unset);
796
- for (const name of names) {
797
- validateAttributeName(name);
798
- if (Object.hasOwn(set, name)) throw new ConflictingMutationError(name);
799
- }
800
- return [...names];
967
+ function assertSchema(schema) {
968
+ if (!isSchema(schema)) throw new SchemaDefinitionError("expected a schema built with defineSchema(...) — writes are typed by the schema they name");
969
+ }
970
+ function keysOf(values, operation) {
971
+ if (typeof values !== "object" || values === null) throw new TypeError(`The ${operation} values must be an object keyed by column name`);
972
+ return Object.keys(values);
973
+ }
974
+ function columnFor(schema, name, operation) {
975
+ const column = lookupColumn(schema, name);
976
+ if (column === void 0) throw new SchemaMismatchError(name, `the ${operation} names a column this schema does not have. Its columns are ` + describeColumns(schema));
977
+ return column;
978
+ }
979
+ /** One column's value, resolved, checked against the column's type, and encoded. */
980
+ function encodeColumn(name, column, input) {
981
+ const value = toValue(input, name);
982
+ if (value.type !== column.type) throw new SchemaMismatchError(name, `a ${column.type} column, but the value is a ${value.type}. Write ${column.type}(...) around the value`);
983
+ return encodeAbiAttribute(name, value, column.isIndexed);
984
+ }
985
+ /** Throws if the `bytes` values among `attributes` add up to more than {@link MAX_ENTITY_BYTES}. */
986
+ function assertBytesFit(attributes) {
987
+ let total = 0;
988
+ for (const { typeId, value } of attributes) if ((typeId & ~INDEXED_FLAG) === TYPE_IDS.bytes) total += (value.length - 2) / 2;
989
+ if (total > MAX_ENTITY_BYTES) throw new EntityTooLargeError(total, MAX_ENTITY_BYTES);
801
990
  }
802
991
  /**
803
992
  * The engine requires attributes **strictly ascending by their `bytes32` name**: that ordering is
@@ -807,12 +996,6 @@ function resolveUnset(unset = [], set = {}) {
807
996
  function byName(a, b) {
808
997
  return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
809
998
  }
810
- /** Raw bytes as lowercase `0x` hex, without viem's hex-string ambiguity. */
811
- function toHexBytes(bytes) {
812
- let out = "0x";
813
- for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
814
- return out;
815
- }
816
999
 
817
1000
  //#endregion
818
1001
  Object.defineProperty(exports, 'ConflictingMutationError', {
@@ -833,6 +1016,12 @@ Object.defineProperty(exports, 'DECIMAL_UNIT', {
833
1016
  return DECIMAL_UNIT;
834
1017
  }
835
1018
  });
1019
+ Object.defineProperty(exports, 'EntityTooLargeError', {
1020
+ enumerable: true,
1021
+ get: function () {
1022
+ return EntityTooLargeError;
1023
+ }
1024
+ });
836
1025
  Object.defineProperty(exports, 'I32_MAX', {
837
1026
  enumerable: true,
838
1027
  get: function () {
@@ -845,6 +1034,12 @@ Object.defineProperty(exports, 'I32_MIN', {
845
1034
  return I32_MIN;
846
1035
  }
847
1036
  });
1037
+ Object.defineProperty(exports, 'INDEXED_FLAG', {
1038
+ enumerable: true,
1039
+ get: function () {
1040
+ return INDEXED_FLAG;
1041
+ }
1042
+ });
848
1043
  Object.defineProperty(exports, 'InvalidAttributeNameError', {
849
1044
  enumerable: true,
850
1045
  get: function () {
@@ -863,16 +1058,22 @@ Object.defineProperty(exports, 'MAX_ATTRIBUTES', {
863
1058
  return MAX_ATTRIBUTES;
864
1059
  }
865
1060
  });
866
- Object.defineProperty(exports, 'MAX_NAME_BYTES', {
1061
+ Object.defineProperty(exports, 'MAX_BYTES_LENGTH', {
867
1062
  enumerable: true,
868
1063
  get: function () {
869
- return MAX_NAME_BYTES;
1064
+ return MAX_BYTES_LENGTH;
1065
+ }
1066
+ });
1067
+ Object.defineProperty(exports, 'MAX_ENTITY_BYTES', {
1068
+ enumerable: true,
1069
+ get: function () {
1070
+ return MAX_ENTITY_BYTES;
870
1071
  }
871
1072
  });
872
- Object.defineProperty(exports, 'MAX_PAYLOAD_BYTES', {
1073
+ Object.defineProperty(exports, 'MAX_NAME_BYTES', {
873
1074
  enumerable: true,
874
1075
  get: function () {
875
- return MAX_PAYLOAD_BYTES;
1076
+ return MAX_NAME_BYTES;
876
1077
  }
877
1078
  });
878
1079
  Object.defineProperty(exports, 'MAX_STRING_BYTES', {
@@ -887,6 +1088,24 @@ Object.defineProperty(exports, 'MissingValueError', {
887
1088
  return MissingValueError;
888
1089
  }
889
1090
  });
1091
+ Object.defineProperty(exports, 'SCHEMA_ATTRIBUTE', {
1092
+ enumerable: true,
1093
+ get: function () {
1094
+ return SCHEMA_ATTRIBUTE;
1095
+ }
1096
+ });
1097
+ Object.defineProperty(exports, 'SchemaDefinitionError', {
1098
+ enumerable: true,
1099
+ get: function () {
1100
+ return SchemaDefinitionError;
1101
+ }
1102
+ });
1103
+ Object.defineProperty(exports, 'SchemaMismatchError', {
1104
+ enumerable: true,
1105
+ get: function () {
1106
+ return SchemaMismatchError;
1107
+ }
1108
+ });
890
1109
  Object.defineProperty(exports, 'TYPE_IDS', {
891
1110
  enumerable: true,
892
1111
  get: function () {
@@ -941,6 +1160,12 @@ Object.defineProperty(exports, 'bool', {
941
1160
  return bool;
942
1161
  }
943
1162
  });
1163
+ Object.defineProperty(exports, 'bytes', {
1164
+ enumerable: true,
1165
+ get: function () {
1166
+ return bytes;
1167
+ }
1168
+ });
944
1169
  Object.defineProperty(exports, 'bytes32', {
945
1170
  enumerable: true,
946
1171
  get: function () {
@@ -971,16 +1196,34 @@ Object.defineProperty(exports, 'decodeRpcValue', {
971
1196
  return decodeRpcValue;
972
1197
  }
973
1198
  });
974
- Object.defineProperty(exports, 'encodeAttributes', {
1199
+ Object.defineProperty(exports, 'decodeTypeId', {
1200
+ enumerable: true,
1201
+ get: function () {
1202
+ return decodeTypeId;
1203
+ }
1204
+ });
1205
+ Object.defineProperty(exports, 'defineSchema', {
975
1206
  enumerable: true,
976
1207
  get: function () {
977
- return encodeAttributes;
1208
+ return defineSchema;
978
1209
  }
979
1210
  });
980
- Object.defineProperty(exports, 'encodeMutations', {
1211
+ Object.defineProperty(exports, 'describeColumns', {
981
1212
  enumerable: true,
982
1213
  get: function () {
983
- return encodeMutations;
1214
+ return describeColumns;
1215
+ }
1216
+ });
1217
+ Object.defineProperty(exports, 'encodeCreate', {
1218
+ enumerable: true,
1219
+ get: function () {
1220
+ return encodeCreate;
1221
+ }
1222
+ });
1223
+ Object.defineProperty(exports, 'encodePatch', {
1224
+ enumerable: true,
1225
+ get: function () {
1226
+ return encodePatch;
984
1227
  }
985
1228
  });
986
1229
  Object.defineProperty(exports, 'i32', {
@@ -995,6 +1238,24 @@ Object.defineProperty(exports, 'isArkivValue', {
995
1238
  return isArkivValue;
996
1239
  }
997
1240
  });
1241
+ Object.defineProperty(exports, 'isColumn', {
1242
+ enumerable: true,
1243
+ get: function () {
1244
+ return isColumn;
1245
+ }
1246
+ });
1247
+ Object.defineProperty(exports, 'isIndexable', {
1248
+ enumerable: true,
1249
+ get: function () {
1250
+ return isIndexable;
1251
+ }
1252
+ });
1253
+ Object.defineProperty(exports, 'isSchema', {
1254
+ enumerable: true,
1255
+ get: function () {
1256
+ return isSchema;
1257
+ }
1258
+ });
998
1259
  Object.defineProperty(exports, 'isValidAttributeName', {
999
1260
  enumerable: true,
1000
1261
  get: function () {
@@ -1007,6 +1268,12 @@ Object.defineProperty(exports, 'key', {
1007
1268
  return key;
1008
1269
  }
1009
1270
  });
1271
+ Object.defineProperty(exports, 'lookupColumn', {
1272
+ enumerable: true,
1273
+ get: function () {
1274
+ return lookupColumn;
1275
+ }
1276
+ });
1010
1277
  Object.defineProperty(exports, 'str', {
1011
1278
  enumerable: true,
1012
1279
  get: function () {