UNPKG

tinybase

Version:

A reactive data store and sync engine.

651 lines (634 loc) 24.9 kB
/** * 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>;