publish bemo canaries (#4175)

Switch on package publishing for sync libraries so we can start building
templates on the canaries.

### Change type

- [x] `other`
This commit is contained in:
alex 2024-07-15 17:08:42 +01:00 committed by GitHub
parent c5b2569bfc
commit 348ff9f66a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 631 additions and 30 deletions

View file

@ -27,3 +27,4 @@ jobs:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }} R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }}
TLDRAW_BEMO_URL: https://canary-demo.tldraw.xyz

View file

@ -34,6 +34,7 @@ jobs:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }} R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }}
TLDRAW_BEMO_URL: https://demo.tldraw.xyz
publish_templates: publish_templates:
name: Publishes code templates to separate repositories name: Publishes code templates to separate repositories

View file

@ -71,6 +71,7 @@ jobs:
HUPPY_TOKEN: ${{ secrets.HUPPY_TOKEN }} HUPPY_TOKEN: ${{ secrets.HUPPY_TOKEN }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }} R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }}
TLDRAW_BEMO_URL: https://demo.tldraw.xyz
publish_templates: publish_templates:
name: Publishes code templates to separate repositories name: Publishes code templates to separate repositories

View file

@ -52,6 +52,7 @@ jobs:
HUPPY_TOKEN: ${{ secrets.HUPPY_TOKEN }} HUPPY_TOKEN: ${{ secrets.HUPPY_TOKEN }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }} R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }}
TLDRAW_BEMO_URL: https://demo.tldraw.xyz
publish_templates: publish_templates:
name: Publishes code templates to separate repositories name: Publishes code templates to separate repositories

View file

@ -0,0 +1,503 @@
## API Report File for "@tldraw/sync-core"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { Atom } from '@tldraw/state';
import { Emitter } from 'nanoevents';
import { RecordsDiff } from '@tldraw/store';
import { RecordType } from '@tldraw/store';
import { Result } from '@tldraw/utils';
import { SerializedSchema } from '@tldraw/store';
import { Signal } from '@tldraw/state';
import { Store } from '@tldraw/store';
import { StoreSchema } from '@tldraw/store';
import { TLRecord } from '@tldraw/tlschema';
import { UnknownRecord } from '@tldraw/store';
// @public (undocumented)
export type AppendOp = [type: typeof ValueOpType.Append, values: unknown[], offset: number];
// @public (undocumented)
export function applyObjectDiff<T extends object>(object: T, objectDiff: ObjectDiff): T;
// @public (undocumented)
export function chunk(msg: string, maxSafeMessageSize?: number): string[];
// @public (undocumented)
export class ClientWebSocketAdapter implements TLPersistentClientSocket<TLRecord> {
constructor(getUri: () => Promise<string> | string);
// (undocumented)
close(): void;
// (undocumented)
_closeSocket(): void;
// (undocumented)
get connectionStatus(): TLPersistentClientSocketStatus;
// (undocumented)
_connectionStatus: Atom<'initial' | TLPersistentClientSocketStatus>;
// (undocumented)
isDisposed: boolean;
// (undocumented)
onReceiveMessage(cb: (val: TLSocketServerSentEvent<TLRecord>) => void): () => void;
// (undocumented)
onStatusChange(cb: (val: TLPersistentClientSocketStatus, closeCode?: number) => void): () => void;
// @internal (undocumented)
readonly _reconnectManager: ReconnectManager;
// (undocumented)
restart(): void;
// (undocumented)
sendMessage(msg: TLSocketClientSentEvent<TLRecord>): void;
// (undocumented)
_setNewSocket(ws: WebSocket): void;
// (undocumented)
_ws: null | WebSocket;
}
// @public (undocumented)
export type DeleteOp = [type: typeof ValueOpType.Delete];
// @public (undocumented)
export function diffRecord(prev: object, next: object): null | ObjectDiff;
// @internal (undocumented)
export class DocumentState<R extends UnknownRecord> {
// (undocumented)
_atom: Atom<{
lastChangedClock: number;
state: R;
}>;
// (undocumented)
static createAndValidate<R extends UnknownRecord>(state: R, lastChangedClock: number, recordType: RecordType<R, any>): Result<DocumentState<R>, Error>;
// (undocumented)
static createWithoutValidating<R extends UnknownRecord>(state: R, lastChangedClock: number, recordType: RecordType<R, any>): DocumentState<R>;
// (undocumented)
get lastChangedClock(): number;
// (undocumented)
mergeDiff(diff: ObjectDiff, clock: number): Result<null | ObjectDiff, Error>;
// (undocumented)
replaceState(state: R, clock: number): Result<null | ObjectDiff, Error>;
// (undocumented)
get state(): R;
}
// @public
export const getNetworkDiff: <R extends UnknownRecord>(diff: RecordsDiff<R>) => NetworkDiff<R> | null;
// @public (undocumented)
export function getTlsyncProtocolVersion(): number;
// @public
export interface NetworkDiff<R extends UnknownRecord> {
// (undocumented)
[id: string]: RecordOp<R>;
}
// @public (undocumented)
export interface ObjectDiff {
// (undocumented)
[k: string]: ValueOp;
}
// @public (undocumented)
export type PatchOp = [type: typeof ValueOpType.Patch, diff: ObjectDiff];
// @public (undocumented)
export interface PersistedRoomSnapshotForSupabase {
// (undocumented)
drawing: RoomSnapshot;
// (undocumented)
id: string;
// (undocumented)
slug: string;
}
// @public (undocumented)
export type PutOp = [type: typeof ValueOpType.Put, value: unknown];
// @internal (undocumented)
export class ReconnectManager {
constructor(socketAdapter: ClientWebSocketAdapter, getUri: () => Promise<string> | string);
// (undocumented)
close(): void;
// (undocumented)
connected(): void;
// (undocumented)
disconnected(): void;
// (undocumented)
intendedDelay: number;
// (undocumented)
maybeReconnected(): void;
}
// @public (undocumented)
export type RecordOp<R extends UnknownRecord> = [typeof RecordOpType.Patch, ObjectDiff] | [typeof RecordOpType.Put, R] | [typeof RecordOpType.Remove];
// @public (undocumented)
export const RecordOpType: {
readonly Patch: "patch";
readonly Put: "put";
readonly Remove: "remove";
};
// @public (undocumented)
export type RecordOpType = (typeof RecordOpType)[keyof typeof RecordOpType];
// @public (undocumented)
export type RoomSession<R extends UnknownRecord, Meta> = {
cancellationTime: number;
meta: Meta;
presenceId: string;
sessionKey: string;
socket: TLRoomSocket<R>;
state: typeof RoomSessionState.AwaitingRemoval;
} | {
debounceTimer: null | ReturnType<typeof setTimeout>;
lastInteractionTime: number;
meta: Meta;
outstandingDataMessages: TLSocketServerSentDataEvent<R>[];
presenceId: string;
serializedSchema: SerializedSchema;
sessionKey: string;
socket: TLRoomSocket<R>;
state: typeof RoomSessionState.Connected;
} | {
meta: Meta;
presenceId: string;
sessionKey: string;
sessionStartTime: number;
socket: TLRoomSocket<R>;
state: typeof RoomSessionState.AwaitingConnectMessage;
};
// @public (undocumented)
export const RoomSessionState: {
readonly AwaitingConnectMessage: "awaiting-connect-message";
readonly AwaitingRemoval: "awaiting-removal";
readonly Connected: "connected";
};
// @public (undocumented)
export type RoomSessionState = (typeof RoomSessionState)[keyof typeof RoomSessionState];
// @public (undocumented)
export interface RoomSnapshot {
// (undocumented)
clock: number;
// (undocumented)
documents: Array<{
lastChangedClock: number;
state: UnknownRecord;
}>;
// (undocumented)
schema?: SerializedSchema;
// (undocumented)
tombstones?: Record<string, number>;
}
// @public (undocumented)
export type SubscribingFn<T> = (cb: (val: T) => void) => () => void;
// @public
export const TLCloseEventCode: {
readonly NOT_FOUND: 4099;
};
// @public (undocumented)
export interface TLConnectRequest {
// (undocumented)
connectRequestId: string;
// (undocumented)
lastServerClock: number;
// (undocumented)
protocolVersion: number;
// (undocumented)
schema: SerializedSchema;
// (undocumented)
type: 'connect';
}
// @public (undocumented)
export const TLIncompatibilityReason: {
readonly ClientTooOld: "clientTooOld";
readonly InvalidOperation: "invalidOperation";
readonly InvalidRecord: "invalidRecord";
readonly RoomNotFound: "roomNotFound";
readonly ServerTooOld: "serverTooOld";
};
// @public (undocumented)
export type TLIncompatibilityReason = (typeof TLIncompatibilityReason)[keyof typeof TLIncompatibilityReason];
// @public
export interface TLPersistentClientSocket<R extends UnknownRecord = UnknownRecord> {
connectionStatus: 'error' | 'offline' | 'online';
onReceiveMessage: SubscribingFn<TLSocketServerSentEvent<R>>;
onStatusChange: SubscribingFn<TLPersistentClientSocketStatus>;
restart: () => void;
sendMessage: (msg: TLSocketClientSentEvent<R>) => void;
}
// @public (undocumented)
export type TLPersistentClientSocketStatus = 'error' | 'offline' | 'online';
// @public (undocumented)
export interface TLPingRequest {
// (undocumented)
type: 'ping';
}
// @public (undocumented)
export interface TLPushRequest<R extends UnknownRecord> {
// (undocumented)
clientClock: number;
// (undocumented)
diff?: NetworkDiff<R>;
// (undocumented)
presence?: [typeof RecordOpType.Patch, ObjectDiff] | [typeof RecordOpType.Put, R];
// (undocumented)
type: 'push';
}
// @public (undocumented)
export class TLRemoteSyncError extends Error {
constructor(reason: TLIncompatibilityReason);
// (undocumented)
name: string;
// (undocumented)
readonly reason: TLIncompatibilityReason;
}
// @public (undocumented)
export interface TLRoomSocket<R extends UnknownRecord> {
// (undocumented)
close: () => void;
// (undocumented)
isOpen: boolean;
// (undocumented)
sendMessage: (msg: TLSocketServerSentEvent<R>) => void;
}
// @public (undocumented)
export type TLSocketClientSentEvent<R extends UnknownRecord> = TLConnectRequest | TLPingRequest | TLPushRequest<R>;
// @public (undocumented)
export class TLSocketRoom<R extends UnknownRecord, SessionMeta> {
constructor(opts: {
clientTimeout?: number;
initialSnapshot?: RoomSnapshot;
log?: TLSyncLog;
onAfterReceiveMessage?: (args: {
message: TLSocketServerSentEvent<R>;
meta: SessionMeta;
sessionId: string;
stringified: string;
}) => void;
onBeforeSendMessage?: (args: {
message: TLSocketServerSentEvent<R>;
meta: SessionMeta;
sessionId: string;
stringified: string;
}) => void;
onDataChange?: () => void;
onSessionRemoved?: (room: TLSocketRoom<R, SessionMeta>, args: {
meta: SessionMeta;
numSessionsRemaining: number;
sessionKey: string;
}) => void;
schema?: StoreSchema<R, any>;
});
// (undocumented)
close(): void;
// (undocumented)
getCurrentDocumentClock(): number;
// (undocumented)
getCurrentSnapshot(): RoomSnapshot;
// (undocumented)
getNumActiveSessions(): number;
// (undocumented)
handleSocketClose(sessionId: string): void;
// (undocumented)
handleSocketConnect(sessionId: string, socket: WebSocket, meta: SessionMeta): void;
// (undocumented)
handleSocketError(sessionId: string): void;
// (undocumented)
handleSocketMessage(sessionId: string, message: ArrayBuffer | string): void;
// (undocumented)
loadSnapshot(snapshot: RoomSnapshot): void;
// (undocumented)
readonly log: TLSyncLog;
// (undocumented)
readonly opts: {
clientTimeout?: number;
initialSnapshot?: RoomSnapshot;
log?: TLSyncLog;
onAfterReceiveMessage?: (args: {
message: TLSocketServerSentEvent<R>;
meta: SessionMeta;
sessionId: string;
stringified: string;
}) => void;
onBeforeSendMessage?: (args: {
message: TLSocketServerSentEvent<R>;
meta: SessionMeta;
sessionId: string;
stringified: string;
}) => void;
onDataChange?: () => void;
onSessionRemoved?: (room: TLSocketRoom<R, SessionMeta>, args: {
meta: SessionMeta;
numSessionsRemaining: number;
sessionKey: string;
}) => void;
schema?: StoreSchema<R, any>;
};
}
// @public (undocumented)
export type TLSocketServerSentDataEvent<R extends UnknownRecord> = {
action: 'commit' | 'discard' | {
rebaseWithDiff: NetworkDiff<R>;
};
clientClock: number;
serverClock: number;
type: 'push_result';
} | {
diff: NetworkDiff<R>;
serverClock: number;
type: 'patch';
};
// @public (undocumented)
export type TLSocketServerSentEvent<R extends UnknownRecord> = {
connectRequestId: string;
diff: NetworkDiff<R>;
hydrationType: 'wipe_all' | 'wipe_presence';
protocolVersion: number;
schema: SerializedSchema;
serverClock: number;
type: 'connect';
} | {
data: TLSocketServerSentDataEvent<R>[];
type: 'data';
} | {
error?: any;
type: 'error';
} | {
reason: TLIncompatibilityReason;
type: 'incompatibility_error';
} | {
type: 'pong';
} | TLSocketServerSentDataEvent<R>;
// @public
export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>> {
constructor(config: {
didCancel?: () => boolean;
onAfterConnect?: (self: TLSyncClient<R, S>, isNew: boolean) => void;
onLoad: (self: TLSyncClient<R, S>) => void;
onLoadError: (error: Error) => void;
onSyncError: (reason: TLIncompatibilityReason) => void;
presence: Signal<null | R>;
socket: TLPersistentClientSocket<R>;
store: S;
});
// (undocumented)
close(): void;
// (undocumented)
didCancel?: () => boolean;
// (undocumented)
incomingDiffBuffer: TLSocketServerSentDataEvent<R>[];
// (undocumented)
isConnectedToRoom: boolean;
// (undocumented)
lastPushedPresenceState: null | R;
// (undocumented)
latestConnectRequestId: null | string;
readonly onAfterConnect?: (self: TLSyncClient<R, S>, isNew: boolean) => void;
// (undocumented)
readonly onSyncError: (reason: TLIncompatibilityReason) => void;
// (undocumented)
readonly presenceState: Signal<null | R> | undefined;
// (undocumented)
readonly socket: TLPersistentClientSocket<R>;
// (undocumented)
readonly store: S;
}
// @public (undocumented)
export interface TLSyncLog {
// (undocumented)
error?: (...args: any[]) => void;
// (undocumented)
info?: (...args: any[]) => void;
// (undocumented)
warn?: (...args: any[]) => void;
}
// @public
export class TLSyncRoom<R extends UnknownRecord, SessionMeta> {
constructor(schema: StoreSchema<R, any>, snapshot?: RoomSnapshot);
broadcastPatch({ diff, sourceSessionKey: sourceSessionKey, }: {
diff: NetworkDiff<R>;
sourceSessionKey: string;
}): this;
// (undocumented)
clock: number;
// (undocumented)
close(): void;
// (undocumented)
documentClock: number;
// (undocumented)
readonly documentTypes: Set<string>;
// (undocumented)
readonly events: Emitter< {
room_became_empty: () => void;
session_removed: (args: {
meta: SessionMeta;
sessionKey: string;
}) => void;
}>;
// (undocumented)
_flushDataMessages(sessionKey: string): void;
// (undocumented)
getSnapshot(): RoomSnapshot;
handleClose: (sessionKey: string) => void;
handleMessage: (sessionKey: string, message: TLSocketClientSentEvent<R>) => Promise<void>;
handleNewSession: (sessionKey: string, socket: TLRoomSocket<R>, meta: SessionMeta) => this;
// (undocumented)
readonly presenceType: RecordType<R, any>;
// (undocumented)
pruneSessions: () => void;
// (undocumented)
readonly schema: StoreSchema<R, any>;
// (undocumented)
readonly serializedSchema: SerializedSchema;
// (undocumented)
readonly sessions: Map<string, RoomSession<R, SessionMeta>>;
// @internal (undocumented)
state: Atom<{
documents: Record<string, DocumentState<R>>;
tombstones: Record<string, number>;
}, unknown>;
// (undocumented)
tombstoneHistoryStartsAtClock: number;
}
// @public (undocumented)
export type ValueOp = AppendOp | DeleteOp | PatchOp | PutOp;
// @public (undocumented)
export const ValueOpType: {
readonly Append: "append";
readonly Delete: "delete";
readonly Patch: "patch";
readonly Put: "put";
};
// @public (undocumented)
export type ValueOpType = (typeof ValueOpType)[keyof typeof ValueOpType];
// (No @packageDocumentation comment for this package)
```

View file

@ -1,8 +1,7 @@
{ {
"name": "@tldraw/sync-core", "name": "@tldraw/sync-core",
"description": "A tiny little drawing app (multiplayer sync).", "description": "A tiny little drawing app (multiplayer sync).",
"version": "2.0.0-alpha.11", "version": "2.3.0",
"private": true,
"author": { "author": {
"name": "tldraw GB Ltd.", "name": "tldraw GB Ltd.",
"email": "hello@tldraw.com" "email": "hello@tldraw.com"
@ -34,7 +33,12 @@
"test-ci": "lazy inherit", "test-ci": "lazy inherit",
"test": "yarn run -T jest", "test": "yarn run -T jest",
"test-coverage": "lazy inherit", "test-coverage": "lazy inherit",
"lint": "yarn run -T tsx ../../scripts/lint.ts" "lint": "yarn run -T tsx ../../scripts/lint.ts",
"build": "yarn run -T tsx ../../scripts/build-package.ts",
"build-api": "yarn run -T tsx ../../scripts/build-api.ts",
"prepack": "yarn run -T tsx ../../scripts/prepack.ts",
"postpack": "../../scripts/postpack.sh",
"pack-tarball": "yarn pack"
}, },
"devDependencies": { "devDependencies": {
"tldraw": "workspace:*", "tldraw": "workspace:*",

View file

@ -1,13 +1,15 @@
export { ClientWebSocketAdapter } from './lib/ClientWebSocketAdapter' export { ClientWebSocketAdapter, ReconnectManager } from './lib/ClientWebSocketAdapter'
export { RoomSessionState, type RoomSession } from './lib/RoomSession'
export { TLRemoteSyncError } from './lib/TLRemoteSyncError' export { TLRemoteSyncError } from './lib/TLRemoteSyncError'
export { TLSocketRoom } from './lib/TLSocketRoom' export { TLSocketRoom, type TLSyncLog } from './lib/TLSocketRoom'
export { export {
TLCloseEventCode, TLCloseEventCode,
TLSyncClient, TLSyncClient,
type SubscribingFn,
type TLPersistentClientSocket, type TLPersistentClientSocket,
type TLPersistentClientSocketStatus, type TLPersistentClientSocketStatus,
} from './lib/TLSyncClient' } from './lib/TLSyncClient'
export { TLSyncRoom, type RoomSnapshot, type TLRoomSocket } from './lib/TLSyncRoom' export { DocumentState, TLSyncRoom, type RoomSnapshot, type TLRoomSocket } from './lib/TLSyncRoom'
export { chunk } from './lib/chunk' export { chunk } from './lib/chunk'
export { export {
RecordOpType, RecordOpType,
@ -31,6 +33,7 @@ export {
type TLPingRequest, type TLPingRequest,
type TLPushRequest, type TLPushRequest,
type TLSocketClientSentEvent, type TLSocketClientSentEvent,
type TLSocketServerSentDataEvent,
type TLSocketServerSentEvent, type TLSocketServerSentEvent,
} from './lib/protocol' } from './lib/protocol'
export type { PersistedRoomSnapshotForSupabase } from './lib/server-types' export type { PersistedRoomSnapshotForSupabase } from './lib/server-types'

View file

@ -40,11 +40,13 @@ function debug(...args: any[]) {
// pings need to be implemented one level up, on the application API side, which for our // pings need to be implemented one level up, on the application API side, which for our
// codebase means whatever code that uses ClientWebSocketAdapter. // codebase means whatever code that uses ClientWebSocketAdapter.
/** @public */
export class ClientWebSocketAdapter implements TLPersistentClientSocket<TLRecord> { export class ClientWebSocketAdapter implements TLPersistentClientSocket<TLRecord> {
_ws: WebSocket | null = null _ws: WebSocket | null = null
isDisposed = false isDisposed = false
/** @internal */
readonly _reconnectManager: ReconnectManager readonly _reconnectManager: ReconnectManager
// TODO: .close should be a project-wide interface with a common contract (.close()d thing // TODO: .close should be a project-wide interface with a common contract (.close()d thing
@ -235,7 +237,8 @@ export const DELAY_EXPONENT = 1.5
// not needlessly reconnecting if the connection is just slow to establish // not needlessly reconnecting if the connection is just slow to establish
export const ATTEMPT_TIMEOUT = 1000 export const ATTEMPT_TIMEOUT = 1000
class ReconnectManager { /** @internal */
export class ReconnectManager {
private isDisposed = false private isDisposed = false
private disposables: (() => void)[] = [ private disposables: (() => void)[] = [
() => { () => {

View file

@ -2,18 +2,21 @@ import { SerializedSchema, UnknownRecord } from '@tldraw/store'
import { TLRoomSocket } from './TLSyncRoom' import { TLRoomSocket } from './TLSyncRoom'
import { TLSocketServerSentDataEvent } from './protocol' import { TLSocketServerSentDataEvent } from './protocol'
/** @public */
export const RoomSessionState = { export const RoomSessionState = {
AwaitingConnectMessage: 'awaiting-connect-message', AwaitingConnectMessage: 'awaiting-connect-message',
AwaitingRemoval: 'awaiting-removal', AwaitingRemoval: 'awaiting-removal',
Connected: 'connected', Connected: 'connected',
} as const } as const
/** @public */
export type RoomSessionState = (typeof RoomSessionState)[keyof typeof RoomSessionState] export type RoomSessionState = (typeof RoomSessionState)[keyof typeof RoomSessionState]
export const SESSION_START_WAIT_TIME = 10000 export const SESSION_START_WAIT_TIME = 10000
export const SESSION_REMOVAL_WAIT_TIME = 10000 export const SESSION_REMOVAL_WAIT_TIME = 10000
export const SESSION_IDLE_TIMEOUT = 20000 export const SESSION_IDLE_TIMEOUT = 20000
/** @public */
export type RoomSession<R extends UnknownRecord, Meta> = export type RoomSession<R extends UnknownRecord, Meta> =
| { | {
state: typeof RoomSessionState.AwaitingConnectMessage state: typeof RoomSessionState.AwaitingConnectMessage

View file

@ -6,12 +6,14 @@ import { JsonChunkAssembler } from './chunk'
import { TLSocketServerSentEvent } from './protocol' import { TLSocketServerSentEvent } from './protocol'
// TODO: structured logging support // TODO: structured logging support
interface TLSyncLog { /** @public */
export interface TLSyncLog {
info?: (...args: any[]) => void info?: (...args: any[]) => void
warn?: (...args: any[]) => void warn?: (...args: any[]) => void
error?: (...args: any[]) => void error?: (...args: any[]) => void
} }
/** @public */
export class TLSocketRoom<R extends UnknownRecord, SessionMeta> { export class TLSocketRoom<R extends UnknownRecord, SessionMeta> {
private room: TLSyncRoom<R, SessionMeta> private room: TLSyncRoom<R, SessionMeta>
private readonly sessions = new Map< private readonly sessions = new Map<

View file

@ -21,10 +21,11 @@ import {
getTlsyncProtocolVersion, getTlsyncProtocolVersion,
} from './protocol' } from './protocol'
type SubscribingFn<T> = (cb: (val: T) => void) => () => void /** @public */
export type SubscribingFn<T> = (cb: (val: T) => void) => () => void
/** /**
* These are our private codes to be sent from server->client. * These are our private codes to be sent from server-\>client.
* They are in the private range of the websocket code range. * They are in the private range of the websocket code range.
* See: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code * See: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
* *

View file

@ -63,7 +63,8 @@ export const DATA_MESSAGE_DEBOUNCE_INTERVAL = 1000 / 60
const timeSince = (time: number) => Date.now() - time const timeSince = (time: number) => Date.now() - time
class DocumentState<R extends UnknownRecord> { /** @internal */
export class DocumentState<R extends UnknownRecord> {
_atom: Atom<{ state: R; lastChangedClock: number }> _atom: Atom<{ state: R; lastChangedClock: number }>
static createWithoutValidating<R extends UnknownRecord>( static createWithoutValidating<R extends UnknownRecord>(
@ -184,6 +185,7 @@ export class TLSyncRoom<R extends UnknownRecord, SessionMeta> {
}>() }>()
// Values associated with each uid (must be serializable). // Values associated with each uid (must be serializable).
/** @internal */
state = atom<{ state = atom<{
documents: Record<string, DocumentState<R>> documents: Record<string, DocumentState<R>>
tombstones: Record<string, number> tombstones: Record<string, number>

View file

@ -8,6 +8,7 @@ const MAX_BYTES_PER_CHAR = 4
// in the (admittedly impossible) worst case, the max size is 1/4 of a megabyte // in the (admittedly impossible) worst case, the max size is 1/4 of a megabyte
const MAX_SAFE_MESSAGE_SIZE = MAX_CLIENT_SENT_MESSAGE_SIZE_BYTES / MAX_BYTES_PER_CHAR const MAX_SAFE_MESSAGE_SIZE = MAX_CLIENT_SENT_MESSAGE_SIZE_BYTES / MAX_BYTES_PER_CHAR
/** @public */
export function chunk(msg: string, maxSafeMessageSize = MAX_SAFE_MESSAGE_SIZE) { export function chunk(msg: string, maxSafeMessageSize = MAX_SAFE_MESSAGE_SIZE) {
if (msg.length < maxSafeMessageSize) { if (msg.length < maxSafeMessageSize) {
return [msg] return [msg]

View file

@ -69,6 +69,7 @@ export const ValueOpType = {
Append: 'append', Append: 'append',
Patch: 'patch', Patch: 'patch',
} as const } as const
/** @public */
export type ValueOpType = (typeof ValueOpType)[keyof typeof ValueOpType] export type ValueOpType = (typeof ValueOpType)[keyof typeof ValueOpType]
/** @public */ /** @public */

View file

@ -1,9 +1,9 @@
import { SerializedSchema, UnknownRecord } from '@tldraw/store' import { SerializedSchema, UnknownRecord } from '@tldraw/store'
import { NetworkDiff, ObjectDiff, RecordOpType } from './diff' import { NetworkDiff, ObjectDiff, RecordOpType } from './diff'
/** @public */
const TLSYNC_PROTOCOL_VERSION = 6 const TLSYNC_PROTOCOL_VERSION = 6
/** @public */
export function getTlsyncProtocolVersion() { export function getTlsyncProtocolVersion() {
return TLSYNC_PROTOCOL_VERSION return TLSYNC_PROTOCOL_VERSION
} }

View file

@ -0,0 +1,64 @@
## API Report File for "@tldraw/sync"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { Editor } from 'tldraw';
import { Signal } from 'tldraw';
import { TLAssetStore } from 'tldraw';
import { TLSchema } from 'tldraw';
import { TLStoreWithStatus } from 'tldraw';
import { TLUserPreferences } from 'tldraw';
// @public (undocumented)
export type RemoteTLStoreWithStatus = Exclude<TLStoreWithStatus, {
status: 'not-synced';
} | {
status: 'synced-local';
}>;
// @public (undocumented)
export function useMultiplayerDemo(options: UseMultiplayerDemoOptions): RemoteTLStoreWithStatus;
// @public (undocumented)
export interface UseMultiplayerDemoOptions {
// @internal (undocumented)
host?: string;
// (undocumented)
roomId: string;
// (undocumented)
schema?: TLSchema;
// (undocumented)
userPreferences?: Signal<TLUserPreferences>;
}
// @public (undocumented)
export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLStoreWithStatus;
// @public (undocumented)
export interface UseMultiplayerSyncOptions {
// (undocumented)
assets?: Partial<TLAssetStore>;
// (undocumented)
onEditorMount?: (editor: Editor) => void;
// (undocumented)
roomId?: string;
// (undocumented)
schema?: TLSchema;
// (undocumented)
trackAnalyticsEvent?(name: string, data: {
[key: string]: any;
}): void;
// (undocumented)
uri: string;
// (undocumented)
userPreferences?: Signal<TLUserPreferences>;
}
export * from "@tldraw/sync-core";
// (No @packageDocumentation comment for this package)
```

