Skip to content

Commit cbaab3d

Browse files
committed
Get it working with base64 encoded row ids, and start adding the better way for indexing.
1 parent 46e3fbd commit cbaab3d

File tree

10 files changed

+385
-44
lines changed

10 files changed

+385
-44
lines changed

examples/quickstart-chat/src/module_bindings/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ const REMOTE_MODULE = {
6363
tableName: 'user',
6464
rowType: User.getTypeScriptAlgebraicType(),
6565
primaryKey: 'identity',
66+
primaryKeyInfo: {
67+
colName: 'identity',
68+
colType:
69+
User.getTypeScriptAlgebraicType().product.elements[0].algebraicType,
70+
},
6671
},
6772
},
6873
reducers: {

packages/sdk/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,8 @@
4545
"@clockworklabs/test-app": "file:../test-app",
4646
"tsup": "^8.1.0",
4747
"undici": "^6.19.2"
48+
},
49+
"dependencies": {
50+
"base64-js": "^1.5.1"
4851
}
4952
}

packages/sdk/src/algebraic_type.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { TimeDuration } from './time_duration';
22
import { Timestamp } from './timestamp';
33
import { ConnectionId } from './connection_id';
44
import type BinaryReader from './binary_reader';
5-
import type BinaryWriter from './binary_writer';
5+
import BinaryWriter from './binary_writer';
66
import { Identity } from './identity';
77
import ScheduleAt from './schedule_at';
88

@@ -164,6 +164,30 @@ export class ProductType {
164164
}
165165
};
166166

