@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
@@ -0,0 +1,250 @@
1
+ import { type AnyColumn, isColumn } from "./column"
2
+ import { InvalidValueError, SchemaDefinitionError, TooManyAttributesError } from "./errors"
3
+ import { validateAttributeName } from "./names"
4
+ import { type ArkivValue, isArkivValue, type StrValue, type TypeTag, type ValueOf } from "./types"
5
+ import { type BareOf, str } from "./values"
6
+
7
+ /**
8
+ * The most attributes one entity operation may carry, and so the most columns a schema may have —
9
+ * one fewer for a named schema, whose tag is one more attribute on every create.
10
+ */
11
+ export const MAX_ATTRIBUTES = 32
12
+
13
+ /**
14
+ * The attribute a named schema tags its entities with: an indexed `str` holding the schema's name.
15
+ * Creates write it, and `.from(schema)` filters on it, so a query through a schema only ever sees
16
+ * entities written through it.
17
+ */
18
+ export const SCHEMA_ATTRIBUTE = "arkiv.schema"
19
+
20
+ /**
21
+ * Marks a schema. A registry symbol rather than `instanceof`, so a schema defined through one copy
22
+ * of the SDK — its CJS build, say — is still recognised by another, such as the ESM build an app
23
+ * loaded.
24
+ */
25
+ const SCHEMA_BRAND = Symbol.for("@arkiv-network/sdk/schema")
26
+
27
+ /** A schema's columns, keyed by attribute name. */
28
+ export type Columns = { readonly [name: string]: AnyColumn }
29
+
30
+ /**
31
+ * A schema: the named, typed attributes an entity of one kind carries, and which of them the node
32
+ * indexes. Built with {@link defineSchema}.
33
+ *
34
+ * A schema is an SDK-side convention. The node sees only attributes, each with a type and an
35
+ * indexed flag; the schema is what types the SDK's writes and reads, and checks them at runtime.
36
+ *
37
+ * A named schema also tags every entity it creates with its name, in the indexed
38
+ * {@link SCHEMA_ATTRIBUTE} attribute, so that a query through it sees only its own entities.
39
+ *
40
+ * @typeParam C - The columns, keyed by attribute name.
41
+ */
42
+ export class Schema<C extends Columns = Columns> {
43
+ /** The name entities are tagged with, or `undefined` for an untagged schema. */
44
+ readonly name: string | undefined
45
+ /** The columns, keyed by attribute name, in the order they were declared. */
46
+ readonly columns: C
47
+ /** The tag every create writes and every query filters on, if the schema is named. */
48
+ readonly tag: StrValue | undefined
49
+
50
+ /** Use {@link defineSchema}. */
51
+ constructor(name: string | undefined, columns: C) {
52
+ this.tag = name === undefined ? undefined : validateName(name)
53
+ validateColumns(columns, name !== undefined)
54
+ this.name = name
55
+ this.columns = Object.freeze({ ...columns })
56
+ Object.defineProperty(this, SCHEMA_BRAND, { value: true })
57
+ Object.freeze(this)
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Declares a schema: the attributes an entity carries, their types, and which of them are indexed.
63
+ *
64
+ * Each column is a type constructor called with no arguments — `str()`, `dec()`, `bytes()` —
65
+ * refined with `.indexed()`, `.optional()` or `.default(...)`. Only indexed columns can be
66
+ * filtered on; the rest are stored and selectable, and do not pay for an index.
67
+ *
68
+ * Give the schema a name to tag its entities: every create then also writes an indexed
69
+ * `arkiv.schema` attribute holding the name, and `.from(schema)` filters on it — the way a
70
+ * collection keeps one model's documents apart from another's. Without a name, a query through the
71
+ * schema sees every entity its `where` matches, whatever wrote it — though the columns it selects
72
+ * are still checked against the schema, so an entity that does not fit is left out of the page, or
73
+ * fails the query with `{ onMismatch: "throw" }`.
74
+ *
75
+ * @param name - The name entities are tagged with — a `str`, so at most 128 bytes. Optional.
76
+ * @param columns - The columns, keyed by attribute name.
77
+ * @throws {InvalidAttributeNameError} If a name violates the attribute-name grammar.
78
+ * @throws {SchemaDefinitionError} If the name is not a valid `str`, an entry is not a column, or a
79
+ * column is named after the schema tag.
80
+ * @throws {TooManyAttributesError} If there are more columns than one create can carry — at most
81
+ * {@link MAX_ATTRIBUTES}, or one fewer for a named schema, whose tag counts as an attribute.
82
+ *
83
+ * @example
84
+ * import { addr, bytes, dec, defineSchema, str } from "@arkiv-network/sdk/schema"
85
+ *
86
+ * const Posts = defineSchema("posts", {
87
+ * author: addr().indexed(),
88
+ * category: str().indexed(),
89
+ * score: dec().indexed(),
90
+ * title: str(), // stored, not indexed
91
+ * body: bytes(), // a blob; bytes can never be indexed
92
+ * thumbnail: bytes().optional(), // bytes columns share one MAX_ENTITY_BYTES budget
93
+ * mimeType: str().default("text/markdown"), // written on create when left out
94
+ * })
95
+ */
96
+ export function defineSchema<const C extends Columns>(name: string, columns: C): Schema<C>
97
+ export function defineSchema<const C extends Columns>(columns: C): Schema<C>
98
+ export function defineSchema<const C extends Columns>(
99
+ ...args: [name: string, columns: C] | [columns: C]
100
+ ): Schema<C> {
101
+ return args.length === 2 ? new Schema(args[0], args[1]) : new Schema(undefined, args[0])
102
+ }
103
+
104
+ function validateName(name: string): StrValue {
105
+ if (typeof name !== "string" || name.length === 0) {
106
+ throw new SchemaDefinitionError(
107
+ "a schema name must be a non-empty string — leave it out for an untagged schema",
108
+ )
109
+ }
110
+ try {
111
+ return str(name)
112
+ } catch (error) {
113
+ if (!(error instanceof InvalidValueError)) throw error
114
+ throw new SchemaDefinitionError(`the name is not a valid str tag. ${error.message}`)
115
+ }
116
+ }
117
+
118
+ function validateColumns(columns: Columns, tagged: boolean): void {
119
+ if (typeof columns !== "object" || columns === null || Array.isArray(columns)) {
120
+ throw new SchemaDefinitionError("defineSchema takes an object of columns keyed by name")
121
+ }
122
+ const entries = Object.entries(columns)
123
+ // The tag is one more attribute on every create, so it counts against the same cap.
124
+ const attributes = entries.length + (tagged ? 1 : 0)
125
+ if (attributes > MAX_ATTRIBUTES) {
126
+ throw new TooManyAttributesError(attributes, MAX_ATTRIBUTES, tagged)
127
+ }
128
+ for (const [name, column] of entries) {
129
+ validateAttributeName(name)
130
+ if (name === SCHEMA_ATTRIBUTE) {
131
+ throw new SchemaDefinitionError(
132
+ `"${name}" is the attribute a named schema tags its entities with, so a column cannot ` +
133
+ "use it. Pick another name",
134
+ name,
135
+ )
136
+ }
137
+ if (!isColumn(column)) throw notAColumn(name, column)
138
+ }
139
+ }
140
+
141
+ /** Whether `input` is a schema built with {@link defineSchema}. */
142
+ export function isSchema(input: unknown): input is Schema {
143
+ return typeof input === "object" && input !== null && SCHEMA_BRAND in input
144
+ }
145
+
146
+ /**
147
+ * The column `name` of `schema`, or `undefined` if it has none. Only the schema's own columns
148
+ * count, so an inherited key such as `toString` or `__proto__` is not mistaken for one.
149
+ */
150
+ export function lookupColumn(schema: Schema, name: string): AnyColumn | undefined {
151
+ return Object.hasOwn(schema.columns, name) ? schema.columns[name] : undefined
152
+ }
153
+
154
+ /** The schema's column names, for an error message that has to say what would have worked. */
155
+ export function describeColumns(schema: Schema): string {
156
+ return Object.keys(schema.columns).join(", ") || "(none)"
157
+ }
158
+
159
+ function notAColumn(name: string, input: unknown): SchemaDefinitionError {
160
+ if (isArkivValue(input)) {
161
+ return new SchemaDefinitionError(
162
+ `got a ${input.type} value where a column belongs. Did you mean ` +
163
+ `${input.type}().default(${defaultArgument(input)})? Called with a value, ${input.type}(...) ` +
164
+ `makes a value; called with none, ${input.type}() declares a column`,
165
+ name,
166
+ )
167
+ }
168
+ return new SchemaDefinitionError(
169
+ "not a column. Declare it with a type constructor called with no arguments, e.g. str() or " +
170
+ "dec().indexed()",
171
+ name,
172
+ )
173
+ }
174
+
175
+ /** A value spelled as the argument its constructor would take, for an error message. */
176
+ function defaultArgument(value: ArkivValue): string {
177
+ switch (value.type) {
178
+ case "bool":
179
+ case "i32":
180
+ return String(value.value)
181
+ case "u64":
182
+ case "u256":
183
+ return `${value.value}n`
184
+ default:
185
+ return JSON.stringify(value.value)
186
+ }
187
+ }
188
+
189
+ /** The columns of a schema. */
190
+ export type ColumnsOf<S extends Schema> = S["columns"]
191
+
192
+ /**
193
+ * What a write accepts for a column of type `T`: a value of that type, or the bare form that
194
+ * defaults to it — a `string` for `str`, a `Uint8Array` for `bytes`, a `number` for `i32`, a
195
+ * `bigint` for `u256`, a `boolean` for `bool`. Every other type is written tagged.
196
+ */
197
+ export type ColumnInput<T extends TypeTag> = ValueOf<T> | BareOf<T>
198
+
199
+ /** The columns a create must provide: neither optional nor defaulted. */
200
+ type RequiredColumnName<C extends Columns> = {
201
+ [K in keyof C]: C[K] extends { readonly isOptional: false; readonly defaultValue: undefined }
202
+ ? K
203
+ : never
204
+ }[keyof C]
205
+
206
+ /** The columns an entity may leave unset — which is to say, the ones a patch may unset. */
207
+ export type OptionalColumnName<S extends Schema> = {
208
+ [K in keyof ColumnsOf<S>]: true extends ColumnsOf<S>[K]["isOptional"] ? K : never
209
+ }[keyof ColumnsOf<S>] &
210
+ string
211
+
212
+ /** The indexed columns, which are the only ones `where` can filter on. */
213
+ export type IndexedColumnName<S extends Schema> = {
214
+ [K in keyof ColumnsOf<S>]: true extends ColumnsOf<S>[K]["isIndexed"] ? K : never
215
+ }[keyof ColumnsOf<S>] &
216
+ string
217
+
218
+ /**
219
+ * The values a create takes for schema `S`: every required column, plus any optional or
220
+ * defaulted one. A defaulted column left out is written with its default.
221
+ *
222
+ * @example
223
+ * const values: CreateEntityValues<typeof Posts> = {
224
+ * author: addr("0x…"),
225
+ * category: "docs", // bare string -> str
226
+ * score: dec("4.1"),
227
+ * title: "Cells, not entities",
228
+ * body: new Uint8Array(), // bare Uint8Array -> bytes
229
+ * }
230
+ */
231
+ export type CreateEntityValues<S extends Schema> = Prettify<
232
+ {
233
+ readonly [K in RequiredColumnName<ColumnsOf<S>>]: ColumnInput<ColumnsOf<S>[K]["type"]>
234
+ } & {
235
+ readonly [K in Exclude<keyof ColumnsOf<S>, RequiredColumnName<ColumnsOf<S>>>]?:
236
+ | ColumnInput<ColumnsOf<S>[K]["type"]>
237
+ | undefined
238
+ }
239
+ >
240
+
241
+ /**
242
+ * What a patch writes for schema `S`: any subset of its columns. Defaults do not apply — a patch
243
+ * changes only what it names — and a column given as `undefined` is left alone, as a create
244
+ * leaves it out.
245
+ */
246
+ export type PatchEntityValues<S extends Schema> = {
247
+ readonly [K in keyof ColumnsOf<S>]?: ColumnInput<ColumnsOf<S>[K]["type"]> | undefined
248
+ }
249
+
250
+ type Prettify<T> = { [K in keyof T]: T[K] } & {}
@@ -9,18 +9,20 @@ import type { decUnits } from "./values"
9
9
  * The typeIds are consensus-critical: they are written into entity records and mixed into index
10
10
  * keys, so this table must never drift from the node's `AttributeType`.
11
11
  *
12
- * | tag | typeId | ABI wire type | indexing |
13
- * | --------- | ------ | ------------------- | ----------------- |
14
- * | `bool` | 1 | `bool` | equality |
15
- * | `i32` | 2 | `int32` | equality + range |
16
- * | `u64` | 3 | `uint64` | equality + range |
17
- * | `u256` | 4 | `uint256` | equality + range |
18
- * | `dec` | 5 | `int256` (18 dp) | equality + range |
19
- * | `bytes32` | 6 | `bytes32` | equality |
20
- * | `bytes` | 7 | `bytes` | none — system-only |
21
- * | `str` | 8 | `string` (<= 128 B) | equality + prefix |
22
- * | `addr` | 9 | `address` | equality |
23
- * | `key` | 10 | `bytes32` | equality |
12
+ * | tag | typeId | ABI wire type | when indexed |
13
+ * | --------- | ------ | -------------------- | ----------------- |
14
+ * | `bool` | 1 | `bool` | equality |
15
+ * | `i32` | 2 | `int32` | equality + range |
16
+ * | `u64` | 3 | `uint64` | equality + range |
17
+ * | `u256` | 4 | `uint256` | equality + range |
18
+ * | `dec` | 5 | `int256` (18 dp) | equality + range |
19
+ * | `bytes32` | 6 | `bytes32` | equality |
20
+ * | `bytes` | 7 | `bytes` (<= 128 KiB) | never indexed |
21
+ * | `str` | 8 | `string` (<= 128 B) | equality + prefix |
22
+ * | `addr` | 9 | `address` | equality |
23
+ * | `key` | 10 | `bytes32` | equality |
24
+ *
25
+ * On the wire the typeId shares its byte with the indexed flag — see {@link INDEXED_FLAG}.
24
26
  */
25
27
  export const TYPE_IDS = {
26
28
  bool: 1,
@@ -35,20 +37,23 @@ export const TYPE_IDS = {
35
37
  key: 10,
36
38
  } as const
37
39
 
38
- /** Every type tag, including the system-only `bytes`. */
39
- export type TypeTag = keyof typeof TYPE_IDS
40
+ /**
41
+ * Bit 7 of the `typeId` byte: set when the node should index the attribute. The low seven bits are
42
+ * the type itself, so `str` travels as `8` unindexed and `0x88` indexed.
43
+ */
44
+ export const INDEXED_FLAG = 0x80
40
45
 
41
- /** Every typeId, including the system-only `7` (`bytes`). */
42
- export type TypeId = (typeof TYPE_IDS)[TypeTag]
46
+ /** Every type tag. */
47
+ export type TypeTag = keyof typeof TYPE_IDS
43
48
 
44
49
  /**
45
- * The tags a client may set on an attribute. `bytes` is excluded: it backs the system `$payload`
46
- * cell, which travels in its own operation field and is never carried in an attribute array.
50
+ * The tags an attribute may be indexed with — every type except `bytes`, which is stored but never
51
+ * indexed, and so can never be filtered on.
47
52
  */
48
- export type UserTypeTag = Exclude<TypeTag, "bytes">
53
+ export type IndexableTypeTag = Exclude<TypeTag, "bytes">
49
54
 
50
- /** Whether a client may set an attribute of this type — everything except `bytes`. */
51
- export function isUserSettable(tag: TypeTag): tag is UserTypeTag {
55
+ /** Whether attributes of this type may be indexed — everything except `bytes`. */
56
+ export function isIndexable(tag: TypeTag): tag is IndexableTypeTag {
52
57
  return tag !== "bytes"
53
58
  }
54
59
 
@@ -61,7 +66,7 @@ declare const validated: unique symbol
61
66
 
62
67
  /**
63
68
  * A typed attribute value: the tag it was created with, plus the JavaScript representation of that
64
- * type. Constructed only by the tagged constructors in `@arkiv-network/sdk/attr`.
69
+ * type. Constructed only by the tagged constructors in `@arkiv-network/sdk/schema`.
65
70
  *
66
71
  * @typeParam tag - The type tag, which also discriminates the {@link ArkivValue} union.
67
72
  * @typeParam value - The decoded JavaScript representation.
@@ -96,12 +101,12 @@ export type AddrValue = Value<"addr", Address>
96
101
  /** A reference to another entity. Weak by spec: dangling references are permitted. */
97
102
  export type KeyValue = Value<"key", Hex>
98
103
  /**
99
- * Opaque variable-length bytes, as lowercase `0x` hex. System-only — it backs `$payload` and is
100
- * never settable as an attribute, so it is produced by decoding but has no constructor.
104
+ * Opaque variable-length bytes, as lowercase `0x` hex — a blob. Stored but never indexed, so it can
105
+ * be selected but not filtered on.
101
106
  */
102
107
  export type BytesValue = Value<"bytes", Hex>
103
108
 
104
- /** Any value a client may set on an attribute. */
109
+ /** Any attribute value. */
105
110
  export type ArkivValue =
106
111
  | BoolValue
107
112
  | I32Value
@@ -109,18 +114,19 @@ export type ArkivValue =
109
114
  | U256Value
110
115
  | DecValue
111
116
  | Bytes32Value
117
+ | BytesValue
112
118
  | StrValue
113
119
  | AddrValue
114
120
  | KeyValue
115
121
 
116
- /** Any value that can come back from the node, including the system-only `bytes`. */
117
- export type AnyArkivValue = ArkivValue | BytesValue
122
+ /** Any value of a type that can be indexed, and so can appear in a query — everything but `bytes`. */
123
+ export type IndexableValue = Exclude<ArkivValue, BytesValue>
118
124
 
119
125
  /** The value type for a given tag — e.g. `ValueOf<"u256">` is {@link U256Value}. */
120
- export type ValueOf<tag extends TypeTag> = Extract<AnyArkivValue, { type: tag }>
126
+ export type ValueOf<tag extends TypeTag> = Extract<ArkivValue, { type: tag }>
121
127
 
122
128
  /** The JavaScript representation carried by a value — e.g. `Decoded<U256Value>` is `bigint`. */
123
- export type Decoded<value extends AnyArkivValue> = value["value"]
129
+ export type Decoded<value extends ArkivValue> = value["value"]
124
130
 
125
131
  /**
126
132
  * Builds a validated value. Module-internal: the constructors in `values.ts` call this **after**
@@ -136,7 +142,7 @@ export function makeValue<tag extends TypeTag>(
136
142
  /**
137
143
  * Whether `input` has the *shape* of an {@link ArkivValue} — a known type tag and a value.
138
144
  */
139
- export function isArkivValue(input: unknown): input is AnyArkivValue {
145
+ export function isArkivValue(input: unknown): input is ArkivValue {
140
146
  return (
141
147
  typeof input === "object" &&
142
148
  input !== null &&
@@ -1,9 +1,11 @@
1
1
  import { describe, expect, it } from "bun:test"
2
+ import { isColumn } from "./column"
2
3
  import { InvalidValueError, MissingValueError, UntypedValueError } from "./errors"
3
4
  import { TYPE_IDS } from "./types"
4
5
  import {
5
6
  addr,
6
7
  bool,
8
+ bytes,
7
9
  bytes32,
8
10
  dec,
9
11
  decFromUnits,
@@ -12,6 +14,7 @@ import {
12
14
  I32_MIN,
13
15
  i32,
14
16
  key,
17
+ MAX_BYTES_LENGTH,
15
18
  str,
16
19
  toValue,
17
20
  U64_MAX,
@@ -196,12 +199,60 @@ describe("key and bytes32", () => {
196
199
  })
197
200
  })
198
201
 
202
+ describe("bytes", () => {
203
+ it("accepts raw bytes or hex, and holds lowercase hex", () => {
204
+ expect(bytes(new Uint8Array([0xde, 0xad])).value).toBe("0xdead")
205
+ expect(bytes("0xDEAD").value).toBe("0xdead")
206
+ expect(bytes(new Uint8Array()).value).toBe("0x")
207
+ })
208
+
209
+ it("takes hex as the bytes it spells, never as text", () => {
210
+ expect(bytes("0xab").value).toBe("0xab")
211
+ })
212
+
213
+ it("rejects text, odd-length hex, and anything over 128 KiB", () => {
214
+ expect(() => bytes("hello" as `0x${string}`)).toThrow(/not 0x-prefixed hex/)
215
+ expect(() => bytes("0xabc")).toThrow(/odd number of hex digits/)
216
+ expect(bytes(new Uint8Array(MAX_BYTES_LENGTH)).type).toBe("bytes")
217
+ expect(() => bytes(new Uint8Array(MAX_BYTES_LENGTH + 1))).toThrow(/131073 bytes exceeds/)
218
+ })
219
+ })
220
+
221
+ describe("dual-mode constructors", () => {
222
+ const constructors = { bool, i32, u64, u256, dec, bytes32, bytes, str, addr, key } as const
223
+
224
+ it("describe a column when called with no arguments", () => {
225
+ for (const [tag, construct] of Object.entries(constructors)) {
226
+ const column = (construct as () => unknown)()
227
+ expect(isColumn(column)).toBe(true)
228
+ expect(column).toMatchObject({ type: tag, isIndexed: false, isOptional: false })
229
+ }
230
+ })
231
+
232
+ it("throw for an explicit undefined rather than silently making a column", () => {
233
+ for (const construct of Object.values(constructors)) {
234
+ expect(() => (construct as (value: unknown) => unknown)(undefined)).toThrow(InvalidValueError)
235
+ }
236
+ })
237
+
238
+ it("make a value, not a column, when given one", () => {
239
+ expect(isColumn(dec("4.1"))).toBe(false)
240
+ expect(dec("4.1")).toEqual(dec("4.10"))
241
+ })
242
+ })
243
+
199
244
  describe("toValue", () => {
200
245
  it("gives each bare form its unambiguous default type", () => {
201
246
  expect(toValue(true)).toEqual(bool(true))
202
247
  expect(toValue(10)).toEqual(i32(10))
203
248
  expect(toValue(10n)).toEqual(u256(10n))
204
249
  expect(toValue("hello")).toEqual(str("hello"))
250
+ expect(toValue(new Uint8Array([1]))).toEqual(bytes("0x01"))
251
+ })
252
+
253
+ it("rejects a column where a value belongs, saying which call was meant", () => {
254
+ expect(() => toValue(dec() as unknown as string)).toThrow(/a column, not a value/)
255
+ expect(() => toValue(dec() as unknown as string)).toThrow(/dec\(\.\.\.\) with an argument/)
205
256
  })
206
257
 
207
258
  it("leaves a hex-looking string a string, rather than guessing at a key", () => {
@@ -209,7 +260,15 @@ describe("toValue", () => {
209
260
  })
210
261
 
211
262
  it("leaves a tagged value's type and value alone", () => {
212
- for (const tagged of [dec("3.5"), i32(-42), u256(10n), str("hi"), bool(true), key(KEY)]) {
263
+ for (const tagged of [
264
+ dec("3.5"),
265
+ i32(-42),
266
+ u256(10n),
267
+ str("hi"),
268
+ bool(true),
269
+ key(KEY),
270
+ bytes(KEY),
271
+ ]) {
213
272
  expect(toValue(tagged)).toEqual(tagged)
214
273
  }
215
274
  })
@@ -239,7 +298,7 @@ describe("toValue", () => {
239
298
  expect(() => toValue({ type: "nope", value: 1 } as unknown as string)).toThrow(
240
299
  UntypedValueError,
241
300
  )
242
- expect(() => toValue(new Uint8Array(32) as unknown as string)).toThrow(UntypedValueError)
301
+ expect(() => toValue(new ArrayBuffer(32) as unknown as string)).toThrow(UntypedValueError)
243
302
  // `in` would have walked the prototype chain and accepted these as type tags.
244
303
  for (const inherited of ["constructor", "toString", "valueOf", "hasOwnProperty"]) {
245
304
  expect(() => toValue({ type: inherited, value: 1 } as unknown as string)).toThrow(
@@ -253,7 +312,7 @@ describe("toValue", () => {
253
312
  // runtime — from a spread of an optional-shaped object, or from data that never saw a type.
254
313
  for (const absent of [undefined, null]) {
255
314
  expect(() => toValue(absent as unknown as string)).toThrow(MissingValueError)
256
- expect(() => toValue(absent as unknown as string, "score")).toThrow(/Omit "score"/)
315
+ expect(() => toValue(absent as unknown as string, "score")).toThrow(/Leave "score" out/)
257
316
  }
258
317
  expect(() => toValue(undefined as unknown as string, "score")).toThrow(
259
318
  /attribute "score" is undefined/,