Skip to content

Commit fb7d439

Browse files
committed
Merge branch 'main' into centril/websocket-light
2 parents 79c76df + 6547882 commit fb7d439

File tree

12 files changed

+338
-84
lines changed

12 files changed

+338
-84
lines changed

.changeset/three-cats-listen.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clockworklabs/spacetimedb-sdk': minor
3+
---
4+
5+
Update Identity and Address to use bigints rather than byte arrays (see https://github.com/clockworklabs/SpacetimeDB/pull/1616)

packages/sdk/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,11 @@ Given a reducer called `CreatePlayer` you can call it using a call method:
6363
```ts
6464
connection.reducers.createPlayer();
6565
```
66+
67+
### Developer notes
68+
69+
To run the tests, do:
70+
71+
```sh
72+
pnpm compile && pnpm test
73+
```

packages/sdk/src/address.ts

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1+
import { hexStringToU128, u128ToHexString, u128ToUint8Array } from './utils';
2+
13
/**
24
* A unique identifier for a client connected to a database.
35
*/
46
export class Address {
5-
data: Uint8Array;
7+
data: bigint;
68

7-
get __address_bytes(): Uint8Array {
8-
return this.toUint8Array();
9+
get __address__(): bigint {
10+
return this.data;
911
}
1012

1113
/**
1214
* Creates a new `Address`.
1315
*/
14-
constructor(data: Uint8Array) {
16+
constructor(data: bigint) {
1517
this.data = data;
1618
}
1719

1820
isZero(): boolean {
19-
return this.data.every(b => b == 0);
21+
return this.data === BigInt(0);
2022
}
2123

2224
static nullIfZero(addr: Address): Address | null {
@@ -28,53 +30,42 @@ export class Address {
2830
}
2931

3032
static random(): Address {
31-
function randomByte(): number {
32-
return Math.floor(Math.random() * 255);
33+
function randomU8(): number {
34+
return Math.floor(Math.random() * 0xff);
3335
}
34-
let data = new Uint8Array(16);
36+
let result = BigInt(0);
3537
for (let i = 0; i < 16; i++) {
36-
data[i] = randomByte();
38+
result = (result << BigInt(8)) | BigInt(randomU8());
3739
}
38-
return new Address(data);
40+
return new Address(result);
3941
}
4042

4143
/**
4244
* Compare two addresses for equality.
4345
*/
4446
isEqual(other: Address): boolean {
45-
if (this.data.length !== other.data.length) {
46-
return false;
47-
}
48-
for (let i = 0; i < this.data.length; i++) {
49-
if (this.data[i] !== other.data[i]) {
50-
return false;
51-
}
52-
}
53-
return true;
47+
return this.data == other.data;
5448
}
5549

5650
/**
5751
* Print the address as a hexadecimal string.
5852
*/
5953
toHexString(): string {
60-
return Array.prototype.map
61-
.call(this.data, x => ('00' + x.toString(16)).slice(-2))
62-
.join('');
54+
return u128ToHexString(this.data);
6355
}
6456

57+
/**
58+
* Convert the address to a Uint8Array.
59+
*/
6560
toUint8Array(): Uint8Array {
66-
return this.data;
61+
return u128ToUint8Array(this.data);
6762
}
6863

6964
/**
7065
* Parse an Address from a hexadecimal string.
7166
*/
7267
static fromString(str: string): Address {
73-
let matches = str.match(/.{1,2}/g) || [];
74-
let data = Uint8Array.from(
75-
matches.map((byte: string) => parseInt(byte, 16))
76-
);
77-
return new Address(data);
68+
return new Address(hexStringToU128(str));
7869
}
7970

8071
static fromStringOrNull(str: string): Address | null {

packages/sdk/src/algebraic_type.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,12 @@ export class ProductType {
165165
deserialize = (reader: BinaryReader): object => {
166166
let result: { [key: string]: any } = {};
167167
if (this.elements.length === 1) {
168-
if (this.elements[0].name === '__identity_bytes') {
169-
return new Identity(reader.readUInt8Array());
168+
if (this.elements[0].name === '__identity__') {
169+
return new Identity(reader.readU256());
170170
}
171171

172-
if (this.elements[0].name === '__address_bytes') {
173-
return new Address(reader.readUInt8Array());
172+
if (this.elements[0].name === '__address__') {
173+
return new Address(reader.readU128());
174174
}
175175
}
176176

@@ -318,6 +318,12 @@ export class AlgebraicType {
318318
static createU128Type(): AlgebraicType {
319319
return this.#createType(Type.U128, null);
320320
}
321+
static createI256Type(): AlgebraicType {
322+
return this.#createType(Type.I256, null);
323+
}
324+
static createU256Type(): AlgebraicType {
325+
return this.#createType(Type.U256, null);
326+
}
321327
static createF32Type(): AlgebraicType {
322328
return this.#createType(Type.F32, null);
323329
}
@@ -338,12 +344,12 @@ export class AlgebraicType {
338344
}
339345
static createIdentityType(): AlgebraicType {
340346
return this.createProductType([
341-
new ProductTypeElement('__identity_bytes', this.createBytesType()),
347+
new ProductTypeElement('__identity__', this.createU256Type()),
342348
]);
343349
}
344350
static createAddressType(): AlgebraicType {
345351
return this.createProductType([
346-
new ProductTypeElement('__address_bytes', this.createBytesType()),
352+
new ProductTypeElement('__address__', this.createU128Type()),
347353
]);
348354
}
349355
static createScheduleAtType(): AlgebraicType {
@@ -377,17 +383,18 @@ export class AlgebraicType {
377383
return (
378384
this.isProductType() &&
379385
this.product.elements.length === 1 &&
380-
this.product.elements[0].algebraicType.#isBytes() &&
386+
(this.product.elements[0].algebraicType.type == Type.U128 ||
387+
this.product.elements[0].algebraicType.type == Type.U256) &&
381388
this.product.elements[0].name === tag
382389
);
383390
}
384391

385392
isIdentity(): boolean {
386-
return this.#isBytesNewtype('__identity_bytes');
393+
return this.#isBytesNewtype('__identity__');
387394
}
388395

389396
isAddress(): boolean {
390-
return this.#isBytesNewtype('__address_bytes');
397+
return this.#isBytesNewtype('__address__');
391398
}
392399

393400
serialize(writer: BinaryWriter, value: any): void {
@@ -444,6 +451,12 @@ export class AlgebraicType {
444451
case Type.U128:
445452
writer.writeU128(value);
446453
break;
454+
case Type.I256:
455+
writer.writeI256(value);
456+
break;
457+
case Type.U256:
458+
writer.writeU256(value);
459+
break;
447460
case Type.F32:
448461
writer.writeF32(value);
449462
break;
@@ -530,6 +543,8 @@ export namespace AlgebraicType {
530543
U64 = 'U64',
531544
I128 = 'I128',
532545
U128 = 'U128',
546+
I256 = 'I256',
547+
U256 = 'U256',
533548
F32 = 'F32',
534549
F64 = 'F64',
535550
/** UTF-8 encoded */

packages/sdk/src/algebraic_value.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ export class AlgebraicValue {
264264
}
265265
}
266266

267+
// TODO: all of the following methods should actually check the type of `self.value`
268+
// and throw if it does not match.
269+
267270
asProductValue(): ProductValue {
268271
return this.value as ProductValue;
269272
}
@@ -305,11 +308,11 @@ export class AlgebraicValue {
305308
}
306309

307310
asIdentity(): Identity {
308-
return new Identity(this.asField(0).asBytes());
311+
return new Identity(this.asField(0).asBigInt());
309312
}
310313

311314
asAddress(): Address {
312-
return new Address(this.asField(0).asBytes());
315+
return new Address(this.asField(0).asBigInt());
313316
}
314317
}
315318

packages/sdk/src/binary_reader.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,43 @@ export default class BinaryReader {
101101
}
102102

103103
readI128(): bigint {
104-
const lowerPart = this.#buffer.getBigInt64(this.#offset, true);
104+
const lowerPart = this.#buffer.getBigUint64(this.#offset, true);
105105
const upperPart = this.#buffer.getBigInt64(this.#offset + 8, true);
106106
this.#offset += 16;
107107

108108
return (upperPart << BigInt(64)) + lowerPart;
109109
}
110110

111+
readU256(): bigint {
112+
const p0 = this.#buffer.getBigUint64(this.#offset, true);
113+
const p1 = this.#buffer.getBigUint64(this.#offset + 8, true);
114+
const p2 = this.#buffer.getBigUint64(this.#offset + 16, true);
115+
const p3 = this.#buffer.getBigUint64(this.#offset + 24, true);
116+
this.#offset += 32;
117+
118+
return (
119+
(p3 << BigInt(3 * 64)) +
120+
(p2 << BigInt(2 * 64)) +
121+
(p1 << BigInt(1 * 64)) +
122+
p0
123+
);
124+
}
125+
126+
readI256(): bigint {
127+
const p0 = this.#buffer.getBigUint64(this.#offset, true);
128+
const p1 = this.#buffer.getBigUint64(this.#offset + 8, true);
129+
const p2 = this.#buffer.getBigUint64(this.#offset + 16, true);
130+
const p3 = this.#buffer.getBigInt64(this.#offset + 24, true);
131+
this.#offset += 32;
132+
133+
return (
134+
(p3 << BigInt(3 * 64)) +
135+
(p2 << BigInt(2 * 64)) +
136+
(p1 << BigInt(1 * 64)) +
137+
p0
138+
);
139+
}
140+
111141
readF32(): number {
112142
const value = this.#buffer.getFloat32(this.#offset, true);
113143
this.#offset += 4;

packages/sdk/src/binary_writer.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,34 @@ export default class BinaryWriter {
111111
this.#offset += 16;
112112
}
113113

114+
writeU256(value: bigint): void {
115+
this.#expandBuffer(32);
116+
const low_64_mask = BigInt('0xFFFFFFFFFFFFFFFF');
117+
const p0 = value & low_64_mask;
118+
const p1 = (value >> BigInt(64 * 1)) & low_64_mask;
119+
const p2 = (value >> BigInt(64 * 2)) & low_64_mask;
120+
const p3 = value >> BigInt(64 * 3);
121+
this.#view.setBigUint64(this.#offset + 8 * 0, p0, true);
122+
this.#view.setBigUint64(this.#offset + 8 * 1, p1, true);
123+
this.#view.setBigUint64(this.#offset + 8 * 2, p2, true);
124+
this.#view.setBigUint64(this.#offset + 8 * 3, p3, true);
125+
this.#offset += 32;
126+
}
127+
128+
writeI256(value: bigint): void {
129+
this.#expandBuffer(32);
130+
const low_64_mask = BigInt('0xFFFFFFFFFFFFFFFF');
131+
const p0 = value & low_64_mask;
132+
const p1 = (value >> BigInt(64 * 1)) & low_64_mask;
133+
const p2 = (value >> BigInt(64 * 2)) & low_64_mask;
134+
const p3 = value >> BigInt(64 * 3);
135+
this.#view.setBigUint64(this.#offset + 8 * 0, p0, true);
136+
this.#view.setBigUint64(this.#offset + 8 * 1, p1, true);
137+
this.#view.setBigUint64(this.#offset + 8 * 2, p2, true);
138+
this.#view.setBigInt64(this.#offset + 8 * 3, p3, true);
139+
this.#offset += 32;
140+
}
141+
114142
writeF32(value: number): void {
115143
this.#expandBuffer(4);
116144
this.#view.setFloat32(this.#offset, value, true);

packages/sdk/src/identity.ts

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,26 @@
1-
// Helper function convert from string to Uint8Array
2-
function hexStringToUint8Array(str: string): Uint8Array {
3-
let matches = str.match(/.{1,2}/g) || [];
4-
let data = Uint8Array.from(matches.map((byte: string) => parseInt(byte, 16)));
5-
return data;
6-
}
7-
8-
// Helper function for converting Uint8Array to hex string
9-
function uint8ArrayToHexString(array: Uint8Array): string {
10-
return Array.prototype.map
11-
.call(array, x => ('00' + x.toString(16)).slice(-2))
12-
.join('');
13-
}
1+
import BinaryReader from './binary_reader';
2+
import BinaryWriter from './binary_writer';
3+
import { hexStringToU256, u256ToHexString, u256ToUint8Array } from './utils';
144

155
/**
166
* A unique identifier for a user connected to a database.
177
*/
188
export class Identity {
19-
data: Uint8Array;
9+
data: bigint;
2010

21-
get __identity_bytes(): Uint8Array {
22-
return this.toUint8Array();
11+
get __identity__(): bigint {
12+
return this.data;
2313
}
2414

2515
/**
2616
* Creates a new `Identity`.
17+
*
18+
* `data` can be a hexadecimal string or a `bigint`.
2719
*/
28-
constructor(data: string | Uint8Array) {
29-
// we get a JSON with __identity_bytes when getting a token with a JSON API
30-
// and an Uint8Array when using BSATN
31-
this.data =
32-
data.constructor === Uint8Array
33-
? data
34-
: hexStringToUint8Array(data as string);
20+
constructor(data: string | bigint) {
21+
// we get a JSON with __identity__ when getting a token with a JSON API
22+
// and an bigint when using BSATN
23+
this.data = typeof data === 'string' ? hexStringToU256(data) : data;
3524
}
3625

3726
/**
@@ -45,11 +34,14 @@ export class Identity {
4534
* Print the identity as a hexadecimal string.
4635
*/
4736
toHexString(): string {
48-
return uint8ArrayToHexString(this.data);
37+
return u256ToHexString(this.data);
4938
}
5039

40+
/**
41+
* Convert the address to a Uint8Array.
42+
*/
5143
toUint8Array(): Uint8Array {
52-
return this.data;
44+
return u256ToUint8Array(this.data);
5345
}
5446

5547
/**

0 commit comments

Comments
 (0)