tinybase
Version: 
A reactive data store and sync engine.
651 lines (634 loc) • 24.9 kB
TypeScript
/**
 * The persister-partykit-server module of the TinyBase project contains the
 * server portion of the PartyKit integration.
 *
 * It contains a class which, when run in a PartyKit server environment, lets
 * you save and load Store data from a client (that is using the complementary
 * persister-partykit-client module) to durable PartyKit cloud storage.
 *
 * This enables synchronization of the same Store across multiple clients in a
 * PartyKit party room.
 *
 * Note that both the client and server parts of this Persister are currently
 * experimental as PartyKit is new and still maturing.
 * @see Persistence guides
 * @packageDocumentation
 * @module persister-partykit-server
 * @since v4.3.0
 */
import type {Id} from '../../common/index.d.ts';
import type {
  Cell,
  CellOrUndefined,
  Changes,
  Content,
  Value,
  ValueOrUndefined,
} from '../../store/index.d.ts';
import type {
  Connection,
  Party,
  Request,
  Server,
  Storage,
} from 'partykit/server';
/**
 * The TinyBasePartyKitServerConfig type describes the configuration of a
 * PartyKit Persister on the server side.
 *
 * The defaults (if used on both the server and client) will work fine, but if
 * you are building more complex PartyKit apps and you need to configure path
 * names, for example, then this is the type to use.
 * @example
 * When set as the config in a TinyBasePartyKitServer, this
 * TinyBasePartyKitServerConfig will expect clients to load and save their JSON
 * serialization from and to an end point in the room called `/my_tinybase`.
 * Note that this would require you to also add the matching storePath setting
 * to the PartyKitPersisterConfig on the client side.
 *
 * It will also store the data in the durable storage with a prefix of
 * 'tinybase_' in case you are worried about colliding with other data stored
 * in the room.
 *
 * ```js
 * import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
 *
 * export class MyServer extends TinyBasePartyKitServer {
 *   readonly config = {
 *     storePath: '/my_tinybase',
 *     storagePrefix: 'tinybase_',
 *   };
 * }
 * ```
 * @category Configuration
 * @since v4.3.9
 */
export type TinyBasePartyKitServerConfig = {
  /**
   * The path used to set and get the whole Store over HTTP(S) on the server.
   * This must match the storePath property of the PartyKitPersisterConfig used
   * on the client. Both default to '/store'.
   * @category Configuration
   * @since v4.3.0
   */
  storePath?: string;
  /**
   * The prefix at the beginning of the web socket messages between the client
   * and the server when synchronizing the Store. Use this to make sure they do
   * not collide with any other message syntax that your room is using. This
   * must match the messagePrefix property of the PartyKitPersisterConfig object
   * used on the client. Both default to an empty string.
   * @category Configuration
   * @since v4.3.0
   */
  messagePrefix?: string;
  /**
   * The prefix used before all the keys in the server's durable storage. Use
   * this in case you are worried about the Store data colliding with other data
   * stored in the room. Defaults to an empty string.
   * @category Configuration
   * @since v4.3.0
   */
  storagePrefix?: string;
  /**
   * An object containing the extra HTTP(S) headers returned to the client from
   * this server. This defaults to the following three headers to allow CORS.
   *
   * ```
   * Access-Control-Allow-Origin: *
   * Access-Control-Allow-Methods: *
   * Access-Control-Allow-Headers: *
   * ```
   *
   * If you set this field, it will override the default completely. So, for
   * example, if you add another header but still want the CORS defaults, you
   * will need to explicitly set the Access-Control-Allow headers above again.
   * @category Configuration
   * @since v4.3.0
   */
  responseHeaders?: HeadersInit;
};
/**
 * A TinyBasePartyKitServer is the server component for persisting the Store to
 * durable PartyKit storage, enabling synchronization of the same Store across
 * multiple clients.
 *
 * This extends the PartyKit Server class, which provides a selection of methods
 * you are expected to implement. The TinyBasePartyKitServer implements only two
 * of them, the onMessage method and the onRequest method, as well as the
 * constructor.
 *
 * If you wish to use TinyBasePartyKitServer as a general PartyKit server, you
 * can implement other methods. But you must remember to call the super
 * implementations of those methods to ensure the TinyBase synchronization stays
 * supported in addition to your own custom functionality. The same applies to
 * the constructor if you choose to implement that.
 *
 * ```js
 * import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
 *
 * // This is your PartyKit server entry point.
 * export class MyServer extends TinyBasePartyKitServer {
 *   constructor(party) {
 *     super(party);
 *     // custom constructor code
 *   }
 *
 *   async onStart() {
 *     // no need to call super.onStart()
 *     console.log('Server started');
 *   }
 *
 *   async onMessage(message, connection) {
 *     await super.onMessage(message, connection);
 *     // custom onMessage code
 *   }
 *
 *   async onRequest(request) {
 *     // custom onRequest code, else:
 *     return await super.onRequest(request);
 *   }
 * }
 * ```
 *
 * See the [PartyKit server API
 * documentation](https://docs.partykit.io/reference/partyserver-api/) for
 * more details.
 * @category Server
 * @since v4.3.0
 */
