@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.
- package/README.md +180 -61
- package/dist/chains/index.d.ts +17 -17
- package/dist/changeOwnership-Cy9gfyjN.d.cts +1160 -0
- package/dist/changeOwnership-Cy9gfyjN.d.cts.map +1 -0
- package/dist/changeOwnership-gXf-lBqI.d.ts +1160 -0
- package/dist/changeOwnership-gXf-lBqI.d.ts.map +1 -0
- package/dist/entity/index.cjs +4 -10
- package/dist/entity/index.d.cts +3 -3
- package/dist/entity/index.d.ts +3 -3
- package/dist/entity/index.js +3 -4
- package/dist/entity-BM1unrQf.cjs +41 -0
- package/dist/{entity-DJ0Gml9c.cjs → entity-BgaFJSaV.cjs} +4 -4
- package/dist/entity-CFAI1KyG.js +36 -0
- package/dist/entity-CFAI1KyG.js.map +1 -0
- package/dist/{entity-CniBgBSd.js → entity-CGqx_AU0.js} +4 -4
- package/dist/entity-CGqx_AU0.js.map +1 -0
- package/dist/{payload-AfUfSctb.d.ts → expirationTime-C7PZK49K.d.ts} +8 -37
- package/dist/expirationTime-C7PZK49K.d.ts.map +1 -0
- package/dist/expirationTime-CkIGNAg8.cjs +140 -0
- package/dist/{payload-D167BlJR.d.cts → expirationTime-DOB8V4B8.d.cts} +8 -37
- package/dist/expirationTime-DOB8V4B8.d.cts.map +1 -0
- package/dist/{expiry-Cgdb_dbq.js → expirationTime-UKPuA-LM.js} +62 -27
- package/dist/expirationTime-UKPuA-LM.js.map +1 -0
- package/dist/expiry-B48I-yTA.d.cts +40 -0
- package/dist/expiry-B48I-yTA.d.cts.map +1 -0
- package/dist/expiry-S2PUL8Hg.d.ts +40 -0
- package/dist/expiry-S2PUL8Hg.d.ts.map +1 -0
- package/dist/index-C-mMF5WZ.d.cts +735 -0
- package/dist/index-C-mMF5WZ.d.cts.map +1 -0
- package/dist/{index-BkqSnGJH.d.ts → index-C4zZqJ9S.d.ts} +5 -27
- package/dist/index-C4zZqJ9S.d.ts.map +1 -0
- package/dist/{index-CLhsFyVH.d.cts → index-CcVWYB6v.d.cts} +5 -27
- package/dist/index-CcVWYB6v.d.cts.map +1 -0
- package/dist/index-DCx2XC6S.d.ts +489 -0
- package/dist/index-DCx2XC6S.d.ts.map +1 -0
- package/dist/index-DLb7BfIw.d.ts +735 -0
- package/dist/index-DLb7BfIw.d.ts.map +1 -0
- package/dist/index-DzriOUm8.d.cts +489 -0
- package/dist/index-DzriOUm8.d.cts.map +1 -0
- package/dist/index.cjs +162 -146
- package/dist/index.d.cts +180 -133
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +180 -133
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +93 -83
- package/dist/index.js.map +1 -1
- package/dist/params-BuIrJA9a.cjs +45 -0
- package/dist/params-DDJOnsNi.js +22 -0
- package/dist/params-DDJOnsNi.js.map +1 -0
- package/dist/query/index.cjs +9 -4
- package/dist/query/index.d.cts +6 -6
- package/dist/query/index.d.ts +6 -6
- package/dist/query/index.js +4 -5
- package/dist/query-D0KtuFh2.cjs +1625 -0
- package/dist/query-DpRDozmj.js +1417 -0
- package/dist/query-DpRDozmj.js.map +1 -0
- package/dist/schema/index.cjs +46 -0
- package/dist/schema/index.d.cts +2 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +3 -0
- package/dist/{attr-BXTkpnoc.cjs → schema-B9AyXcgU.cjs} +513 -246
- package/dist/{attr-tmyREHtw.js → schema-DY4sqehl.js} +427 -244
- package/dist/schema-DY4sqehl.js.map +1 -0
- package/dist/types/index.cjs +1 -1
- package/dist/types/index.d.cts +5 -5
- package/dist/types/index.d.ts +5 -5
- package/dist/types/index.js +1 -1
- package/dist/utils/index.cjs +3 -5
- package/dist/utils/index.d.cts +3 -3
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.js +3 -3
- package/package.json +9 -12
- package/src/actions/public/getEntity.test.ts +69 -0
- package/src/actions/public/getEntity.ts +34 -20
- package/src/actions/public/predictEntityKeys.ts +8 -7
- package/src/actions/public/query.test.ts +58 -0
- package/src/actions/public/query.ts +41 -13
- package/src/actions/wallet/createEntity.ts +34 -63
- package/src/actions/wallet/executeBatch.test.ts +38 -0
- package/src/actions/wallet/executeBatch.ts +26 -9
- package/src/actions/wallet/extendEntity.ts +9 -8
- package/src/actions/wallet/patchEntity.ts +43 -52
- package/src/clients/createPublicClient.test.ts +10 -2
- package/src/clients/createWalletClient.ts +4 -1
- package/src/clients/decorators/arkivPublic.ts +123 -80
- package/src/clients/decorators/arkivWallet.ts +113 -65
- package/src/entity/create.test.ts +25 -21
- package/src/entity/expiry.ts +8 -6
- package/src/entity/flags.ts +1 -1
- package/src/entity/index.ts +2 -4
- package/src/entity/key.ts +3 -3
- package/src/entity/operations.ts +1 -1
- package/src/errors.ts +2 -11
- package/src/index.ts +18 -6
- package/src/query/builderBase.ts +166 -0
- package/src/query/countBuilder.test.ts +91 -0
- package/src/query/countBuilder.ts +67 -0
- package/src/query/engine.ts +9 -3
- package/src/query/errors.ts +24 -14
- package/src/query/expression.test.ts +129 -21
- package/src/query/expression.ts +323 -94
- package/src/query/index.ts +26 -4
- package/src/query/injection.test.ts +5 -11
- package/src/query/queryBuilder.test.ts +467 -58
- package/src/query/queryBuilder.ts +191 -98
- package/src/query/queryResult.ts +35 -9
- package/src/query/selection.test.ts +278 -24
- package/src/query/selection.ts +427 -86
- package/src/schema/attributes.test.ts +266 -0
- package/src/schema/attributes.ts +212 -0
- package/src/{attr → schema}/codec.test.ts +37 -5
- package/src/{attr → schema}/codec.ts +40 -11
- package/src/schema/column.ts +193 -0
- package/src/{attr → schema}/errors.ts +67 -10
- package/src/schema/index.ts +76 -0
- package/src/{attr → schema}/names.ts +4 -2
- package/src/schema/schema.test.ts +171 -0
- package/src/schema/schema.ts +250 -0
- package/src/{attr → schema}/types.ts +36 -30
- package/src/{attr → schema}/values.test.ts +62 -3
- package/src/{attr → schema}/values.ts +246 -65
- package/src/types/entity.test.ts +1 -44
- package/src/types/entity.ts +9 -55
- package/src/types/events.ts +1 -1
- package/src/types/index.ts +11 -4
- package/src/types/rpcSchema.ts +22 -16
- package/src/utils/arkivTransactions.test.ts +356 -116
- package/src/utils/arkivTransactions.ts +30 -38
- package/src/utils/entities.ts +10 -9
- package/src/utils/expirationTime.ts +5 -5
- package/src/utils/index.ts +0 -1
- package/src/utils/logger.test.ts +45 -0
- package/src/utils/logger.ts +5 -39
- package/src/utils/revert.test.ts +25 -0
- package/src/utils/revert.ts +28 -3
- package/dist/attr/index.cjs +0 -34
- package/dist/attr/index.d.cts +0 -2
- package/dist/attr/index.d.ts +0 -2
- package/dist/attr/index.js +0 -3
- package/dist/attr-tmyREHtw.js.map +0 -1
- package/dist/entity-B0t6sN7F.cjs +0 -71
- package/dist/entity-BIp_df9u.js +0 -67
- package/dist/entity-BIp_df9u.js.map +0 -1
- package/dist/entity-CniBgBSd.js.map +0 -1
- package/dist/expiry-BxOlvVWC.d.ts +0 -79
- package/dist/expiry-BxOlvVWC.d.ts.map +0 -1
- package/dist/expiry-CG9GhVXv.cjs +0 -135
- package/dist/expiry-Cgdb_dbq.js.map +0 -1
- package/dist/expiry-DRV29F1r.d.cts +0 -79
- package/dist/expiry-DRV29F1r.d.cts.map +0 -1
- package/dist/flags-DCD26uhe.cjs +0 -43
- package/dist/flags-DUT2gHEU.js +0 -33
- package/dist/flags-DUT2gHEU.js.map +0 -1
- package/dist/index-BkqSnGJH.d.ts.map +0 -1
- package/dist/index-C7fbakoI.d.cts +0 -278
- package/dist/index-C7fbakoI.d.cts.map +0 -1
- package/dist/index-CIcHPx2j.d.cts +0 -424
- package/dist/index-CIcHPx2j.d.cts.map +0 -1
- package/dist/index-CLhsFyVH.d.cts.map +0 -1
- package/dist/index-Cd2aaWme.d.ts +0 -424
- package/dist/index-Cd2aaWme.d.ts.map +0 -1
- package/dist/index-Cq6fa8AI.d.ts +0 -278
- package/dist/index-Cq6fa8AI.d.ts.map +0 -1
- package/dist/index-DaNeJUw8.d.ts +0 -955
- package/dist/index-DaNeJUw8.d.ts.map +0 -1
- package/dist/index-Dv16LO2s.d.cts +0 -955
- package/dist/index-Dv16LO2s.d.cts.map +0 -1
- package/dist/payload-AfUfSctb.d.ts.map +0 -1
- package/dist/payload-D167BlJR.d.cts.map +0 -1
- package/dist/payload-DE7wMztL.js +0 -92
- package/dist/payload-DE7wMztL.js.map +0 -1
- package/dist/payload-DqFweqiO.cjs +0 -108
- package/dist/query-9oojUdM7.cjs +0 -1128
- package/dist/query-Ber-lOJQ.js +0 -968
- package/dist/query-Ber-lOJQ.js.map +0 -1
- package/src/actions/public/getEntityCount.ts +0 -10
- package/src/attr/attributes.test.ts +0 -120
- package/src/attr/attributes.ts +0 -196
- package/src/attr/index.ts +0 -50
- package/src/types/mimeTypes.ts +0 -27
- package/src/utils/compression.test.ts +0 -32
- package/src/utils/compression.ts +0 -102
- package/src/utils/payload.ts +0 -36
- /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
|
|
13
|
-
* | --------- | ------ |
|
|
14
|
-
* | `bool` | 1 | `bool`
|
|
15
|
-
* | `i32` | 2 | `int32`
|
|
16
|
-
* | `u64` | 3 | `uint64`
|
|
17
|
-
* | `u256` | 4 | `uint256`
|
|
18
|
-
* | `dec` | 5 | `int256` (18 dp)
|
|
19
|
-
* | `bytes32` | 6 | `bytes32`
|
|
20
|
-
* | `bytes` | 7 | `bytes`
|
|
21
|
-
* | `str` | 8 | `string` (<= 128 B)
|
|
22
|
-
* | `addr` | 9 | `address`
|
|
23
|
-
* | `key` | 10 | `bytes32`
|
|
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
|
-
/**
|
|
39
|
-
|
|
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
|
|
42
|
-
export type
|
|
46
|
+
/** Every type tag. */
|
|
47
|
+
export type TypeTag = keyof typeof TYPE_IDS
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
|
-
* The tags
|
|
46
|
-
*
|
|
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
|
|
53
|
+
export type IndexableTypeTag = Exclude<TypeTag, "bytes">
|
|
49
54
|
|
|
50
|
-
/** Whether
|
|
51
|
-
export function
|
|
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/
|
|
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.
|
|
100
|
-
*
|
|
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
|
|
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
|
|
117
|
-
export type
|
|
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<
|
|
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
|
|
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
|
|
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 [
|
|
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
|
|
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(/
|
|
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/,
|