@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,193 @@
|
|
|
1
|
+
import type { Hex } from "viem"
|
|
2
|
+
import { SchemaDefinitionError } from "./errors"
|
|
3
|
+
import type { ArkivValue, IndexableTypeTag, TypeTag, ValueOf } from "./types"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What each type's constructor accepts as a value — `dec("4.1")`, `addr("0x…")`, `bytes(buf)`.
|
|
7
|
+
* A column's `.default(...)` takes the same thing, or an already-typed value.
|
|
8
|
+
*/
|
|
9
|
+
export type ValueArgument<T extends TypeTag> = {
|
|
10
|
+
bool: boolean
|
|
11
|
+
i32: number | bigint
|
|
12
|
+
u64: bigint | number | string
|
|
13
|
+
u256: bigint | number | string
|
|
14
|
+
dec: string | number | bigint
|
|
15
|
+
bytes32: Hex | Uint8Array
|
|
16
|
+
bytes: Hex | Uint8Array
|
|
17
|
+
str: string
|
|
18
|
+
addr: string
|
|
19
|
+
key: Hex | Uint8Array
|
|
20
|
+
}[T]
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A column: the declaration of one attribute in a schema — its type, whether the node indexes it,
|
|
24
|
+
* whether an entity may leave it unset, and the value the SDK writes when a create leaves it out.
|
|
25
|
+
*
|
|
26
|
+
* Built by calling a type constructor with no arguments — `str()`, `dec()`, `bytes()` — and refined
|
|
27
|
+
* by chaining. Every refinement returns a new column; a column is never modified in place.
|
|
28
|
+
*
|
|
29
|
+
* @typeParam T - The attribute's type tag.
|
|
30
|
+
* @typeParam I - Whether the attribute is indexed.
|
|
31
|
+
* @typeParam O - Whether the attribute may be absent.
|
|
32
|
+
* @typeParam D - Whether the column has a default.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* str() // stored, not indexed, required
|
|
36
|
+
* str().indexed() // filterable with eq / ne / startsWith
|
|
37
|
+
* dec().indexed().optional() // filterable, and an entity may leave it unset
|
|
38
|
+
* str().default("text/markdown") // filled in on create when left out
|
|
39
|
+
* bytes() // a blob; bytes has no .indexed()
|
|
40
|
+
*/
|
|
41
|
+
export type Column<
|
|
42
|
+
T extends TypeTag = TypeTag,
|
|
43
|
+
I extends boolean = boolean,
|
|
44
|
+
O extends boolean = boolean,
|
|
45
|
+
D extends boolean = boolean,
|
|
46
|
+
> = ColumnProperties<T, I, O, D> & (T extends IndexableTypeTag ? IndexableColumn<T, O, D> : unknown)
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Any column, whatever its type and flags — the shape a schema is built from. Written out rather
|
|
50
|
+
* than derived from {@link Column}, whose refinement methods would make every column's type
|
|
51
|
+
* invariant in its tag.
|
|
52
|
+
*/
|
|
53
|
+
export type AnyColumn = {
|
|
54
|
+
readonly type: TypeTag
|
|
55
|
+
readonly isIndexed: boolean
|
|
56
|
+
readonly isOptional: boolean
|
|
57
|
+
readonly defaultValue: ArkivValue | undefined
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type ColumnProperties<
|
|
61
|
+
T extends TypeTag,
|
|
62
|
+
I extends boolean,
|
|
63
|
+
O extends boolean,
|
|
64
|
+
D extends boolean,
|
|
65
|
+
> = {
|
|
66
|
+
/** The attribute's type tag. */
|
|
67
|
+
readonly type: T
|
|
68
|
+
/** Whether the node indexes the attribute, which is what makes it usable in `where`. */
|
|
69
|
+
readonly isIndexed: I
|
|
70
|
+
/**
|
|
71
|
+
* Whether an entity may leave the attribute unset. A create may then omit it, a patch may unset
|
|
72
|
+
* it, and a read gives back `undefined` where it is missing.
|
|
73
|
+
*/
|
|
74
|
+
readonly isOptional: O
|
|
75
|
+
/** The value a create writes when the attribute is left out, if the column has one. */
|
|
76
|
+
readonly defaultValue: D extends true ? ValueOf<T> : undefined
|
|
77
|
+
|
|
78
|
+
/** The same column, allowed to be absent. */
|
|
79
|
+
optional(): Column<T, I, true, D>
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The same column, with a value that a create writes whenever the attribute is left out. The
|
|
83
|
+
* default is applied by the SDK, not the node, and only on create.
|
|
84
|
+
*
|
|
85
|
+
* @param value - What the type's constructor would accept — `dec().default("4.1")` — or a value
|
|
86
|
+
* of the column's type.
|
|
87
|
+
* @throws {InvalidValueError} If the value does not fit the column's type.
|
|
88
|
+
*/
|
|
89
|
+
default(value: ValueArgument<T> | ValueOf<T>): Column<T, I, O, true>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
type IndexableColumn<T extends IndexableTypeTag, O extends boolean, D extends boolean> = {
|
|
93
|
+
/**
|
|
94
|
+
* The same column, indexed by the node. Only indexed attributes can be filtered on in `where`;
|
|
95
|
+
* an unindexed one can be stored and selected, and does not pay for an index.
|
|
96
|
+
*/
|
|
97
|
+
indexed(): Column<T, true, O, D>
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Marks a column. A registry symbol rather than `instanceof`, so a column made by one copy of the
|
|
102
|
+
* SDK — its ESM build, say — is still recognised by another, such as the CJS build a dependency
|
|
103
|
+
* loaded.
|
|
104
|
+
*/
|
|
105
|
+
const COLUMN_BRAND = Symbol.for("@arkiv-network/sdk/column")
|
|
106
|
+
|
|
107
|
+
/** Resolves the input to `.default(...)` into a value of the column's type. */
|
|
108
|
+
type Coerce = (tag: TypeTag, input: unknown) => ArkivValue
|
|
109
|
+
|
|
110
|
+
class ColumnDefinition {
|
|
111
|
+
readonly type: TypeTag
|
|
112
|
+
readonly isIndexed: boolean
|
|
113
|
+
readonly isOptional: boolean
|
|
114
|
+
readonly defaultValue: ArkivValue | undefined
|
|
115
|
+
readonly #coerce: Coerce
|
|
116
|
+
|
|
117
|
+
constructor(
|
|
118
|
+
type: TypeTag,
|
|
119
|
+
isIndexed: boolean,
|
|
120
|
+
isOptional: boolean,
|
|
121
|
+
defaultValue: ArkivValue | undefined,
|
|
122
|
+
coerce: Coerce,
|
|
123
|
+
) {
|
|
124
|
+
this.type = type
|
|
125
|
+
this.isIndexed = isIndexed
|
|
126
|
+
this.isOptional = isOptional
|
|
127
|
+
this.defaultValue = defaultValue
|
|
128
|
+
this.#coerce = coerce
|
|
129
|
+
Object.defineProperty(this, COLUMN_BRAND, { value: true })
|
|
130
|
+
Object.freeze(this)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
indexed(): ColumnDefinition {
|
|
134
|
+
// Unreachable from typed code — a bytes column has no `indexed` — so this is for the callers
|
|
135
|
+
// the type system does not cover.
|
|
136
|
+
if (this.type === "bytes") {
|
|
137
|
+
throw new SchemaDefinitionError(
|
|
138
|
+
"a bytes column cannot be indexed. Bytes are stored, never indexed, so they can be " +
|
|
139
|
+
"selected but not filtered on — index a str, bytes32 or key derived from them instead",
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
return this.#with({ isIndexed: true })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
optional(): ColumnDefinition {
|
|
146
|
+
return this.#with({ isOptional: true })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
default(...args: unknown[]): ColumnDefinition {
|
|
150
|
+
// Counted rather than compared to undefined: `.default(undefined)` is a mistake to report, not
|
|
151
|
+
// a request for no default.
|
|
152
|
+
if (args.length === 0) {
|
|
153
|
+
throw new SchemaDefinitionError(
|
|
154
|
+
`.default() needs a value — e.g. ${this.type}().default(...). Leave .default() off for a ` +
|
|
155
|
+
"column the create must always provide",
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
return this.#with({ defaultValue: this.#coerce(this.type, args[0]) })
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#with(
|
|
162
|
+
changes: Partial<Pick<ColumnDefinition, "isIndexed" | "isOptional" | "defaultValue">>,
|
|
163
|
+
): ColumnDefinition {
|
|
164
|
+
return new ColumnDefinition(
|
|
165
|
+
this.type,
|
|
166
|
+
changes.isIndexed ?? this.isIndexed,
|
|
167
|
+
changes.isOptional ?? this.isOptional,
|
|
168
|
+
changes.defaultValue ?? this.defaultValue,
|
|
169
|
+
this.#coerce,
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* A fresh column of type `tag`: unindexed, required, no default. Internal seam for the type
|
|
176
|
+
* constructors, which return one when called with no arguments.
|
|
177
|
+
*/
|
|
178
|
+
export function createColumn<T extends TypeTag>(
|
|
179
|
+
tag: T,
|
|
180
|
+
coerce: Coerce,
|
|
181
|
+
): Column<T, false, false, false> {
|
|
182
|
+
return new ColumnDefinition(tag, false, false, undefined, coerce) as unknown as Column<
|
|
183
|
+
T,
|
|
184
|
+
false,
|
|
185
|
+
false,
|
|
186
|
+
false
|
|
187
|
+
>
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Whether `input` is a column, as opposed to a value or anything else. */
|
|
191
|
+
export function isColumn(input: unknown): input is AnyColumn {
|
|
192
|
+
return typeof input === "object" && input !== null && COLUMN_BRAND in input
|
|
193
|
+
}
|
|
@@ -23,8 +23,8 @@ export class InvalidValueError extends Error {
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* A bare JavaScript value could not be given a type. Raised when an attribute or predicate is
|
|
26
|
-
* handed something that is neither a tagged value nor one of the
|
|
27
|
-
* (`boolean`, `number`, `bigint`, `string`).
|
|
26
|
+
* handed something that is neither a tagged value nor one of the five bare forms
|
|
27
|
+
* (`boolean`, `number`, `bigint`, `string`, `Uint8Array`).
|
|
28
28
|
*/
|
|
29
29
|
export class UntypedValueError extends Error {
|
|
30
30
|
readonly input: unknown
|
|
@@ -33,7 +33,7 @@ export class UntypedValueError extends Error {
|
|
|
33
33
|
super(
|
|
34
34
|
`Cannot infer an Arkiv type for ${describe(input)}. ` +
|
|
35
35
|
"Attribute values must be a tagged value (i32, u64, u256, dec, str, addr, key, bytes32, " +
|
|
36
|
-
"bool) or a bare boolean, number, bigint or
|
|
36
|
+
"bytes, bool) or a bare boolean, number, bigint, string or Uint8Array.",
|
|
37
37
|
)
|
|
38
38
|
this.name = "UntypedValueError"
|
|
39
39
|
this.input = input
|
|
@@ -44,8 +44,12 @@ export class UntypedValueError extends Error {
|
|
|
44
44
|
* An attribute was given `undefined` or `null` as its value.
|
|
45
45
|
*
|
|
46
46
|
* There is no such thing as an attribute with no value: an attribute either exists with a type, or
|
|
47
|
-
* is not set. The fix is always to leave the name out
|
|
48
|
-
*
|
|
47
|
+
* is not set. The fix is always to leave the name out rather than to write an empty value — or, to
|
|
48
|
+
* remove a column from an existing entity, to `unset` it in a patch — so this is called out
|
|
49
|
+
* separately from {@link UntypedValueError}: the remedy is different.
|
|
50
|
+
*
|
|
51
|
+
* `createEntity` and `patchEntity` already treat a column given as `undefined` as left out, so
|
|
52
|
+
* through them only `null` raises this.
|
|
49
53
|
*/
|
|
50
54
|
export class MissingValueError extends Error {
|
|
51
55
|
/** The attribute whose value was missing, when raised from an attribute map. */
|
|
@@ -57,8 +61,9 @@ export class MissingValueError extends Error {
|
|
|
57
61
|
`The ${subject} is ${input === null ? "null" : "undefined"}. An attribute is either set ` +
|
|
58
62
|
"with a value and a type or not set at all, so there is nothing to write here. " +
|
|
59
63
|
(attributeName
|
|
60
|
-
? `
|
|
61
|
-
"conditionally, or drop the key when the value is absent."
|
|
64
|
+
? `Leave "${attributeName}" out of the values (or a patch's "set") instead — e.g. ` +
|
|
65
|
+
"build the object conditionally, or drop the key when the value is absent. To remove " +
|
|
66
|
+
`the column from an existing entity, list it in a patch's "unset".`
|
|
62
67
|
: "Omit the attribute instead of giving it an empty value."),
|
|
63
68
|
)
|
|
64
69
|
this.name = "MissingValueError"
|
|
@@ -97,14 +102,66 @@ export class ConflictingMutationError extends Error {
|
|
|
97
102
|
}
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* More attributes were supplied than an entity operation can carry — raised by `defineSchema` for a
|
|
107
|
+
* schema with more columns than one create could write.
|
|
108
|
+
*/
|
|
101
109
|
export class TooManyAttributesError extends Error {
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
/**
|
|
111
|
+
* @param tagged - Whether `count` includes a named schema's tag, which the message then calls out:
|
|
112
|
+
* the tag is not among the declared columns, so the count would otherwise look off by one.
|
|
113
|
+
*/
|
|
114
|
+
constructor(count: number, max: number, tagged = false) {
|
|
115
|
+
super(
|
|
116
|
+
`An entity may carry at most ${max} attributes, got ${count}.` +
|
|
117
|
+
(tagged
|
|
118
|
+
? ` A named schema's tag counts as one of them, so it may declare at most ${max - 1} ` +
|
|
119
|
+
"columns."
|
|
120
|
+
: ""),
|
|
121
|
+
)
|
|
104
122
|
this.name = "TooManyAttributesError"
|
|
105
123
|
}
|
|
106
124
|
}
|
|
107
125
|
|
|
126
|
+
/** The `bytes` values of one create or patch add up to more than `MAX_ENTITY_BYTES`. */
|
|
127
|
+
export class EntityTooLargeError extends Error {
|
|
128
|
+
constructor(total: number, max: number) {
|
|
129
|
+
super(
|
|
130
|
+
`The bytes columns of one create or patch may hold at most ${max} bytes in total, got ` +
|
|
131
|
+
`${total}. Split the data across entities, or store it elsewhere and keep a reference`,
|
|
132
|
+
)
|
|
133
|
+
this.name = "EntityTooLargeError"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** A schema, or one of its columns, was defined in a way the SDK or the protocol cannot honour. */
|
|
138
|
+
export class SchemaDefinitionError extends Error {
|
|
139
|
+
/** The column at fault, when there is one. */
|
|
140
|
+
readonly columnName: string | undefined
|
|
141
|
+
|
|
142
|
+
constructor(reason: string, columnName?: string) {
|
|
143
|
+
super(`Invalid schema${columnName === undefined ? "" : ` column "${columnName}"`}: ${reason}.`)
|
|
144
|
+
this.name = "SchemaDefinitionError"
|
|
145
|
+
this.columnName = columnName
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Data did not fit its schema: a write named a column the schema does not have, left out one it
|
|
151
|
+
* requires, or gave one a value of the wrong type — or an entity read back through a schema lacks a
|
|
152
|
+
* required column or holds one with a different type.
|
|
153
|
+
*/
|
|
154
|
+
export class SchemaMismatchError extends Error {
|
|
155
|
+
/** The column at fault. */
|
|
156
|
+
readonly columnName: string
|
|
157
|
+
|
|
158
|
+
constructor(columnName: string, reason: string) {
|
|
159
|
+
super(`Column "${columnName}": ${reason}.`)
|
|
160
|
+
this.name = "SchemaMismatchError"
|
|
161
|
+
this.columnName = columnName
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
108
165
|
/** The node sent back an attribute this SDK cannot decode. */
|
|
109
166
|
export class UnknownAttributeTypeError extends Error {
|
|
110
167
|
/** The type as the node named it — a tag over JSON-RPC, a typeId on the wire. */
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export type { AttributeSchema, Attributes } from "./attributes"
|
|
2
|
+
export { MAX_ENTITY_BYTES } from "./attributes"
|
|
3
|
+
export type { AnyColumn, Column, ValueArgument } from "./column"
|
|
4
|
+
export { isColumn } from "./column"
|
|
5
|
+
export { DECIMAL_SCALE, DECIMAL_UNIT } from "./decimal"
|
|
6
|
+
export {
|
|
7
|
+
ConflictingMutationError,
|
|
8
|
+
EntityTooLargeError,
|
|
9
|
+
InvalidAttributeNameError,
|
|
10
|
+
InvalidValueError,
|
|
11
|
+
MissingValueError,
|
|
12
|
+
SchemaDefinitionError,
|
|
13
|
+
SchemaMismatchError,
|
|
14
|
+
TooManyAttributesError,
|
|
15
|
+
UnknownAttributeTypeError,
|
|
16
|
+
UntypedValueError,
|
|
17
|
+
} from "./errors"
|
|
18
|
+
export { isValidAttributeName, MAX_NAME_BYTES, validateAttributeName } from "./names"
|
|
19
|
+
export type {
|
|
20
|
+
ColumnInput,
|
|
21
|
+
Columns,
|
|
22
|
+
ColumnsOf,
|
|
23
|
+
CreateEntityValues,
|
|
24
|
+
IndexedColumnName,
|
|
25
|
+
OptionalColumnName,
|
|
26
|
+
PatchEntityValues,
|
|
27
|
+
Schema,
|
|
28
|
+
} from "./schema"
|
|
29
|
+
export {
|
|
30
|
+
defineSchema,
|
|
31
|
+
describeColumns,
|
|
32
|
+
isSchema,
|
|
33
|
+
lookupColumn,
|
|
34
|
+
MAX_ATTRIBUTES,
|
|
35
|
+
SCHEMA_ATTRIBUTE,
|
|
36
|
+
} from "./schema"
|
|
37
|
+
export type {
|
|
38
|
+
AddrValue,
|
|
39
|
+
ArkivValue,
|
|
40
|
+
BoolValue,
|
|
41
|
+
Bytes32Value,
|
|
42
|
+
BytesValue,
|
|
43
|
+
DecValue,
|
|
44
|
+
I32Value,
|
|
45
|
+
IndexableTypeTag,
|
|
46
|
+
IndexableValue,
|
|
47
|
+
KeyValue,
|
|
48
|
+
StrValue,
|
|
49
|
+
TypeTag,
|
|
50
|
+
U64Value,
|
|
51
|
+
U256Value,
|
|
52
|
+
ValueOf,
|
|
53
|
+
} from "./types"
|
|
54
|
+
export { INDEXED_FLAG, isArkivValue } from "./types"
|
|
55
|
+
export type { BareOf, TagOf, ValueInput } from "./values"
|
|
56
|
+
export {
|
|
57
|
+
addr,
|
|
58
|
+
bool,
|
|
59
|
+
bytes,
|
|
60
|
+
bytes32,
|
|
61
|
+
dec,
|
|
62
|
+
decFromUnits,
|
|
63
|
+
decUnits,
|
|
64
|
+
I32_MAX,
|
|
65
|
+
I32_MIN,
|
|
66
|
+
i32,
|
|
67
|
+
key,
|
|
68
|
+
MAX_BYTES_LENGTH,
|
|
69
|
+
MAX_STRING_BYTES,
|
|
70
|
+
str,
|
|
71
|
+
toValue,
|
|
72
|
+
U64_MAX,
|
|
73
|
+
U256_MAX,
|
|
74
|
+
u64,
|
|
75
|
+
u256,
|
|
76
|
+
} from "./values"
|
|
@@ -28,6 +28,7 @@ const RESERVED = new Set([
|
|
|
28
28
|
"not",
|
|
29
29
|
"true",
|
|
30
30
|
"false",
|
|
31
|
+
"between",
|
|
31
32
|
"startswith",
|
|
32
33
|
"exists",
|
|
33
34
|
"typeof",
|
|
@@ -49,8 +50,9 @@ const RESERVED = new Set([
|
|
|
49
50
|
* reserved words.
|
|
50
51
|
*
|
|
51
52
|
* @param name - The attribute name.
|
|
52
|
-
* @throws {InvalidAttributeNameError} If the name is empty,
|
|
53
|
-
* grammar,
|
|
53
|
+
* @throws {InvalidAttributeNameError} If the name is empty, starts with `$` (reserved for system
|
|
54
|
+
* attributes), too long, uses characters outside the grammar, contains `--` (which opens a query
|
|
55
|
+
* comment), or collides with a reserved word.
|
|
54
56
|
*/
|
|
55
57
|
export function validateAttributeName(name: string): void {
|
|
56
58
|
if (typeof name !== "string" || name.length === 0) {
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test"
|
|
2
|
+
import { isColumn } from "./column"
|
|
3
|
+
import {
|
|
4
|
+
InvalidAttributeNameError,
|
|
5
|
+
InvalidValueError,
|
|
6
|
+
SchemaDefinitionError,
|
|
7
|
+
TooManyAttributesError,
|
|
8
|
+
} from "./errors"
|
|
9
|
+
import { defineSchema, isSchema, MAX_ATTRIBUTES, SCHEMA_ATTRIBUTE } from "./schema"
|
|
10
|
+
import { addr, bytes, dec, i32, str, u64 } from "./values"
|
|
11
|
+
|
|
12
|
+
describe("columns", () => {
|
|
13
|
+
it("start unindexed, required and without a default", () => {
|
|
14
|
+
expect(str()).toMatchObject({
|
|
15
|
+
type: "str",
|
|
16
|
+
isIndexed: false,
|
|
17
|
+
isOptional: false,
|
|
18
|
+
defaultValue: undefined,
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it("refine by returning a new column, leaving the original alone", () => {
|
|
23
|
+
const base = dec()
|
|
24
|
+
const refined = base.indexed().optional()
|
|
25
|
+
expect(refined).toMatchObject({ type: "dec", isIndexed: true, isOptional: true })
|
|
26
|
+
expect(base).toMatchObject({ isIndexed: false, isOptional: false })
|
|
27
|
+
expect(Object.isFrozen(refined)).toBe(true)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it("keep every refinement, whatever order they are chained in", () => {
|
|
31
|
+
const a = i32().default(3).indexed().optional()
|
|
32
|
+
const b = i32().optional().indexed().default(3)
|
|
33
|
+
for (const column of [a, b]) {
|
|
34
|
+
expect(column).toMatchObject({ isIndexed: true, isOptional: true, defaultValue: i32(3) })
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it("take a default as the constructor's argument, or as a value of the column's type", () => {
|
|
39
|
+
expect(dec().default("4.1").defaultValue).toEqual(dec("4.1"))
|
|
40
|
+
expect(dec().default(dec("4.1")).defaultValue).toEqual(dec("4.1"))
|
|
41
|
+
expect(u64().default(5).defaultValue).toEqual(u64(5n))
|
|
42
|
+
expect(bytes().default(new Uint8Array([1])).defaultValue?.value).toBe("0x01")
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("validate a default when it is declared, not when it is first written", () => {
|
|
46
|
+
expect(() => dec().default("4.1.1")).toThrow(InvalidValueError)
|
|
47
|
+
expect(() => u64().default(-1)).toThrow(/negative/)
|
|
48
|
+
// A value of another type is not silently converted.
|
|
49
|
+
expect(() => dec().default(i32(4) as never)).toThrow(/i32 value, not a dec/)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("reject a default that is missing or undefined", () => {
|
|
53
|
+
expect(() => (str() as unknown as { default(): unknown }).default()).toThrow(
|
|
54
|
+
SchemaDefinitionError,
|
|
55
|
+
)
|
|
56
|
+
expect(() => str().default(undefined as unknown as string)).toThrow(InvalidValueError)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("never index bytes, even past the type system", () => {
|
|
60
|
+
const column = bytes() as unknown as { indexed(): unknown }
|
|
61
|
+
expect(() => column.indexed()).toThrow(/bytes column cannot be indexed/)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe("defineSchema", () => {
|
|
66
|
+
it("keeps the columns, in the order they were declared", () => {
|
|
67
|
+
const Posts = defineSchema({ title: str(), score: dec().indexed(), body: bytes() })
|
|
68
|
+
expect(isSchema(Posts)).toBe(true)
|
|
69
|
+
expect(Object.keys(Posts.columns)).toEqual(["title", "score", "body"])
|
|
70
|
+
expect(isColumn(Posts.columns.score)).toBe(true)
|
|
71
|
+
expect(Object.isFrozen(Posts.columns)).toBe(true)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it("rejects a value where a column belongs, suggesting the default it may have meant", () => {
|
|
75
|
+
const define = () => defineSchema({ score: dec("4.1") } as never)
|
|
76
|
+
expect(define).toThrow(SchemaDefinitionError)
|
|
77
|
+
expect(define).toThrow(/did you mean dec\(\)\.default\("4\.1"\)/i)
|
|
78
|
+
expect(() => defineSchema({ n: u64(5n) } as never)).toThrow(/u64\(\)\.default\(5n\)/)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it("rejects anything else that is not a column", () => {
|
|
82
|
+
expect(() => defineSchema({ title: "str" } as never)).toThrow(/not a column/)
|
|
83
|
+
expect(() => defineSchema({ title: str } as never)).toThrow(/not a column/)
|
|
84
|
+
expect(() => defineSchema([] as never)).toThrow(SchemaDefinitionError)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it("holds column names to the attribute-name grammar", () => {
|
|
88
|
+
expect(() => defineSchema({ "1st": str() })).toThrow(InvalidAttributeNameError)
|
|
89
|
+
expect(() => defineSchema({ $owner: addr() })).toThrow(/reserved for system attributes/)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it("lets a column take a name an entity field has without the $, which a row keeps apart", () => {
|
|
93
|
+
for (const name of ["owner", "creator", "createdAt", "expiresAt", "attributes"]) {
|
|
94
|
+
expect(() => defineSchema({ [name]: str() })).not.toThrow()
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it("caps the number of columns at what one operation can carry", () => {
|
|
99
|
+
const columns = (count: number) =>
|
|
100
|
+
Object.fromEntries(Array.from({ length: count }, (_, i) => [`c${i}`, i32()]))
|
|
101
|
+
expect(() => defineSchema(columns(MAX_ATTRIBUTES))).not.toThrow()
|
|
102
|
+
expect(() => defineSchema(columns(MAX_ATTRIBUTES + 1))).toThrow(TooManyAttributesError)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it("accepts a schema with no columns", () => {
|
|
106
|
+
expect(defineSchema({}).columns).toEqual({})
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe("named schemas", () => {
|
|
111
|
+
it("carry their name, and the str tag their entities are marked with", () => {
|
|
112
|
+
const Posts = defineSchema("posts", { title: str() })
|
|
113
|
+
expect(Posts.name).toBe("posts")
|
|
114
|
+
expect(Posts.tag).toEqual(str("posts"))
|
|
115
|
+
expect(Object.keys(Posts.columns)).toEqual(["title"])
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it("leave an unnamed schema untagged", () => {
|
|
119
|
+
const Posts = defineSchema({ title: str() })
|
|
120
|
+
expect(Posts.name).toBeUndefined()
|
|
121
|
+
expect(Posts.tag).toBeUndefined()
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it("reject a name that is not a valid str", () => {
|
|
125
|
+
expect(() => defineSchema("", { title: str() })).toThrow(/non-empty string/)
|
|
126
|
+
expect(() => defineSchema("a\nb", { title: str() })).toThrow(SchemaDefinitionError)
|
|
127
|
+
expect(() => defineSchema("x".repeat(129), { title: str() })).toThrow(/not a valid str tag/)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it("reserve the tag's attribute name, named or not", () => {
|
|
131
|
+
expect(() => defineSchema({ [SCHEMA_ATTRIBUTE]: str() })).toThrow(
|
|
132
|
+
/the attribute a named schema tags its entities with/,
|
|
133
|
+
)
|
|
134
|
+
expect(() => defineSchema("posts", { [SCHEMA_ATTRIBUTE]: str() })).toThrow(
|
|
135
|
+
SchemaDefinitionError,
|
|
136
|
+
)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it("count the tag against the attribute cap", () => {
|
|
140
|
+
const columns = Object.fromEntries(
|
|
141
|
+
Array.from({ length: MAX_ATTRIBUTES }, (_, i) => [`c${i}`, i32()]),
|
|
142
|
+
)
|
|
143
|
+
expect(() => defineSchema(columns)).not.toThrow()
|
|
144
|
+
expect(() => defineSchema("full", columns)).toThrow(/at most 32 attributes, got 33/)
|
|
145
|
+
expect(() => defineSchema("full", columns)).toThrow(/tag counts as one of them/)
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe("recognising schemas and columns", () => {
|
|
150
|
+
it("goes by a registry brand, so another copy of the SDK recognises them too", () => {
|
|
151
|
+
// What a schema built by a second copy of this module — the CJS build next to the ESM one —
|
|
152
|
+
// looks like from here: same shape, same registry symbol, a different class.
|
|
153
|
+
const foreign = Object.freeze(
|
|
154
|
+
Object.defineProperty(
|
|
155
|
+
{ name: undefined, tag: undefined, columns: {} },
|
|
156
|
+
Symbol.for("@arkiv-network/sdk/schema"),
|
|
157
|
+
{ value: true },
|
|
158
|
+
),
|
|
159
|
+
)
|
|
160
|
+
expect(isSchema(foreign)).toBe(true)
|
|
161
|
+
expect(isSchema(defineSchema({ title: str() }))).toBe(true)
|
|
162
|
+
expect(
|
|
163
|
+
isColumn(Object.defineProperty({}, Symbol.for("@arkiv-network/sdk/column"), { value: true })),
|
|
164
|
+
).toBe(true)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it("does not take a look-alike without the brand", () => {
|
|
168
|
+
expect(isSchema({ columns: {} })).toBe(false)
|
|
169
|
+
expect(isColumn({ type: "str", isIndexed: false, isOptional: false })).toBe(false)
|
|
170
|
+
})
|
|
171
|
+
})
|