tetherdb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +277 -0
- package/bin/tetherdb.js +7 -0
- package/dist/cli/args.d.cts +20 -0
- package/dist/cli/args.d.ts +20 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/backend.d.cts +15 -0
- package/dist/cli/backend.d.ts +15 -0
- package/dist/cli/backend.d.ts.map +1 -0
- package/dist/cli/cli.d.cts +8 -0
- package/dist/cli/cli.d.ts +8 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/cli/commands/apps.d.cts +9 -0
- package/dist/cli/commands/apps.d.ts +9 -0
- package/dist/cli/commands/apps.d.ts.map +1 -0
- package/dist/cli/commands/help.d.cts +5 -0
- package/dist/cli/commands/help.d.ts +5 -0
- package/dist/cli/commands/help.d.ts.map +1 -0
- package/dist/cli/commands/index.d.cts +8 -0
- package/dist/cli/commands/index.d.ts +8 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/maintenance.d.cts +9 -0
- package/dist/cli/commands/maintenance.d.ts +9 -0
- package/dist/cli/commands/maintenance.d.ts.map +1 -0
- package/dist/cli/commands/serve.d.cts +14 -0
- package/dist/cli/commands/serve.d.ts +14 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/status.d.cts +9 -0
- package/dist/cli/commands/status.d.ts +9 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/tables.d.cts +9 -0
- package/dist/cli/commands/tables.d.ts +9 -0
- package/dist/cli/commands/tables.d.ts.map +1 -0
- package/dist/cli/commands/users.d.cts +9 -0
- package/dist/cli/commands/users.d.ts +9 -0
- package/dist/cli/commands/users.d.ts.map +1 -0
- package/dist/cli/index.cjs +3999 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +7 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +3958 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/auth.d.cts +155 -0
- package/dist/client/auth.d.ts +155 -0
- package/dist/client/auth.d.ts.map +1 -0
- package/dist/client/client.d.cts +122 -0
- package/dist/client/client.d.ts +122 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/errors.d.cts +39 -0
- package/dist/client/errors.d.ts +39 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/index.cjs +1742 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +12 -0
- package/dist/client/index.d.ts +12 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +1707 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/shared/event.d.cts +29 -0
- package/dist/client/shared/event.d.ts +29 -0
- package/dist/client/shared/event.d.ts.map +1 -0
- package/dist/client/shared/id.d.cts +5 -0
- package/dist/client/shared/id.d.ts +5 -0
- package/dist/client/shared/id.d.ts.map +1 -0
- package/dist/client/storage.d.cts +183 -0
- package/dist/client/storage.d.ts +183 -0
- package/dist/client/storage.d.ts.map +1 -0
- package/dist/client/sync.d.cts +126 -0
- package/dist/client/sync.d.ts +126 -0
- package/dist/client/sync.d.ts.map +1 -0
- package/dist/client/table.d.cts +146 -0
- package/dist/client/table.d.ts +146 -0
- package/dist/client/table.d.ts.map +1 -0
- package/dist/index.cjs +1742 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1707 -0
- package/dist/index.js.map +1 -0
- package/dist/server/crypto.d.cts +55 -0
- package/dist/server/crypto.d.ts +55 -0
- package/dist/server/crypto.d.ts.map +1 -0
- package/dist/server/errors.d.cts +39 -0
- package/dist/server/errors.d.ts +39 -0
- package/dist/server/errors.d.ts.map +1 -0
- package/dist/server/index.cjs +3621 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +10 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3569 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/lock.d.cts +56 -0
- package/dist/server/lock.d.ts +56 -0
- package/dist/server/lock.d.ts.map +1 -0
- package/dist/server/rate-limiter.d.cts +81 -0
- package/dist/server/rate-limiter.d.ts +81 -0
- package/dist/server/rate-limiter.d.ts.map +1 -0
- package/dist/server/server.d.cts +206 -0
- package/dist/server/server.d.ts +206 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/storage/app.d.cts +70 -0
- package/dist/server/storage/app.d.ts +70 -0
- package/dist/server/storage/app.d.ts.map +1 -0
- package/dist/server/storage/base/app.d.cts +42 -0
- package/dist/server/storage/base/app.d.ts +42 -0
- package/dist/server/storage/base/app.d.ts.map +1 -0
- package/dist/server/storage/base/index.d.cts +5 -0
- package/dist/server/storage/base/index.d.ts +5 -0
- package/dist/server/storage/base/index.d.ts.map +1 -0
- package/dist/server/storage/base/storage.d.cts +40 -0
- package/dist/server/storage/base/storage.d.ts +40 -0
- package/dist/server/storage/base/storage.d.ts.map +1 -0
- package/dist/server/storage/base/table.d.cts +32 -0
- package/dist/server/storage/base/table.d.ts +32 -0
- package/dist/server/storage/base/table.d.ts.map +1 -0
- package/dist/server/storage/base/user.d.cts +31 -0
- package/dist/server/storage/base/user.d.ts +31 -0
- package/dist/server/storage/base/user.d.ts.map +1 -0
- package/dist/server/storage/file/app.d.cts +50 -0
- package/dist/server/storage/file/app.d.ts +50 -0
- package/dist/server/storage/file/app.d.ts.map +1 -0
- package/dist/server/storage/file/index.d.cts +5 -0
- package/dist/server/storage/file/index.d.ts +5 -0
- package/dist/server/storage/file/index.d.ts.map +1 -0
- package/dist/server/storage/file/storage.d.cts +67 -0
- package/dist/server/storage/file/storage.d.ts +67 -0
- package/dist/server/storage/file/storage.d.ts.map +1 -0
- package/dist/server/storage/file/table.d.cts +14 -0
- package/dist/server/storage/file/table.d.ts +14 -0
- package/dist/server/storage/file/table.d.ts.map +1 -0
- package/dist/server/storage/file/user.d.cts +14 -0
- package/dist/server/storage/file/user.d.ts +14 -0
- package/dist/server/storage/file/user.d.ts.map +1 -0
- package/dist/server/storage/index.d.cts +14 -0
- package/dist/server/storage/index.d.ts +14 -0
- package/dist/server/storage/index.d.ts.map +1 -0
- package/dist/server/storage/memory/app.d.cts +29 -0
- package/dist/server/storage/memory/app.d.ts +29 -0
- package/dist/server/storage/memory/app.d.ts.map +1 -0
- package/dist/server/storage/memory/index.d.cts +5 -0
- package/dist/server/storage/memory/index.d.ts +5 -0
- package/dist/server/storage/memory/index.d.ts.map +1 -0
- package/dist/server/storage/memory/storage.d.cts +47 -0
- package/dist/server/storage/memory/storage.d.ts +47 -0
- package/dist/server/storage/memory/storage.d.ts.map +1 -0
- package/dist/server/storage/memory/table.d.cts +17 -0
- package/dist/server/storage/memory/table.d.ts +17 -0
- package/dist/server/storage/memory/table.d.ts.map +1 -0
- package/dist/server/storage/memory/user.d.cts +21 -0
- package/dist/server/storage/memory/user.d.ts +21 -0
- package/dist/server/storage/memory/user.d.ts.map +1 -0
- package/dist/server/storage/sqlite/app.d.cts +31 -0
- package/dist/server/storage/sqlite/app.d.ts +31 -0
- package/dist/server/storage/sqlite/app.d.ts.map +1 -0
- package/dist/server/storage/sqlite/index.d.cts +5 -0
- package/dist/server/storage/sqlite/index.d.ts +5 -0
- package/dist/server/storage/sqlite/index.d.ts.map +1 -0
- package/dist/server/storage/sqlite/storage.d.cts +92 -0
- package/dist/server/storage/sqlite/storage.d.ts +92 -0
- package/dist/server/storage/sqlite/storage.d.ts.map +1 -0
- package/dist/server/storage/sqlite/table.d.cts +15 -0
- package/dist/server/storage/sqlite/table.d.ts +15 -0
- package/dist/server/storage/sqlite/table.d.ts.map +1 -0
- package/dist/server/storage/sqlite/user.d.cts +14 -0
- package/dist/server/storage/sqlite/user.d.ts +14 -0
- package/dist/server/storage/sqlite/user.d.ts.map +1 -0
- package/dist/server/storage/storage.d.cts +152 -0
- package/dist/server/storage/storage.d.ts +152 -0
- package/dist/server/storage/storage.d.ts.map +1 -0
- package/dist/server/storage/table.d.cts +45 -0
- package/dist/server/storage/table.d.ts +45 -0
- package/dist/server/storage/table.d.ts.map +1 -0
- package/dist/server/storage/user.d.cts +45 -0
- package/dist/server/storage/user.d.ts +45 -0
- package/dist/server/storage/user.d.ts.map +1 -0
- package/dist/server/sync.d.cts +67 -0
- package/dist/server/sync.d.ts +67 -0
- package/dist/server/sync.d.ts.map +1 -0
- package/dist/server/validate.d.cts +113 -0
- package/dist/server/validate.d.ts +113 -0
- package/dist/server/validate.d.ts.map +1 -0
- package/dist/shared/clock.d.cts +22 -0
- package/dist/shared/clock.d.ts +22 -0
- package/dist/shared/clock.d.ts.map +1 -0
- package/dist/shared/path.d.cts +12 -0
- package/dist/shared/path.d.ts +12 -0
- package/dist/shared/path.d.ts.map +1 -0
- package/dist/shared/types.d.cts +234 -0
- package/dist/shared/types.d.ts +234 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/package.json +133 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/types.ts","../src/client/errors.ts","../src/client/shared/event.ts","../src/client/auth.ts","../src/shared/path.ts","../src/shared/clock.ts","../src/client/shared/id.ts","../src/client/table.ts","../src/client/storage.ts","../src/client/sync.ts","../src/client/client.ts"],"sourcesContent":["/**\n * The type of mutation operation performed on a record.\n */\nexport enum OperationType {\n /** Insert or update a record payload. */\n Put = 'put',\n /** Delete a record (tombstone). */\n Delete = 'delete',\n}\n\n/**\n * Represents an individual mutation operation record to be synced.\n *\n * @typeParam T - The data type of the record payload.\n */\nexport interface ChangeRecord<T = unknown> {\n /** The target table name. */\n table: string;\n /** The unique record identifier within the table. */\n id: string;\n /** The mutation operation type. */\n op: OperationType;\n /** The data payload for 'put' operations. */\n data?: T;\n /** Monotonic epoch timestamp when the change was initiated. */\n timestamp: number;\n /** Identifier of the client that originated the change. */\n clientId: string;\n /** Incremental version counter for the record. */\n version?: number;\n /** Server-assigned global sequential index. */\n seq?: number;\n}\n\n/**\n * Represents a persisted record with local metadata.\n *\n * @typeParam T - The data type of the stored record value.\n */\nexport interface StoredRecord<T = unknown> {\n /** The unique record identifier. */\n id: string;\n /** The stored value payload. */\n data: T;\n /** Epoch timestamp of the last write. */\n timestamp: number;\n /** Record revision version. */\n version: number;\n /** Flag indicating whether the record is marked as deleted (tombstone). */\n deleted?: boolean;\n /** Identifier of client that performed the write. */\n clientId?: string;\n}\n\n/**\n * Represents a single record entry in a full database snapshot.\n *\n * @typeParam T - The data type of the record payload.\n */\nexport interface SnapshotRecord<T = unknown> extends StoredRecord<T> {\n /** The table name. */\n table: string;\n}\n\n/**\n * Current wire protocol version number.\n */\nexport const PROTOCOL_VERSION = 1;\n\n/**\n * Types of messages sent from the client to the server over the WebSocket sync connection.\n */\nexport enum ClientMessageType {\n /** Authenticate connection with user token and initial sync sequence. */\n Auth = 'auth',\n /** Submit a batch of local pending changes to the server. */\n ChangeBatch = 'change_batch',\n /** Heartbeat ping message to verify connection liveness. */\n Ping = 'ping',\n}\n\n/**\n * Client authentication handshake message.\n */\nexport interface AuthClientMessage {\n type: ClientMessageType.Auth;\n /** Wire protocol version number (must equal `PROTOCOL_VERSION`). */\n protocolVersion: number;\n /** Signed authentication session token. */\n token: string;\n /** Unique client instance identifier. */\n clientId: string;\n /** Last synchronized sequence number known to the client. */\n lastSyncSeq?: number;\n /** Application namespace identifier. */\n appId: string;\n /** Optional client capabilities supported by this client session. */\n capabilities?: string[];\n}\n\n/**\n * Client mutation batch message.\n */\nexport interface ChangeBatchClientMessage {\n type: ClientMessageType.ChangeBatch;\n /** Unique client instance identifier. */\n clientId: string;\n /** Unique batch correlation identifier. */\n batchId: string;\n /** Array of change operations to apply. */\n changes: ChangeRecord[];\n}\n\n/**\n * Client heartbeat ping message.\n */\nexport interface PingClientMessage {\n type: ClientMessageType.Ping;\n}\n\n/**\n * Discriminated union of all messages sent from client to server.\n */\nexport type ClientMessage =\n | AuthClientMessage\n | ChangeBatchClientMessage\n | PingClientMessage;\n\n/**\n * Types of messages sent from the server to the client over the WebSocket sync connection.\n */\nexport enum ServerMessageType {\n /** Authentication succeeded. */\n AuthSuccess = 'auth_success',\n /** Authentication failed. */\n AuthError = 'auth_error',\n /** Full dataset snapshot sent when client connects without prior sync sequence. */\n SyncSnapshot = 'sync_snapshot',\n /** Delta diff of changes that occurred since client's lastSyncSeq. */\n SyncDiff = 'sync_diff',\n /** Confirmation that a client change batch was applied. */\n ChangeAck = 'change_ack',\n /** Real-time broadcast of changes applied by another client session of the same user. */\n BroadcastChanges = 'broadcast_changes',\n /** Heartbeat pong response. */\n Pong = 'pong',\n /** General server error notification. */\n Error = 'error',\n}\n\n/**\n * Server authentication success response message.\n */\nexport interface AuthSuccessServerMessage {\n type: ServerMessageType.AuthSuccess;\n /** Wire protocol version number. */\n protocolVersion: number;\n /** Authenticated user account identifier. */\n userId: string;\n /** Current global sequence number of the user's data on the server. */\n currentSeq: number;\n /** Refreshed session token for sliding session validity. */\n token?: string;\n /** Optional server capabilities supported by this server instance. */\n capabilities?: string[];\n}\n\n/**\n * Server authentication failure response message.\n */\nexport interface AuthErrorServerMessage {\n type: ServerMessageType.AuthError;\n /** Error description message. */\n message: string;\n}\n\n/**\n * Server full dataset snapshot message.\n */\nexport interface SyncSnapshotServerMessage {\n type: ServerMessageType.SyncSnapshot;\n /** Sequence number corresponding to the snapshot state. */\n seq: number;\n /** All active records across tables. */\n snapshot: SnapshotRecord[];\n}\n\n/**\n * Server incremental delta diff message.\n */\nexport interface SyncDiffServerMessage {\n type: ServerMessageType.SyncDiff;\n /** Starting sequence number (exclusive). */\n fromSeq: number;\n /** Ending sequence number (inclusive). */\n toSeq: number;\n /** Array of applied changes in sequential order. */\n changes: ChangeRecord[];\n}\n\n/**\n * Server batch acknowledgement message.\n */\nexport interface ChangeAckServerMessage {\n type: ServerMessageType.ChangeAck;\n /** The correlation identifier of the acknowledged batch. */\n batchId: string;\n /** The new global sequence number after applying the batch. */\n appliedSeq: number;\n}\n\n/**\n * Server real-time changes broadcast message.\n */\nexport interface BroadcastChangesServerMessage {\n type: ServerMessageType.BroadcastChanges;\n /** Client ID that originated the change. */\n fromClientId: string;\n /** Global sequence number assigned to these changes. */\n seq: number;\n /** Array of applied changes. */\n changes: ChangeRecord[];\n}\n\n/**\n * Server heartbeat pong response message.\n */\nexport interface PongServerMessage {\n type: ServerMessageType.Pong;\n}\n\n/**\n * Server error notification message.\n */\nexport interface ErrorServerMessage {\n type: ServerMessageType.Error;\n /** Error description message. */\n message: string;\n}\n\n/**\n * Discriminated union of all messages sent from server to client.\n */\nexport type ServerMessage =\n | AuthSuccessServerMessage\n | AuthErrorServerMessage\n | SyncSnapshotServerMessage\n | SyncDiffServerMessage\n | ChangeAckServerMessage\n | BroadcastChangesServerMessage\n | PongServerMessage\n | ErrorServerMessage;\n\n/**\n * Metadata stored locally tracking synchronization progress.\n */\nexport interface SyncMetadata {\n /** The latest sequence number synchronized with the server. */\n lastSyncSeq: number;\n /** Epoch timestamp of the last successful synchronization. */\n lastSyncTimestamp: number;\n /** Unique client instance identifier. */\n clientId: string;\n}\n","/**\n * Error codes identifying broader client-side error categories.\n */\nexport enum TetherClientErrorCode {\n /** A required configuration option or parameter is missing. */\n MissingConfiguration,\n /** An invalid parameter or argument was provided to a client method. */\n InvalidInput,\n /** No suitable fetch implementation is available in the runtime environment. */\n FetchUnavailable,\n /** Network or connection failure when communicating with the server. */\n NetworkError,\n /** No valid credentials or session available for authentication. */\n MissingCredentials,\n /** User registration failed. */\n RegistrationFailed,\n /** User authentication / login failed. */\n AuthenticationFailed,\n /** Local storage operation failed. */\n StorageError,\n /** Real-time synchronization encountered an error. */\n SyncError,\n}\n\n/**\n * Dedicated error class for TetherDB client errors.\n * Error messages are user-safe and contain no internal technical details.\n */\nexport class TetherClientError extends Error {\n /** Error category code identifying the broad error type. */\n readonly code: TetherClientErrorCode;\n\n /**\n * Initializes a new `TetherClientError`.\n *\n * @param code - The error category code.\n * @param message - User-safe error description message.\n */\n constructor(code: TetherClientErrorCode, message?: string) {\n super(message ?? getDefaultClientErrorMessage(code));\n this.name = 'TetherClientError';\n this.code = code;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// -- Private Helpers --------------------------------------------------------\n\nfunction getDefaultClientErrorMessage(code: TetherClientErrorCode): string {\n switch (code) {\n case TetherClientErrorCode.MissingConfiguration:\n return 'Missing required configuration option';\n case TetherClientErrorCode.InvalidInput:\n return 'Invalid input parameter provided';\n case TetherClientErrorCode.FetchUnavailable:\n return 'No fetch implementation available';\n case TetherClientErrorCode.NetworkError:\n return 'Network communication error';\n case TetherClientErrorCode.MissingCredentials:\n return 'Missing authentication credentials';\n case TetherClientErrorCode.RegistrationFailed:\n return 'Registration failed';\n case TetherClientErrorCode.AuthenticationFailed:\n return 'Authentication failed';\n case TetherClientErrorCode.StorageError:\n return 'Local storage operation failed';\n case TetherClientErrorCode.SyncError:\n return 'Synchronization error';\n }\n}\n","/**\n * Type of callback listener registered with an EventRegistry.\n *\n * @typeParam T - The event payload type.\n */\nexport type EventListener<T = void> = (event: T) => void;\n\n/**\n * A lightweight typed event registry managing subscriber registration and event publishing.\n *\n * @typeParam T - The event payload type (defaults to void for parameterless events).\n */\nexport class EventRegistry<T = void> {\n private readonly listeners: Set<EventListener<T>> = new Set();\n\n /**\n * Registers a callback listener to be notified when events are published.\n *\n * @param listener - Callback function receiving event payloads.\n * @returns An unregister function that removes the listener.\n */\n register(listener: EventListener<T>): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Dispatches an event payload to all registered listeners in registration order.\n * Catches and logs listener exceptions to prevent aborting dispatch to remaining listeners.\n *\n * @param args - The event data payload (optional if payload type is void).\n */\n publish(...args: T extends void ? [event?: T] : [event: T]): void {\n const event = args[0] as T;\n for (const listener of this.listeners) {\n try {\n listener(event);\n } catch (err) {\n console.error('Unhandled error:', err);\n }\n }\n }\n}\n","import { TetherClientError, TetherClientErrorCode } from './errors.js';\nimport { EventRegistry } from './shared/event.js';\nimport type { Storage } from './storage.js';\n\n/**\n * Data reconciliation and persistence strategy for auth and session operations.\n */\nexport enum DataMode {\n /** Overwrite local data with remote server snapshot. */\n Remote,\n /** Preserve local data and upload/reconcile with remote. */\n Local,\n /** Merge local and remote changes using Last-Write-Wins. */\n Merge,\n /** Wipe local data tables and reset sync sequence. */\n Clear,\n}\n\n/**\n * Authentication lifecycle states of the client.\n */\nexport enum AuthStatus {\n /** No active session. */\n SignedOut,\n /** Authenticating or restoring remembered session. */\n SigningIn,\n /** Authenticated and ready for synchronized operations. */\n SignedIn,\n /** Authentication failed (e.g. invalid credentials or expired session). */\n Error,\n}\n\n/**\n * Options for registering a new user account.\n */\nexport interface RegisterOptions {\n /** Unique username (required). */\n username: string;\n /** Account password (required). */\n password: string;\n /** Persist session in IndexedDB for automatic login across sessions (default: false). */\n remember?: boolean;\n /** Local data handling mode (defaults to DataMode.Local when signed out, or DataMode.Clear when already signed in). */\n dataMode?: DataMode;\n}\n\n/**\n * Options for logging into an existing user account.\n */\nexport interface LoginOptions {\n /** Unique username (optional if already remembered). */\n username?: string;\n /** Account password (optional if already remembered). */\n password?: string;\n /** Persist session for automatic login across sessions (default: false). */\n remember?: boolean;\n /** Data reconciliation mode (default: DataMode.Remote). */\n dataMode?: DataMode;\n}\n\n/**\n * Options for logging out of the current user session.\n */\nexport interface LogoutOptions {\n /** Data preservation mode (default: DataMode.Clear to wipe local data). */\n dataMode?: DataMode;\n}\n\n/**\n * Result returned upon successful user registration or login.\n */\nexport interface AuthResult {\n /** Unique user identifier (UUID). */\n userId: string;\n /** User's unique username. */\n username: string;\n /** Signed session authentication token for sync and API calls. */\n token: string;\n}\n\n/**\n * Internal persisted session representation in IndexedDB metadata.\n */\nexport interface StoredAuthSession {\n token: string;\n userId: string;\n username: string;\n}\n\n/**\n * Internal dependencies provided to Auth by TetherClient.\n */\nexport interface AuthDependencies {\n baseUrl: string;\n storage: Storage;\n fetchFn?: typeof fetch;\n}\n\n/**\n * Authentication coordinator managing user sessions, HTTP requests,\n * metadata persistence, and reconciliation state transitions.\n */\nexport class Auth {\n /** Base URL for remote authentication REST API endpoints. */\n readonly baseUrl: string;\n /** Reactive event registry triggered whenever the authentication status changes. */\n readonly onStatusChange = new EventRegistry<AuthStatus>();\n\n private storage: Storage;\n private fetchFn: typeof fetch;\n private currentAuthStatus: AuthStatus = AuthStatus.SignedOut;\n private currentUsername?: string;\n private currentUserId?: string;\n private currentToken?: string;\n private autoRestorePromise: Promise<void>;\n\n constructor(dependencies: AuthDependencies) {\n this.baseUrl = dependencies.baseUrl;\n this.storage = dependencies.storage;\n\n const rawFetch =\n dependencies.fetchFn ??\n (typeof globalThis !== 'undefined' && globalThis.fetch\n ? globalThis.fetch\n : typeof fetch !== 'undefined'\n ? fetch\n : undefined);\n\n if (!rawFetch) {\n throw new TetherClientError(\n TetherClientErrorCode.FetchUnavailable,\n 'No fetch implementation available',\n );\n }\n this.fetchFn = rawFetch.bind(globalThis);\n this.autoRestorePromise = this.restoreSession();\n }\n\n /**\n * Current authentication lifecycle status.\n */\n get status(): AuthStatus {\n return this.currentAuthStatus;\n }\n\n /**\n * The authenticated user's username, or `undefined` if signed out.\n */\n get username(): string | undefined {\n return this.currentUsername;\n }\n\n /**\n * The authenticated user's ID, or `undefined` if signed out.\n */\n get userId(): string | undefined {\n return this.currentUserId;\n }\n\n /**\n * The active authentication session token, or `undefined` if signed out.\n */\n get token(): string | undefined {\n return this.currentToken;\n }\n\n /**\n * Restores any remembered session from IndexedDB metadata on startup.\n */\n async restoreSession(): Promise<void> {\n try {\n const session = await this.storage.getMeta<StoredAuthSession>('auth');\n if (session?.token && session.username) {\n this.currentUserId = session.userId;\n this.currentUsername = session.username;\n this.currentToken = session.token;\n this.setStatus(AuthStatus.SignedIn);\n }\n } catch {\n // Ignored during initial background boot\n }\n }\n\n /**\n * Registers a new user account, applies local data mode, and notifies listeners.\n */\n async register(options: RegisterOptions): Promise<boolean> {\n if (!options?.username || !options?.password) {\n throw new TetherClientError(\n TetherClientErrorCode.MissingCredentials,\n 'Registration requires username and password',\n );\n }\n\n const previousStatus = this.currentAuthStatus;\n this.setStatus(AuthStatus.SigningIn);\n\n try {\n const defaultDataMode =\n previousStatus === AuthStatus.SignedIn\n ? DataMode.Clear\n : DataMode.Local;\n const dataMode = options.dataMode ?? defaultDataMode;\n\n const data = await this.sendAuthRequest(\n '/auth/register',\n { username: options.username, password: options.password },\n TetherClientErrorCode.RegistrationFailed,\n 'Registration failed',\n );\n\n this.currentUserId = data.userId;\n this.currentUsername = data.username;\n this.currentToken = data.token;\n\n await this.applyDataMode(dataMode);\n await this.updateStoredAuth(options.remember ?? false, data);\n\n this.setStatus(AuthStatus.SignedIn);\n return true;\n } catch {\n this.setStatus(AuthStatus.Error);\n return false;\n }\n }\n\n /**\n * Logs into an account (or reconnects with remembered credentials), applies data mode, and notifies listeners.\n */\n async login(options: LoginOptions = {}): Promise<boolean> {\n await this.autoRestorePromise;\n\n this.setStatus(AuthStatus.SigningIn);\n\n try {\n let token = this.currentToken;\n let username = this.currentUsername;\n let userId = this.currentUserId;\n\n if (options.username && options.password) {\n const data = await this.sendAuthRequest(\n '/auth/login',\n { username: options.username, password: options.password },\n TetherClientErrorCode.AuthenticationFailed,\n 'Authentication failed',\n );\n\n token = data.token;\n username = data.username;\n userId = data.userId;\n\n await this.updateStoredAuth(options.remember ?? false, data);\n } else if (!token) {\n const stored = await this.storage.getMeta<StoredAuthSession>('auth');\n if (stored?.token) {\n token = stored.token;\n username = stored.username;\n userId = stored.userId;\n }\n }\n\n if (!token) {\n throw new TetherClientError(\n TetherClientErrorCode.MissingCredentials,\n 'No login credentials or saved session available',\n );\n }\n\n this.currentToken = token;\n this.currentUsername = username;\n this.currentUserId = userId;\n\n await this.applyDataMode(options.dataMode ?? DataMode.Remote);\n\n this.setStatus(AuthStatus.SignedIn);\n return true;\n } catch {\n this.setStatus(AuthStatus.Error);\n return false;\n }\n }\n\n /**\n * Logs out of the current session, removes stored credentials, and applies data mode.\n */\n async logout(options: LogoutOptions = {}): Promise<boolean> {\n this.currentUserId = undefined;\n this.currentUsername = undefined;\n this.currentToken = undefined;\n\n await this.storage.deleteMeta('auth');\n await this.applyDataMode(options.dataMode ?? DataMode.Clear);\n\n this.setStatus(AuthStatus.SignedOut);\n return true;\n }\n\n /**\n * Updates the in-memory and persisted session token when refreshed by the server.\n *\n * @param token - The new refreshed session token.\n */\n async handleTokenRefresh(token: string): Promise<void> {\n this.currentToken = token;\n const session = await this.storage.getMeta<StoredAuthSession>('auth');\n if (session) {\n await this.storage.setMeta('auth', {\n ...session,\n token,\n });\n }\n }\n\n /**\n * Cleans up local session and transitions to SignedOut when authentication fails or expires.\n *\n * @param _message - Optional error message from the server.\n */\n async handleAuthError(_message?: string): Promise<void> {\n this.currentUserId = undefined;\n this.currentUsername = undefined;\n this.currentToken = undefined;\n await this.storage.deleteMeta('auth');\n this.setStatus(AuthStatus.SignedOut);\n }\n\n // -- Private Helpers ------------------------------------------------------\n\n private async sendAuthRequest(\n path: string,\n credentials: { username?: string; password?: string },\n errorCode: TetherClientErrorCode,\n defaultErrorMessage: string,\n ): Promise<AuthResult> {\n const res = await this.fetchFn(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(credentials),\n });\n\n const data = (await res.json()) as AuthResult & { error?: string };\n if (!res.ok) {\n throw new TetherClientError(errorCode, data.error ?? defaultErrorMessage);\n }\n return data;\n }\n\n private async applyDataMode(dataMode: DataMode): Promise<void> {\n if (dataMode === DataMode.Clear || dataMode === DataMode.Remote) {\n await this.storage.clearTables(true);\n await this.storage.setMeta('lastSyncSeq', 0);\n }\n }\n\n private async updateStoredAuth(\n remember: boolean,\n auth: AuthResult,\n ): Promise<void> {\n if (remember) {\n await this.storage.setMeta('auth', {\n token: auth.token,\n userId: auth.userId,\n username: auth.username,\n });\n } else {\n await this.storage.deleteMeta('auth');\n }\n }\n\n private setStatus(status: AuthStatus): void {\n if (this.currentAuthStatus === status) return;\n this.currentAuthStatus = status;\n this.onStatusChange.publish(status);\n }\n}\n","/**\n * Utility functions for path normalization across client and server.\n */\n\n/**\n * Normalizes a base path ensuring it starts with a leading slash and has no trailing slash.\n * An empty string or single slash normalizes to an empty string.\n *\n * @param path - The base path to normalize.\n * @returns Normalized base path (e.g. '/api' or '').\n */\nexport function normalizeBasePath(path: string): string {\n if (path === '' || path === '/') return '';\n if (path.endsWith('/')) path = path.slice(0, path.length - 1);\n if (!path.startsWith('/')) path = `/${path}`;\n return path === '/' ? '' : path;\n}\n","import type { ChangeRecord, StoredRecord } from './types.js';\n\n/**\n * Determines whether an incoming mutation should overwrite an existing stored record\n * using Last-Write-Wins (LWW) conflict resolution.\n *\n * Conflict resolution rules:\n * 1. If no existing record exists, returns `true`.\n * 2. If incoming timestamp is strictly greater, returns `true`.\n * 3. If incoming timestamp is strictly less, returns `false`.\n * 4. If timestamps are equal, performs deterministic lexicographical tie-breaking using `clientId`.\n *\n * @param incoming - The candidate change record with timestamp and optional client metadata.\n * @param existing - The current record stored locally or on the server.\n * @returns `true` if the incoming change wins the conflict and should overwrite the existing record; otherwise `false`.\n */\nexport function shouldOverwrite(\n incoming: Pick<ChangeRecord, 'timestamp'> & {\n clientId?: string;\n version?: number;\n },\n existing?: Pick<StoredRecord, 'timestamp' | 'version'> & {\n clientId?: string;\n },\n): boolean {\n if (!existing) return true;\n\n if (incoming.timestamp > existing.timestamp) {\n return true;\n }\n if (incoming.timestamp < existing.timestamp) {\n return false;\n }\n\n const incomingClient = incoming.clientId ?? '';\n const existingClient = existing.clientId ?? '';\n return incomingClient >= existingClient;\n}\n","/**\n * Generates a random UUID v4.\n */\nexport function randomUUID(): string {\n if (typeof crypto !== 'undefined') {\n if (typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n if (typeof crypto.getRandomValues === 'function') {\n return '10000000-1000-4000-8000-100000000000'.replace(\n /[018]/g,\n (char) => {\n const num = Number(char);\n const randomByte = crypto.getRandomValues(new Uint8Array(1))[0];\n return (num ^ (randomByte & (15 >> (num / 4)))).toString(16);\n },\n );\n }\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {\n const randomByte = (Math.random() * 16) | 0;\n return (char === 'x' ? randomByte : (randomByte & 0x3) | 0x8).toString(16);\n });\n}\n","import {\n type ChangeRecord,\n OperationType,\n type StoredRecord,\n} from '../shared/types.js';\nimport { EventRegistry } from './shared/event.js';\nimport type { LocalMutationItem, Storage } from './storage.js';\n\n/**\n * Event describing a mutation (insert, update, delete) on a table record.\n *\n * @typeParam T - Data payload type.\n */\nexport interface TableChangeEvent<T = unknown> {\n /** The mutation operation type. */\n op: OperationType;\n /** The affected record identifier. */\n id: string;\n /** The new record data (defined on 'put' operations). */\n data?: T;\n /** `true` if the change originated from remote synchronization; `false` if triggered locally. */\n isRemote?: boolean;\n}\n\n/**\n * Entry item for bulk insertion/update via `Table.putAll`.\n *\n * @typeParam T - Data payload type.\n */\nexport interface TablePutEntry<T = unknown> {\n /** Target record identifier. */\n id: string;\n /** Record data payload. */\n data: T;\n}\n\n/**\n * Event notification callback fired when records are inserted, updated, or deleted.\n * Always receives a list of change events.\n *\n * @typeParam T - Data payload type.\n * @param events - List of change events that occurred.\n */\nexport type TableChangeListener<T = unknown> = (\n events: TableChangeEvent<T>[],\n) => void;\n\n/**\n * Typed table wrapper providing local-first CRUD operations and reactive event subscriptions\n * against an underlying IndexedDB table.\n * Operations are batched by default for maximum performance.\n *\n * @typeParam T - The data type of records stored in this table.\n */\nexport class Table<T = unknown> {\n /** Reactive event registry triggered when records in this table are created, updated, or deleted. */\n readonly onChange = new EventRegistry<TableChangeEvent<T>[]>();\n private tableName: string;\n private storage: Storage;\n\n /**\n * Creates a new Table instance.\n *\n * @param tableName - Name of the table.\n * @param storage - Local storage coordinator.\n */\n constructor(tableName: string, storage: Storage) {\n this.tableName = tableName;\n this.storage = storage;\n }\n\n /**\n * The name of the table.\n */\n get name(): string {\n return this.tableName;\n }\n\n /**\n * The client identifier for local mutations.\n */\n get clientId(): string {\n return this.storage.clientId;\n }\n\n /**\n * Retrieves a single record by its identifier.\n *\n * @param id - The unique record identifier.\n * @returns A promise resolving to the record data, or `undefined` if not found.\n */\n async get(id: string): Promise<T | undefined> {\n const record = await this.storage.getRecord<T>(this.tableName, id);\n return record?.data;\n }\n\n /**\n * Retrieves records stored in this table, optionally filtered by a list of IDs.\n *\n * @param ids - Optional list of record identifiers to fetch. If omitted, retrieves all records.\n * @returns A promise resolving to an array of record data objects.\n */\n async getAll(ids?: string[]): Promise<T[]> {\n if (ids !== undefined) {\n if (ids.length === 0) return [];\n const map = await this.storage.getRecords<T>(this.tableName, ids);\n const results: T[] = [];\n for (const id of ids) {\n const rec = map.get(id);\n if (rec) {\n results.push(rec.data);\n }\n }\n return results;\n }\n const records = await this.storage.getAllRecords<T>(this.tableName);\n return records.map((r) => r.data);\n }\n\n /**\n * Deletes all records in this table and queues tombstones for synchronization.\n *\n * @returns A promise resolving to the number of deleted records.\n */\n async clear(): Promise<number> {\n const records = await this.storage.getAllRecords<T>(this.tableName);\n const ids = records.map((r) => r.id);\n return this.deleteAll(ids);\n }\n\n /**\n * Retrieves a single stored record including metadata (version, timestamp).\n *\n * @param id - The unique record identifier.\n * @returns A promise resolving to the stored record with metadata, or `undefined` if not found.\n */\n async getWithMetadata(id: string): Promise<StoredRecord<T> | undefined> {\n return this.storage.getRecord<T>(this.tableName, id);\n }\n\n /**\n * Retrieves all records with storage metadata (version, timestamp).\n *\n * @returns A promise resolving to an array of stored records with metadata.\n */\n async getAllWithMetadata(): Promise<StoredRecord<T>[]> {\n return this.storage.getAllRecords<T>(this.tableName);\n }\n\n /**\n * Stores or updates a record locally and triggers synchronization.\n *\n * @param id - The unique record identifier.\n * @param data - The data payload to save.\n * @returns A promise resolving to the saved data.\n */\n async put(id: string, data: T): Promise<T> {\n await this.putAll([{ id, data }]);\n return data;\n }\n\n /**\n * Atomically stores or updates multiple records locally in a single batch,\n * queues outbox mutations, notifies subscribers with an event list, and triggers synchronization.\n *\n * @param entries - Array of record entries to save.\n * @returns A promise resolving to the array of saved data objects.\n */\n async putAll(entries: TablePutEntry<T>[]): Promise<T[]> {\n if (entries.length === 0) return [];\n\n const ids = entries.map((e) => e.id);\n const existingMap = await this.storage.getRecords<T>(this.tableName, ids);\n const now = Date.now();\n\n const mutations: LocalMutationItem<T>[] = [];\n const events: TableChangeEvent<T>[] = [];\n const savedData: T[] = [];\n\n for (const entry of entries) {\n const { mutation, event } = this.createMutationItem(\n entry.id,\n OperationType.Put,\n existingMap.get(entry.id),\n now,\n entry.data,\n );\n mutations.push(mutation);\n events.push(event);\n savedData.push(entry.data);\n }\n\n await this.storage.applyLocalChanges(this.tableName, mutations);\n this.onChange.publish(events);\n return savedData;\n }\n\n /**\n * Deletes a record locally by creating a tombstone and triggers synchronization.\n *\n * @param id - The unique record identifier to delete.\n * @returns A promise resolving to `true` if the record existed and was deleted; otherwise `false`.\n */\n async delete(id: string): Promise<boolean> {\n const deletedCount = await this.deleteAll([id]);\n return deletedCount > 0;\n }\n\n /**\n * Atomically deletes multiple records locally in a single batch,\n * creates tombstones, notifies subscribers with an event list, and triggers synchronization.\n *\n * @param ids - Array of record identifiers to delete.\n * @returns A promise resolving to the count of records that existed and were deleted.\n */\n async deleteAll(ids: string[]): Promise<number> {\n if (ids.length === 0) return 0;\n\n const existingMap = await this.storage.getRecords<T>(this.tableName, ids);\n const now = Date.now();\n\n const mutations: LocalMutationItem<T>[] = [];\n const events: TableChangeEvent<T>[] = [];\n\n for (const id of ids) {\n const existing = existingMap.get(id);\n if (!existing || existing.deleted) continue;\n\n const { mutation, event } = this.createMutationItem(\n id,\n OperationType.Delete,\n existing,\n now,\n );\n mutations.push(mutation);\n events.push(event);\n }\n\n if (mutations.length > 0) {\n await this.storage.applyLocalChanges(this.tableName, mutations);\n this.onChange.publish(events);\n }\n\n return mutations.length;\n }\n\n /**\n * Subscribes to the complete list of records in this table.\n * Immediately invokes the listener with the current records, and re-invokes it\n * whenever any local or remote mutations occur on this table.\n *\n * @param listener - Callback receiving the latest array of records.\n * @returns An unsubscribe function.\n */\n subscribeAll(listener: (items: T[]) => void): () => void {\n let isActive = true;\n let currentVersion = 0;\n const fetchAndNotify = () => {\n const version = ++currentVersion;\n this.getAll()\n .then((items) => {\n if (isActive && version === currentVersion) {\n listener(items);\n }\n })\n .catch(() => {\n // Ignore fetch error during unmounted subscription initialization\n });\n };\n\n fetchAndNotify();\n const unsubscribe = this.onChange.register(() => {\n fetchAndNotify();\n });\n\n return () => {\n isActive = false;\n unsubscribe();\n };\n }\n\n /**\n * Notifies registered subscribers of remote change events.\n *\n * @param events - The list of remote change events.\n */\n notifyRemoteChanges(events: TableChangeEvent<unknown>[]): void {\n if (events.length === 0) return;\n this.onChange.publish(events as TableChangeEvent<T>[]);\n }\n\n // -- Private Helpers ------------------------------------------------------\n\n private createMutationItem(\n id: string,\n op: OperationType,\n existing: StoredRecord<T> | undefined,\n now: number,\n data?: T,\n ): { mutation: LocalMutationItem<T>; event: TableChangeEvent<T> } {\n const version = (existing?.version ?? 0) + 1;\n const timestamp = Math.max(now, (existing?.timestamp ?? 0) + 1);\n const change: ChangeRecord<T> = {\n table: this.tableName,\n id,\n op,\n data,\n timestamp,\n clientId: this.clientId,\n version,\n };\n return {\n mutation: { id, op, data, change },\n event: { op, id, data, isRemote: false },\n };\n }\n}\n","import { shouldOverwrite } from '../shared/clock.js';\nimport {\n type ChangeRecord,\n OperationType,\n type SnapshotRecord,\n type StoredRecord,\n} from '../shared/types.js';\nimport { EventRegistry } from './shared/event.js';\nimport { randomUUID } from './shared/id.js';\nimport { Table } from './table.js';\n\n/**\n * Represents a pending mutation queue item within the internal IndexedDB outbox.\n */\nexport interface OutboxEntry {\n /** Auto-incrementing primary key in the outbox object store. */\n localId?: number;\n /** Transient batch correlation identifier. */\n batchId?: string;\n /** The mutation change record to be synced to the server. */\n change: ChangeRecord;\n /** Epoch timestamp when the entry was queued locally. */\n createdAt: number;\n}\n\n/**\n * Mutation item payload passed to `Storage.applyLocalChanges`.\n */\nexport interface LocalMutationItem<T = unknown> {\n /** Target record identifier. */\n id: string;\n /** Mutation operation type. */\n op: OperationType;\n /** Record data payload (defined on put). */\n data?: T;\n /** Correlated changelog entry to queue in outbox. */\n change: ChangeRecord<T>;\n}\n\n/**\n * Atomic transaction and local persistence manager wrapping IndexedDB.\n * Coordinates user table stores, pending outbox changelogs, and sync metadata.\n */\nexport class Storage {\n /** Name of the local IndexedDB database. */\n readonly name: string;\n /** Unique client instance identifier used for monotonic logical clock tie-breaking. */\n readonly clientId: string;\n /** Reactive event registry triggered when mutations are committed to the local database. */\n readonly onLocalChange = new EventRegistry<void>();\n private databasePromise: Promise<IDBDatabase> | null = null;\n private schemaMutex: Promise<void> = Promise.resolve();\n private tables: Map<string, Table<unknown>> = new Map();\n\n /**\n * Creates a new Storage instance.\n *\n * @param name - Name of the IndexedDB database.\n */\n constructor(name: string) {\n this.name = name;\n this.clientId = randomUUID();\n }\n\n /**\n * Obtains a typed table reference for reading, mutating, and subscribing to records.\n * Tables are created dynamically on-demand if not already declared.\n *\n * @typeParam T - Data payload model type for records in this table.\n * @param name - The table name.\n * @returns A typed `Table<T>` instance.\n */\n table<T = unknown>(name: string): Table<T> {\n let table = this.tables.get(name);\n if (!table) {\n table = new Table(name, this) as Table<unknown>;\n this.tables.set(name, table);\n }\n return table as unknown as Table<T>;\n }\n\n /**\n * Opens or returns the active IndexedDB connection, executing schema upgrades when needed.\n *\n * @returns A promise resolving to the open IDBDatabase instance.\n */\n async getDatabase(): Promise<IDBDatabase> {\n if (this.databasePromise) return this.databasePromise;\n this.databasePromise = this.openDatabase().catch((err) => {\n this.databasePromise = null;\n throw err;\n });\n return this.databasePromise;\n }\n\n /**\n * Dynamically ensures that multiple application tables exist in IndexedDB.\n *\n * @param tableNames - Array of table names to ensure.\n */\n async ensureTables(tableNames: string[]): Promise<void> {\n this.schemaMutex = this.schemaMutex.then(async () => {\n const database = await this.getDatabase();\n const missing = tableNames.filter(\n (s) => !database.objectStoreNames.contains(s),\n );\n if (missing.length === 0) return;\n const nextVersion = database.version + 1;\n this.databasePromise = this.upgradeDatabase(\n database,\n nextVersion,\n missing,\n ).catch((err) => {\n this.databasePromise = null;\n throw err;\n });\n await this.databasePromise;\n });\n return this.schemaMutex;\n }\n\n /**\n * Ensures a single application table exists in IndexedDB.\n *\n * @param tableName - Name of the table to ensure.\n */\n async ensureTable(tableName: string): Promise<void> {\n await this.ensureTables([tableName]);\n }\n\n /**\n * Retrieves a metadata value from the internal metadata store.\n *\n * @typeParam T - Expected value type.\n * @param key - The metadata key identifier.\n * @returns The stored metadata value, or `undefined` if not set.\n */\n async getMeta<T = unknown>(key: string): Promise<T | undefined> {\n return this.withDatabase(async (database) => {\n const entry = await promisifyRequest<\n { key: string; value: T } | undefined\n >(\n database\n .transaction(META_STORE, 'readonly')\n .objectStore(META_STORE)\n .get(key),\n );\n return entry?.value;\n });\n }\n\n /**\n * Sets a metadata value in the internal metadata store.\n *\n * @param key - The metadata key identifier.\n * @param value - The value to store.\n */\n async setMeta(key: string, value: unknown): Promise<void> {\n return this.withDatabase(async (database) => {\n await promisifyRequest(\n database\n .transaction(META_STORE, 'readwrite')\n .objectStore(META_STORE)\n .put({ key, value }),\n );\n });\n }\n\n /**\n * Deletes a metadata entry from the internal metadata store.\n *\n * @param key - The metadata key identifier.\n */\n async deleteMeta(key: string): Promise<void> {\n return this.withDatabase(async (database) => {\n await promisifyRequest(\n database\n .transaction(META_STORE, 'readwrite')\n .objectStore(META_STORE)\n .delete(key),\n );\n });\n }\n\n /**\n * Retrieves a single stored record by table and identifier.\n *\n * @typeParam T - Expected payload type.\n * @param tableName - Table name.\n * @param id - Record identifier.\n * @returns Stored record or `undefined` if not found.\n */\n async getRecord<T = unknown>(\n tableName: string,\n id: string,\n ): Promise<StoredRecord<T> | undefined> {\n await this.ensureTable(tableName);\n return this.withDatabase(async (database) => {\n return promisifyRequest<StoredRecord<T> | undefined>(\n database\n .transaction(tableName, 'readonly')\n .objectStore(tableName)\n .get(id),\n );\n });\n }\n\n /**\n * Retrieves multiple stored records by their identifiers in a single readonly transaction.\n *\n * @typeParam T - Expected payload type.\n * @param tableName - Table name.\n * @param ids - Array of record identifiers.\n * @returns Map of found stored records keyed by id.\n */\n async getRecords<T = unknown>(\n tableName: string,\n ids: string[],\n ): Promise<Map<string, StoredRecord<T>>> {\n if (ids.length === 0) return new Map();\n await this.ensureTable(tableName);\n return this.withDatabase(async (database) => {\n const tx = database.transaction(tableName, 'readonly');\n const store = tx.objectStore(tableName);\n const results = new Map<string, StoredRecord<T>>();\n for (const id of ids) {\n const request = store.get(id);\n request.onsuccess = () => {\n if (request.result) {\n results.set(id, request.result);\n }\n };\n }\n await promisifyTransaction(tx);\n return results;\n });\n }\n\n /**\n * Retrieves stored records from a specified table.\n *\n * @typeParam T - Data payload type.\n * @param tableName - Table name.\n * @returns Array of stored records with metadata.\n */\n async getAllRecords<T = unknown>(\n tableName: string,\n ): Promise<StoredRecord<T>[]> {\n await this.ensureTable(tableName);\n return this.withDatabase(async (database) => {\n const records = await promisifyRequest<StoredRecord<T>[]>(\n database\n .transaction(tableName, 'readonly')\n .objectStore(tableName)\n .getAll(),\n );\n return records ?? [];\n });\n }\n\n /**\n * Atomically persists a batch of local mutations alongside outbox changelog entries\n * within a single IndexedDB transaction.\n *\n * @typeParam T - Data payload type.\n * @param tableName - Target table name.\n * @param mutations - List of mutations to apply and queue into outbox.\n * @returns Array of newly stored records with updated metadata.\n */\n async applyLocalChanges<T = unknown>(\n tableName: string,\n mutations: LocalMutationItem<T>[],\n ): Promise<StoredRecord<T>[]> {\n if (mutations.length === 0) return [];\n await this.ensureTable(tableName);\n return this.withDatabase(async (database) => {\n const tx = database.transaction([tableName, OUTBOX_STORE], 'readwrite');\n const dataStore = tx.objectStore(tableName);\n const outboxStore = tx.objectStore(OUTBOX_STORE);\n const records: StoredRecord<T>[] = [];\n const now = Date.now();\n for (const item of mutations) {\n const isDelete = item.op === OperationType.Delete;\n const record: StoredRecord<T> = {\n id: item.id,\n data: (item.data ?? null) as T,\n timestamp: item.change.timestamp,\n version: item.change.version ?? 1,\n clientId: item.change.clientId,\n deleted: isDelete,\n };\n if (isDelete) {\n dataStore.delete(item.id);\n } else {\n dataStore.put(record);\n }\n outboxStore.add({\n change: item.change,\n createdAt: now,\n });\n records.push(record);\n }\n await promisifyTransaction(tx);\n this.onLocalChange.publish();\n return records;\n });\n }\n\n /**\n * Retrieves all currently queued outbox entries awaiting sync.\n *\n * @param limit - Optional maximum number of entries to retrieve.\n * @returns Array of pending outbox queue items.\n */\n async getPendingOutbox(limit?: number): Promise<OutboxEntry[]> {\n return this.withDatabase(async (database) => {\n const store = database\n .transaction(OUTBOX_STORE, 'readonly')\n .objectStore(OUTBOX_STORE);\n const req = limit ? store.getAll(null, limit) : store.getAll();\n const results = await promisifyRequest<OutboxEntry[]>(req);\n return results ?? [];\n });\n }\n\n /**\n * Clears acknowledged changes from the outbox by their local database IDs.\n *\n * @param localIds - Array of auto-incremented local outbox IDs to remove.\n */\n async removeOutboxEntries(localIds: number[]): Promise<void> {\n if (localIds.length === 0) return;\n return this.withDatabase(async (database) => {\n const tx = database.transaction(OUTBOX_STORE, 'readwrite');\n const store = tx.objectStore(OUTBOX_STORE);\n for (const id of localIds) {\n store.delete(id);\n }\n await promisifyTransaction(tx);\n });\n }\n\n /**\n * Applies an entire snapshot batch across tables atomically in a single transaction.\n *\n * @param snapshot - Array of record items to persist.\n * @param seq - Global sequence number corresponding to this snapshot.\n */\n async applySnapshotBatch(\n snapshot: SnapshotRecord[],\n seq: number,\n ): Promise<void> {\n await this.applyBatchRecords(snapshot, seq);\n }\n\n /**\n * Applies a batch of incoming remote delta mutations atomically across tables\n * in a single IndexedDB transaction without generating local outbox echo entries.\n *\n * @param changes - Array of change records received from the server.\n * @param seq - New sequence number to record upon transaction commit.\n */\n async applyRemoteChangesBatch(\n changes: ChangeRecord[],\n seq: number,\n ): Promise<void> {\n if (changes.length === 0) {\n await this.setMeta('lastSyncSeq', seq);\n return;\n }\n\n const records: SnapshotRecord[] = changes.map((change) => ({\n table: change.table,\n id: change.id,\n data: change.data,\n timestamp: change.timestamp,\n version: change.version ?? 1,\n deleted: change.op === OperationType.Delete,\n clientId: change.clientId,\n }));\n\n await this.applyBatchRecords(records, seq);\n }\n\n /**\n * Clears only user table stores, leaving internal metadata and outbox intact if desired.\n *\n * @param clearOutbox - Whether to clear the pending outbox queue as well (defaults to `true`).\n */\n async clearTables(clearOutbox = true): Promise<void> {\n return this.withDatabase(async (db) => {\n const storeNames = Array.from(db.objectStoreNames).filter((name) => {\n if (name === META_STORE) return false;\n if (name === OUTBOX_STORE && !clearOutbox) return false;\n return true;\n });\n await this.clearStores(db, storeNames);\n });\n }\n\n /**\n * Clears all local table stores, outbox changelog entries, and metadata.\n */\n async clearAllData(): Promise<void> {\n return this.withDatabase(async (db) => {\n await this.clearStores(db, Array.from(db.objectStoreNames));\n });\n }\n\n /**\n * Closes the active IndexedDB connection.\n */\n async close(): Promise<void> {\n if (this.databasePromise) {\n const db = await this.databasePromise;\n db.close();\n this.databasePromise = null;\n }\n }\n\n // -- Private Helpers ------------------------------------------------------\n\n private async withDatabase<R>(\n fn: (db: IDBDatabase) => Promise<R>,\n ): Promise<R> {\n return this.schemaMutex.then(async () => {\n const db = await this.getDatabase();\n return fn(db);\n });\n }\n\n private async applyBatchRecords(\n records: SnapshotRecord[],\n seq: number,\n ): Promise<void> {\n if (records.length === 0) {\n await this.setMeta('lastSyncSeq', seq);\n return;\n }\n\n const tableNames = Array.from(\n new Set(records.map((item) => item.table)),\n ).filter(Boolean);\n await this.ensureTables(tableNames);\n\n await this.withDatabase(async (database) => {\n const tx = database.transaction([...tableNames, META_STORE], 'readwrite');\n const tableStores = new Map<string, IDBObjectStore>();\n for (const name of tableNames) {\n tableStores.set(name, tx.objectStore(name));\n }\n for (const item of records) {\n const store = tableStores.get(item.table);\n if (!store) continue;\n\n const getReq = store.get(item.id);\n getReq.onsuccess = () => {\n const existing = getReq.result as StoredRecord | undefined;\n if (!existing || shouldOverwrite(item, existing)) {\n if (item.deleted) {\n store.delete(item.id);\n } else {\n const record: StoredRecord = {\n id: item.id,\n data: item.data,\n timestamp: item.timestamp,\n version: item.version ?? 1,\n clientId: item.clientId,\n };\n store.put(record);\n }\n }\n };\n }\n const metaStore = tx.objectStore(META_STORE);\n metaStore.put({ key: 'lastSyncSeq', value: seq });\n metaStore.put({ key: 'lastSyncTimestamp', value: Date.now() });\n await promisifyTransaction(tx);\n });\n }\n\n private async clearStores(\n database: IDBDatabase,\n storeNames: string[],\n ): Promise<void> {\n if (storeNames.length === 0) return;\n const tx = database.transaction(storeNames, 'readwrite');\n for (const name of storeNames) {\n tx.objectStore(name).clear();\n }\n await promisifyTransaction(tx);\n }\n\n private async openDatabase(): Promise<IDBDatabase> {\n return new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDB.open(this.name);\n request.onupgradeneeded = () => {\n this.createInternalStores(request.result);\n };\n request.onsuccess = () => {\n const database = request.result;\n if (\n !database.objectStoreNames.contains(OUTBOX_STORE) ||\n !database.objectStoreNames.contains(META_STORE)\n ) {\n const nextVersion = database.version + 1;\n this.databasePromise = this.upgradeDatabase(\n database,\n nextVersion,\n [],\n );\n this.databasePromise.then(resolve).catch(reject);\n } else {\n resolve(database);\n }\n };\n request.onerror = () => reject(request.error);\n });\n }\n\n private async upgradeDatabase(\n currentDb: IDBDatabase,\n nextVersion: number,\n newTables: string[],\n ): Promise<IDBDatabase> {\n currentDb.close();\n return new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDB.open(this.name, nextVersion);\n request.onupgradeneeded = () => {\n const database = request.result;\n this.createInternalStores(database);\n for (const tableName of newTables) {\n if (!database.objectStoreNames.contains(tableName)) {\n database.createObjectStore(tableName, { keyPath: 'id' });\n }\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n }\n\n private createInternalStores(db: IDBDatabase): void {\n if (!db.objectStoreNames.contains(OUTBOX_STORE)) {\n db.createObjectStore(OUTBOX_STORE, {\n keyPath: 'localId',\n autoIncrement: true,\n });\n }\n if (!db.objectStoreNames.contains(META_STORE)) {\n db.createObjectStore(META_STORE, { keyPath: 'key' });\n }\n }\n}\n\n// -- Private Helpers --------------------------------------------------------\n\nconst OUTBOX_STORE = '__tether_outbox';\nconst META_STORE = '__tether_meta';\n\nfunction promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nfunction promisifyTransaction(tx: IDBTransaction): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n tx.onabort = () =>\n reject(tx.error ?? new Error('IndexedDB transaction aborted'));\n });\n}\n","import {\n type ChangeRecord,\n type ClientMessage,\n ClientMessageType,\n OperationType,\n PROTOCOL_VERSION,\n type ServerMessage,\n ServerMessageType,\n type SnapshotRecord,\n} from '../shared/types.js';\nimport { TetherClientError, TetherClientErrorCode } from './errors.js';\nimport { EventRegistry } from './shared/event.js';\nimport type { Storage } from './storage.js';\n\n/**\n * Operational state of the synchronization coordinator.\n */\nexport enum SyncStatus {\n /** Disconnected from the remote synchronization server. */\n Disconnected,\n /** Currently establishing a WebSocket connection. */\n Connecting,\n /** Authenticated and actively synchronizing in real time. */\n Connected,\n /** Synchronization halted due to an unrecoverable error (e.g. invalid auth token). */\n Error,\n}\n\n/**\n * Constructor signature for WebSocket implementations (native browser WebSocket or 'ws' package).\n */\nexport type WebSocketConstructor = new (\n url: string | URL,\n protocols?: string | string[] | WebSocketInit,\n) => WebSocket;\n\n/**\n * Configuration options for Sync.\n */\nexport interface SyncOptions {\n /** WebSocket URL of the sync endpoint (e.g. 'ws://localhost:8080/sync'). */\n url?: string;\n /** Signed authentication session token. */\n token?: string;\n /** Application namespace identifier. */\n appId: string;\n /** Unique client instance identifier. */\n clientId: string;\n /** Initial reconnection backoff delay in milliseconds (defaults to 1000). */\n reconnectIntervalMs?: number;\n /** Maximum reconnection backoff delay in milliseconds (defaults to 30000). */\n maxReconnectIntervalMs?: number;\n /** Periodic keepalive ping interval in milliseconds (defaults to 30000). Set to 0 to disable. */\n pingIntervalMs?: number;\n /** Debounce delay in milliseconds before pushing queued local outbox changes (defaults to 10). */\n pushDebounceMs?: number;\n /** Custom WebSocket constructor for Node.js environments. */\n webSocketClass?: WebSocketConstructor;\n /** Callback invoked when the server provides a refreshed session token. */\n onTokenRefresh?: (token: string) => void;\n /** Callback invoked when the server rejects authentication. */\n onAuthError?: (message: string) => void;\n}\n\n/**\n * Two-way WebSocket sync coordinator managing initial snapshot / diff downloads,\n * batched outbox queue flushing, acknowledgments, and auto-reconnect backoff.\n */\nexport class Sync {\n /** Remote WebSocket endpoint URL. */\n url?: string;\n /** Application namespace identifier for partitioning synchronization channels. */\n readonly appId: string;\n /** Client identifier used for conflict resolution tie-breaking. */\n readonly clientId: string;\n /** Reactive event registry triggered whenever synchronization status transitions. */\n readonly onStatusChange = new EventRegistry<SyncStatus>();\n /** Reactive event registry triggered whenever background sync or network errors occur. */\n readonly onError = new EventRegistry<TetherClientError>();\n\n private token?: string;\n private storage: Storage;\n private options: SyncOptions;\n private webSocket: WebSocket | null = null;\n private currentStatus: SyncStatus = SyncStatus.Disconnected;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private reconnectAttempts = 0;\n private pingTimer: ReturnType<typeof setInterval> | null = null;\n private pushTimer: ReturnType<typeof setTimeout> | null = null;\n private isPushing = false;\n private isDestroyed = false;\n private pendingBatches: Map<string, number[]> = new Map(); // batchId -> localIds\n private messageQueue: Promise<void> = Promise.resolve();\n\n /**\n * Creates a new Sync instance.\n *\n * @param storage - Local storage coordinator.\n * @param options - Configuration options for sync and connection.\n */\n constructor(storage: Storage, options: SyncOptions) {\n if (!options.appId) {\n throw new TetherClientError(\n TetherClientErrorCode.MissingConfiguration,\n 'Missing required appId in SyncOptions',\n );\n }\n if (!options.clientId) {\n throw new TetherClientError(\n TetherClientErrorCode.MissingConfiguration,\n 'Missing required clientId in SyncOptions',\n );\n }\n this.url = options.url;\n this.token = options.token;\n this.storage = storage;\n this.appId = options.appId;\n this.clientId = options.clientId;\n this.options = {\n reconnectIntervalMs: 1000,\n maxReconnectIntervalMs: 30000,\n pingIntervalMs: 30000,\n ...options,\n };\n\n if (\n typeof window !== 'undefined' &&\n typeof window.addEventListener === 'function'\n ) {\n window.addEventListener('online', this.handleOnline);\n window.addEventListener('offline', this.handleOffline);\n }\n\n if (this.token && this.url) {\n this.connect();\n }\n }\n\n /**\n * Current operational status of the sync coordinator.\n */\n get status(): SyncStatus {\n return this.currentStatus;\n }\n\n /**\n * Initiates a WebSocket connection to the sync endpoint and sends authentication.\n *\n * @param token - Optional session token to connect with.\n * @param url - Optional WebSocket URL override.\n */\n connect(token?: string, url?: string): void {\n if (token) this.token = token;\n if (url) this.url = url;\n\n if (!this.url) {\n return;\n }\n\n if (this.webSocket) {\n this.disconnect();\n }\n\n this.setStatus(SyncStatus.Connecting);\n\n const wsUrl = this.url;\n const WebSocketImpl =\n this.options.webSocketClass ??\n (typeof WebSocket !== 'undefined' ? WebSocket : null);\n\n if (!WebSocketImpl) {\n this.setStatus(SyncStatus.Error);\n this.onError.publish(\n new TetherClientError(\n TetherClientErrorCode.MissingConfiguration,\n 'No WebSocket implementation available in this environment',\n ),\n );\n return;\n }\n\n try {\n this.webSocket = new WebSocketImpl(wsUrl);\n } catch (err) {\n this.setStatus(SyncStatus.Error);\n this.onError.publish(\n new TetherClientError(\n TetherClientErrorCode.NetworkError,\n err instanceof Error\n ? err.message\n : 'Failed to construct WebSocket connection',\n ),\n );\n this.scheduleReconnect();\n return;\n }\n\n this.webSocket.onopen = () => {\n this.reconnectAttempts = 0;\n this.startPing();\n this.sendAuth();\n };\n\n this.webSocket.onmessage = (event) => {\n try {\n const raw =\n typeof event.data === 'string'\n ? event.data\n : event.data.toString('utf8');\n const msg: ServerMessage = JSON.parse(raw);\n this.messageQueue = this.messageQueue\n .then(() => this.handleServerMessage(msg))\n .catch((err) => {\n this.onError.publish(\n new TetherClientError(\n TetherClientErrorCode.SyncError,\n err instanceof Error\n ? err.message\n : 'Failed to process incoming WebSocket message',\n ),\n );\n });\n } catch (err) {\n this.onError.publish(\n new TetherClientError(\n TetherClientErrorCode.SyncError,\n err instanceof Error\n ? err.message\n : 'Failed to parse incoming WebSocket message',\n ),\n );\n }\n };\n\n this.webSocket.onerror = () => {\n this.onError.publish(\n new TetherClientError(\n TetherClientErrorCode.NetworkError,\n 'WebSocket connection encountered an error',\n ),\n );\n };\n\n this.webSocket.onclose = (event) => {\n this.stopPing();\n this.pendingBatches.clear();\n this.messageQueue = Promise.resolve();\n this.webSocket = null;\n if (!this.isDestroyed) {\n this.setStatus(SyncStatus.Disconnected);\n if (event.code !== 1000 && event.code !== 1005) {\n this.scheduleReconnect();\n }\n }\n };\n }\n\n /**\n * Disconnects the active WebSocket connection.\n */\n disconnect(): void {\n this.stopPing();\n this.pendingBatches.clear();\n this.messageQueue = Promise.resolve();\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.webSocket) {\n const ws = this.webSocket;\n this.webSocket = null;\n ws.onopen = null;\n ws.onmessage = null;\n ws.onerror = () => {};\n ws.onclose = null;\n try {\n const wsWithTerminate = ws as unknown as { terminate?: () => void };\n if (typeof wsWithTerminate.terminate === 'function') {\n wsWithTerminate.terminate();\n } else {\n ws.close();\n }\n } catch {\n // Ignored during intentional disconnect\n }\n }\n this.setStatus(SyncStatus.Disconnected);\n }\n\n /**\n * Permanently tears down the sync coordinator and cancels reconnection timers.\n */\n destroy(): void {\n this.isDestroyed = true;\n if (\n typeof window !== 'undefined' &&\n typeof window.removeEventListener === 'function'\n ) {\n window.removeEventListener('online', this.handleOnline);\n window.removeEventListener('offline', this.handleOffline);\n }\n this.disconnect();\n }\n\n /**\n * Schedules a debounced push of pending outbox mutations.\n *\n * @param delayMs - Optional override for debounce delay in milliseconds.\n */\n schedulePush(delayMs?: number): void {\n const delay = delayMs ?? this.options.pushDebounceMs ?? 10;\n if (this.pushTimer) clearTimeout(this.pushTimer);\n this.pushTimer = setTimeout(() => {\n this.pushTimer = null;\n this.pushOutbox();\n }, delay);\n }\n\n /**\n * Immediately extracts queued outbox changes and transmits them to the server.\n */\n async pushOutbox(): Promise<void> {\n if (\n this.isPushing ||\n this.pendingBatches.size > 0 ||\n this.currentStatus !== SyncStatus.Connected ||\n !this.webSocket ||\n this.webSocket.readyState !== (this.webSocket.OPEN ?? 1)\n ) {\n return;\n }\n\n this.isPushing = true;\n try {\n const pending = await this.storage.getPendingOutbox(500);\n if (pending.length === 0) return;\n\n const batchId = `batch_${Math.random().toString(36).substring(2, 10)}`;\n const localIds: number[] = [];\n const changes: ChangeRecord[] = [];\n\n for (const entry of pending) {\n if (entry.localId !== undefined) {\n localIds.push(entry.localId);\n }\n changes.push(entry.change);\n }\n\n this.pendingBatches.set(batchId, localIds);\n\n this.send({\n type: ClientMessageType.ChangeBatch,\n clientId: this.clientId,\n batchId,\n changes,\n });\n } catch (err) {\n this.onError.publish(\n new TetherClientError(\n TetherClientErrorCode.SyncError,\n err instanceof Error\n ? err.message\n : 'Failed to push outbox batch to server',\n ),\n );\n } finally {\n this.isPushing = false;\n }\n }\n\n // -- Private Helpers ------------------------------------------------------\n\n private setStatus(newStatus: SyncStatus) {\n if (this.currentStatus === newStatus) return;\n this.currentStatus = newStatus;\n this.onStatusChange.publish(newStatus);\n }\n\n private startPing() {\n this.stopPing();\n const interval = this.options.pingIntervalMs ?? 30000;\n if (interval <= 0) return;\n\n this.pingTimer = setInterval(() => {\n this.send({ type: ClientMessageType.Ping });\n }, interval);\n }\n\n private stopPing() {\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n }\n\n private isOnline(): boolean {\n if (\n typeof navigator !== 'undefined' &&\n typeof navigator.onLine === 'boolean'\n ) {\n return navigator.onLine;\n }\n return true;\n }\n\n private handleOnline = () => {\n if (this.isDestroyed || !this.token || !this.url) return;\n this.reconnectAttempts = 0;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n this.connect();\n };\n\n private handleOffline = () => {\n if (this.isDestroyed) return;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.webSocket) {\n this.disconnect();\n }\n };\n\n private scheduleReconnect() {\n if (this.reconnectTimer || this.isDestroyed || !this.isOnline()) return;\n const base = this.options.reconnectIntervalMs ?? 1000;\n const max = this.options.maxReconnectIntervalMs ?? 30000;\n const delay = Math.min(base * 2 ** this.reconnectAttempts, max);\n this.reconnectAttempts++;\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, delay);\n }\n\n private send(msg: ClientMessage) {\n if (\n this.webSocket &&\n this.webSocket.readyState === (this.webSocket.OPEN ?? 1)\n ) {\n this.webSocket.send(JSON.stringify(msg));\n }\n }\n\n private async sendAuth() {\n const lastSyncSeq =\n (await this.storage.getMeta<number>('lastSyncSeq')) ?? 0;\n this.send({\n type: ClientMessageType.Auth,\n protocolVersion: PROTOCOL_VERSION,\n token: this.token ?? '',\n appId: this.options.appId,\n clientId: this.clientId,\n lastSyncSeq,\n });\n }\n\n private async handleServerMessage(msg: ServerMessage) {\n switch (msg.type) {\n case ServerMessageType.AuthSuccess: {\n this.reconnectAttempts = 0;\n this.setStatus(SyncStatus.Connected);\n if (msg.token) {\n this.token = msg.token;\n this.options.onTokenRefresh?.(msg.token);\n }\n this.startPing();\n await this.pushOutbox();\n break;\n }\n case ServerMessageType.AuthError: {\n this.setStatus(SyncStatus.Error);\n this.disconnect();\n this.options.onAuthError?.(msg.message);\n this.onError.publish(\n new TetherClientError(\n TetherClientErrorCode.AuthenticationFailed,\n msg.message,\n ),\n );\n break;\n }\n case ServerMessageType.SyncSnapshot: {\n await this.handleSnapshot(msg.snapshot, msg.seq);\n await this.pushOutbox();\n break;\n }\n case ServerMessageType.SyncDiff: {\n await this.handleDiff(msg.changes, msg.toSeq);\n await this.pushOutbox();\n break;\n }\n case ServerMessageType.BroadcastChanges: {\n await this.handleDiff(msg.changes, msg.seq);\n break;\n }\n case ServerMessageType.ChangeAck: {\n const localIds = this.pendingBatches.get(msg.batchId);\n if (localIds) {\n this.pendingBatches.delete(msg.batchId);\n await this.storage.removeOutboxEntries(localIds);\n }\n if (msg.appliedSeq !== undefined) {\n await this.storage.setMeta('lastSyncSeq', msg.appliedSeq);\n }\n await this.pushOutbox();\n break;\n }\n case ServerMessageType.Pong:\n break;\n case ServerMessageType.Error:\n this.onError.publish(\n new TetherClientError(TetherClientErrorCode.SyncError, msg.message),\n );\n break;\n }\n }\n\n private async handleSnapshot(\n records: SnapshotRecord[],\n seq: number,\n ): Promise<void> {\n await this.storage.applySnapshotBatch(records, seq);\n this.notifyTableRemoteEvents(\n records.map((item) => ({\n table: item.table,\n id: item.id,\n isDelete: item.deleted ?? false,\n data: item.data,\n })),\n );\n }\n\n private async handleDiff(\n changes: ChangeRecord[],\n seq: number,\n ): Promise<void> {\n await this.storage.applyRemoteChangesBatch(changes, seq);\n this.notifyTableRemoteEvents(\n changes.map((change) => ({\n table: change.table,\n id: change.id,\n isDelete: change.op === OperationType.Delete,\n data: change.data,\n })),\n );\n }\n\n private notifyTableRemoteEvents(\n items: Array<{\n table: string;\n id: string;\n isDelete: boolean;\n data?: unknown;\n }>,\n ): void {\n const tableEvents = new Map<\n string,\n Array<{ op: OperationType; id: string; data?: unknown; isRemote: true }>\n >();\n\n for (const item of items) {\n let events = tableEvents.get(item.table);\n if (!events) {\n events = [];\n tableEvents.set(item.table, events);\n }\n events.push({\n op: item.isDelete ? OperationType.Delete : OperationType.Put,\n id: item.id,\n data: item.isDelete ? undefined : item.data,\n isRemote: true,\n });\n }\n\n for (const [tableName, events] of tableEvents.entries()) {\n const table = this.storage.table(tableName);\n table.notifyRemoteChanges(events);\n }\n }\n}\n","import { normalizeBasePath } from '../shared/path.js';\nimport {\n Auth,\n AuthStatus,\n type LoginOptions,\n type LogoutOptions,\n type RegisterOptions,\n} from './auth.js';\nimport type { TetherClientError } from './errors.js';\nimport { EventRegistry } from './shared/event.js';\nimport { Storage } from './storage.js';\nimport { Sync, type SyncStatus, type WebSocketConstructor } from './sync.js';\nimport type { Table } from './table.js';\n\nexport {\n AuthStatus,\n DataMode,\n type LoginOptions,\n type LogoutOptions,\n type RegisterOptions,\n} from './auth.js';\nexport { TetherClientError, TetherClientErrorCode } from './errors.js';\n\n/**\n * Options for configuring a TetherClient database instance.\n */\nexport interface TetherClientOptions {\n /** Application namespace identifier (defaults to database name). */\n appId?: string;\n /**\n * Unified connection URL for the remote server (e.g. `'http://localhost:8080'`, `'https://api.example.com/db'`,\n * `'ws://localhost:8080/sync'`, or `'wss://api.example.com/db/sync'`). When provided, host, port, secure protocol,\n * and base path are extracted automatically and can be overridden by explicit options below.\n */\n url?: string;\n /** Remote server host or hostname (e.g. 'localhost' or 'api.example.com'). Defaults to current browser hostname. */\n host?: string;\n /** Remote server port number (e.g. 8080). Defaults to current browser port. */\n port?: number;\n /** Whether to use secure HTTPS and WSS protocols (defaults to `true` in secure browser contexts, `false` otherwise). */\n secure?: boolean;\n /** Base path for HTTP REST endpoints (defaults to ''). */\n basePath?: string;\n /** Path for WebSocket upgrade requests (defaults to `${basePath}/sync`). */\n webSocketPath?: string;\n /** Optional custom fetch implementation for authentication requests. */\n fetch?: typeof fetch;\n /** Custom WebSocket constructor for streaming requests. */\n webSocketClass?: WebSocketConstructor;\n /** Debounce delay in milliseconds before pushing queued local outbox changes (defaults to 10). */\n pushDebounceMs?: number;\n /** Initial reconnection backoff delay in milliseconds (defaults to 1000). */\n reconnectIntervalMs?: number;\n /** Maximum reconnection backoff delay in milliseconds (defaults to 30000). */\n maxReconnectIntervalMs?: number;\n /** Periodic keepalive ping interval in milliseconds (defaults to 30000). Set to 0 to disable. */\n pingIntervalMs?: number;\n}\n\n/**\n * Main client-side database entry point providing reactive IndexedDB tables,\n * local-first storage, automatic auth lifecycle, and background synchronization.\n */\nexport class TetherClient {\n /** Reactive event registry triggered whenever the authentication status changes. */\n readonly onAuthStatusChange = new EventRegistry<AuthStatus>();\n /** Reactive event registry triggered whenever the synchronization status changes. */\n readonly onSyncStatusChange = new EventRegistry<SyncStatus>();\n /** Reactive event registry triggered whenever background sync or network errors occur. */\n readonly onError = new EventRegistry<TetherClientError>();\n private readonly storage: Storage;\n private readonly auth: Auth;\n private readonly sync: Sync;\n\n /**\n * Initializes a new TetherClient instance and wires reactive auth & sync coordination.\n *\n * @param name - Name of the local IndexedDB database.\n * @param options - Configuration options for the server connection and sync behavior.\n */\n constructor(name: string, options: TetherClientOptions = {}) {\n this.storage = this.createStorage(name);\n this.auth = this.createAuth(options, this.storage);\n this.sync = this.createSync(name, options, this.storage);\n\n // Push local changes reactively.\n this.storage.onLocalChange.register(() => {\n this.sync.schedulePush();\n });\n\n // Coordinate auth and sync lifecycle reactively.\n this.auth.onStatusChange.register((status) => {\n this.onAuthStatusChange.publish(status);\n if (status === AuthStatus.SignedIn && this.auth.token) {\n this.sync.connect(this.auth.token);\n } else {\n this.sync.disconnect();\n }\n });\n\n this.sync.onStatusChange.register((status) => {\n this.onSyncStatusChange.publish(status);\n });\n\n this.sync.onError.register((err) => {\n this.onError.publish(err);\n });\n }\n\n // -- Database / Storage ------------------------------------------------------\n\n /**\n * Obtains a typed table reference for reading, mutating, and subscribing to records.\n * Tables are created dynamically on-demand if not already declared.\n *\n * @typeParam T - Data payload model type for records in this table.\n * @param name - The table name.\n * @returns A typed `Table<T>` instance.\n */\n table<T = unknown>(name: string): Table<T> {\n return this.storage.table<T>(name);\n }\n\n /**\n * Clears all local application tables, outbox entries, and sync metadata.\n */\n async clear(): Promise<void> {\n await this.storage.clearAllData();\n }\n\n /**\n * Closes active synchronization connections and closes the IndexedDB database handle.\n */\n async close(): Promise<void> {\n this.sync.destroy();\n await this.storage.close();\n }\n\n // -- Authentication ------------------------------------------------------\n\n /**\n * Current authentication lifecycle status.\n */\n get authStatus(): AuthStatus {\n return this.auth.status;\n }\n\n /**\n * The authenticated user's username, or `undefined` if signed out.\n */\n get username(): string | undefined {\n return this.auth.username;\n }\n\n /**\n * Registers a new user account, applies local data mode, and connects synchronization.\n *\n * @param options - Registration credentials and data reconciliation settings.\n * @returns `true` if registration succeeded; `false` otherwise.\n */\n async register(options: RegisterOptions): Promise<boolean> {\n return this.auth.register(options);\n }\n\n /**\n * Logs into an account (or reconnects using remembered credentials), applies data mode, and connects synchronization.\n *\n * @param options - Optional login credentials and data reconciliation mode.\n * @returns `true` if authentication succeeded; `false` otherwise.\n */\n async login(options: LoginOptions = {}): Promise<boolean> {\n return this.auth.login(options);\n }\n\n /**\n * Logs out of the current session, disconnects sync, and optionally clears local tables.\n *\n * @param options - Options controlling whether local data should be wiped.\n * @returns `true` on successful logout.\n */\n async logout(options: LogoutOptions = {}): Promise<boolean> {\n return this.auth.logout(options);\n }\n\n // -- Synchronization ------------------------------------------------------\n\n /**\n * Retrieves the current synchronization status (e.g. Connected, Connecting, Disconnected, Error).\n */\n get syncStatus(): SyncStatus {\n return this.sync.status;\n }\n\n // -- Private Helpers ------------------------------------------------------\n\n private createStorage(name: string): Storage {\n return new Storage(name);\n }\n\n private createAuth(options: TetherClientOptions, storage: Storage): Auth {\n return new Auth({\n baseUrl: this.resolveBaseUrl(options),\n storage,\n fetchFn: options.fetch,\n });\n }\n\n private createSync(\n name: string,\n options: TetherClientOptions,\n storage: Storage,\n ): Sync {\n return new Sync(storage, {\n url: this.resolveWebSocketUrl(options),\n appId: options.appId ?? name,\n clientId: storage.clientId,\n webSocketClass: options.webSocketClass,\n pushDebounceMs: options.pushDebounceMs,\n reconnectIntervalMs: options.reconnectIntervalMs,\n maxReconnectIntervalMs: options.maxReconnectIntervalMs,\n pingIntervalMs: options.pingIntervalMs,\n onTokenRefresh: (token) => {\n this.auth.handleTokenRefresh(token);\n },\n onAuthError: (message) => {\n this.auth.handleAuthError(message);\n },\n });\n }\n\n private parseUrl(urlStr?: string): {\n host?: string;\n port?: number;\n secure?: boolean;\n basePath?: string;\n webSocketPath?: string;\n } {\n if (!urlStr) return {};\n try {\n const isWs = /^wss?:/i.test(urlStr);\n const isSecure = /^https:|^wss:/i.test(urlStr);\n const httpNormalized = urlStr.replace(/^ws(s)?:/i, 'http$1:');\n const parsed = new URL(httpNormalized);\n\n const host = parsed.hostname || undefined;\n const port = parsed.port ? Number.parseInt(parsed.port, 10) : undefined;\n const pathname = parsed.pathname;\n\n let basePath: string | undefined;\n let webSocketPath: string | undefined;\n\n if (isWs) {\n if (pathname === '/sync' || pathname === '/sync/') {\n basePath = '';\n webSocketPath = '/sync';\n } else if (pathname.endsWith('/sync') || pathname.endsWith('/sync/')) {\n basePath = pathname.replace(/\\/sync\\/?$/, '');\n webSocketPath = pathname.replace(/\\/$/, '');\n } else if (pathname && pathname !== '/') {\n basePath = pathname.replace(/\\/$/, '');\n webSocketPath = `${basePath}/sync`;\n }\n } else {\n if (pathname && pathname !== '/') {\n basePath = pathname.replace(/\\/$/, '');\n } else {\n basePath = '';\n }\n }\n\n return {\n host,\n port,\n secure: isSecure,\n basePath,\n webSocketPath,\n };\n } catch {\n return {};\n }\n }\n\n private resolveHostHeader(options: TetherClientOptions): {\n host?: string;\n secure: boolean;\n } {\n const urlConfig = this.parseUrl(options.url);\n const isBrowser = typeof window !== 'undefined' && Boolean(window.location);\n const host =\n options.host ??\n urlConfig.host ??\n (isBrowser && window.location.hostname !== ''\n ? window.location.hostname\n : undefined);\n const port =\n options.port ??\n urlConfig.port ??\n (isBrowser && window.location.port\n ? Number.parseInt(window.location.port, 10)\n : undefined);\n const secure =\n options.secure ??\n urlConfig.secure ??\n (isBrowser ? window.location.protocol === 'https:' : false);\n\n if (!host) {\n return { host: undefined, secure };\n }\n\n const hostHeader =\n port !== undefined && !host.includes(':') ? `${host}:${port}` : host;\n return { host: hostHeader, secure };\n }\n\n private resolveBaseUrl(options: TetherClientOptions): string {\n const urlConfig = this.parseUrl(options.url);\n const basePath = normalizeBasePath(\n options.basePath ?? urlConfig.basePath ?? '',\n );\n const { host, secure } = this.resolveHostHeader(options);\n if (!host) return basePath;\n const proto = secure ? 'https' : 'http';\n return `${proto}://${host}${basePath}`;\n }\n\n private resolveWebSocketUrl(\n options: TetherClientOptions,\n ): string | undefined {\n const urlConfig = this.parseUrl(options.url);\n const { host, secure } = this.resolveHostHeader(options);\n if (!host) return undefined;\n const basePath = normalizeBasePath(\n options.basePath ?? urlConfig.basePath ?? '',\n );\n const webSocketPath =\n options.webSocketPath ?? urlConfig.webSocketPath ?? `${basePath}/sync`;\n const proto = secure ? 'wss' : 'ws';\n return `${proto}://${host}${webSocketPath}`;\n }\n}\n"],"mappings":";AAGO,IAAK,gBAAL,kBAAKA,mBAAL;AAEL,EAAAA,eAAA,SAAM;AAEN,EAAAA,eAAA,YAAS;AAJC,SAAAA;AAAA,GAAA;AAgEL,IAAM,mBAAmB;;;AChEzB,IAAK,wBAAL,kBAAKC,2BAAL;AAEL,EAAAA,8CAAA;AAEA,EAAAA,8CAAA;AAEA,EAAAA,8CAAA;AAEA,EAAAA,8CAAA;AAEA,EAAAA,8CAAA;AAEA,EAAAA,8CAAA;AAEA,EAAAA,8CAAA;AAEA,EAAAA,8CAAA;AAEA,EAAAA,8CAAA;AAlBU,SAAAA;AAAA,GAAA;AAyBL,IAAM,oBAAN,cAAgC,MAAM;AAAA;AAAA,EAElC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQT,YAAY,MAA6B,SAAkB;AACzD,UAAM,WAAW,6BAA6B,IAAI,CAAC;AACnD,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AACF;AAIA,SAAS,6BAA6B,MAAqC;AACzE,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,EACX;AACF;;;ACzDO,IAAM,gBAAN,MAA8B;AAAA,EAClB,YAAmC,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ5D,SAAS,UAAwC;AAC/C,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM;AACX,WAAK,UAAU,OAAO,QAAQ;AAAA,IAChC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,WAAW,MAAuD;AAChE,UAAM,QAAQ,KAAK,CAAC;AACpB,eAAW,YAAY,KAAK,WAAW;AACrC,UAAI;AACF,iBAAS,KAAK;AAAA,MAChB,SAAS,KAAK;AACZ,gBAAQ,MAAM,oBAAoB,GAAG;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AACF;;;ACrCO,IAAK,WAAL,kBAAKC,cAAL;AAEL,EAAAA,oBAAA;AAEA,EAAAA,oBAAA;AAEA,EAAAA,oBAAA;AAEA,EAAAA,oBAAA;AARU,SAAAA;AAAA,GAAA;AAcL,IAAK,aAAL,kBAAKC,gBAAL;AAEL,EAAAA,wBAAA;AAEA,EAAAA,wBAAA;AAEA,EAAAA,wBAAA;AAEA,EAAAA,wBAAA;AARU,SAAAA;AAAA,GAAA;AAiFL,IAAM,OAAN,MAAW;AAAA;AAAA,EAEP;AAAA;AAAA,EAEA,iBAAiB,IAAI,cAA0B;AAAA,EAEhD;AAAA,EACA;AAAA,EACA,oBAAgC;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,cAAgC;AAC1C,SAAK,UAAU,aAAa;AAC5B,SAAK,UAAU,aAAa;AAE5B,UAAM,WACJ,aAAa,YACZ,OAAO,eAAe,eAAe,WAAW,QAC7C,WAAW,QACX,OAAO,UAAU,cACf,QACA;AAER,QAAI,CAAC,UAAU;AACb,YAAM,IAAI;AAAA;AAAA,QAER;AAAA,MACF;AAAA,IACF;AACA,SAAK,UAAU,SAAS,KAAK,UAAU;AACvC,SAAK,qBAAqB,KAAK,eAAe;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,SAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,WAA+B;AACjC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,SAA6B;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,QAA4B;AAC9B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAgC;AACpC,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,QAAQ,QAA2B,MAAM;AACpE,UAAI,SAAS,SAAS,QAAQ,UAAU;AACtC,aAAK,gBAAgB,QAAQ;AAC7B,aAAK,kBAAkB,QAAQ;AAC/B,aAAK,eAAe,QAAQ;AAC5B,aAAK,UAAU,gBAAmB;AAAA,MACpC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,SAA4C;AACzD,QAAI,CAAC,SAAS,YAAY,CAAC,SAAS,UAAU;AAC5C,YAAM,IAAI;AAAA;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAEA,UAAM,iBAAiB,KAAK;AAC5B,SAAK,UAAU,iBAAoB;AAEnC,QAAI;AACF,YAAM,kBACJ,mBAAmB,mBACf,gBACA;AACN,YAAM,WAAW,QAAQ,YAAY;AAErC,YAAM,OAAO,MAAM,KAAK;AAAA,QACtB;AAAA,QACA,EAAE,UAAU,QAAQ,UAAU,UAAU,QAAQ,SAAS;AAAA;AAAA,QAEzD;AAAA,MACF;AAEA,WAAK,gBAAgB,KAAK;AAC1B,WAAK,kBAAkB,KAAK;AAC5B,WAAK,eAAe,KAAK;AAEzB,YAAM,KAAK,cAAc,QAAQ;AACjC,YAAM,KAAK,iBAAiB,QAAQ,YAAY,OAAO,IAAI;AAE3D,WAAK,UAAU,gBAAmB;AAClC,aAAO;AAAA,IACT,QAAQ;AACN,WAAK,UAAU,aAAgB;AAC/B,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,UAAwB,CAAC,GAAqB;AACxD,UAAM,KAAK;AAEX,SAAK,UAAU,iBAAoB;AAEnC,QAAI;AACF,UAAI,QAAQ,KAAK;AACjB,UAAI,WAAW,KAAK;AACpB,UAAI,SAAS,KAAK;AAElB,UAAI,QAAQ,YAAY,QAAQ,UAAU;AACxC,cAAM,OAAO,MAAM,KAAK;AAAA,UACtB;AAAA,UACA,EAAE,UAAU,QAAQ,UAAU,UAAU,QAAQ,SAAS;AAAA;AAAA,UAEzD;AAAA,QACF;AAEA,gBAAQ,KAAK;AACb,mBAAW,KAAK;AAChB,iBAAS,KAAK;AAEd,cAAM,KAAK,iBAAiB,QAAQ,YAAY,OAAO,IAAI;AAAA,MAC7D,WAAW,CAAC,OAAO;AACjB,cAAM,SAAS,MAAM,KAAK,QAAQ,QAA2B,MAAM;AACnE,YAAI,QAAQ,OAAO;AACjB,kBAAQ,OAAO;AACf,qBAAW,OAAO;AAClB,mBAAS,OAAO;AAAA,QAClB;AAAA,MACF;AAEA,UAAI,CAAC,OAAO;AACV,cAAM,IAAI;AAAA;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAEA,WAAK,eAAe;AACpB,WAAK,kBAAkB;AACvB,WAAK,gBAAgB;AAErB,YAAM,KAAK,cAAc,QAAQ,YAAY,cAAe;AAE5D,WAAK,UAAU,gBAAmB;AAClC,aAAO;AAAA,IACT,QAAQ;AACN,WAAK,UAAU,aAAgB;AAC/B,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,UAAyB,CAAC,GAAqB;AAC1D,SAAK,gBAAgB;AACrB,SAAK,kBAAkB;AACvB,SAAK,eAAe;AAEpB,UAAM,KAAK,QAAQ,WAAW,MAAM;AACpC,UAAM,KAAK,cAAc,QAAQ,YAAY,aAAc;AAE3D,SAAK,UAAU,iBAAoB;AACnC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,mBAAmB,OAA8B;AACrD,SAAK,eAAe;AACpB,UAAM,UAAU,MAAM,KAAK,QAAQ,QAA2B,MAAM;AACpE,QAAI,SAAS;AACX,YAAM,KAAK,QAAQ,QAAQ,QAAQ;AAAA,QACjC,GAAG;AAAA,QACH;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,gBAAgB,UAAkC;AACtD,SAAK,gBAAgB;AACrB,SAAK,kBAAkB;AACvB,SAAK,eAAe;AACpB,UAAM,KAAK,QAAQ,WAAW,MAAM;AACpC,SAAK,UAAU,iBAAoB;AAAA,EACrC;AAAA;AAAA,EAIA,MAAc,gBACZ,MACA,aACA,WACA,qBACqB;AACrB,UAAM,MAAM,MAAM,KAAK,QAAQ,GAAG,KAAK,OAAO,GAAG,IAAI,IAAI;AAAA,MACvD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,WAAW;AAAA,IAClC,CAAC;AAED,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,kBAAkB,WAAW,KAAK,SAAS,mBAAmB;AAAA,IAC1E;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,cAAc,UAAmC;AAC7D,QAAI,aAAa,iBAAkB,aAAa,gBAAiB;AAC/D,YAAM,KAAK,QAAQ,YAAY,IAAI;AACnC,YAAM,KAAK,QAAQ,QAAQ,eAAe,CAAC;AAAA,IAC7C;AAAA,EACF;AAAA,EAEA,MAAc,iBACZ,UACA,MACe;AACf,QAAI,UAAU;AACZ,YAAM,KAAK,QAAQ,QAAQ,QAAQ;AAAA,QACjC,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,UAAU,KAAK;AAAA,MACjB,CAAC;AAAA,IACH,OAAO;AACL,YAAM,KAAK,QAAQ,WAAW,MAAM;AAAA,IACtC;AAAA,EACF;AAAA,EAEQ,UAAU,QAA0B;AAC1C,QAAI,KAAK,sBAAsB,OAAQ;AACvC,SAAK,oBAAoB;AACzB,SAAK,eAAe,QAAQ,MAAM;AAAA,EACpC;AACF;;;AC3WO,SAAS,kBAAkB,MAAsB;AACtD,MAAI,SAAS,MAAM,SAAS,IAAK,QAAO;AACxC,MAAI,KAAK,SAAS,GAAG,EAAG,QAAO,KAAK,MAAM,GAAG,KAAK,SAAS,CAAC;AAC5D,MAAI,CAAC,KAAK,WAAW,GAAG,EAAG,QAAO,IAAI,IAAI;AAC1C,SAAO,SAAS,MAAM,KAAK;AAC7B;;;ACAO,SAAS,gBACd,UAIA,UAGS;AACT,MAAI,CAAC,SAAU,QAAO;AAEtB,MAAI,SAAS,YAAY,SAAS,WAAW;AAC3C,WAAO;AAAA,EACT;AACA,MAAI,SAAS,YAAY,SAAS,WAAW;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB,SAAS,YAAY;AAC5C,QAAM,iBAAiB,SAAS,YAAY;AAC5C,SAAO,kBAAkB;AAC3B;;;AClCO,SAAS,aAAqB;AACnC,MAAI,OAAO,WAAW,aAAa;AACjC,QAAI,OAAO,OAAO,eAAe,YAAY;AAC3C,aAAO,OAAO,WAAW;AAAA,IAC3B;AACA,QAAI,OAAO,OAAO,oBAAoB,YAAY;AAChD,aAAO,uCAAuC;AAAA,QAC5C;AAAA,QACA,CAAC,SAAS;AACR,gBAAM,MAAM,OAAO,IAAI;AACvB,gBAAM,aAAa,OAAO,gBAAgB,IAAI,WAAW,CAAC,CAAC,EAAE,CAAC;AAC9D,kBAAQ,MAAO,aAAc,MAAO,MAAM,GAAM,SAAS,EAAE;AAAA,QAC7D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,uCAAuC,QAAQ,SAAS,CAAC,SAAS;AACvE,UAAM,aAAc,KAAK,OAAO,IAAI,KAAM;AAC1C,YAAQ,SAAS,MAAM,aAAc,aAAa,IAAO,GAAK,SAAS,EAAE;AAAA,EAC3E,CAAC;AACH;;;AC+BO,IAAM,QAAN,MAAyB;AAAA;AAAA,EAErB,WAAW,IAAI,cAAqC;AAAA,EACrD;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQR,YAAY,WAAmB,SAAkB;AAC/C,SAAK,YAAY;AACjB,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,OAAe;AACjB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,WAAmB;AACrB,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,IAAI,IAAoC;AAC5C,UAAM,SAAS,MAAM,KAAK,QAAQ,UAAa,KAAK,WAAW,EAAE;AACjE,WAAO,QAAQ;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,KAA8B;AACzC,QAAI,QAAQ,QAAW;AACrB,UAAI,IAAI,WAAW,EAAG,QAAO,CAAC;AAC9B,YAAM,MAAM,MAAM,KAAK,QAAQ,WAAc,KAAK,WAAW,GAAG;AAChE,YAAM,UAAe,CAAC;AACtB,iBAAW,MAAM,KAAK;AACpB,cAAM,MAAM,IAAI,IAAI,EAAE;AACtB,YAAI,KAAK;AACP,kBAAQ,KAAK,IAAI,IAAI;AAAA,QACvB;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,UAAM,UAAU,MAAM,KAAK,QAAQ,cAAiB,KAAK,SAAS;AAClE,WAAO,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAyB;AAC7B,UAAM,UAAU,MAAM,KAAK,QAAQ,cAAiB,KAAK,SAAS;AAClE,UAAM,MAAM,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE;AACnC,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,gBAAgB,IAAkD;AACtE,WAAO,KAAK,QAAQ,UAAa,KAAK,WAAW,EAAE;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,qBAAiD;AACrD,WAAO,KAAK,QAAQ,cAAiB,KAAK,SAAS;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,IAAI,IAAY,MAAqB;AACzC,UAAM,KAAK,OAAO,CAAC,EAAE,IAAI,KAAK,CAAC,CAAC;AAChC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OAAO,SAA2C;AACtD,QAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAElC,UAAM,MAAM,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE;AACnC,UAAM,cAAc,MAAM,KAAK,QAAQ,WAAc,KAAK,WAAW,GAAG;AACxE,UAAM,MAAM,KAAK,IAAI;AAErB,UAAM,YAAoC,CAAC;AAC3C,UAAM,SAAgC,CAAC;AACvC,UAAM,YAAiB,CAAC;AAExB,eAAW,SAAS,SAAS;AAC3B,YAAM,EAAE,UAAU,MAAM,IAAI,KAAK;AAAA,QAC/B,MAAM;AAAA;AAAA,QAEN,YAAY,IAAI,MAAM,EAAE;AAAA,QACxB;AAAA,QACA,MAAM;AAAA,MACR;AACA,gBAAU,KAAK,QAAQ;AACvB,aAAO,KAAK,KAAK;AACjB,gBAAU,KAAK,MAAM,IAAI;AAAA,IAC3B;AAEA,UAAM,KAAK,QAAQ,kBAAkB,KAAK,WAAW,SAAS;AAC9D,SAAK,SAAS,QAAQ,MAAM;AAC5B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,IAA8B;AACzC,UAAM,eAAe,MAAM,KAAK,UAAU,CAAC,EAAE,CAAC;AAC9C,WAAO,eAAe;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,UAAU,KAAgC;AAC9C,QAAI,IAAI,WAAW,EAAG,QAAO;AAE7B,UAAM,cAAc,MAAM,KAAK,QAAQ,WAAc,KAAK,WAAW,GAAG;AACxE,UAAM,MAAM,KAAK,IAAI;AAErB,UAAM,YAAoC,CAAC;AAC3C,UAAM,SAAgC,CAAC;AAEvC,eAAW,MAAM,KAAK;AACpB,YAAM,WAAW,YAAY,IAAI,EAAE;AACnC,UAAI,CAAC,YAAY,SAAS,QAAS;AAEnC,YAAM,EAAE,UAAU,MAAM,IAAI,KAAK;AAAA,QAC/B;AAAA;AAAA,QAEA;AAAA,QACA;AAAA,MACF;AACA,gBAAU,KAAK,QAAQ;AACvB,aAAO,KAAK,KAAK;AAAA,IACnB;AAEA,QAAI,UAAU,SAAS,GAAG;AACxB,YAAM,KAAK,QAAQ,kBAAkB,KAAK,WAAW,SAAS;AAC9D,WAAK,SAAS,QAAQ,MAAM;AAAA,IAC9B;AAEA,WAAO,UAAU;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,aAAa,UAA4C;AACvD,QAAI,WAAW;AACf,QAAI,iBAAiB;AACrB,UAAM,iBAAiB,MAAM;AAC3B,YAAM,UAAU,EAAE;AAClB,WAAK,OAAO,EACT,KAAK,CAAC,UAAU;AACf,YAAI,YAAY,YAAY,gBAAgB;AAC1C,mBAAS,KAAK;AAAA,QAChB;AAAA,MACF,CAAC,EACA,MAAM,MAAM;AAAA,MAEb,CAAC;AAAA,IACL;AAEA,mBAAe;AACf,UAAM,cAAc,KAAK,SAAS,SAAS,MAAM;AAC/C,qBAAe;AAAA,IACjB,CAAC;AAED,WAAO,MAAM;AACX,iBAAW;AACX,kBAAY;AAAA,IACd;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAoB,QAA2C;AAC7D,QAAI,OAAO,WAAW,EAAG;AACzB,SAAK,SAAS,QAAQ,MAA+B;AAAA,EACvD;AAAA;AAAA,EAIQ,mBACN,IACA,IACA,UACA,KACA,MACgE;AAChE,UAAM,WAAW,UAAU,WAAW,KAAK;AAC3C,UAAM,YAAY,KAAK,IAAI,MAAM,UAAU,aAAa,KAAK,CAAC;AAC9D,UAAM,SAA0B;AAAA,MAC9B,OAAO,KAAK;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,KAAK;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,MACL,UAAU,EAAE,IAAI,IAAI,MAAM,OAAO;AAAA,MACjC,OAAO,EAAE,IAAI,IAAI,MAAM,UAAU,MAAM;AAAA,IACzC;AAAA,EACF;AACF;;;ACjRO,IAAM,UAAN,MAAc;AAAA;AAAA,EAEV;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA,gBAAgB,IAAI,cAAoB;AAAA,EACzC,kBAA+C;AAAA,EAC/C,cAA6B,QAAQ,QAAQ;AAAA,EAC7C,SAAsC,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOtD,YAAY,MAAc;AACxB,SAAK,OAAO;AACZ,SAAK,WAAW,WAAW;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAmB,MAAwB;AACzC,QAAI,QAAQ,KAAK,OAAO,IAAI,IAAI;AAChC,QAAI,CAAC,OAAO;AACV,cAAQ,IAAI,MAAM,MAAM,IAAI;AAC5B,WAAK,OAAO,IAAI,MAAM,KAAK;AAAA,IAC7B;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cAAoC;AACxC,QAAI,KAAK,gBAAiB,QAAO,KAAK;AACtC,SAAK,kBAAkB,KAAK,aAAa,EAAE,MAAM,CAAC,QAAQ;AACxD,WAAK,kBAAkB;AACvB,YAAM;AAAA,IACR,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAa,YAAqC;AACtD,SAAK,cAAc,KAAK,YAAY,KAAK,YAAY;AACnD,YAAM,WAAW,MAAM,KAAK,YAAY;AACxC,YAAM,UAAU,WAAW;AAAA,QACzB,CAAC,MAAM,CAAC,SAAS,iBAAiB,SAAS,CAAC;AAAA,MAC9C;AACA,UAAI,QAAQ,WAAW,EAAG;AAC1B,YAAM,cAAc,SAAS,UAAU;AACvC,WAAK,kBAAkB,KAAK;AAAA,QAC1B;AAAA,QACA;AAAA,QACA;AAAA,MACF,EAAE,MAAM,CAAC,QAAQ;AACf,aAAK,kBAAkB;AACvB,cAAM;AAAA,MACR,CAAC;AACD,YAAM,KAAK;AAAA,IACb,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YAAY,WAAkC;AAClD,UAAM,KAAK,aAAa,CAAC,SAAS,CAAC;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,QAAqB,KAAqC;AAC9D,WAAO,KAAK,aAAa,OAAO,aAAa;AAC3C,YAAM,QAAQ,MAAM;AAAA,QAGlB,SACG,YAAY,YAAY,UAAU,EAClC,YAAY,UAAU,EACtB,IAAI,GAAG;AAAA,MACZ;AACA,aAAO,OAAO;AAAA,IAChB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQ,KAAa,OAA+B;AACxD,WAAO,KAAK,aAAa,OAAO,aAAa;AAC3C,YAAM;AAAA,QACJ,SACG,YAAY,YAAY,WAAW,EACnC,YAAY,UAAU,EACtB,IAAI,EAAE,KAAK,MAAM,CAAC;AAAA,MACvB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,WAAW,KAA4B;AAC3C,WAAO,KAAK,aAAa,OAAO,aAAa;AAC3C,YAAM;AAAA,QACJ,SACG,YAAY,YAAY,WAAW,EACnC,YAAY,UAAU,EACtB,OAAO,GAAG;AAAA,MACf;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,UACJ,WACA,IACsC;AACtC,UAAM,KAAK,YAAY,SAAS;AAChC,WAAO,KAAK,aAAa,OAAO,aAAa;AAC3C,aAAO;AAAA,QACL,SACG,YAAY,WAAW,UAAU,EACjC,YAAY,SAAS,EACrB,IAAI,EAAE;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,WACJ,WACA,KACuC;AACvC,QAAI,IAAI,WAAW,EAAG,QAAO,oBAAI,IAAI;AACrC,UAAM,KAAK,YAAY,SAAS;AAChC,WAAO,KAAK,aAAa,OAAO,aAAa;AAC3C,YAAM,KAAK,SAAS,YAAY,WAAW,UAAU;AACrD,YAAM,QAAQ,GAAG,YAAY,SAAS;AACtC,YAAM,UAAU,oBAAI,IAA6B;AACjD,iBAAW,MAAM,KAAK;AACpB,cAAM,UAAU,MAAM,IAAI,EAAE;AAC5B,gBAAQ,YAAY,MAAM;AACxB,cAAI,QAAQ,QAAQ;AAClB,oBAAQ,IAAI,IAAI,QAAQ,MAAM;AAAA,UAChC;AAAA,QACF;AAAA,MACF;AACA,YAAM,qBAAqB,EAAE;AAC7B,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,cACJ,WAC4B;AAC5B,UAAM,KAAK,YAAY,SAAS;AAChC,WAAO,KAAK,aAAa,OAAO,aAAa;AAC3C,YAAM,UAAU,MAAM;AAAA,QACpB,SACG,YAAY,WAAW,UAAU,EACjC,YAAY,SAAS,EACrB,OAAO;AAAA,MACZ;AACA,aAAO,WAAW,CAAC;AAAA,IACrB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,kBACJ,WACA,WAC4B;AAC5B,QAAI,UAAU,WAAW,EAAG,QAAO,CAAC;AACpC,UAAM,KAAK,YAAY,SAAS;AAChC,WAAO,KAAK,aAAa,OAAO,aAAa;AAC3C,YAAM,KAAK,SAAS,YAAY,CAAC,WAAW,YAAY,GAAG,WAAW;AACtE,YAAM,YAAY,GAAG,YAAY,SAAS;AAC1C,YAAM,cAAc,GAAG,YAAY,YAAY;AAC/C,YAAM,UAA6B,CAAC;AACpC,YAAM,MAAM,KAAK,IAAI;AACrB,iBAAW,QAAQ,WAAW;AAC5B,cAAM,WAAW,KAAK;AACtB,cAAM,SAA0B;AAAA,UAC9B,IAAI,KAAK;AAAA,UACT,MAAO,KAAK,QAAQ;AAAA,UACpB,WAAW,KAAK,OAAO;AAAA,UACvB,SAAS,KAAK,OAAO,WAAW;AAAA,UAChC,UAAU,KAAK,OAAO;AAAA,UACtB,SAAS;AAAA,QACX;AACA,YAAI,UAAU;AACZ,oBAAU,OAAO,KAAK,EAAE;AAAA,QAC1B,OAAO;AACL,oBAAU,IAAI,MAAM;AAAA,QACtB;AACA,oBAAY,IAAI;AAAA,UACd,QAAQ,KAAK;AAAA,UACb,WAAW;AAAA,QACb,CAAC;AACD,gBAAQ,KAAK,MAAM;AAAA,MACrB;AACA,YAAM,qBAAqB,EAAE;AAC7B,WAAK,cAAc,QAAQ;AAC3B,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBAAiB,OAAwC;AAC7D,WAAO,KAAK,aAAa,OAAO,aAAa;AAC3C,YAAM,QAAQ,SACX,YAAY,cAAc,UAAU,EACpC,YAAY,YAAY;AAC3B,YAAM,MAAM,QAAQ,MAAM,OAAO,MAAM,KAAK,IAAI,MAAM,OAAO;AAC7D,YAAM,UAAU,MAAM,iBAAgC,GAAG;AACzD,aAAO,WAAW,CAAC;AAAA,IACrB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,oBAAoB,UAAmC;AAC3D,QAAI,SAAS,WAAW,EAAG;AAC3B,WAAO,KAAK,aAAa,OAAO,aAAa;AAC3C,YAAM,KAAK,SAAS,YAAY,cAAc,WAAW;AACzD,YAAM,QAAQ,GAAG,YAAY,YAAY;AACzC,iBAAW,MAAM,UAAU;AACzB,cAAM,OAAO,EAAE;AAAA,MACjB;AACA,YAAM,qBAAqB,EAAE;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,mBACJ,UACA,KACe;AACf,UAAM,KAAK,kBAAkB,UAAU,GAAG;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,wBACJ,SACA,KACe;AACf,QAAI,QAAQ,WAAW,GAAG;AACxB,YAAM,KAAK,QAAQ,eAAe,GAAG;AACrC;AAAA,IACF;AAEA,UAAM,UAA4B,QAAQ,IAAI,CAAC,YAAY;AAAA,MACzD,OAAO,OAAO;AAAA,MACd,IAAI,OAAO;AAAA,MACX,MAAM,OAAO;AAAA,MACb,WAAW,OAAO;AAAA,MAClB,SAAS,OAAO,WAAW;AAAA,MAC3B,SAAS,OAAO;AAAA,MAChB,UAAU,OAAO;AAAA,IACnB,EAAE;AAEF,UAAM,KAAK,kBAAkB,SAAS,GAAG;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YAAY,cAAc,MAAqB;AACnD,WAAO,KAAK,aAAa,OAAO,OAAO;AACrC,YAAM,aAAa,MAAM,KAAK,GAAG,gBAAgB,EAAE,OAAO,CAAC,SAAS;AAClE,YAAI,SAAS,WAAY,QAAO;AAChC,YAAI,SAAS,gBAAgB,CAAC,YAAa,QAAO;AAClD,eAAO;AAAA,MACT,CAAC;AACD,YAAM,KAAK,YAAY,IAAI,UAAU;AAAA,IACvC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAA8B;AAClC,WAAO,KAAK,aAAa,OAAO,OAAO;AACrC,YAAM,KAAK,YAAY,IAAI,MAAM,KAAK,GAAG,gBAAgB,CAAC;AAAA,IAC5D,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,KAAK,iBAAiB;AACxB,YAAM,KAAK,MAAM,KAAK;AACtB,SAAG,MAAM;AACT,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA;AAAA,EAIA,MAAc,aACZ,IACY;AACZ,WAAO,KAAK,YAAY,KAAK,YAAY;AACvC,YAAM,KAAK,MAAM,KAAK,YAAY;AAClC,aAAO,GAAG,EAAE;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,kBACZ,SACA,KACe;AACf,QAAI,QAAQ,WAAW,GAAG;AACxB,YAAM,KAAK,QAAQ,eAAe,GAAG;AACrC;AAAA,IACF;AAEA,UAAM,aAAa,MAAM;AAAA,MACvB,IAAI,IAAI,QAAQ,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC;AAAA,IAC3C,EAAE,OAAO,OAAO;AAChB,UAAM,KAAK,aAAa,UAAU;AAElC,UAAM,KAAK,aAAa,OAAO,aAAa;AAC1C,YAAM,KAAK,SAAS,YAAY,CAAC,GAAG,YAAY,UAAU,GAAG,WAAW;AACxE,YAAM,cAAc,oBAAI,IAA4B;AACpD,iBAAW,QAAQ,YAAY;AAC7B,oBAAY,IAAI,MAAM,GAAG,YAAY,IAAI,CAAC;AAAA,MAC5C;AACA,iBAAW,QAAQ,SAAS;AAC1B,cAAM,QAAQ,YAAY,IAAI,KAAK,KAAK;AACxC,YAAI,CAAC,MAAO;AAEZ,cAAM,SAAS,MAAM,IAAI,KAAK,EAAE;AAChC,eAAO,YAAY,MAAM;AACvB,gBAAM,WAAW,OAAO;AACxB,cAAI,CAAC,YAAY,gBAAgB,MAAM,QAAQ,GAAG;AAChD,gBAAI,KAAK,SAAS;AAChB,oBAAM,OAAO,KAAK,EAAE;AAAA,YACtB,OAAO;AACL,oBAAM,SAAuB;AAAA,gBAC3B,IAAI,KAAK;AAAA,gBACT,MAAM,KAAK;AAAA,gBACX,WAAW,KAAK;AAAA,gBAChB,SAAS,KAAK,WAAW;AAAA,gBACzB,UAAU,KAAK;AAAA,cACjB;AACA,oBAAM,IAAI,MAAM;AAAA,YAClB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,YAAM,YAAY,GAAG,YAAY,UAAU;AAC3C,gBAAU,IAAI,EAAE,KAAK,eAAe,OAAO,IAAI,CAAC;AAChD,gBAAU,IAAI,EAAE,KAAK,qBAAqB,OAAO,KAAK,IAAI,EAAE,CAAC;AAC7D,YAAM,qBAAqB,EAAE;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,YACZ,UACA,YACe;AACf,QAAI,WAAW,WAAW,EAAG;AAC7B,UAAM,KAAK,SAAS,YAAY,YAAY,WAAW;AACvD,eAAW,QAAQ,YAAY;AAC7B,SAAG,YAAY,IAAI,EAAE,MAAM;AAAA,IAC7B;AACA,UAAM,qBAAqB,EAAE;AAAA,EAC/B;AAAA,EAEA,MAAc,eAAqC;AACjD,WAAO,IAAI,QAAqB,CAAC,SAAS,WAAW;AACnD,YAAM,UAAU,UAAU,KAAK,KAAK,IAAI;AACxC,cAAQ,kBAAkB,MAAM;AAC9B,aAAK,qBAAqB,QAAQ,MAAM;AAAA,MAC1C;AACA,cAAQ,YAAY,MAAM;AACxB,cAAM,WAAW,QAAQ;AACzB,YACE,CAAC,SAAS,iBAAiB,SAAS,YAAY,KAChD,CAAC,SAAS,iBAAiB,SAAS,UAAU,GAC9C;AACA,gBAAM,cAAc,SAAS,UAAU;AACvC,eAAK,kBAAkB,KAAK;AAAA,YAC1B;AAAA,YACA;AAAA,YACA,CAAC;AAAA,UACH;AACA,eAAK,gBAAgB,KAAK,OAAO,EAAE,MAAM,MAAM;AAAA,QACjD,OAAO;AACL,kBAAQ,QAAQ;AAAA,QAClB;AAAA,MACF;AACA,cAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,IAC9C,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,gBACZ,WACA,aACA,WACsB;AACtB,cAAU,MAAM;AAChB,WAAO,IAAI,QAAqB,CAAC,SAAS,WAAW;AACnD,YAAM,UAAU,UAAU,KAAK,KAAK,MAAM,WAAW;AACrD,cAAQ,kBAAkB,MAAM;AAC9B,cAAM,WAAW,QAAQ;AACzB,aAAK,qBAAqB,QAAQ;AAClC,mBAAW,aAAa,WAAW;AACjC,cAAI,CAAC,SAAS,iBAAiB,SAAS,SAAS,GAAG;AAClD,qBAAS,kBAAkB,WAAW,EAAE,SAAS,KAAK,CAAC;AAAA,UACzD;AAAA,QACF;AAAA,MACF;AACA,cAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAM;AAChD,cAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,IAC9C,CAAC;AAAA,EACH;AAAA,EAEQ,qBAAqB,IAAuB;AAClD,QAAI,CAAC,GAAG,iBAAiB,SAAS,YAAY,GAAG;AAC/C,SAAG,kBAAkB,cAAc;AAAA,QACjC,SAAS;AAAA,QACT,eAAe;AAAA,MACjB,CAAC;AAAA,IACH;AACA,QAAI,CAAC,GAAG,iBAAiB,SAAS,UAAU,GAAG;AAC7C,SAAG,kBAAkB,YAAY,EAAE,SAAS,MAAM,CAAC;AAAA,IACrD;AAAA,EACF;AACF;AAIA,IAAM,eAAe;AACrB,IAAM,aAAa;AAEnB,SAAS,iBAAoB,SAAoC;AAC/D,SAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,YAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAM;AAChD,YAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,EAC9C,CAAC;AACH;AAEA,SAAS,qBAAqB,IAAmC;AAC/D,SAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,OAAG,aAAa,MAAM,QAAQ;AAC9B,OAAG,UAAU,MAAM,OAAO,GAAG,KAAK;AAClC,OAAG,UAAU,MACX,OAAO,GAAG,SAAS,IAAI,MAAM,+BAA+B,CAAC;AAAA,EACjE,CAAC;AACH;;;AC7iBO,IAAK,aAAL,kBAAKC,gBAAL;AAEL,EAAAA,wBAAA;AAEA,EAAAA,wBAAA;AAEA,EAAAA,wBAAA;AAEA,EAAAA,wBAAA;AARU,SAAAA;AAAA,GAAA;AAmDL,IAAM,OAAN,MAAW;AAAA;AAAA,EAEhB;AAAA;AAAA,EAES;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA,iBAAiB,IAAI,cAA0B;AAAA;AAAA,EAE/C,UAAU,IAAI,cAAiC;AAAA,EAEhD;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAA8B;AAAA,EAC9B,gBAA4B;AAAA,EAC5B,iBAAuD;AAAA,EACvD,oBAAoB;AAAA,EACpB,YAAmD;AAAA,EACnD,YAAkD;AAAA,EAClD,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,iBAAwC,oBAAI,IAAI;AAAA;AAAA,EAChD,eAA8B,QAAQ,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQtD,YAAY,SAAkB,SAAsB;AAClD,QAAI,CAAC,QAAQ,OAAO;AAClB,YAAM,IAAI;AAAA;AAAA,QAER;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,QAAQ,UAAU;AACrB,YAAM,IAAI;AAAA;AAAA,QAER;AAAA,MACF;AAAA,IACF;AACA,SAAK,MAAM,QAAQ;AACnB,SAAK,QAAQ,QAAQ;AACrB,SAAK,UAAU;AACf,SAAK,QAAQ,QAAQ;AACrB,SAAK,WAAW,QAAQ;AACxB,SAAK,UAAU;AAAA,MACb,qBAAqB;AAAA,MACrB,wBAAwB;AAAA,MACxB,gBAAgB;AAAA,MAChB,GAAG;AAAA,IACL;AAEA,QACE,OAAO,WAAW,eAClB,OAAO,OAAO,qBAAqB,YACnC;AACA,aAAO,iBAAiB,UAAU,KAAK,YAAY;AACnD,aAAO,iBAAiB,WAAW,KAAK,aAAa;AAAA,IACvD;AAEA,QAAI,KAAK,SAAS,KAAK,KAAK;AAC1B,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,SAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAQ,OAAgB,KAAoB;AAC1C,QAAI,MAAO,MAAK,QAAQ;AACxB,QAAI,IAAK,MAAK,MAAM;AAEpB,QAAI,CAAC,KAAK,KAAK;AACb;AAAA,IACF;AAEA,QAAI,KAAK,WAAW;AAClB,WAAK,WAAW;AAAA,IAClB;AAEA,SAAK,UAAU,kBAAqB;AAEpC,UAAM,QAAQ,KAAK;AACnB,UAAM,gBACJ,KAAK,QAAQ,mBACZ,OAAO,cAAc,cAAc,YAAY;AAElD,QAAI,CAAC,eAAe;AAClB,WAAK,UAAU,aAAgB;AAC/B,WAAK,QAAQ;AAAA,QACX,IAAI;AAAA;AAAA,UAEF;AAAA,QACF;AAAA,MACF;AACA;AAAA,IACF;AAEA,QAAI;AACF,WAAK,YAAY,IAAI,cAAc,KAAK;AAAA,IAC1C,SAAS,KAAK;AACZ,WAAK,UAAU,aAAgB;AAC/B,WAAK,QAAQ;AAAA,QACX,IAAI;AAAA;AAAA,UAEF,eAAe,QACX,IAAI,UACJ;AAAA,QACN;AAAA,MACF;AACA,WAAK,kBAAkB;AACvB;AAAA,IACF;AAEA,SAAK,UAAU,SAAS,MAAM;AAC5B,WAAK,oBAAoB;AACzB,WAAK,UAAU;AACf,WAAK,SAAS;AAAA,IAChB;AAEA,SAAK,UAAU,YAAY,CAAC,UAAU;AACpC,UAAI;AACF,cAAM,MACJ,OAAO,MAAM,SAAS,WAClB,MAAM,OACN,MAAM,KAAK,SAAS,MAAM;AAChC,cAAM,MAAqB,KAAK,MAAM,GAAG;AACzC,aAAK,eAAe,KAAK,aACtB,KAAK,MAAM,KAAK,oBAAoB,GAAG,CAAC,EACxC,MAAM,CAAC,QAAQ;AACd,eAAK,QAAQ;AAAA,YACX,IAAI;AAAA;AAAA,cAEF,eAAe,QACX,IAAI,UACJ;AAAA,YACN;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACL,SAAS,KAAK;AACZ,aAAK,QAAQ;AAAA,UACX,IAAI;AAAA;AAAA,YAEF,eAAe,QACX,IAAI,UACJ;AAAA,UACN;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,SAAK,UAAU,UAAU,MAAM;AAC7B,WAAK,QAAQ;AAAA,QACX,IAAI;AAAA;AAAA,UAEF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,SAAK,UAAU,UAAU,CAAC,UAAU;AAClC,WAAK,SAAS;AACd,WAAK,eAAe,MAAM;AAC1B,WAAK,eAAe,QAAQ,QAAQ;AACpC,WAAK,YAAY;AACjB,UAAI,CAAC,KAAK,aAAa;AACrB,aAAK,UAAU,oBAAuB;AACtC,YAAI,MAAM,SAAS,OAAQ,MAAM,SAAS,MAAM;AAC9C,eAAK,kBAAkB;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,SAAK,SAAS;AACd,SAAK,eAAe,MAAM;AAC1B,SAAK,eAAe,QAAQ,QAAQ;AACpC,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AACA,QAAI,KAAK,WAAW;AAClB,YAAM,KAAK,KAAK;AAChB,WAAK,YAAY;AACjB,SAAG,SAAS;AACZ,SAAG,YAAY;AACf,SAAG,UAAU,MAAM;AAAA,MAAC;AACpB,SAAG,UAAU;AACb,UAAI;AACF,cAAM,kBAAkB;AACxB,YAAI,OAAO,gBAAgB,cAAc,YAAY;AACnD,0BAAgB,UAAU;AAAA,QAC5B,OAAO;AACL,aAAG,MAAM;AAAA,QACX;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,UAAU,oBAAuB;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,cAAc;AACnB,QACE,OAAO,WAAW,eAClB,OAAO,OAAO,wBAAwB,YACtC;AACA,aAAO,oBAAoB,UAAU,KAAK,YAAY;AACtD,aAAO,oBAAoB,WAAW,KAAK,aAAa;AAAA,IAC1D;AACA,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,SAAwB;AACnC,UAAM,QAAQ,WAAW,KAAK,QAAQ,kBAAkB;AACxD,QAAI,KAAK,UAAW,cAAa,KAAK,SAAS;AAC/C,SAAK,YAAY,WAAW,MAAM;AAChC,WAAK,YAAY;AACjB,WAAK,WAAW;AAAA,IAClB,GAAG,KAAK;AAAA,EACV;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,QACE,KAAK,aACL,KAAK,eAAe,OAAO,KAC3B,KAAK,kBAAkB,qBACvB,CAAC,KAAK,aACN,KAAK,UAAU,gBAAgB,KAAK,UAAU,QAAQ,IACtD;AACA;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,QAAQ,iBAAiB,GAAG;AACvD,UAAI,QAAQ,WAAW,EAAG;AAE1B,YAAM,UAAU,SAAS,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC;AACpE,YAAM,WAAqB,CAAC;AAC5B,YAAM,UAA0B,CAAC;AAEjC,iBAAW,SAAS,SAAS;AAC3B,YAAI,MAAM,YAAY,QAAW;AAC/B,mBAAS,KAAK,MAAM,OAAO;AAAA,QAC7B;AACA,gBAAQ,KAAK,MAAM,MAAM;AAAA,MAC3B;AAEA,WAAK,eAAe,IAAI,SAAS,QAAQ;AAEzC,WAAK,KAAK;AAAA,QACR;AAAA,QACA,UAAU,KAAK;AAAA,QACf;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,QAAQ;AAAA,QACX,IAAI;AAAA;AAAA,UAEF,eAAe,QACX,IAAI,UACJ;AAAA,QACN;AAAA,MACF;AAAA,IACF,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA,EAIQ,UAAU,WAAuB;AACvC,QAAI,KAAK,kBAAkB,UAAW;AACtC,SAAK,gBAAgB;AACrB,SAAK,eAAe,QAAQ,SAAS;AAAA,EACvC;AAAA,EAEQ,YAAY;AAClB,SAAK,SAAS;AACd,UAAM,WAAW,KAAK,QAAQ,kBAAkB;AAChD,QAAI,YAAY,EAAG;AAEnB,SAAK,YAAY,YAAY,MAAM;AACjC,WAAK,KAAK,EAAE,wBAA6B,CAAC;AAAA,IAC5C,GAAG,QAAQ;AAAA,EACb;AAAA,EAEQ,WAAW;AACjB,QAAI,KAAK,WAAW;AAClB,oBAAc,KAAK,SAAS;AAC5B,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEQ,WAAoB;AAC1B,QACE,OAAO,cAAc,eACrB,OAAO,UAAU,WAAW,WAC5B;AACA,aAAO,UAAU;AAAA,IACnB;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAe,MAAM;AAC3B,QAAI,KAAK,eAAe,CAAC,KAAK,SAAS,CAAC,KAAK,IAAK;AAClD,SAAK,oBAAoB;AACzB,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AACA,SAAK,QAAQ;AAAA,EACf;AAAA,EAEQ,gBAAgB,MAAM;AAC5B,QAAI,KAAK,YAAa;AACtB,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AACA,QAAI,KAAK,WAAW;AAClB,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA,EAEQ,oBAAoB;AAC1B,QAAI,KAAK,kBAAkB,KAAK,eAAe,CAAC,KAAK,SAAS,EAAG;AACjE,UAAM,OAAO,KAAK,QAAQ,uBAAuB;AACjD,UAAM,MAAM,KAAK,QAAQ,0BAA0B;AACnD,UAAM,QAAQ,KAAK,IAAI,OAAO,KAAK,KAAK,mBAAmB,GAAG;AAC9D,SAAK;AAEL,SAAK,iBAAiB,WAAW,MAAM;AACrC,WAAK,iBAAiB;AACtB,WAAK,QAAQ;AAAA,IACf,GAAG,KAAK;AAAA,EACV;AAAA,EAEQ,KAAK,KAAoB;AAC/B,QACE,KAAK,aACL,KAAK,UAAU,gBAAgB,KAAK,UAAU,QAAQ,IACtD;AACA,WAAK,UAAU,KAAK,KAAK,UAAU,GAAG,CAAC;AAAA,IACzC;AAAA,EACF;AAAA,EAEA,MAAc,WAAW;AACvB,UAAM,cACH,MAAM,KAAK,QAAQ,QAAgB,aAAa,KAAM;AACzD,SAAK,KAAK;AAAA,MACR;AAAA,MACA,iBAAiB;AAAA,MACjB,OAAO,KAAK,SAAS;AAAA,MACrB,OAAO,KAAK,QAAQ;AAAA,MACpB,UAAU,KAAK;AAAA,MACf;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,oBAAoB,KAAoB;AACpD,YAAQ,IAAI,MAAM;AAAA,MAChB,uCAAoC;AAClC,aAAK,oBAAoB;AACzB,aAAK,UAAU,iBAAoB;AACnC,YAAI,IAAI,OAAO;AACb,eAAK,QAAQ,IAAI;AACjB,eAAK,QAAQ,iBAAiB,IAAI,KAAK;AAAA,QACzC;AACA,aAAK,UAAU;AACf,cAAM,KAAK,WAAW;AACtB;AAAA,MACF;AAAA,MACA,mCAAkC;AAChC,aAAK,UAAU,aAAgB;AAC/B,aAAK,WAAW;AAChB,aAAK,QAAQ,cAAc,IAAI,OAAO;AACtC,aAAK,QAAQ;AAAA,UACX,IAAI;AAAA;AAAA,YAEF,IAAI;AAAA,UACN;AAAA,QACF;AACA;AAAA,MACF;AAAA,MACA,yCAAqC;AACnC,cAAM,KAAK,eAAe,IAAI,UAAU,IAAI,GAAG;AAC/C,cAAM,KAAK,WAAW;AACtB;AAAA,MACF;AAAA,MACA,iCAAiC;AAC/B,cAAM,KAAK,WAAW,IAAI,SAAS,IAAI,KAAK;AAC5C,cAAM,KAAK,WAAW;AACtB;AAAA,MACF;AAAA,MACA,iDAAyC;AACvC,cAAM,KAAK,WAAW,IAAI,SAAS,IAAI,GAAG;AAC1C;AAAA,MACF;AAAA,MACA,mCAAkC;AAChC,cAAM,WAAW,KAAK,eAAe,IAAI,IAAI,OAAO;AACpD,YAAI,UAAU;AACZ,eAAK,eAAe,OAAO,IAAI,OAAO;AACtC,gBAAM,KAAK,QAAQ,oBAAoB,QAAQ;AAAA,QACjD;AACA,YAAI,IAAI,eAAe,QAAW;AAChC,gBAAM,KAAK,QAAQ,QAAQ,eAAe,IAAI,UAAU;AAAA,QAC1D;AACA,cAAM,KAAK,WAAW;AACtB;AAAA,MACF;AAAA,MACA;AACE;AAAA,MACF;AACE,aAAK,QAAQ;AAAA,UACX,IAAI,qCAAmD,IAAI,OAAO;AAAA,QACpE;AACA;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,MAAc,eACZ,SACA,KACe;AACf,UAAM,KAAK,QAAQ,mBAAmB,SAAS,GAAG;AAClD,SAAK;AAAA,MACH,QAAQ,IAAI,CAAC,UAAU;AAAA,QACrB,OAAO,KAAK;AAAA,QACZ,IAAI,KAAK;AAAA,QACT,UAAU,KAAK,WAAW;AAAA,QAC1B,MAAM,KAAK;AAAA,MACb,EAAE;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,MAAc,WACZ,SACA,KACe;AACf,UAAM,KAAK,QAAQ,wBAAwB,SAAS,GAAG;AACvD,SAAK;AAAA,MACH,QAAQ,IAAI,CAAC,YAAY;AAAA,QACvB,OAAO,OAAO;AAAA,QACd,IAAI,OAAO;AAAA,QACX,UAAU,OAAO;AAAA,QACjB,MAAM,OAAO;AAAA,MACf,EAAE;AAAA,IACJ;AAAA,EACF;AAAA,EAEQ,wBACN,OAMM;AACN,UAAM,cAAc,oBAAI,IAGtB;AAEF,eAAW,QAAQ,OAAO;AACxB,UAAI,SAAS,YAAY,IAAI,KAAK,KAAK;AACvC,UAAI,CAAC,QAAQ;AACX,iBAAS,CAAC;AACV,oBAAY,IAAI,KAAK,OAAO,MAAM;AAAA,MACpC;AACA,aAAO,KAAK;AAAA,QACV,IAAI,KAAK;AAAA,QACT,IAAI,KAAK;AAAA,QACT,MAAM,KAAK,WAAW,SAAY,KAAK;AAAA,QACvC,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAEA,eAAW,CAAC,WAAW,MAAM,KAAK,YAAY,QAAQ,GAAG;AACvD,YAAM,QAAQ,KAAK,QAAQ,MAAM,SAAS;AAC1C,YAAM,oBAAoB,MAAM;AAAA,IAClC;AAAA,EACF;AACF;;;ACzgBO,IAAM,eAAN,MAAmB;AAAA;AAAA,EAEf,qBAAqB,IAAI,cAA0B;AAAA;AAAA,EAEnD,qBAAqB,IAAI,cAA0B;AAAA;AAAA,EAEnD,UAAU,IAAI,cAAiC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQjB,YAAY,MAAc,UAA+B,CAAC,GAAG;AAC3D,SAAK,UAAU,KAAK,cAAc,IAAI;AACtC,SAAK,OAAO,KAAK,WAAW,SAAS,KAAK,OAAO;AACjD,SAAK,OAAO,KAAK,WAAW,MAAM,SAAS,KAAK,OAAO;AAGvD,SAAK,QAAQ,cAAc,SAAS,MAAM;AACxC,WAAK,KAAK,aAAa;AAAA,IACzB,CAAC;AAGD,SAAK,KAAK,eAAe,SAAS,CAAC,WAAW;AAC5C,WAAK,mBAAmB,QAAQ,MAAM;AACtC,UAAI,+BAAkC,KAAK,KAAK,OAAO;AACrD,aAAK,KAAK,QAAQ,KAAK,KAAK,KAAK;AAAA,MACnC,OAAO;AACL,aAAK,KAAK,WAAW;AAAA,MACvB;AAAA,IACF,CAAC;AAED,SAAK,KAAK,eAAe,SAAS,CAAC,WAAW;AAC5C,WAAK,mBAAmB,QAAQ,MAAM;AAAA,IACxC,CAAC;AAED,SAAK,KAAK,QAAQ,SAAS,CAAC,QAAQ;AAClC,WAAK,QAAQ,QAAQ,GAAG;AAAA,IAC1B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAmB,MAAwB;AACzC,WAAO,KAAK,QAAQ,MAAS,IAAI;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,UAAM,KAAK,QAAQ,aAAa;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,SAAK,KAAK,QAAQ;AAClB,UAAM,KAAK,QAAQ,MAAM;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,aAAyB;AAC3B,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,WAA+B;AACjC,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,SAAS,SAA4C;AACzD,WAAO,KAAK,KAAK,SAAS,OAAO;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,MAAM,UAAwB,CAAC,GAAqB;AACxD,WAAO,KAAK,KAAK,MAAM,OAAO;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,UAAyB,CAAC,GAAqB;AAC1D,WAAO,KAAK,KAAK,OAAO,OAAO;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,aAAyB;AAC3B,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA;AAAA,EAIQ,cAAc,MAAuB;AAC3C,WAAO,IAAI,QAAQ,IAAI;AAAA,EACzB;AAAA,EAEQ,WAAW,SAA8B,SAAwB;AACvE,WAAO,IAAI,KAAK;AAAA,MACd,SAAS,KAAK,eAAe,OAAO;AAAA,MACpC;AAAA,MACA,SAAS,QAAQ;AAAA,IACnB,CAAC;AAAA,EACH;AAAA,EAEQ,WACN,MACA,SACA,SACM;AACN,WAAO,IAAI,KAAK,SAAS;AAAA,MACvB,KAAK,KAAK,oBAAoB,OAAO;AAAA,MACrC,OAAO,QAAQ,SAAS;AAAA,MACxB,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,gBAAgB,QAAQ;AAAA,MACxB,qBAAqB,QAAQ;AAAA,MAC7B,wBAAwB,QAAQ;AAAA,MAChC,gBAAgB,QAAQ;AAAA,MACxB,gBAAgB,CAAC,UAAU;AACzB,aAAK,KAAK,mBAAmB,KAAK;AAAA,MACpC;AAAA,MACA,aAAa,CAAC,YAAY;AACxB,aAAK,KAAK,gBAAgB,OAAO;AAAA,MACnC;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,SAAS,QAMf;AACA,QAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,QAAI;AACF,YAAM,OAAO,UAAU,KAAK,MAAM;AAClC,YAAM,WAAW,iBAAiB,KAAK,MAAM;AAC7C,YAAM,iBAAiB,OAAO,QAAQ,aAAa,SAAS;AAC5D,YAAM,SAAS,IAAI,IAAI,cAAc;AAErC,YAAM,OAAO,OAAO,YAAY;AAChC,YAAM,OAAO,OAAO,OAAO,OAAO,SAAS,OAAO,MAAM,EAAE,IAAI;AAC9D,YAAM,WAAW,OAAO;AAExB,UAAI;AACJ,UAAI;AAEJ,UAAI,MAAM;AACR,YAAI,aAAa,WAAW,aAAa,UAAU;AACjD,qBAAW;AACX,0BAAgB;AAAA,QAClB,WAAW,SAAS,SAAS,OAAO,KAAK,SAAS,SAAS,QAAQ,GAAG;AACpE,qBAAW,SAAS,QAAQ,cAAc,EAAE;AAC5C,0BAAgB,SAAS,QAAQ,OAAO,EAAE;AAAA,QAC5C,WAAW,YAAY,aAAa,KAAK;AACvC,qBAAW,SAAS,QAAQ,OAAO,EAAE;AACrC,0BAAgB,GAAG,QAAQ;AAAA,QAC7B;AAAA,MACF,OAAO;AACL,YAAI,YAAY,aAAa,KAAK;AAChC,qBAAW,SAAS,QAAQ,OAAO,EAAE;AAAA,QACvC,OAAO;AACL,qBAAW;AAAA,QACb;AAAA,MACF;AAEA,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEQ,kBAAkB,SAGxB;AACA,UAAM,YAAY,KAAK,SAAS,QAAQ,GAAG;AAC3C,UAAM,YAAY,OAAO,WAAW,eAAe,QAAQ,OAAO,QAAQ;AAC1E,UAAM,OACJ,QAAQ,QACR,UAAU,SACT,aAAa,OAAO,SAAS,aAAa,KACvC,OAAO,SAAS,WAChB;AACN,UAAM,OACJ,QAAQ,QACR,UAAU,SACT,aAAa,OAAO,SAAS,OAC1B,OAAO,SAAS,OAAO,SAAS,MAAM,EAAE,IACxC;AACN,UAAM,SACJ,QAAQ,UACR,UAAU,WACT,YAAY,OAAO,SAAS,aAAa,WAAW;AAEvD,QAAI,CAAC,MAAM;AACT,aAAO,EAAE,MAAM,QAAW,OAAO;AAAA,IACnC;AAEA,UAAM,aACJ,SAAS,UAAa,CAAC,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,IAAI,IAAI,KAAK;AAClE,WAAO,EAAE,MAAM,YAAY,OAAO;AAAA,EACpC;AAAA,EAEQ,eAAe,SAAsC;AAC3D,UAAM,YAAY,KAAK,SAAS,QAAQ,GAAG;AAC3C,UAAM,WAAW;AAAA,MACf,QAAQ,YAAY,UAAU,YAAY;AAAA,IAC5C;AACA,UAAM,EAAE,MAAM,OAAO,IAAI,KAAK,kBAAkB,OAAO;AACvD,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,QAAQ,SAAS,UAAU;AACjC,WAAO,GAAG,KAAK,MAAM,IAAI,GAAG,QAAQ;AAAA,EACtC;AAAA,EAEQ,oBACN,SACoB;AACpB,UAAM,YAAY,KAAK,SAAS,QAAQ,GAAG;AAC3C,UAAM,EAAE,MAAM,OAAO,IAAI,KAAK,kBAAkB,OAAO;AACvD,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,WAAW;AAAA,MACf,QAAQ,YAAY,UAAU,YAAY;AAAA,IAC5C;AACA,UAAM,gBACJ,QAAQ,iBAAiB,UAAU,iBAAiB,GAAG,QAAQ;AACjE,UAAM,QAAQ,SAAS,QAAQ;AAC/B,WAAO,GAAG,KAAK,MAAM,IAAI,GAAG,aAAa;AAAA,EAC3C;AACF;","names":["OperationType","TetherClientErrorCode","DataMode","AuthStatus","SyncStatus"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Default token expiration window in seconds (7 days). */
|
|
2
|
+
export declare const DEFAULT_TOKEN_EXPIRES_IN: number;
|
|
3
|
+
/**
|
|
4
|
+
* Loads a persistent HMAC signing secret from `<baseDir>/.secret`, or generates and saves one
|
|
5
|
+
* with restricted permissions (`0o600`) if it does not yet exist.
|
|
6
|
+
*
|
|
7
|
+
* @param baseDir - Directory path where `.secret` is stored.
|
|
8
|
+
* @returns 64-character hex secret string.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getOrCreateKeyfileSecret(baseDir: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Hashes a plaintext password using standard scrypt key derivation.
|
|
13
|
+
*
|
|
14
|
+
* @param password - The plaintext password to hash.
|
|
15
|
+
* @returns Formatted hash string `scrypt$salt$derivedKey`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function hashPassword(password: string): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Verifies a plaintext password against a stored scrypt hash using timing-safe comparison.
|
|
20
|
+
*
|
|
21
|
+
* @param password - The plaintext password to check.
|
|
22
|
+
* @param storedHash - Stored hash string `scrypt$salt$derivedKey`.
|
|
23
|
+
* @returns `true` if password matches; otherwise `false`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function verifyPasswordHash(password: string, storedHash: string): Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Performs a constant-time dummy password verification to prevent user enumeration timing attacks.
|
|
28
|
+
*
|
|
29
|
+
* @param password - The plaintext password supplied by client.
|
|
30
|
+
* @returns Always resolves to `false`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function verifyDummyPasswordHash(password: string): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Generates a signed, URL-safe session token.
|
|
35
|
+
*
|
|
36
|
+
* @param userId - User account identifier.
|
|
37
|
+
* @param username - Normalized username.
|
|
38
|
+
* @param secret - Signing secret.
|
|
39
|
+
* @param expiresInSeconds - Token expiration duration.
|
|
40
|
+
* @returns Signed token string.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createSessionToken(userId: string, username: string, secret: string, expiresInSeconds?: number): string;
|
|
43
|
+
/**
|
|
44
|
+
* Verifies a signed session token and returns decoded payload.
|
|
45
|
+
*
|
|
46
|
+
* @param token - Token string to verify.
|
|
47
|
+
* @param secret - Signing secret.
|
|
48
|
+
* @returns Decoded payload or `null` if invalid or expired.
|
|
49
|
+
*/
|
|
50
|
+
export declare function verifySessionToken(token: string, secret: string): {
|
|
51
|
+
userId: string;
|
|
52
|
+
username: string;
|
|
53
|
+
expiresAt: number;
|
|
54
|
+
} | null;
|
|
55
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Default token expiration window in seconds (7 days). */
|
|
2
|
+
export declare const DEFAULT_TOKEN_EXPIRES_IN: number;
|
|
3
|
+
/**
|
|
4
|
+
* Loads a persistent HMAC signing secret from `<baseDir>/.secret`, or generates and saves one
|
|
5
|
+
* with restricted permissions (`0o600`) if it does not yet exist.
|
|
6
|
+
*
|
|
7
|
+
* @param baseDir - Directory path where `.secret` is stored.
|
|
8
|
+
* @returns 64-character hex secret string.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getOrCreateKeyfileSecret(baseDir: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Hashes a plaintext password using standard scrypt key derivation.
|
|
13
|
+
*
|
|
14
|
+
* @param password - The plaintext password to hash.
|
|
15
|
+
* @returns Formatted hash string `scrypt$salt$derivedKey`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function hashPassword(password: string): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Verifies a plaintext password against a stored scrypt hash using timing-safe comparison.
|
|
20
|
+
*
|
|
21
|
+
* @param password - The plaintext password to check.
|
|
22
|
+
* @param storedHash - Stored hash string `scrypt$salt$derivedKey`.
|
|
23
|
+
* @returns `true` if password matches; otherwise `false`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function verifyPasswordHash(password: string, storedHash: string): Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Performs a constant-time dummy password verification to prevent user enumeration timing attacks.
|
|
28
|
+
*
|
|
29
|
+
* @param password - The plaintext password supplied by client.
|
|
30
|
+
* @returns Always resolves to `false`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function verifyDummyPasswordHash(password: string): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Generates a signed, URL-safe session token.
|
|
35
|
+
*
|
|
36
|
+
* @param userId - User account identifier.
|
|
37
|
+
* @param username - Normalized username.
|
|
38
|
+
* @param secret - Signing secret.
|
|
39
|
+
* @param expiresInSeconds - Token expiration duration.
|
|
40
|
+
* @returns Signed token string.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createSessionToken(userId: string, username: string, secret: string, expiresInSeconds?: number): string;
|
|
43
|
+
/**
|
|
44
|
+
* Verifies a signed session token and returns decoded payload.
|
|
45
|
+
*
|
|
46
|
+
* @param token - Token string to verify.
|
|
47
|
+
* @param secret - Signing secret.
|
|
48
|
+
* @returns Decoded payload or `null` if invalid or expired.
|
|
49
|
+
*/
|
|
50
|
+
export declare function verifySessionToken(token: string, secret: string): {
|
|
51
|
+
userId: string;
|
|
52
|
+
username: string;
|
|
53
|
+
expiresAt: number;
|
|
54
|
+
} | null;
|
|
55
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/server/crypto.ts"],"names":[],"mappings":"AAIA,2DAA2D;AAC3D,eAAO,MAAM,wBAAwB,QAAmB,CAAC;AAEzD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAwBhE;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAcpE;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAoBlB;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,gBAAgB,SAA2B,GAC1C,MAAM,CAaR;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA6ChE"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes identifying broader server-side error categories.
|
|
3
|
+
*/
|
|
4
|
+
export declare enum TetherServerErrorCode {
|
|
5
|
+
/** The requested input or parameter is invalid or malformed. */
|
|
6
|
+
InvalidInput = 0,
|
|
7
|
+
/** The requested resource (user, application, or table) was not found. */
|
|
8
|
+
NotFound = 1,
|
|
9
|
+
/** The resource (user, application, or table) already exists. */
|
|
10
|
+
AlreadyExists = 2,
|
|
11
|
+
/** Authentication is missing, invalid, or expired. */
|
|
12
|
+
Unauthorized = 3,
|
|
13
|
+
/** The provided credentials are invalid. */
|
|
14
|
+
AuthenticationFailed = 4,
|
|
15
|
+
/** A storage capacity or payload size limit was exceeded. */
|
|
16
|
+
LimitExceeded = 5,
|
|
17
|
+
/** Configuration or command-line option is invalid. */
|
|
18
|
+
ConfigurationError = 6,
|
|
19
|
+
/** The requested operation is not supported by this backend or engine. */
|
|
20
|
+
NotSupported = 7,
|
|
21
|
+
/** An unexpected internal server error occurred. */
|
|
22
|
+
InternalError = 8
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Dedicated error class for TetherDB server errors.
|
|
26
|
+
* Error messages are user-safe and contain no internal technical details.
|
|
27
|
+
*/
|
|
28
|
+
export declare class TetherServerError extends Error {
|
|
29
|
+
/** Error category code identifying the broad error type. */
|
|
30
|
+
readonly code: TetherServerErrorCode;
|
|
31
|
+
/**
|
|
32
|
+
* Initializes a new `TetherServerError`.
|
|
33
|
+
*
|
|
34
|
+
* @param code - The error category code.
|
|
35
|
+
* @param message - User-safe error description message.
|
|
36
|
+
*/
|
|
37
|
+
constructor(code: TetherServerErrorCode, message?: string);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes identifying broader server-side error categories.
|
|
3
|
+
*/
|
|
4
|
+
export declare enum TetherServerErrorCode {
|
|
5
|
+
/** The requested input or parameter is invalid or malformed. */
|
|
6
|
+
InvalidInput = 0,
|
|
7
|
+
/** The requested resource (user, application, or table) was not found. */
|
|
8
|
+
NotFound = 1,
|
|
9
|
+
/** The resource (user, application, or table) already exists. */
|
|
10
|
+
AlreadyExists = 2,
|
|
11
|
+
/** Authentication is missing, invalid, or expired. */
|
|
12
|
+
Unauthorized = 3,
|
|
13
|
+
/** The provided credentials are invalid. */
|
|
14
|
+
AuthenticationFailed = 4,
|
|
15
|
+
/** A storage capacity or payload size limit was exceeded. */
|
|
16
|
+
LimitExceeded = 5,
|
|
17
|
+
/** Configuration or command-line option is invalid. */
|
|
18
|
+
ConfigurationError = 6,
|
|
19
|
+
/** The requested operation is not supported by this backend or engine. */
|
|
20
|
+
NotSupported = 7,
|
|
21
|
+
/** An unexpected internal server error occurred. */
|
|
22
|
+
InternalError = 8
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Dedicated error class for TetherDB server errors.
|
|
26
|
+
* Error messages are user-safe and contain no internal technical details.
|
|
27
|
+
*/
|
|
28
|
+
export declare class TetherServerError extends Error {
|
|
29
|
+
/** Error category code identifying the broad error type. */
|
|
30
|
+
readonly code: TetherServerErrorCode;
|
|
31
|
+
/**
|
|
32
|
+
* Initializes a new `TetherServerError`.
|
|
33
|
+
*
|
|
34
|
+
* @param code - The error category code.
|
|
35
|
+
* @param message - User-safe error description message.
|
|
36
|
+
*/
|
|
37
|
+
constructor(code: TetherServerErrorCode, message?: string);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/server/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,oBAAY,qBAAqB;IAC/B,gEAAgE;IAChE,YAAY,IAAA;IACZ,0EAA0E;IAC1E,QAAQ,IAAA;IACR,iEAAiE;IACjE,aAAa,IAAA;IACb,sDAAsD;IACtD,YAAY,IAAA;IACZ,4CAA4C;IAC5C,oBAAoB,IAAA;IACpB,6DAA6D;IAC7D,aAAa,IAAA;IACb,uDAAuD;IACvD,kBAAkB,IAAA;IAClB,0EAA0E;IAC1E,YAAY,IAAA;IACZ,oDAAoD;IACpD,aAAa,IAAA;CACd;AAED;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IAErC;;;;;OAKG;IACH,YAAY,IAAI,EAAE,qBAAqB,EAAE,OAAO,CAAC,EAAE,MAAM,EAKxD;CACF"}
|