diff --git a/examples/quickstart-chat/src/index.css b/examples/quickstart-chat/src/index.css index 9390800..12a6ed0 100644 --- a/examples/quickstart-chat/src/index.css +++ b/examples/quickstart-chat/src/index.css @@ -32,16 +32,16 @@ body, } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', + 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; + font-family: + source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } /* ----- Buttons ----- */ diff --git a/packages/sdk/src/connection_id.ts b/packages/sdk/src/connection_id.ts index c8d9501..e96b5e1 100644 --- a/packages/sdk/src/connection_id.ts +++ b/packages/sdk/src/connection_id.ts @@ -47,6 +47,10 @@ export class ConnectionId { return this.data == other.data; } + toPrimaryKey(): bigint { + return this.data; + } + /** * Print the connection ID as a hexadecimal string. */ diff --git a/packages/sdk/src/db_connection_impl.ts b/packages/sdk/src/db_connection_impl.ts index 6d99d9b..e555abd 100644 --- a/packages/sdk/src/db_connection_impl.ts +++ b/packages/sdk/src/db_connection_impl.ts @@ -520,14 +520,14 @@ export class DbConnectionImpl< tableUpdates: TableUpdate[], eventContext: EventContextInterface ): PendingCallback[] { - const pendingCallbacks: PendingCallback[] = []; + let pendingCallbacks: PendingCallback[] = []; for (let tableUpdate of tableUpdates) { // Get table information for the table being updated const tableName = tableUpdate.tableName; const tableTypeInfo = this.#remoteModule.tables[tableName]!; const table = this.clientCache.getOrCreateTable(tableTypeInfo); - pendingCallbacks.push( - ...table.applyOperations(tableUpdate.operations, eventContext) + pendingCallbacks = pendingCallbacks.concat( + table.applyOperations(tableUpdate.operations, eventContext) ); } return pendingCallbacks; diff --git a/packages/sdk/src/identity.ts b/packages/sdk/src/identity.ts index 9ce25dc..d2df27b 100644 --- a/packages/sdk/src/identity.ts +++ b/packages/sdk/src/identity.ts @@ -37,6 +37,10 @@ export class Identity { return u256ToHexString(this.data); } + toPrimaryKey(): string { + return this.toHexString(); + } + /** * Convert the address to a Uint8Array. */ diff --git a/packages/sdk/src/operations_map.ts b/packages/sdk/src/operations_map.ts deleted file mode 100644 index 6ba22f4..0000000 --- a/packages/sdk/src/operations_map.ts +++ /dev/null @@ -1,63 +0,0 @@ -export default class OperationsMap { - #items: { key: K; value: V }[] = []; - - #isEqual(a: K, b: K): boolean { - if (a && typeof a === 'object' && 'isEqual' in a) { - return (a as any).isEqual(b); - } - return a === b; - } - - set(key: K, value: V): void { - const existingIndex = this.#items.findIndex(({ key: k }) => - this.#isEqual(k, key) - ); - if (existingIndex > -1) { - this.#items[existingIndex].value = value; - } else { - this.#items.push({ key, value }); - } - } - - get(key: K): V | undefined { - const item = this.#items.find(({ key: k }) => this.#isEqual(k, key)); - return item ? item.value : undefined; - } - - delete(key: K): boolean { - const existingIndex = this.#items.findIndex(({ key: k }) => - this.#isEqual(k, key) - ); - if (existingIndex > -1) { - this.#items.splice(existingIndex, 1); - return true; - } - return false; - } - - has(key: K): boolean { - return this.#items.some(({ key: k }) => this.#isEqual(k, key)); - } - - values(): Array { - return this.#items.map(i => i.value); - } - - entries(): Array<{ key: K; value: V }> { - return this.#items; - } - - [Symbol.iterator](): Iterator<{ key: K; value: V }> { - let index = 0; - const items = this.#items; - return { - next(): IteratorResult<{ key: K; value: V }> { - if (index < items.length) { - return { value: items[index++], done: false }; - } else { - return { value: null, done: true }; - } - }, - }; - } -} diff --git a/packages/sdk/src/table_cache.ts b/packages/sdk/src/table_cache.ts index d23968c..88c5318 100644 --- a/packages/sdk/src/table_cache.ts +++ b/packages/sdk/src/table_cache.ts @@ -1,5 +1,4 @@ import { EventEmitter } from './event_emitter.ts'; -import OperationsMap from './operations_map.ts'; import type { TableRuntimeTypeInfo } from './spacetime_module.ts'; import { type EventContextInterface } from './db_connection_impl.ts'; @@ -61,47 +60,61 @@ export class TableCache { ): PendingCallback[] => { const pendingCallbacks: PendingCallback[] = []; if (this.tableTypeInfo.primaryKey !== undefined) { + let hasDelete = false; const primaryKey = this.tableTypeInfo.primaryKey; - const insertMap = new OperationsMap(); - const deleteMap = new OperationsMap(); + const insertMap = new Map(); + const deleteMap = new Map(); for (const op of operations) { + let key = op.row[primaryKey]; + if (typeof key === 'object' && 'toPrimaryKey' in key) { + key = key.toPrimaryKey(); + } + if (op.type === 'insert') { - const [_, prevCount] = insertMap.get(op.row[primaryKey]) || [op, 0]; - insertMap.set(op.row[primaryKey], [op, prevCount + 1]); + const [_, prevCount] = insertMap.get(key) || [op, 0]; + insertMap.set(key, [op, prevCount + 1]); } else { - const [_, prevCount] = deleteMap.get(op.row[primaryKey]) || [op, 0]; - deleteMap.set(op.row[primaryKey], [op, prevCount + 1]); + hasDelete = true; + const [_, prevCount] = deleteMap.get(key) || [op, 0]; + deleteMap.set(key, [op, prevCount + 1]); } } - for (const { - key: primaryKey, - value: [insertOp, refCount], - } of insertMap) { - const deleteEntry = deleteMap.get(primaryKey); - if (deleteEntry) { - const [deleteOp, deleteCount] = deleteEntry; - // In most cases the refCountDelta will be either 0 or refCount, but if - // an update moves a row in or out of the result set of different queries, then - // other deltas are possible. - const refCountDelta = refCount - deleteCount; - const maybeCb = this.update(ctx, insertOp, deleteOp, refCountDelta); + + if (hasDelete) { + for (const [primaryKey, [insertOp, refCount]] of insertMap.entries()) { + const deleteEntry = deleteMap.get(primaryKey); + if (deleteEntry) { + const [deleteOp, deleteCount] = deleteEntry; + // In most cases the refCountDelta will be either 0 or refCount, but if + // an update moves a row in or out of the result set of different queries, then + // other deltas are possible. + const refCountDelta = refCount - deleteCount; + const maybeCb = this.update(ctx, insertOp, deleteOp, refCountDelta); + if (maybeCb) { + pendingCallbacks.push(maybeCb); + } + deleteMap.delete(primaryKey); + } else { + const maybeCb = this.insert(ctx, insertOp, refCount); + if (maybeCb) { + pendingCallbacks.push(maybeCb); + } + } + } + for (const [deleteOp, refCount] of deleteMap.values()) { + const maybeCb = this.delete(ctx, deleteOp, refCount); if (maybeCb) { pendingCallbacks.push(maybeCb); } - deleteMap.delete(primaryKey); - } else { + } + } else { + for (const [insertOp, refCount] of insertMap.values()) { const maybeCb = this.insert(ctx, insertOp, refCount); if (maybeCb) { pendingCallbacks.push(maybeCb); } } } - for (const [deleteOp, refCount] of deleteMap.values()) { - const maybeCb = this.delete(ctx, deleteOp, refCount); - if (maybeCb) { - pendingCallbacks.push(maybeCb); - } - } } else { for (const op of operations) { if (op.type === 'insert') {