167+
intoMapKey(value: any): any {
168+
if (this.elements.length === 1) {
169+
if (this.elements[0].name === '__time_duration_micros__') {
170+
return (value as TimeDuration).__time_duration_micros__;
171+
}
172+
173+
if (this.elements[0].name === '__timestamp_micros_since_unix_epoch__') {
174+
return (value as Timestamp).__timestamp_micros_since_unix_epoch__;
175+
}
176+
177+
if (this.elements[0].name === '__identity__') {
178+
return (value as Identity).__identity__;
179+
}
180+
181+
if (this.elements[0].name === '__connection_id__') {
182+
return (value as ConnectionId).__connection_id__;
183+
}
184+
}
185+
// The fallback is to serialize and base64 encode the bytes.
186+
const writer = new BinaryWriter(10);
187+
this.serialize(writer, value);
188+
return writer.toBase64();
189+
}
190+
167191
deserialize = (reader: BinaryReader): any => {
168192
let result: { [key: string]: any } = {};
169193
if (this.elements.length === 1) {
@@ -449,6 +473,39 @@ export class AlgebraicType {
449473
return this.#isI64Newtype('__time_duration_micros__');
450474
}
451475

476+
/**
477+
* Convert a value of the algebraic type into something that can be used as a key in a map.
478+
* There are no guarantees about being able to order it.
479+
* This is only guaranteed to be comparable to other values of the same type.
480+
* @param value A value of the algebraic type
481+
* @returns Something that can be used as a key in a map.
482+
*/
483+
intoMapKey(value: any): any {
484+
switch (this.type) {
485+
case Type.U8:
486+
case Type.U16:
487+
case Type.U32:
488+
case Type.U64:
489+
case Type.U128:
490+
case Type.U256:
491+
case Type.I8:
492+
case Type.I16:
493+
case Type.I64:
494+
case Type.I128:
495+
case Type.F32:
496+
case Type.F64:
497+
case Type.String:
498+
case Type.Bool:
499+
return value;
500+
case Type.ProductType:
501+
return this.product.intoMapKey(value);
502+
default:
503+
const writer = new BinaryWriter(10);
504+
this.serialize(writer, value);
505+
return writer.toBase64();
506+
}
507+
}
508+
452509
serialize(writer: BinaryWriter, value: any): void {
453510
switch (this.type) {
454511
case Type.ProductType:

packages/sdk/src/binary_writer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { fromByteArray } from 'base64-js';
2+
13
export default class BinaryWriter {
24
#buffer: Uint8Array;
35
#view: DataView;
@@ -19,6 +21,10 @@ export default class BinaryWriter {
1921
this.#view = new DataView(this.#buffer.buffer);
2022
}
2123

24+
toBase64(): String {
25+
return fromByteArray(this.#buffer.subarray(0, this.#offset));
26+
}
27+
2228
getBuffer(): Uint8Array {
2329
return this.#buffer.slice(0, this.#offset);
2430
}

packages/sdk/src/db_connection_impl.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import {
5454
} from './subscription_builder_impl.ts';
5555
import { stdbLogger } from './logger.ts';
5656
import type { ReducerRuntimeTypeInfo } from './spacetime_module.ts';
57+
import { fromByteArray } from 'base64-js';
5758

5859
export {
5960
AlgebraicType,
@@ -306,19 +307,23 @@ export class DbConnectionImpl<
306307
): Operation[] => {
307308
const buffer = rowList.rowsData;
308309
const reader = new BinaryReader(buffer);
309-
const rows: any[] = [];
310+
const rows: Operation[] = [];
310311
const rowType = this.#remoteModule.tables[tableName]!.rowType;
311312
while (reader.offset < buffer.length + buffer.byteOffset) {
312313
const initialOffset = reader.offset;
313314
const row = rowType.deserialize(reader);
314-
// This is super inefficient, but the buffer indexes are weird, so we are doing this for now.
315-
// We should just base64 encode the bytes.
316-
const rowId = JSON.stringify(row, (_, v) =>
317-
typeof v === 'bigint' ? v.toString() : v
315+
316+
// Get a view of the bytes for this row.
317+
const rowBytes = buffer.subarray(
318+
initialOffset - buffer.byteOffset,
319+
reader.offset - buffer.byteOffset
318320
);
321+
// Convert it to a base64 string, so we can use it as a map key.
322+
const asBase64 = fromByteArray(rowBytes);
323+
319324
rows.push({
320325
type,
321-
rowId,
326+
rowId: asBase64,
322327
row,
323328
});
324329
}

packages/sdk/src/spacetime_module.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import type { DbConnectionImpl } from './db_connection_impl';
44
export interface TableRuntimeTypeInfo {
55
tableName: string;
66
rowType: AlgebraicType;
7-
primaryKey?: string | undefined;
7+
primaryKeyInfo?: PrimaryKeyInfo | undefined;
8+
}
9+
10+
export interface PrimaryKeyInfo {
11+
colName: string;
12+
colType: AlgebraicType;
813
}
914

1015
export interface ReducerRuntimeTypeInfo {

packages/sdk/src/table_cache.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ import { EventEmitter } from './event_emitter.ts';
22
import OperationsMap from './operations_map.ts';
33
import type { TableRuntimeTypeInfo } from './spacetime_module.ts';
44

5-
import { type EventContextInterface } from './db_connection_impl.ts';
5+
import {
6+
BinaryWriter,
7+
type EventContextInterface,
8+
} from './db_connection_impl.ts';
69
import { stdbLogger } from './logger.ts';
710

811
export type Operation = {
912
type: 'insert' | 'delete';
13+
// rowId: string;
1014
rowId: string;
1115
row: any;
1216
};
@@ -60,17 +64,25 @@ export class TableCache<RowType = any> {
6064
ctx: EventContextInterface
6165
): PendingCallback[] => {
6266
const pendingCallbacks: PendingCallback[] = [];
63-
if (this.tableTypeInfo.primaryKey !== undefined) {
64-
const primaryKey = this.tableTypeInfo.primaryKey;
67+
if (this.tableTypeInfo.primaryKeyInfo !== undefined) {
68+
const primaryKeyCol = this.tableTypeInfo.primaryKeyInfo.colName;
69+
const primaryKeyType = this.tableTypeInfo.primaryKeyInfo.colType;
70+
const getPrimaryKey = (row: any) => {
71+
const primaryKeyValue = row[primaryKeyCol];
72+
const writer = new BinaryWriter(10);
73+
primaryKeyType.serialize(writer, primaryKeyValue);
74+
return writer.toBase64();
75+
};
6576
const insertMap = new OperationsMap<any, [Operation, number]>();
6677
const deleteMap = new OperationsMap<any, [Operation, number]>();
6778
for (const op of operations) {
79+
const primaryKey = getPrimaryKey(op.row);
6880
if (op.type === 'insert') {
69-
const [_, prevCount] = insertMap.get(op.row[primaryKey]) || [op, 0];
70-
insertMap.set(op.row[primaryKey], [op, prevCount + 1]);
81+
const [_, prevCount] = insertMap.get(primaryKey) || [op, 0];
82+
insertMap.set(primaryKey, [op, prevCount + 1]);
7183
} else {
72-
const [_, prevCount] = deleteMap.get(op.row[primaryKey]) || [op, 0];
73-
deleteMap.set(op.row[primaryKey], [op, prevCount + 1]);
84+
const [_, prevCount] = deleteMap.get(primaryKey) || [op, 0];
85+
deleteMap.set(primaryKey, [op, prevCount + 1]);
7486
}
7587
}
7688
for (const {
@@ -175,6 +187,7 @@ export class TableCache<RowType = any> {
175187
},
176188
};
177189
}
190+
console.log(`previousCount of ${previousCount} for ${operation.rowId}`);
178191
return undefined;
179192
};
180193

packages/test-app/src/module_bindings/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,22 @@ const REMOTE_MODULE = {
5454
player: {
5555
tableName: 'player',
5656
rowType: Player.getTypeScriptAlgebraicType(),
57-
primaryKey: 'owner_id',
57+
primaryKey: 'ownerId',
58+
primaryKeyInfo: {
59+
colName: 'ownerId',
60+
colType:
61+
Player.getTypeScriptAlgebraicType().product.elements[0].algebraicType,
62+
},
5863
},
5964
user: {
6065
tableName: 'user',
6166
rowType: User.getTypeScriptAlgebraicType(),
6267
primaryKey: 'identity',
68+
primaryKeyInfo: {
69+
colName: 'identity',
70+
colType:
71+
User.getTypeScriptAlgebraicType().product.elements[0].algebraicType,
72+
},
6373
},
6474
},
6575
reducers: {

packages/test-app/src/module_bindings/player_table.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,22 +60,22 @@ export class PlayerTableHandle {
6060
return this.tableCache.iter();
6161
}
6262
/**
63-
* Access to the `owner_id` unique index on the table `player`,
63+
* Access to the `ownerId` unique index on the table `player`,
6464
* which allows point queries on the field of the same name
6565
* via the [`PlayerOwnerIdUnique.find`] method.
6666
*
6767
* Users are encouraged not to explicitly reference this type,
6868
* but to directly chain method calls,
69-
* like `ctx.db.player.owner_id().find(...)`.
69+
* like `ctx.db.player.ownerId().find(...)`.
7070
*
71-
* Get a handle on the `owner_id` unique index on the table `player`.
71+
* Get a handle on the `ownerId` unique index on the table `player`.
7272
*/
73-
owner_id = {
74-
// Find the subscribed row whose `owner_id` column value is equal to `col_val`,
73+
ownerId = {
74+
// Find the subscribed row whose `ownerId` column value is equal to `col_val`,
7575
// if such a row is present in the client cache.
7676
find: (col_val: string): Player | undefined => {
7777
for (let row of this.tableCache.iter()) {
78-
if (deepEqual(row.owner_id, col_val)) {
78+
if (deepEqual(row.ownerId, col_val)) {
7979
return row;
8080
}
8181
}

0 commit comments

Comments
 (0)