export class TinyBasePartyKitServer implements Server {
  /**
   * The constructor is used to create the server.
   * @returns A new instance of the TinyBasePartyKitServer.
   * @category Creation
   * @since v4.3.9
   */
  constructor(party: Party);
  /**
   * The config property is used to optionally configure the server, using an
   * object of the TinyBasePartyKitServerConfig type.
   *
   * See the documentation for that type for more details.
   * @category Configuration
   * @since v4.3.9
   */
  readonly config: TinyBasePartyKitServerConfig;
  /**
   * The onRequest method is called when a HTTP request is made to the party
   * URL.
   *
   * If you choose to implement additional functionality in this method, you
   * must remember to call the super implementation to ensure the TinyBase
   * synchronization stays supported.
   *
   * ```js
   * import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
   *
   * export class MyServer extends TinyBasePartyKitServer {
   *   async onRequest(request) {
   *     // custom onRequest code, else:
   *     return await super.onRequest(request);
   *   }
   * }
   * ```
   *
   * See the [PartyKit server API
   * documentation](https://docs.partykit.io/reference/partyserver-api/) for
   * more details.
   * @category Connection
   * @since v4.3.0
   */
  onRequest(request: Request): Promise<Response>;
  /**
   * The onMessage method is called when the server receives a message from a
   * client.
   *
   * If you choose to implement additional functionality in this method, you
   * must remember to call the super implementation to ensure the TinyBase
   * synchronization stays supported.
   *
   * ```js
   * import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
   *
   * export class MyServer extends TinyBasePartyKitServer {
   *   async onMessage(message, connection) {
   *     await super.onMessage(message, connection);
   *     // custom onMessage code
   *   }
   * }
   * ```
   *
   * See the [PartyKit server API
   * documentation](https://docs.partykit.io/reference/partyserver-api/) for
   * more details.
   * @category Connection
   * @since v4.3.0
   */
  onMessage(message: string, connection: Connection): Promise<void>;
  /**
   * The canSetTable method lets you allow or disallow any changes to a Table
   * stored on the server, as sent from a client.
   *
   * This is one of the functions use to sanitize the data that is being sent
   * from a client. Perhaps you might want to make sure the server-stored data
   * adheres to a particular schema, or you might want to make certain data
   * read-only. Remember that you cannot trust the client to only send data that
   * the server considers valid or safe.
   *
   * This method is passed the Table Id that the client is trying to change. The
   * `initialSave` parameter distinguishes between the first bulk save of the
   * Store to the PartyKit room over HTTP (`true`), and subsequent incremental
   * updates over a web sockets (`false`).
   *
   * The `requestOrConnection` parameter will either be the HTTP(S) request or
   * the web socket connection, in those two cases respectively. You can, for
   * instance, use this to distinguish between different users.
   *
   * Since v4.3.13, the final parameter is the Cell previously stored on the
   * server, if any. Use this to distinguish between the addition of a new Cell
   * (in which case it will be undefined) and the updating of an existing one.
   *
   * Return `false` from this method to disallow changes to this Table on the
   * server, or `true` to allow them (subject to subsequent canSetRow method,
   * canDelRow method, canSetCell method, and canSetCell method checks). The
   * default implementation returns `true` to allow all changes.
   * @example
   * The following implementation will strip out any attempts by the client to
   * update any 'user' tabular data after the initial save:
   *
   * ```js
   * import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
   *
   * export class MyServer extends TinyBasePartyKitServer {
   *   canSetTable(tableId, initialSave) {
   *     return initialSave || tableId != 'user';
   *   }
   * }
   * ```
   * @category Sanitization
   * @since v4.3.12
   */
  canSetTable(
    tableId: Id,
    initialSave: boolean,
    requestOrConnection: Request | Connection,
  ): Promise<boolean>;
  /**
   * The canDelTable method lets you allow or disallow deletions of a Table
   * stored on the server, as sent from a client.
   *
   * This is one of the functions use to sanitize the data that is being sent
   * from a client. Perhaps you might want to make sure the server-stored data
   * adheres to a particular schema, or you might want to make certain data
   * read-only. Remember that you cannot trust the client to only send data that
   * the server considers valid or safe.
   *
   * This method is passed the Table Id that the client is trying to delete. The
   * `connection` parameter will be the web socket connection of that client.
   * You can, for instance, use this to distinguish between different users.
   *
   * Return `false` from this method to disallow this Table from being deleted
   * on the server, or `true` to allow it. The default implementation returns
   * `true` to allow deletion.
   * @example
   * The following implementation will strip out any attempts by the client to
   * delete the 'user' Table:
   *
   * ```js
   * import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
   *
   * export class MyServer extends TinyBasePartyKitServer {
   *   canDelTable(tableId) {
   *     return tableId != 'user';
   *   }
   * }
   * ```
   * @category Sanitization
   * @since v4.3.12
   */
  canDelTable(tableId: Id, connection: Connection): Promise<boolean>;
  /**
   * The canSetRow method lets you allow or disallow any changes to a Row stored
   * on the server, as sent from a client.
   *
   * This is one of the functions use to sanitize the data that is being sent
   * from a client. Perhaps you might want to make sure the server-stored data
   * adheres to a particular schema, or you might want to make certain data
   * read-only. Remember that you cannot trust the client to only send data that
   * the server considers valid or safe.
   *
   * This method is passed the Table Id and Row Id that the client is trying to
   * change. The `initialSave` parameter distinguishes between the first bulk
   * save of the Store to the PartyKit room over HTTP (`true`), and subsequent
   * incremental updates over a web sockets (`false`).
   *
   * The final `requestOrConnection` parameter will either be the HTTP(S)
   * request or the web socket connection, in those two cases respectively. You
   * can, for instance, use this to distinguish between different users.
   *
   * Return `false` from this method to disallow changes to this Row on the
   * server, or `true` to allow them (subject to subsequent canSetCell method
   * and canSetCell method checks). The default implementation returns `true` to
   * allow all changes.
   * @example
   * The following implementation will strip out any attempts by the client to
   * update the 'me' Row of the 'user' Table after the initial save:
   *
   * ```js
   * import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
   *
   * export class MyServer extends TinyBasePartyKitServer {
   *   canSetRow(tableId, rowId, initialSave) {
   *     return initialSave || tableId != 'user' || rowId != 'me';
   *   }
   * }
   * ```
   * @category Sanitization
   * @since v4.3.12
   */
  canSetRow(
    tableId: Id,
    rowId: Id,
    initialSave: boolean,
    requestOrConnection: Request | Connection,
  ): Promise<boolean>;
  /**
   * The canDelRow method lets you allow or disallow deletions of a Row stored
   * on the server, as sent from a client.
   *
   * This is one of the functions use to sanitize the data that is being sent
   * from a client. Perhaps you might want to make sure the server-stored data
   * adheres to a particular schema, or you might want to make certain data
   * read-only. Remember that you cannot trust the client to only send data that
   * the server considers valid or safe.
   *
   * This method is passed the Table Id and Row Id that the client is trying to
   * delete. The `connection` parameter will be the web socket connection of
   * that client. You can, for instance, use this to distinguish between
   * different users.
   *
   * Return `false` from this method to disallow this Row from being deleted
   * on the server, or `true` to allow it. The default implementation returns
   * `true` to allow deletion.
   * @example
   * The following implementation will strip out any attempts by the client to
   * delete the 'me' Row of the 'user' Table:
   *
   * ```js
   * import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
   *
   * export class MyServer extends TinyBasePartyKitServer {
   *   canDelRow(tableId, rowId) {
   *     return tableId != 'user' || rowId != 'me';
   *   }
   * }
   * ```
   * @category Sanitization
   * @since v4.3.12
   */
  canDelRow(tableId: Id, rowId: Id, connection: Connection): Promise<boolean>;
  /**
   * The canSetCell method lets you allow or disallow any changes to a Cell
   * stored on the server, as sent from a client.
   *
   * This is one of the functions use to sanitize the data that is being sent
   * from a client. Perhaps you might want to make sure the server-stored data
   * adheres to a particular schema, or you might want to make certain data
   * read-only. Remember that you cannot trust the client to only send data that
   * the server considers valid or safe.
   *
   * This method is passed the Table Id, Row Id, and Cell Id that the client is
   * trying to change - as well as the Cell value itself. The `initialSave`
   * parameter distinguishes between the first bulk save of the Store to the
   * PartyKit room over HTTP (`true`), and subsequent incremental updates over a
   * web sockets (`false`).
   *
   * The final `requestOrConnection` parameter will either be the HTTP(S)
   * request or the web socket connection, in those two cases respectively. You
   * can, for instance, use this to distinguish between different users.
   *
   * Return `false` from this method to disallow changes to this Cell on the
   * server, or `true` to allow them. The default implementation returns `true`
   * to allow all changes.
   * @example
   * The following implementation will strip out any attempts by the client to
   * update the 'name' Cell of the 'me' Row of the 'user' Table after the
   * initial save:
   *
   * ```js
   * import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
   *
   * export class MyServer extends TinyBasePartyKitServer {
   *   canSetCell(tableId, rowId, cellId, cell, initialSave) {
   *     return (
   *       initialSave || tableId != 'user' || rowId != 'me' || cellId != 'name'
   *     );
   *   }
   * }
   * ```
   * @category Sanitization
   * @since v4.3.12
   */
  canSetCell(
    tableId: Id,
    rowId: Id,
    cellId: Id,
    cell: Cell,
    initialSave: boolean,
    requestOrConnection: Request | Connection,
    oldCell: CellOrUndefined,
  ): Promise<boolean>;
  /**
   * The canDelCell method lets you allow or disallow deletions of a Cell stored
   * on the server, as sent from a client.
   *
   * This is one of the functions use to sanitize the data that is being sent
   * from a client. Perhaps you might want to make sure the server-stored data
   * adheres to a particular schema, or you might want to make certain data
   * read-only. Remember that you cannot trust the client to only send data that
   * the server considers valid or safe.
   *
   * This method is passed the Table Id, Row Id, and Cell Id that the client is
   * trying to delete. The `connection` parameter will be the web socket
   * connection of that client. You can, for instance, use this to distinguish
   * between different users.
   *
   * Return `false` from this method to disallow this Cell from being deleted on
   * the server, or `true` to allow it. The default implementation returns
   * `true` to allow deletion.
   * @example
   * The following implementation will strip out any attempts by the client to
   * delete the 'name' Cell of the 'me' Row of the 'user' Table:
   *
   * ```js
   * import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
   *
   * export class MyServer extends TinyBasePartyKitServer {
   *   canDelCell(tableId, rowId, cellId) {
   *     return tableId != 'user' || rowId != 'me' || cellId != 'name';
   *   }
   * }
   * ```
   * @category Sanitization
   * @since v4.3.12
   */
  canDelCell(
    tableId: Id,
    rowId: Id,
    cellId: Id,
    connection: Connection,
  ): Promise<boolean>;
  /**
   * The canSetValue method lets you allow or disallow any changes to a Value
   * stored on the server, as sent from a client.
   *
   * This is one of the functions use to sanitize the data that is being sent
   * from a client. Perhaps you might want to make sure the server-stored data
   * adheres to a particular schema, or you might want to make certain data
   * read-only. Remember that you cannot trust the client to only send data that
   * the server considers valid or safe.
   *
   * This method is passed the Value Id that the client is trying to change - as
   * well as the Value itself. The `initialSave` parameter distinguishes between
   * the first bulk save of the Store to the PartyKit room over HTTP (`true`),
   * and subsequent incremental updates over a web sockets (`false`).
   *
   * The `requestOrConnection` parameter will either be the HTTP(S) request or
   * the web socket connection, in those two cases respectively. You can, for
   * instance, use this to distinguish between different users.
   *
   * Since v4.3.13, the final parameter is the Value previously stored on the
   * server, if any. Use this to distinguish between the addition of a new Value
   * (in which case it will be undefined) and the updating of an existing one.
   *
   * Return `false` from this method to disallow changes to this Value on the
   * server, or `true` to allow them. The default implementation returns `true`
   * to allow all changes.
   * @example
   * The following implementation will strip out any attempts by the client to
   * update the 'userId' Value after the initial save:
   *
   * ```js
   * import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
   *
   * export class MyServer extends TinyBasePartyKitServer {
   *   canSetValue(valueId, value, initialSave) {
   *     return initialSave || valueId != 'userId';
   *   }
   * }
   * ```
   * @category Sanitization
   * @since v4.3.12
   */
  canSetValue(
    valueId: Id,
    value: Value,
    initialSave: boolean,
    requestOrConnection: Request | Connection,
    oldValue: ValueOrUndefined,
  ): Promise<boolean>;
  /**
   * The canDelValue method lets you allow or disallow deletions of a Value
   * stored on the server, as sent from a client.
   *
   * This is one of the functions use to sanitize the data that is being sent
   * from a client. Perhaps you might want to make sure the server-stored data
   * adheres to a particular schema, or you might want to make certain data
   * read-only. Remember that you cannot trust the client to only send data that
   * the server considers valid or safe.
   *
   * This method is passed the Value Id that the client is trying to delete. The
   * `connection` parameter will be the web socket connection of that client.
   * You can, for instance, use this to distinguish between different users.
   *
   * Return `false` from this method to disallow this Value from being deleted
   * on the server, or `true` to allow it. The default implementation returns
   * `true` to allow deletion.
   * @example
   * The following implementation will strip out any attempts by the client to
   * delete the 'userId' Value:
   *
   * ```js
   * import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
   *
   * export class MyServer extends TinyBasePartyKitServer {
   *   canDelValue(valueId) {
   *     return valueId != 'userId';
   *   }
   * }
   * ```
   * @category Sanitization
   * @since v4.3.12
   */
  canDelValue(valueId: Id, connection: Connection): Promise<boolean>;
}
/**
 * The hasStoreInStorage function returns a boolean indicating whether durable
 * PartyKit storage contains a serialized Store.
 *
 * This is intended for specialist applications that require the ability to
 * inspect or load a TinyBase Store from a server's storage outside of the
 * normal context of a TinyBasePartyKitServer.
 *
 * The function is asynchronous, so you should use the `await` keyword or handle
 * the result as a promise.
 * @param storage A reference to the storage object, as would normally be
 * accessible from the `TinyBasePartyKitServer.party` object.
 * @param storagePrefix An optional prefix used before all the keys in the
 * server's durable storage, to match the equivalent property in the server's
 * TinyBasePartyKitServerConfig.
 * @returns A promised boolean indicating whether a Store is present in the
 * storage.
 * @category Storage
 * @since v4.4.1
 */
