Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Rename some types and improve README #26

Merged
merged 1 commit into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 37 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand All @@ -202,70 +201,86 @@ const {
} = generatePresence<Cursor>('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,
});
```

## Reference for Presence State

### `set: (tx: WriteTransaction, value: OptionalIDs<T>) => Promise<void>`
### `set: (tx: WriteTransaction, value: OptionalClientID<T>) => Promise<void>`

Write `value`, overwriting any previous version of same value.

### `init: (tx: WriteTransaction, value: OptionalIDs<T>) => Promise<boolean>`
### `init: (tx: WriteTransaction, value: OptionalClientID<T>) => Promise<boolean>`

Write `value` only if no previous version of this value exists.

### `update: (tx: WriteTransaction, value: Update<LookupID, T>) => Promise<void>`
### `update: (tx: WriteTransaction, value: PresenceUpdate<T>) => Promise<void>`

Update existing value with new fields.

### `delete: (tx: WriteTransaction, id?: LookupID) => Promise<void>`
### `delete: (tx: WriteTransaction, id?: PresenceID<T>) => Promise<void>`

Delete any existing value or do nothing if none exist.

### `has: (tx: ReadTransaction, id?: LookupID) => Promise<boolean>`
### `has: (tx: ReadTransaction, id?: PresenceID<T>) => Promise<boolean>`

Return true if specified value exists, false otherwise.

### `get: (tx: ReadTransaction, id?: LookupID) => Promise<T | undefined>`
### `get: (tx: ReadTransaction, id?: PresenceID<T>) => Promise<T | undefined>`

Get value by ID, or return undefined if none exists.

### `mustGet: (tx: ReadTransaction, id?: LookupID) => Promise<T>`
### `mustGet: (tx: ReadTransaction, id?: PresenceID<T>) => Promise<T>`

Get value by ID, or throw if none exists.

### `list: (tx: ReadTransaction, options?: ListOptionsForPresence) => Promise<T[]>`
### `list: (tx: ReadTransaction, options?: PresenceListOptions<T>) => Promise<T[]>`

List values matching criteria.

### `listIDs: (tx: ReadTransaction, options?: ListOptionsForPresence) => Promise<PresenceEntity[]>`
### `listIDs: (tx: ReadTransaction, options?: PresenceListOptions<T>) => Promise<ListID<T>[]>`

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<T>) => Promise<string[]>`

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<T>) => Promise<[ListID<T>, T][]>`

List [id, value] entries matching criteria.

Expand Down
22 changes: 17 additions & 5 deletions src/generate-presence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1405,7 +1405,10 @@ suite('list', () => {
name: string;
collectionName: 'entryNoID' | 'entryID';
stored: Record<string, ReadonlyJSONValue>;
param: ListOptions<EntryID> | ListOptions<EntryNoID> | undefined;
param:
| PresenceListOptions<EntryID>
| PresenceListOptions<EntryNoID>
| undefined;
expectedValues: ReadonlyJSONValue[] | undefined;
expectedError?: ReadonlyJSONValue;
};
Expand Down Expand Up @@ -1628,7 +1631,10 @@ suite('listIDs', () => {
name: string;
collectionName: 'entryNoID' | 'entryID';
stored: Record<string, ReadonlyJSONValue>;
param: ListOptions<EntryID> | ListOptions<EntryNoID> | undefined;
param:
| PresenceListOptions<EntryID>
| PresenceListOptions<EntryNoID>
| undefined;
expectedValues: ReadonlyJSONValue[] | undefined;
expectedError?: ReadonlyJSONValue;
};
Expand Down Expand Up @@ -1843,7 +1849,10 @@ suite('listClientIDs', () => {
name: string;
collectionName: 'entryNoID' | 'entryID';
stored: Record<string, ReadonlyJSONValue>;
param: ListOptions<EntryID> | ListOptions<EntryNoID> | undefined;
param:
| PresenceListOptions<EntryID>
| PresenceListOptions<EntryNoID>
| undefined;
expectedValues: ReadonlyJSONValue[] | undefined;
expectedError?: ReadonlyJSONValue;
};
Expand Down Expand Up @@ -2025,7 +2034,10 @@ suite('listEntries', () => {
name: string;
collectionName: 'entryNoID' | 'entryID';
stored: Record<string, ReadonlyJSONValue>;
param: ListOptions<EntryID> | ListOptions<EntryNoID> | undefined;
param:
| PresenceListOptions<EntryID>
| PresenceListOptions<EntryNoID>
| undefined;
expectedValues: ReadonlyJSONValue[] | undefined;
expectedError?: ReadonlyJSONValue;
};
Expand Down
26 changes: 13 additions & 13 deletions src/generate-presence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ export type OptionalClientID<T extends PresenceEntity> = {
clientID?: string | undefined;
} & Omit<T, 'clientID'>;

export type ListOptions<T extends PresenceEntity> = {
export type PresenceListOptions<T extends PresenceEntity> = {
startAtID?: StartAtID<T>;
limit?: number;
};

export type Update<T extends PresenceEntity> =
export type PresenceUpdate<T extends PresenceEntity> =
IsIDMissing<T> extends false ? Pick<T, 'id'> & Partial<T> : Partial<T>;

export type GeneratePresenceResult<T extends PresenceEntity> = {
Expand All @@ -92,7 +92,7 @@ export type GeneratePresenceResult<T extends PresenceEntity> = {
/** Write `value` only if no previous version of this value exists. */
init: (tx: WriteTransaction, value: OptionalClientID<T>) => Promise<boolean>;
/** Update existing value with new fields. */
update: (tx: WriteTransaction, value: Update<T>) => Promise<void>;
update: (tx: WriteTransaction, value: PresenceUpdate<T>) => Promise<void>;
/** Delete any existing value or do nothing if none exist. */
delete: (tx: WriteTransaction, id?: PresenceID<T>) => Promise<void>;
/** Return true if specified value exists, false otherwise. */
Expand All @@ -102,30 +102,30 @@ export type GeneratePresenceResult<T extends PresenceEntity> = {
/** Get value by ID, or throw if none exists. */
mustGet: (tx: ReadTransaction, id?: PresenceID<T>) => Promise<T>;
/** List values matching criteria. */
list: (tx: ReadTransaction, options?: ListOptions<T>) => Promise<T[]>;
list: (tx: ReadTransaction, options?: PresenceListOptions<T>) => Promise<T[]>;

/**
* 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<T>,
options?: PresenceListOptions<T>,
) => Promise<ListID<T>[]>;

/**
* 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<T>,
options?: PresenceListOptions<T>,
) => Promise<string[]>;

/** List [id, value] entries matching criteria. */
listEntries: (
tx: ReadTransaction,
options?: ListOptions<T>,
options?: PresenceListOptions<T>,
) => Promise<[ListID<T>, T][]>;
};

Expand Down Expand Up @@ -197,8 +197,8 @@ function normalizePresenceID<T extends PresenceEntity>(

function normalizeForUpdate<T extends PresenceEntity>(
tx: {clientID: string},
v: Update<T>,
): Update<T> & {clientID: string} {
v: PresenceUpdate<T>,
): PresenceUpdate<T> & {clientID: string} {
return normalizeForSet(tx, v);
}

Expand All @@ -222,7 +222,7 @@ function normalizeForSet<
}

export function normalizeScanOptions<T extends PresenceEntity>(
options?: ListOptions<T>,
options?: PresenceListOptions<T>,
): ListOptionsWith<ListID<T>> | undefined {
if (!options) {
return options;
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading