-
Notifications
You must be signed in to change notification settings - Fork 66
/
utils.ts
362 lines (331 loc) · 10.7 KB
/
utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
import { GetOptions } from "./types/collections";
import { cloneDeep } from "lodash-es";
export interface Env {
uint8array_to_latin1_string(a: Uint8Array): string;
uint8array_to_utf8_string(a: Uint8Array): string;
latin1_string_to_uint8array(s: string): Uint8Array;
utf8_string_to_uint8array(s: string): Uint8Array;
}
declare const env: Env;
/**
* A PromiseIndex which represents the ID of a NEAR Promise.
*/
export type PromiseIndex = number | bigint;
/**
* A number that specifies the amount of NEAR in yoctoNEAR.
*/
export type NearAmount = number | bigint;
/**
* A number that specifies the ID of a register in the NEAR WASM virtual machine.
*/
export type Register = number | bigint;
const TYPE_KEY = "typeInfo";
enum TypeBrand {
BIGINT = "bigint",
DATE = "date",
}
export const ERR_INCONSISTENT_STATE =
"The collection is an inconsistent state. Did previous smart contract execution terminate unexpectedly?";
export const ERR_INDEX_OUT_OF_BOUNDS = "Index out of bounds";
const ACCOUNT_ID_REGEX =
/^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/;
/**
* Concat two Uint8Array
* @param array1
* @param array2
* @returns the concatenation of two array
*/
export function concat(array1: Uint8Array, array2: Uint8Array): Uint8Array {
const mergedArray = new Uint8Array(array1.length + array2.length);
mergedArray.set(array1);
mergedArray.set(array2, array1.length);
return mergedArray;
}
/**
* Asserts that the expression passed to the function is truthy, otherwise throws a new Error with the provided message.
*
* @param expression - The expression to be asserted.
* @param message - The error message to be printed.
*/
export function assert(
expression: unknown,
message: string
): asserts expression {
if (!expression) {
throw new Error("assertion failed: " + message);
}
}
export type Mutable<T> = { -readonly [P in keyof T]: T[P] };
export function getValueWithOptions<DataType>(
subDatatype: unknown,
value: Uint8Array | null,
options: Omit<GetOptions<DataType>, "serializer"> = {
deserializer: deserialize,
}
): DataType | null {
if (value === null) {
return options?.defaultValue ?? null;
}
// here is an obj
let deserialized = deserialize(value);
if (deserialized === undefined || deserialized === null) {
return options?.defaultValue ?? null;
}
if (options?.reconstructor) {
// example: // { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}}
const collection = options.reconstructor(deserialized);
if (
subDatatype !== undefined &&
// eslint-disable-next-line no-prototype-builtins
subDatatype.hasOwnProperty("class") &&
// eslint-disable-next-line no-prototype-builtins
subDatatype["class"].hasOwnProperty("value")
) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
collection.subtype = function () {
// example: {class: UnorderedMap, value: UnorderedMap}
return subDatatype["class"]["value"];
};
}
return collection;
}
// example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}}
if (subDatatype !== undefined) {
// subtype info is a class constructor, Such as Car
if (typeof subDatatype === "function") {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
deserialized = decodeObj2class(new subDatatype(), deserialized);
} else if (typeof subDatatype === "object") {
// normal collections of array, map; subtype will be:
// {map: { key: 'string', value: 'string' }} or {array: {value: 'string'}} ..
// eslint-disable-next-line no-prototype-builtins
if (subDatatype.hasOwnProperty("map")) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
for (const mkey in deserialized) {
if (subDatatype["map"]["value"] !== "string") {
deserialized[mkey] = decodeObj2class(
new subDatatype["map"]["value"](),
value[mkey]
);
}
}
// eslint-disable-next-line no-prototype-builtins
} else if (subDatatype.hasOwnProperty("array")) {
const new_vec = [];
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
for (const k in deserialized) {
if (subDatatype["array"]["value"] !== "string") {
new_vec.push(
decodeObj2class(new subDatatype["array"]["value"](), value[k])
);
}
}
deserialized = new_vec;
// eslint-disable-next-line no-prototype-builtins
}
}
}
return deserialized as DataType;
}
export function serializeValueWithOptions<DataType>(
value: DataType,
{ serializer }: Pick<GetOptions<DataType>, "serializer"> = {
serializer: serialize,
}
): Uint8Array {
return serializer(value);
}
export function serialize(valueToSerialize: unknown): Uint8Array {
return encode(
JSON.stringify(valueToSerialize, function (key, value) {
if (typeof value === "bigint") {
return {
value: value.toString(),
[TYPE_KEY]: TypeBrand.BIGINT,
};
}
if (
typeof this[key] === "object" &&
this[key] !== null &&
this[key] instanceof Date
) {
return {
value: this[key].toISOString(),
[TYPE_KEY]: TypeBrand.DATE,
};
}
return value;
})
);
}
export function deserialize(valueToDeserialize: Uint8Array): unknown {
return JSON.parse(decode(valueToDeserialize), (_, value) => {
if (
value !== null &&
typeof value === "object" &&
Object.keys(value).length === 2 &&
Object.keys(value).every((key) => ["value", TYPE_KEY].includes(key))
) {
switch (value[TYPE_KEY]) {
case TypeBrand.BIGINT:
return BigInt(value["value"]);
case TypeBrand.DATE:
return new Date(value["value"]);
}
}
return value;
});
}
export function decodeObj2class(class_instance, obj) {
if (
typeof obj != "object" ||
typeof obj === "bigint" ||
obj instanceof Date ||
class_instance.constructor.schema === undefined
) {
return obj;
}
let key;
for (key in obj) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const value = obj[key];
if (typeof value == "object") {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const ty = class_instance.constructor.schema[key];
// eslint-disable-next-line no-prototype-builtins
if (ty !== undefined && ty.hasOwnProperty("map")) {
for (const mkey in value) {
if (ty["map"]["value"] === "string") {
class_instance[key][mkey] = value[mkey];
} else {
class_instance[key][mkey] = decodeObj2class(
new ty["map"]["value"](),
value[mkey]
);
}
}
// eslint-disable-next-line no-prototype-builtins
} else if (ty !== undefined && ty.hasOwnProperty("array")) {
for (const k in value) {
if (ty["array"]["value"] === "string") {
class_instance[key].push(value[k]);
} else {
class_instance[key].push(
decodeObj2class(new ty["array"]["value"](), value[k])
);
}
}
// eslint-disable-next-line no-prototype-builtins
} else if (ty !== undefined && ty.hasOwnProperty("class")) {
// => nested_lookup_recordes: {class: UnorderedMap, value: {class: LookupMap }},
class_instance[key] = ty["class"].reconstruct(obj[key]);
// eslint-disable-next-line no-prototype-builtins
if (ty.hasOwnProperty("value")) {
const subtype_value = ty["value"];
class_instance[key].subtype = function () {
// example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}}
// example: UnorderedMap or {class: UnorderedMap, value: 'string'}
return subtype_value;
};
}
} else if (ty !== undefined && typeof ty.reconstruct === "function") {
class_instance[key] = ty.reconstruct(obj[key]);
} else {
// normal case with nested Class, such as field is truck: Truck,
class_instance[key] = decodeObj2class(class_instance[key], obj[key]);
}
} else {
class_instance[key] = obj[key];
}
}
const instance_tmp = cloneDeep(class_instance);
for (key in obj) {
if (
typeof class_instance[key] == "object" &&
!(class_instance[key] instanceof Date)
) {
class_instance[key] = instance_tmp[key];
}
}
return class_instance;
}
/**
* Validates the Account ID according to the NEAR protocol
* [Account ID rules](https://nomicon.io/DataStructures/Account#account-id-rules).
*
* @param accountId - The Account ID string you want to validate.
*/
export function validateAccountId(accountId: string): boolean {
return (
accountId.length >= 2 &&
accountId.length <= 64 &&
ACCOUNT_ID_REGEX.test(accountId)
);
}
/**
* A subset of NodeJS TextEncoder API
*/
export class TextEncoder {
encode(s: string): Uint8Array {
return env.utf8_string_to_uint8array(s);
}
}
/**
* A subset of NodeJS TextDecoder API. Only support utf-8 and latin1 encoding.
*/
export class TextDecoder {
constructor(public encoding: string = "utf-8") {}
decode(a: Uint8Array): string {
if (this.encoding == "utf-8") {
return env.uint8array_to_utf8_string(a);
} else if (this.encoding == "latin1") {
return env.uint8array_to_latin1_string(a);
} else {
throw new Error("unsupported encoding: " + this.encoding);
}
}
}
/**
* Convert a string to Uint8Array, each character must have a char code between 0-255.
* @param s - string that with only Latin1 character to convert
* @returns result Uint8Array
*/
export function bytes(s: string): Uint8Array {
return env.latin1_string_to_uint8array(s);
}
/**
* Convert a Uint8Array to string, each uint8 to the single character of that char code
* @param a - Uint8Array to convert
* @returns result string
*/
export function str(a: Uint8Array): string {
return env.uint8array_to_latin1_string(a);
}
/**
* Encode the string to Uint8Array with UTF-8 encoding
* @param s - String to encode
* @returns result Uint8Array
*/
export function encode(s: string): Uint8Array {
return env.utf8_string_to_uint8array(s);
}
/**
* Decode the Uint8Array to string in UTF-8 encoding
* @param a - array to decode
* @returns result string
*/
export function decode(a: Uint8Array): string {
return env.uint8array_to_utf8_string(a);
}
/**
* When implemented, allow object to be stored as collection key
*/
export interface IntoStorageKey {
into_storage_key(): string;
}