export function hasStoreInStorage(
  storage: Storage,
  storagePrefix?: string,
): Promise<boolean>;
/**
 * The loadStoreFromStorage function returns the content of a Store from durable
 * PartyKit storage.
 *
 * This is intended for specialist applications that require the ability to
 * inspect or load a TinyBase Store from a server's storage outside of the
 * normal context of a TinyBasePartyKitServer.
 *
 * The function is asynchronous, so you should use the `await` keyword or handle
 * the result as a promise.
 * @param storage A reference to the storage object, as would normally be
 * accessible from the `TinyBasePartyKitServer.party` object.
 * @param storagePrefix An optional prefix used before all the keys in the
 * server's durable storage, to match the equivalent property in the server's
 * TinyBasePartyKitServerConfig.
 * @returns A promised array of a Tables object and a Values object.
 * @category Storage
 * @since v4.4.1
 */
export function loadStoreFromStorage(
  storage: Storage,
  storagePrefix?: string,
): Promise<Content>;
/**
 * The broadcastChanges function allows you to broadcast Store
 * changes to all the client connections of a TinyBasePartyKitServer.
 *
 * This is intended for specialist applications that require the ability to
 * update clients of a TinyBasePartyKitServer in their own custom ways.
 *
 * The function is asynchronous, so you should use the `await` keyword or handle
 * its completion as a promise.
 * @param server A reference to the TinyBasePartyKitServer object.
 * @param changes The Store changes to broadcast to the server's
 * clients.
 * @param without An optional array of client connection Ids to exclude from the
 * broadcast.
 * @category Connection
 * @since v4.5.1
 */
export function broadcastChanges(
  server: TinyBasePartyKitServer,
  changes: Changes,
  without?: string[],
): Promise<void>;