Skip to content

Commit

Permalink
docs: 0.12 public facing docs (#108)
Browse files Browse the repository at this point in the history
* Push

* Add changeset
  • Loading branch information
PuruVJ authored Oct 8, 2024
1 parent 79c278b commit b9db9b6
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 117 deletions.
5 changes: 5 additions & 0 deletions .changeset/orange-dragons-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clockworklabs/spacetimedb-sdk': patch
---

docs: Public facing docs for 0.12
72 changes: 29 additions & 43 deletions packages/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,68 +12,54 @@ The SDK is an NPM package, thus you can use your package manager of choice like
npm install --save @clockworklabs/spacetimedb-sdk
```

You can use the package in the browser, using a bundler like webpack of vite, and in terminal applications
You can use the package in the browser, using a bundler like vite/parcel/rsbuild, in server-side applications like NodeJS, Deno, Bun and in Cloudflare Workers.

> NOTE: For usage in NodeJS 18-21, you need to install the `undici` package as a peer dependency as well: `npm install @clockworklabs/spacetimedb-sdk undici`. Node 22 and later are supported out of the box.
> NOTE: For usage in NodeJS 18-21, you need to install the `undici` package as a peer dependency: `npm install @clockworklabs/spacetimedb-sdk undici`. Node 22 and later are supported out of the box.
### Usage

In order to connect to a database you have to create a new client:
In order to connect to a database you have to generate module bindings for your database.

```ts
import { SpacetimeDBClient } from '@clockworklabs/spacetimedb-sdk';

let client = new SpacetimeDBClient('spacetimedb.com/spacetimedb', '<db-name>');
```

If you would like to connect to the client you can call the below method. This also takes optional parameters to override the host or credentials:

```ts
client.connect();
import { DBConnection } from './module_bindings';

const connection = DBConnection.builder()
.withUri('ws://localhost:3000')
.withModuleName('MODULE_NAME')
.onDisconnect(() => {
console.log('disconnected');
})
.onConnectError(() => {
console.log('client_error');
})
.onConnect((connection, identity, _token) => {
console.log(
'Connected to SpacetimeDB with identity:',
identity.toHexString()
);

connection.subscriptionBuilder().subscribe(['SELECT * FROM player']);
})
.withCredentials([Identity.fromString('IDENTITY'), 'TOKEN'])
.build();
```

If for some reason you need to disconnect the client:

```ts
client.disconnect();
```

This will connect to a database instance without a specified identity. If you want to persist an identity fetched on connection you can register an `onConnect` callback, which will receive a new assigned identity as an argument:

```ts
client.onConnect((identity: string) => {
console.log(identity);
console.log(client.token);
});
```

You may also pass credentials as an optional third argument:

```ts
let credentials = { identity: '<identity>', token: '<token>' };
let client = new SpacetimeDBClient(
'spacetimedb.com/spacetimedb',
'<db-name>',
credentials
);
```

Typically, you will use the SDK with types generated from a backend DB service. For example, given a component named `Player` you can subscribe to player updates by registering the component:

```ts
client.registerComponent(Player, 'Player');
connection.disconnect();
```

Then you will be able to register callbacks on insert and delete events, for example:
Typically, you will use the SDK with types generated from a backend DB service. For example, given a table named `Player` you can subscribe to player updates like this:

```ts
Player.onInsert((newPlayer: Player) => {
console.log(newPlayer);
connection.db.player.onInsert((ctx, player) => {
console.log(player);
});
```

Given a reducer called `CreatePlayer` you can call it using a call method:

```ts
CreatePlayer.call('Nickname');
connection.reducers.createPlayer();
```
2 changes: 1 addition & 1 deletion packages/sdk/src/db_connection_builder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DBConnectionImpl, type ConnectionEvent } from './db_connection_impl';
import { DBConnectionImpl } from './db_connection_impl';
import { EventEmitter } from './event_emitter';
import type { Identity } from './identity';
import { stdbLogger } from './logger';
Expand Down
32 changes: 14 additions & 18 deletions packages/sdk/src/db_connection_impl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Address } from './address.ts';
import decompress from 'brotli/decompress';
import { Address } from './address.ts';
import {
AlgebraicType,
ProductType,
Expand All @@ -18,43 +18,40 @@ import BinaryReader from './binary_reader.ts';
import BinaryWriter from './binary_writer.ts';
import * as ws from './client_api/index.ts';
import { ClientCache } from './client_cache.ts';
import { DBConnectionBuilder } from './db_connection_builder.ts';
import { SubscriptionBuilder, type DBContext } from './db_context.ts';
import type { Event } from './event.ts';
import { type EventContextInterface } from './event_context.ts';
import { EventEmitter } from './event_emitter.ts';
import type { Identity } from './identity.ts';
import { stdbLogger } from './logger.ts';
import type { IdentityTokenMessage, Message } from './message_types.ts';
import type { ReducerEvent } from './reducer_event.ts';
import type SpacetimeModule from './spacetime_module.ts';
import { TableCache, type Operation, type TableUpdate } from './table_cache.ts';
import { toPascalCase } from './utils.ts';
import { deepEqual, toPascalCase } from './utils.ts';
import { WebsocketDecompressAdapter } from './websocket_decompress_adapter.ts';
import type { WebsocketTestAdapter } from './websocket_test_adapter.ts';
import type { Event } from './event.ts';
import { DBConnectionBuilder } from './db_connection_builder.ts';
import { deepEqual } from './utils.ts';

export {
AlgebraicType,
AlgebraicValue,
BinaryReader,
BinaryWriter,
DBConnectionBuilder,
deepEqual,
ProductType,
ProductTypeElement,
ProductValue,
SubscriptionBuilder,
SumType,
SumTypeVariant,
type ReducerArgsAdapter,
type ValueAdapter,
BinaryReader,
BinaryWriter,
TableCache,
DBConnectionBuilder,
SubscriptionBuilder,
type Event,
deepEqual,
type ReducerArgsAdapter,
type ValueAdapter,
};

export type { DBContext, EventContextInterface };
export type { ReducerEvent };
export type { DBContext, EventContextInterface, ReducerEvent };

export type ConnectionEvent = 'connect' | 'disconnect' | 'connectError';

Expand Down Expand Up @@ -105,9 +102,8 @@ export class DBConnectionImpl<DBView = any, Reducers = any>
* @example
*
* ```ts
* var spacetimeDBClient = new SpacetimeDBClient("ws://localhost:3000", "database_name");
*
* spacetimeDBClient.disconnect()
* const connection = DBConnection.builder().build();
* connection.disconnect()
* ```
*/
disconnect(): void {
Expand Down
151 changes: 97 additions & 54 deletions packages/sdk/src/db_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,45 @@ type Result<T = undefined> =
value: Error;
};

/**
* Interface representing a subscription handle for managing queries.
*/
interface SubscriptionHandle {
/// Consumes self and issues an `Unsubscribe` message,
/// removing this query from the client's set of subscribed queries.
/// It is only valid to call this method if `is_active()` is `true`.
/**
* Consumes self and issues an `Unsubscribe` message,
* removing this query from the client's set of subscribed queries.
* It is only valid to call this method if `is_active()` is `true`.
*/
unsubscribe(): Result;

/// `Unsubscribe`s and also registers a callback to run upon success.
/// I.e. when an `UnsubscribeApplied` message is received.
///
/// If `Unsubscribe` returns an error,
/// or if the `on_error` callback(s) are invoked before this subscription would end normally,
/// the `on_end` callback is not invoked.
/**
* Unsubscribes and also registers a callback to run upon success.
* I.e. when an `UnsubscribeApplied` message is received.
*
* If `Unsubscribe` returns an error,
* or if the `on_error` callback(s) are invoked before this subscription would end normally,
* the `on_end` callback is not invoked.
*
* @param onEnd - Callback to run upon successful unsubscribe.
*/
unsubscribeThen(onEnd: () => void): Result;

/// True if this `SubscriptionHandle` has ended,
/// either due to an error or a call to `unsubscribe`.
///
/// This is initially false, and becomes true when either the `on_end` or `on_error` callback is invoked.
/// A subscription which has not yet been applied is not active, but is also not ended.
/**
* True if this `SubscriptionHandle` has ended,
* either due to an error or a call to `unsubscribe`.
*
* This is initially false, and becomes true when either the `on_end` or `on_error` callback is invoked.
* A subscription which has not yet been applied is not active, but is also not ended.
*/
isEnded(): boolean;

/// True if this `SubscriptionHandle` is active, meaning it has been successfully applied
/// and has not since ended, either due to an error or a complete `unsubscribe` request-response pair.
///
/// This corresponds exactly to the interval bounded at the start by the `on_applied` callback
/// and at the end by either the `on_end` or `on_error` callback.
/**
* True if this `SubscriptionHandle` is active, meaning it has been successfully applied
* and has not since ended, either due to an error or a complete `unsubscribe` request-response pair.
*
* This corresponds exactly to the interval bounded at the start by the `on_applied` callback
* and at the end by either the `on_end` or `on_error` callback.
*/
isActive(): boolean;
}

Expand All @@ -46,60 +59,90 @@ export class SubscriptionBuilder {

constructor(private db: DBConnectionImpl) {}

/// Registers `callback` to run when this query is successfully added to our subscribed set,
/// I.e. when its `SubscriptionApplied` message is received.
///
/// The database state exposed via the `&EventContext` argument
/// includes all the rows added to the client cache as a result of the new subscription.
///
/// The event in the `&EventContext` argument is `Event::SubscribeApplied`.
///
/// Multiple `on_applied` callbacks for the same query may coexist.
/// No mechanism for un-registering `on_applied` callbacks is exposed.
/**
* Registers `callback` to run when this query is successfully added to our subscribed set,
* I.e. when its `SubscriptionApplied` message is received.
*
* The database state exposed via the `&EventContext` argument
* includes all the rows added to the client cache as a result of the new subscription.
*
* The event in the `&EventContext` argument is `Event::SubscribeApplied`.
*
* Multiple `on_applied` callbacks for the same query may coexist.
* No mechanism for un-registering `on_applied` callbacks is exposed.
*
* @param cb - Callback to run when the subscription is applied.
* @returns The current `SubscriptionBuilder` instance.
*/
onApplied(cb: (ctx: EventContextInterface) => void): SubscriptionBuilder {
this.#onApplied = cb;
return this;
}

/// Registers `callback` to run when this query either:
/// - Fails to be added to our subscribed set.
/// - Is unexpectedly removed from our subscribed set.
///
/// If the subscription had previously started and has been unexpectedly removed,
/// the database state exposed via the `&EventContext` argument contains no rows
/// from any subscriptions removed within the same error event.
/// As proposed, it must therefore contain no rows.
///
/// The event in the `&EventContext` argument is `Event::SubscribeError`,
/// containing a dynamic error object with a human-readable description of the error
/// for diagnostic purposes.
///
/// Multiple `on_error` callbacks for the same query may coexist.
/// No mechanism for un-registering `on_error` callbacks is exposed.
/**
* Registers `callback` to run when this query either:
* - Fails to be added to our subscribed set.
* - Is unexpectedly removed from our subscribed set.
*
* If the subscription had previously started and has been unexpectedly removed,
* the database state exposed via the `&EventContext` argument contains no rows
* from any subscriptions removed within the same error event.
* As proposed, it must therefore contain no rows.
*
* The event in the `&EventContext` argument is `Event::SubscribeError`,
* containing a dynamic error object with a human-readable description of the error
* for diagnostic purposes.
*
* Multiple `on_error` callbacks for the same query may coexist.
* No mechanism for un-registering `on_error` callbacks is exposed.
*
* @param cb - Callback to run when there is an error in subscription.
* @returns The current `SubscriptionBuilder` instance.
*/
onError(cb: (ctx: EventContextInterface) => void): SubscriptionBuilder {
this.#onError = cb;
return this;
}

/// Issues a new `Subscribe` message,
/// adding `query` to the client's set of subscribed queries.
///
/// `query` should be a single SQL `SELECT` statement.
///
/// Installs the above callbacks into the new `SubscriptionHandle`,
/// before issuing the `Subscribe` message, to avoid race conditions.
///
/// Consumes the `SubscriptionBuilder`,
/// because the callbacks are not necessarily `Clone`.
/**
* Issues a new `Subscribe` message,
* adding `query` to the client's set of subscribed queries.
*
* `query` should be a single SQL `SELECT` statement.
*
* Installs the above callbacks into the new `SubscriptionHandle`,
* before issuing the `Subscribe` message, to avoid race conditions.
*
* Consumes the `SubscriptionBuilder`,
* because the callbacks are not necessarily `Clone`.
*
* @param query_sql - The SQL query to subscribe to.
*/
subscribe(query_sql: string[]): void {
this.db['subscribe'](query_sql, this.#onApplied, this.#onError);
}
}

/**
* Interface representing a database context.
*
* @template DBView - Type representing the database view.
* @template Reducers - Type representing the reducers.
*/
export interface DBContext<DBView = any, Reducers = any> {
db: DBView;
reducers: Reducers;
isActive: boolean;

/**
* Disconnects from the database.
*/
disconnect(): void;

/**
* Creates a new `SubscriptionBuilder` for building subscription queries.
*
* @returns A new `SubscriptionBuilder` instance.
*/
subscriptionBuilder(): SubscriptionBuilder;
}
2 changes: 1 addition & 1 deletion packages/sdk/src/event_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export interface EventContextInterface<
Reducers = any,
Reducer extends { name: string; args?: any } = { name: string; args?: any },
> extends DBContext<DBView, Reducers> {
/// Enum with variants for all possible events.
/** Enum with variants for all possible events. */
event: Event<Reducer>;
}

0 comments on commit b9db9b6

Please sign in to comment.