From c3d82d712836c855470ba29ff6636fe0c5177580 Mon Sep 17 00:00:00 2001 From: Erik Arvidsson Date: Tue, 30 Jan 2024 11:37:13 +0100 Subject: [PATCH] chore: Rename some types and improve README --- README.md | 59 ++++++++++++++++++++++------------- src/generate-presence.test.ts | 22 ++++++++++--- src/generate-presence.ts | 26 +++++++-------- src/index.ts | 4 +-- 4 files changed, 69 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index f74c116..4542368 100644 --- a/README.md +++ b/README.md @@ -185,12 +185,11 @@ type PresenceEntity = { ### Generate Presence State Helpers The function `generatePresence` is similar to the `generate` function but it -generates functions that are to be used with presences state. +generates functions that are to be used with presence state. ```ts type Cursor { clientID: string; - id: '', x: number; y: number; }; @@ -202,26 +201,36 @@ const { } = generatePresence('cursor'); ``` +For presence entities there are two common cases: + +1. The entity does not have an `id` field. Then there can only be one entity per + client. This case is useful for keeping track of things like the cursor + position. +2. The entity has an `id` field. Then there can be multiple entities per client. + This case is useful for keeping track of things like multiple selection or + multiple cursors (aka multi touch). + ### Lookup Presence State -Both the clientID and the id are significant when reading presence state. However, -for convenience, you can omit the `clientID` and it will default to the current -client ID. You can also omit the `id` and it will default to `''`. +The `clientID` field (and `id` if used) is significant when reading presence +state. However, for convenience, you can omit the `clientID` and it will default +to the `clientID` of current client. You may not omit the `id` if your entity +type requires an `id` field. When reading presence state you may also omit the +lookup argument completely. ### Mutating Presence State When writing you may only change the presence state entities for the current -client. If you pass in a `clientID` that is different from the current client ID -a runtime error will be thrown. +client. If you pass in a `clientID` that is different from the `clientID` of the +current client a runtime error will be thrown. -When writing you may also omit the `clientID` and the `id` and they will default -to the current client ID and `''` respectively. +When writing you may also omit the `clientID` it will default to the `clientID` +of the current client. ```ts await setCursor(tx, {x: 10, y: 20}); -expect(await getCursor(tx, {})).toEqual({ +expect(await getCursor(tx)).toEqual({ clientID: tx.clientID, - id: '', x: 10, y: 20, }); @@ -229,43 +238,49 @@ expect(await getCursor(tx, {})).toEqual({ ## Reference for Presence State -### `set: (tx: WriteTransaction, value: OptionalIDs) => Promise` +### `set: (tx: WriteTransaction, value: OptionalClientID) => Promise` Write `value`, overwriting any previous version of same value. -### `init: (tx: WriteTransaction, value: OptionalIDs) => Promise` +### `init: (tx: WriteTransaction, value: OptionalClientID) => Promise` Write `value` only if no previous version of this value exists. -### `update: (tx: WriteTransaction, value: Update) => Promise` +### `update: (tx: WriteTransaction, value: PresenceUpdate) => Promise` Update existing value with new fields. -### `delete: (tx: WriteTransaction, id?: LookupID) => Promise` +### `delete: (tx: WriteTransaction, id?: PresenceID) => Promise` Delete any existing value or do nothing if none exist. -### `has: (tx: ReadTransaction, id?: LookupID) => Promise` +### `has: (tx: ReadTransaction, id?: PresenceID) => Promise` Return true if specified value exists, false otherwise. -### `get: (tx: ReadTransaction, id?: LookupID) => Promise` +### `get: (tx: ReadTransaction, id?: PresenceID) => Promise` Get value by ID, or return undefined if none exists. -### `mustGet: (tx: ReadTransaction, id?: LookupID) => Promise` +### `mustGet: (tx: ReadTransaction, id?: PresenceID) => Promise` Get value by ID, or throw if none exists. -### `list: (tx: ReadTransaction, options?: ListOptionsForPresence) => Promise` +### `list: (tx: ReadTransaction, options?: PresenceListOptions) => Promise` List values matching criteria. -### `listIDs: (tx: ReadTransaction, options?: ListOptionsForPresence) => Promise` +### `listIDs: (tx: ReadTransaction, options?: PresenceListOptions) => Promise[]>` -List ids matching criteria. +List IDs matching criteria. The returned ID is `{clientID: string}` if the entry +has no `id` field, otherwise it is `{clientID: string, id: string}`. + +### `listClientIDs: (tx: ReadTransaction, options?: PresenceListOptions) => Promise` + +List `clientID`s matching criteria. Unlike `listIDs` this returns an array of +strings consisting of the `clientID`s. -### `listEntries: (tx: ReadTransaction, options?: ListOptionsForPresence) => Promise<[PresenceEntity, T][]>` +### `listEntries: (tx: ReadTransaction, options?: PresenceListOptions) => Promise<[ListID, T][]>` List [id, value] entries matching criteria. diff --git a/src/generate-presence.test.ts b/src/generate-presence.test.ts index ce3b239..3836319 100644 --- a/src/generate-presence.test.ts +++ b/src/generate-presence.test.ts @@ -6,8 +6,8 @@ import {MutatorDefs, Replicache, TEST_LICENSE_KEY} from 'replicache'; import {expect, suite, test} from 'vitest'; import {ZodError, z} from 'zod'; import { - ListOptions, PresenceEntity, + PresenceListOptions, generatePresence, keyFromID, normalizeScanOptions, @@ -1405,7 +1405,10 @@ suite('list', () => { name: string; collectionName: 'entryNoID' | 'entryID'; stored: Record; - param: ListOptions | ListOptions | undefined; + param: + | PresenceListOptions + | PresenceListOptions + | undefined; expectedValues: ReadonlyJSONValue[] | undefined; expectedError?: ReadonlyJSONValue; }; @@ -1628,7 +1631,10 @@ suite('listIDs', () => { name: string; collectionName: 'entryNoID' | 'entryID'; stored: Record; - param: ListOptions | ListOptions | undefined; + param: + | PresenceListOptions + | PresenceListOptions + | undefined; expectedValues: ReadonlyJSONValue[] | undefined; expectedError?: ReadonlyJSONValue; }; @@ -1843,7 +1849,10 @@ suite('listClientIDs', () => { name: string; collectionName: 'entryNoID' | 'entryID'; stored: Record; - param: ListOptions | ListOptions | undefined; + param: + | PresenceListOptions + | PresenceListOptions + | undefined; expectedValues: ReadonlyJSONValue[] | undefined; expectedError?: ReadonlyJSONValue; }; @@ -2025,7 +2034,10 @@ suite('listEntries', () => { name: string; collectionName: 'entryNoID' | 'entryID'; stored: Record; - param: ListOptions | ListOptions | undefined; + param: + | PresenceListOptions + | PresenceListOptions + | undefined; expectedValues: ReadonlyJSONValue[] | undefined; expectedError?: ReadonlyJSONValue; }; diff --git a/src/generate-presence.ts b/src/generate-presence.ts index eea77df..5afd23b 100644 --- a/src/generate-presence.ts +++ b/src/generate-presence.ts @@ -73,12 +73,12 @@ export type OptionalClientID = { clientID?: string | undefined; } & Omit; -export type ListOptions = { +export type PresenceListOptions = { startAtID?: StartAtID; limit?: number; }; -export type Update = +export type PresenceUpdate = IsIDMissing extends false ? Pick & Partial : Partial; export type GeneratePresenceResult = { @@ -92,7 +92,7 @@ export type GeneratePresenceResult = { /** Write `value` only if no previous version of this value exists. */ init: (tx: WriteTransaction, value: OptionalClientID) => Promise; /** Update existing value with new fields. */ - update: (tx: WriteTransaction, value: Update) => Promise; + update: (tx: WriteTransaction, value: PresenceUpdate) => Promise; /** Delete any existing value or do nothing if none exist. */ delete: (tx: WriteTransaction, id?: PresenceID) => Promise; /** Return true if specified value exists, false otherwise. */ @@ -102,30 +102,30 @@ export type GeneratePresenceResult = { /** Get value by ID, or throw if none exists. */ mustGet: (tx: ReadTransaction, id?: PresenceID) => Promise; /** List values matching criteria. */ - list: (tx: ReadTransaction, options?: ListOptions) => Promise; + list: (tx: ReadTransaction, options?: PresenceListOptions) => Promise; /** - * List ids matching criteria. Here the id is `{clientID: string}` if the + * List IDs matching criteria. The returned ID is `{clientID: string}` if the * entry has no `id` field, otherwise it is `{clientID: string, id: string}`. */ listIDs: ( tx: ReadTransaction, - options?: ListOptions, + options?: PresenceListOptions, ) => Promise[]>; /** - * List clientIDs matching criteria. Unlike listIDs this returns an array of strings - * consisting of the clientIDs + * List `clientID`s matching criteria. Unlike `listIDs` this returns an array + * of strings consisting of the `clientID`s. */ listClientIDs: ( tx: ReadTransaction, - options?: ListOptions, + options?: PresenceListOptions, ) => Promise; /** List [id, value] entries matching criteria. */ listEntries: ( tx: ReadTransaction, - options?: ListOptions, + options?: PresenceListOptions, ) => Promise<[ListID, T][]>; }; @@ -197,8 +197,8 @@ function normalizePresenceID( function normalizeForUpdate( tx: {clientID: string}, - v: Update, -): Update & {clientID: string} { + v: PresenceUpdate, +): PresenceUpdate & {clientID: string} { return normalizeForSet(tx, v); } @@ -222,7 +222,7 @@ function normalizeForSet< } export function normalizeScanOptions( - options?: ListOptions, + options?: PresenceListOptions, ): ListOptionsWith> | undefined { if (!options) { return options; diff --git a/src/index.ts b/src/index.ts index 93c7ecc..a49bd24 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,8 +4,8 @@ export { type OptionalClientID, type PresenceEntity, type PresenceID, - type ListOptions as PresenceListOptions, - type Update as PresenceUpdate, + type PresenceListOptions, + type PresenceUpdate, } from './generate-presence.js'; export { generate,