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