View file

@ -1,8 +1,7 @@
{ {
"name": "@tldraw/sync", "name": "@tldraw/sync",
"description": "A tiny little drawing app (multiplayer sync react bindings).", "description": "A tiny little drawing app (multiplayer sync react bindings).",
"version": "2.0.0-alpha.11", "version": "2.3.0",
"private": true,
"author": { "author": {
"name": "tldraw GB Ltd.", "name": "tldraw GB Ltd.",
"email": "hello@tldraw.com" "email": "hello@tldraw.com"
@ -34,7 +33,12 @@
"test-ci": "lazy inherit", "test-ci": "lazy inherit",
"test": "yarn run -T jest", "test": "yarn run -T jest",
"test-coverage": "lazy inherit", "test-coverage": "lazy inherit",
"lint": "yarn run -T tsx ../../scripts/lint.ts" "lint": "yarn run -T tsx ../../scripts/lint.ts",
"build": "yarn run -T tsx ../../scripts/build-package.ts",
"build-api": "yarn run -T tsx ../../scripts/build-api.ts",
"prepack": "yarn run -T tsx ../../scripts/prepack.ts",
"postpack": "../../scripts/postpack.sh",
"pack-tarball": "yarn pack"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.3.3", "typescript": "^5.3.3",

View file

@ -41,12 +41,9 @@ function getEnv(cb: () => string | undefined): string | undefined {
const DEMO_WORKER = getEnv(() => process.env.TLDRAW_BEMO_URL) ?? 'https://demo.tldraw.xyz' const DEMO_WORKER = getEnv(() => process.env.TLDRAW_BEMO_URL) ?? 'https://demo.tldraw.xyz'
const IMAGE_WORKER = getEnv(() => process.env.TLDRAW_IMAGE_URL) ?? 'https://images.tldraw.xyz' const IMAGE_WORKER = getEnv(() => process.env.TLDRAW_IMAGE_URL) ?? 'https://images.tldraw.xyz'
export function useMultiplayerDemo({ /** @public */
roomId, export function useMultiplayerDemo(options: UseMultiplayerDemoOptions): RemoteTLStoreWithStatus {
userPreferences, const { roomId, userPreferences, host = DEMO_WORKER, schema } = options
host = DEMO_WORKER,
schema,
}: UseMultiplayerDemoOptions): RemoteTLStoreWithStatus {
const assets = useMemo(() => createDemoAssetStore(host), [host]) const assets = useMemo(() => createDemoAssetStore(host), [host])
return useMultiplayerSync({ return useMultiplayerSync({

View file

@ -11,8 +11,7 @@ const packagesOurTypesCanDependOn = [
'@types/react', '@types/react',
'@types/react-dom', '@types/react-dom',
'eventemitter3', 'eventemitter3',
// todo: external types shouldn't depend on this 'nanoevents',
'@types/ws',
] ]
main() main()

View file

@ -1,4 +1,4 @@
import { build } from 'esbuild' import { BuildOptions, build } from 'esbuild'
import { copyFileSync, existsSync } from 'fs' import { copyFileSync, existsSync } from 'fs'
import glob from 'glob' import glob from 'glob'
import kleur from 'kleur' import kleur from 'kleur'
@ -42,6 +42,20 @@ async function buildPackage({ sourcePackageDir }: { sourcePackageDir: string })
) )
} }
function getCommonEsbuildOptions() {
const define: Record<string, string> = {}
if (process.env.TLDRAW_BEMO_URL) {
define['process.env.TLDRAW_BEMO_URL'] = JSON.stringify(process.env.TLDRAW_BEMO_URL)
}
return {
bundle: false,
platform: 'neutral',
sourcemap: true,
define,
} satisfies BuildOptions
}
/** This uses esbuild to build the esm version of the package */ /** This uses esbuild to build the esm version of the package */
async function buildEsm({ async function buildEsm({
sourceFiles, sourceFiles,
@ -55,11 +69,9 @@ async function buildEsm({
const res = await build({ const res = await build({
entryPoints: sourceFiles, entryPoints: sourceFiles,
outdir, outdir,
bundle: false,
platform: 'neutral',
sourcemap: true,
format: 'esm', format: 'esm',
outExtension: { '.js': '.mjs' }, outExtension: { '.js': '.mjs' },
...getCommonEsbuildOptions(),
}) })
addJsExtensions(path.join(sourcePackageDir, 'dist-esm')) addJsExtensions(path.join(sourcePackageDir, 'dist-esm'))
@ -84,10 +96,8 @@ async function buildCjs({
const res = await build({ const res = await build({
entryPoints: sourceFiles, entryPoints: sourceFiles,
outdir, outdir,
bundle: false,
platform: 'neutral',
sourcemap: true,
format: 'cjs', format: 'cjs',
...getCommonEsbuildOptions(),
}) })
if (res.errors.length) { if (res.errors.length) {