{"version":3,"file":"chunk-sync-client.mjs","names":[],"sources":["../../src/errors.ts","../../src/feed-store.ts","../../src/sync-server.ts","../../src/sync-client.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { BaseError } from '@dxos/errors';\n\nexport class SyncRpcTimeoutError extends BaseError.extend('SyncRpcTimeoutError') {\n  constructor(args: { requestId: string; spaceId: string; feedNamespace: string; rpcTag: string; timeoutMs: number }) {\n    super({\n      message: `Feed sync ${args.rpcTag} timed out after ${args.timeoutMs}ms (spaceId=${args.spaceId} feedNamespace=${args.feedNamespace} requestId=${args.requestId}).`,\n      context: args,\n    });\n  }\n}\n\nexport class PositionConflictError extends BaseError.extend('PositionConflictError') {\n  constructor(args: {\n    feedId?: string;\n    actorId: string;\n    sequence: number;\n    currentPosition: number;\n    requestedPosition: number | null;\n  }) {\n    super({\n      message: `Block already has position ${args.currentPosition}, cannot set to ${args.requestedPosition} (feedId=${args.feedId} actorId=${args.actorId} sequence=${args.sequence}).`,\n      context: args,\n    });\n  }\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport * as EffectContext from 'effect/Context';\nimport * as Effect from 'effect/Effect';\n\nimport { Event } from '@dxos/async';\nimport { assertArgument } from '@dxos/invariant';\nimport { type SpaceId } from '@dxos/keys';\nimport { FeedProtocol } from '@dxos/protocols';\nimport { SqlTransaction } from '@dxos/sql-sqlite';\n\nimport { PositionConflictError } from './errors';\n\ntype AppendRequest = FeedProtocol.AppendRequest;\ntype AppendResponse = FeedProtocol.AppendResponse;\ntype Block = FeedProtocol.Block;\nconst FeedCursor = FeedProtocol.FeedCursor;\ntype FeedCursor = FeedProtocol.FeedCursor;\nconst isWellKnownNamespace = FeedProtocol.isWellKnownNamespace;\ntype QueryRequest = FeedProtocol.QueryRequest;\ntype QueryResponse = FeedProtocol.QueryResponse;\ntype SubscribeRequest = FeedProtocol.SubscribeRequest;\ntype SubscribeResponse = FeedProtocol.SubscribeResponse;\n\nexport interface FeedStoreOptions {\n  /**\n   * The actor ID of the local user.\n   */\n  localActorId: string;\n\n  /**\n   * Whether to assign positions to appended blocks.\n   * Only a single peer (usually the server) can assign positions.\n   */\n  assignPositions: boolean;\n}\n\n/**\n * Effect service tag for {@link FeedStore}.\n */\nexport class FeedStoreService extends EffectContext.Tag('@dxos/feed/FeedStore')<FeedStoreService, FeedStore>() {}\n\n/**\n * Persistent storage for feed metadata, blocks, subscriptions, and sync state.\n *\n */\nexport class FeedStore {\n  readonly #options: FeedStoreOptions;\n\n  constructor(options: FeedStoreOptions) {\n    this.#options = options;\n  }\n\n  /**\n   * Emits after successful block append operations.\n   */\n  readonly onNewBlocks = new Event<void>();\n\n  /**\n   * Creates required feed store tables and indexes if they do not exist.\n   */\n  migrate = Effect.fn('FeedStore.migrate')(() =>\n    Effect.gen(function* () {\n      const sql = yield* SqlClient.SqlClient;\n\n      // Feeds Table\n      yield* sql`CREATE TABLE IF NOT EXISTS feeds (\n        feedPrivateId INTEGER PRIMARY KEY AUTOINCREMENT,\n        spaceId TEXT NOT NULL,\n        feedId TEXT NOT NULL,\n        feedNamespace TEXT\n      )`;\n      yield* sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_feeds_spaceId_feedId ON feeds(spaceId, feedId)`;\n\n      // Blocks Table\n      yield* sql`CREATE TABLE IF NOT EXISTS blocks (\n        insertionId INTEGER PRIMARY KEY AUTOINCREMENT,\n        feedPrivateId INTEGER NOT NULL,\n        position INTEGER,\n        sequence INTEGER NOT NULL,\n        actorId TEXT NOT NULL,\n        prevSequence INTEGER,\n        prevActorId TEXT,\n        timestamp INTEGER NOT NULL,\n        data BLOB NOT NULL,\n        FOREIGN KEY(feedPrivateId) REFERENCES feeds(feedPrivateId)\n      )`;\n      yield* sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_blocks_feedPrivateId_position ON blocks(feedPrivateId, position)`;\n      yield* sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_blocks_feedPrivateId_sequence_actorId ON blocks(feedPrivateId, sequence, actorId)`;\n\n      // Subscriptions Table\n      yield* sql`CREATE TABLE IF NOT EXISTS subscriptions (\n          subscriptionId TEXT PRIMARY KEY,\n          expiresAt INTEGER NOT NULL,\n          feedPrivateIds TEXT NOT NULL -- JSON array\n      )`;\n\n      // Cursor Tokens Table\n      yield* sql`CREATE TABLE IF NOT EXISTS cursor_tokens (\n          spaceId TEXT PRIMARY KEY,\n          token TEXT NOT NULL\n      )`;\n\n      // Sync State Table.\n      yield* sql`CREATE TABLE IF NOT EXISTS sync_state (\n          spaceId TEXT NOT NULL,\n          feedNamespace TEXT NOT NULL,\n          lastPulledPosition INTEGER NOT NULL DEFAULT -1,\n          PRIMARY KEY (spaceId, feedNamespace)\n      )`;\n    }).pipe(Effect.withSpan('FeedStore.migrate')),\n  );\n\n  /**\n   * Ensures a feed row exists and returns its internal feed ID.\n   */\n  #ensureFeed = Effect.fn('Feed.ensureFeed')(\n    (\n      spaceId: string,\n      feedId: string,\n      namespace?: string,\n    ): Effect.Effect<number, SqlError.SqlError, SqlClient.SqlClient> =>\n      Effect.gen(this, function* () {\n        const sql = yield* SqlClient.SqlClient;\n\n        const rows = yield* sql<{ feedPrivateId: number }>`\n              SELECT feedPrivateId FROM feeds WHERE spaceId = ${spaceId} AND feedId = ${feedId}\n          `;\n        if (rows.length > 0) {\n          return rows[0].feedPrivateId;\n        }\n\n        const newRows = yield* sql<{ feedPrivateId: number }>`\n              INSERT INTO feeds (spaceId, feedId, feedNamespace) VALUES (${spaceId}, ${feedId}, ${namespace}) RETURNING feedPrivateId\n          `;\n        return newRows[0].feedPrivateId;\n      }).pipe(Effect.withSpan('FeedStore.ensureFeed')),\n  );\n\n  /**\n   * Ensures cursor token exists for a space and returns it.\n   */\n  #ensureCursorToken = Effect.fn('Feed.ensureCursorToken')(\n    (spaceId: string): Effect.Effect<string, SqlError.SqlError, SqlClient.SqlClient> =>\n      Effect.gen(this, function* () {\n        const sql = yield* SqlClient.SqlClient;\n        const rows = yield* sql<{ token: string }>`SELECT token FROM cursor_tokens WHERE spaceId = ${spaceId}`;\n        if (rows.length > 0) {\n          return rows[0].token;\n        }\n\n        const token = crypto.randomUUID().replace(/-/g, '').slice(0, 6);\n        yield* sql`INSERT INTO cursor_tokens (spaceId, token) VALUES (${spaceId}, ${token})`;\n        return token;\n      }).pipe(Effect.withSpan('FeedStore.ensureCursorToken')),\n  );\n\n  /**\n   * Queries feed blocks by feed IDs or subscription with cursor/position pagination.\n   */\n  query = Effect.fn('Feed.query')(\n    (request: QueryRequest): Effect.Effect<QueryResponse, SqlError.SqlError, SqlClient.SqlClient> =>\n      Effect.gen(this, function* () {\n        const sql = yield* SqlClient.SqlClient;\n        let feedIds: string[] | undefined = [];\n        let cursorInsertionId = -1;\n        let cursorToken: string | undefined;\n\n        if (!request.spaceId) {\n          return yield* Effect.die(new Error('spaceId is required'));\n        }\n\n        if (\n          (request.position !== undefined ? 1 : 0) +\n            (request.cursor !== undefined ? 1 : 0) +\n            (request.unpositionedOnly === true ? 1 : 0) >\n          1\n        ) {\n          return yield* Effect.die(new Error('Only one of position, cursor, or unpositionedOnly can be used'));\n        }\n\n        if (request.cursor) {\n          const { token, insertionId } = decodeCursor(request.cursor as FeedCursor);\n          if (!token || insertionId === undefined || isNaN(insertionId)) {\n            return yield* Effect.die(new Error(`Invalid cursor format`));\n          }\n          cursorToken = token;\n          cursorInsertionId = insertionId;\n        }\n\n        // Validate Token if cursor used.\n        const validCursorToken = yield* this.#ensureCursorToken(request.spaceId);\n        if (request.cursor && cursorToken !== validCursorToken) {\n          return yield* Effect.die(new Error(`Cursor token mismatch`));\n        }\n\n        // If cursor is provided, we must validate it against the space token.\n        // If spaceId is not provided in request (e.g. feedIds query), we can't easily validate token unless we look up spaceId for feedIds.\n        // Ideally spaceId should be required for token validation.\n\n        /*\n           Logic:\n           1. If `cursor` is present, it's `token|insertionId`.\n           2. If `position` is present, it's `position` (legacy/manual).\n           \n           We prioritize `cursor`.\n        */\n\n        const position = request.position ?? -1;\n\n        // Resolve subscriptions or feed IDs.\n        if (request.query && 'subscriptionId' in request.query) {\n          const rows = yield* sql<{ feedPrivateIds: string; expiresAt: number }>`\n                SELECT feedPrivateIds, expiresAt FROM subscriptions WHERE subscriptionId = ${request.query.subscriptionId}\n            `;\n          if (rows.length > 0) {\n            const { feedPrivateIds, expiresAt } = rows[0];\n            if (Date.now() <= expiresAt) {\n              const privateIds = JSON.parse(feedPrivateIds) as number[];\n              if (privateIds.length > 0) {\n                const feedRows = yield* sql<{ feedId: string }>`\n                             SELECT feedId FROM feeds WHERE feedPrivateId IN ${sql.in(privateIds)}\n                         `;\n                feedIds = feedRows.map((r) => r.feedId);\n              }\n            }\n          }\n        } else if (request.query && 'feedIds' in request.query) {\n          feedIds = [...request.query.feedIds];\n        } else {\n          feedIds = undefined;\n        }\n\n        if (feedIds !== undefined && feedIds.length === 0) {\n          return {\n            requestId: request.requestId,\n            blocks: [],\n            nextCursor: encodeCursor(validCursorToken, -1),\n            hasMore: false,\n          };\n        }\n\n        // Fetch blocks.\n        const query = sql<Block>`\n            SELECT blocks.*, feeds.feedId, feeds.feedNamespace\n            FROM blocks\n            JOIN feeds ON blocks.feedPrivateId = feeds.feedPrivateId\n            WHERE 1=1\n            ${feedIds !== undefined ? sql`AND feeds.feedId IN ${sql.in(feedIds)}` : sql``}\n            ${request.spaceId ? sql`AND feeds.spaceId = ${request.spaceId}` : sql``}\n            ${sql`AND feeds.feedNamespace = ${request.feedNamespace}`}\n        `;\n\n        // Add filter based on cursor or position.\n        const filter = request.cursor\n          ? sql`AND blocks.insertionId > ${cursorInsertionId}`\n          : request.unpositionedOnly\n            ? sql`AND blocks.position IS NULL`\n            : sql`AND (blocks.position > ${position} OR blocks.position IS NULL)`;\n\n        const orderBy = request.cursor\n          ? sql`ORDER BY blocks.insertionId ASC`\n          : sql`ORDER BY blocks.position ASC NULLS LAST`;\n\n        const requestLimit = request.limit;\n        const queryLimit = requestLimit != null ? requestLimit + 1 : undefined;\n        const rows = yield* sql<Block>`\n            ${query}\n            ${filter}\n            ${orderBy}\n            ${queryLimit != null ? sql`LIMIT ${queryLimit}` : sql``}\n        `;\n\n        const hasMore = requestLimit != null && rows.length > requestLimit;\n        const slice = hasMore ? rows.slice(0, requestLimit) : rows;\n        const blocks = slice.map((row) => ({\n          ...row,\n          // Have to clone buffer otherwise we get empty Uint8Array.\n          data: new Uint8Array(row.data),\n        }));\n\n        let nextCursor: FeedCursor = request.cursor ?? encodeCursor(validCursorToken, -1);\n        if (blocks.length > 0 && request.spaceId) {\n          const lastBlock = blocks[blocks.length - 1];\n          if (lastBlock.insertionId !== undefined) {\n            nextCursor = encodeCursor(validCursorToken, lastBlock.insertionId);\n          }\n        }\n\n        return { requestId: request.requestId, blocks, nextCursor, hasMore } satisfies QueryResponse;\n      }).pipe(Effect.withSpan('FeedStore.query')),\n  );\n\n  /**\n   * Creates a subscription and stores the resolved internal feed IDs.\n   */\n  subscribe = Effect.fn('Feed.subscribe')(\n    (request: SubscribeRequest): Effect.Effect<SubscribeResponse, SqlError.SqlError, SqlClient.SqlClient> =>\n      Effect.gen(this, function* () {\n        const sql = yield* SqlClient.SqlClient;\n        const ttl = 60 * 60 * 1000;\n        const subscriptionId = crypto.randomUUID();\n        const expiresAt = Date.now() + ttl;\n\n        if (!request.spaceId) {\n          // TODO(dmaretskyi): Define error type.\n          return yield* Effect.die(new Error('spaceId required for subscribe'));\n        }\n\n        const feedPrivateIds = yield* Effect.forEach(\n          request.feedIds,\n          (feedId) => this.#ensureFeed(request.spaceId!, feedId),\n          { concurrency: 'unbounded' },\n        );\n\n        yield* sql`\n            INSERT INTO subscriptions (subscriptionId, expiresAt, feedPrivateIds)\n            VALUES (${subscriptionId}, ${expiresAt}, ${JSON.stringify(feedPrivateIds)})\n        `;\n\n        return {\n          requestId: request.requestId,\n          subscriptionId,\n          expiresAt,\n        };\n      }).pipe(Effect.withSpan('FeedStore.subscribe')),\n  );\n\n  /**\n   * Get the last pulled position for the given space and namespace.\n   * Returns -1 if no sync state exists yet.\n   */\n  getSyncState = (opts: {\n    spaceId: SpaceId;\n    feedNamespace: string;\n  }): Effect.Effect<number, SqlError.SqlError, SqlClient.SqlClient> =>\n    Effect.gen(this, function* () {\n      const sql = yield* SqlClient.SqlClient;\n      const rows = yield* sql<{ lastPulledPosition: number }>`\n        SELECT lastPulledPosition FROM sync_state\n        WHERE spaceId = ${opts.spaceId} AND feedNamespace = ${opts.feedNamespace}\n      `;\n      return rows[0]?.lastPulledPosition ?? -1;\n    }).pipe(Effect.withSpan('FeedStore.getSyncState'));\n\n  /**\n   * Update the last pulled position for the given space and namespace.\n   */\n  setSyncState = (opts: {\n    spaceId: SpaceId;\n    feedNamespace: string;\n    lastPulledPosition: number;\n  }): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n    Effect.gen(this, function* () {\n      const sql = yield* SqlClient.SqlClient;\n      yield* sql`\n        INSERT INTO sync_state (spaceId, feedNamespace, lastPulledPosition)\n        VALUES (${opts.spaceId}, ${opts.feedNamespace}, ${opts.lastPulledPosition})\n        ON CONFLICT (spaceId, feedNamespace) DO UPDATE SET lastPulledPosition = ${opts.lastPulledPosition}\n      `;\n    }).pipe(Effect.withSpan('FeedStore.setSyncState'));\n\n  /**\n   * Returns the number of blocks pending push (no global position yet) in a space/namespace.\n   */\n  countUnpositionedBlocks = (opts: {\n    spaceId: SpaceId;\n    feedNamespace: string;\n  }): Effect.Effect<number, SqlError.SqlError, SqlClient.SqlClient> =>\n    Effect.gen(this, function* () {\n      const sql = yield* SqlClient.SqlClient;\n      const rows = yield* sql<{ count: number }>`\n        SELECT COUNT(*) AS count\n        FROM blocks\n        JOIN feeds ON blocks.feedPrivateId = feeds.feedPrivateId\n        WHERE feeds.spaceId = ${opts.spaceId}\n          AND feeds.feedNamespace = ${opts.feedNamespace}\n          AND blocks.position IS NULL\n      `;\n      return rows[0]?.count ?? 0;\n    }).pipe(Effect.withSpan('FeedStore.countUnpositionedBlocks'));\n\n  /**\n   * Returns the total number of blocks stored locally for a space/namespace.\n   */\n  countNamespaceBlocks = (opts: {\n    spaceId: SpaceId;\n    feedNamespace: string;\n  }): Effect.Effect<number, SqlError.SqlError, SqlClient.SqlClient> =>\n    Effect.gen(this, function* () {\n      const sql = yield* SqlClient.SqlClient;\n      const rows = yield* sql<{ count: number }>`\n        SELECT COUNT(*) AS count\n        FROM blocks\n        JOIN feeds ON blocks.feedPrivateId = feeds.feedPrivateId\n        WHERE feeds.spaceId = ${opts.spaceId}\n          AND feeds.feedNamespace = ${opts.feedNamespace}\n      `;\n      return rows[0]?.count ?? 0;\n    }).pipe(Effect.withSpan('FeedStore.countNamespaceBlocks'));\n\n  /**\n   * Returns the number of stored blocks for a single feed in a space/namespace.\n   * Intended as a low-level primitive for callers (for example Cloudflare Worker code)\n   * that need to make retention decisions under constrained storage resources.\n   */\n  countBlocks = (opts: {\n    spaceId: SpaceId;\n    feedNamespace: string;\n    feedId: string;\n  }): Effect.Effect<number, SqlError.SqlError, SqlClient.SqlClient> =>\n    Effect.gen(this, function* () {\n      const sql = yield* SqlClient.SqlClient;\n      const rows = yield* sql<{ count: number }>`\n        SELECT COUNT(*) AS count\n        FROM blocks\n        JOIN feeds ON blocks.feedPrivateId = feeds.feedPrivateId\n        WHERE feeds.spaceId = ${opts.spaceId}\n          AND feeds.feedNamespace = ${opts.feedNamespace}\n          AND feeds.feedId = ${opts.feedId}\n      `;\n      return rows[0]?.count ?? 0;\n    }).pipe(Effect.withSpan('FeedStore.countBlocks'));\n\n  /**\n   * Deletes the oldest blocks for a single feed in a space/namespace.\n   * This API intentionally does not enforce any retention policy (such as max size);\n   * callers decide when and how much to prune, which is useful for constrained\n   * environments like Cloudflare Workers.\n   *\n   * @returns Number of deleted rows.\n   */\n  deleteOldestBlocks = (opts: {\n    spaceId: SpaceId;\n    feedNamespace: string;\n    feedId: string;\n    count: number;\n  }): Effect.Effect<number, SqlError.SqlError, SqlClient.SqlClient> =>\n    Effect.gen(this, function* () {\n      const sql = yield* SqlClient.SqlClient;\n      if (opts.count <= 0) {\n        return 0;\n      }\n\n      const deletedRows = yield* sql<{ insertionId: number }>`\n        DELETE FROM blocks\n        WHERE insertionId IN (\n          SELECT blocks.insertionId\n          FROM blocks\n          JOIN feeds ON blocks.feedPrivateId = feeds.feedPrivateId\n          WHERE feeds.spaceId = ${opts.spaceId}\n            AND feeds.feedNamespace = ${opts.feedNamespace}\n            AND feeds.feedId = ${opts.feedId}\n          ORDER BY blocks.insertionId ASC\n          LIMIT ${opts.count}\n        )\n        RETURNING insertionId\n      `;\n\n      return deletedRows.length;\n    }).pipe(Effect.withSpan('FeedStore.deleteOldestBlocks'));\n\n  /**\n   * Appends blocks for a space/namespace and optionally assigns global positions.\n   */\n  append = (\n    request: AppendRequest,\n  ): Effect.Effect<AppendResponse, SqlError.SqlError, SqlClient.SqlClient | SqlTransaction.SqlTransaction> =>\n    Effect.gen(this, function* () {\n      if (!request.spaceId) {\n        return yield* Effect.die(new Error('spaceId required for append'));\n      }\n\n      assertArgument(\n        isWellKnownNamespace(request.feedNamespace),\n        'request.feedNamespace',\n        'specified well-known namespace',\n      );\n\n      // Validate all blocks upfront.\n      for (const block of request.blocks) {\n        assertArgument(block.feedId, 'block.feedId', 'feedId is required');\n      }\n\n      // Wrap in transaction to ensure atomicity when assigning positions.\n      const sqlTransaction = yield* SqlTransaction.SqlTransaction;\n      const positions = yield* sqlTransaction.withTransaction(\n        Effect.gen(this, function* () {\n          const sql = yield* SqlClient.SqlClient;\n\n          // 1. Collect unique feed IDs and batch #ensureFeed calls.\n          const feedKeys = new Map<string, { feedId: string }>();\n          for (const block of request.blocks) {\n            const key = block.feedId!;\n            if (!feedKeys.has(key)) {\n              feedKeys.set(key, { feedId: block.feedId! });\n            }\n          }\n\n          const feedPrivateIds = new Map<string, number>();\n          yield* Effect.forEach(\n            [...feedKeys.entries()],\n            ([key, { feedId }]) =>\n              Effect.gen(this, function* () {\n                const id = yield* this.#ensureFeed(request.spaceId!, feedId, request.feedNamespace);\n                feedPrivateIds.set(key, id);\n              }),\n            { concurrency: 'unbounded' },\n          );\n\n          // 2. Get max position per namespace ONCE (not per block).\n          const maxPositions = new Map<string, number>();\n          if (this.#options.assignPositions) {\n            const maxPosResult = yield* sql<{ maxPos: number | null }>`\n              SELECT MAX(position) as maxPos \n              FROM blocks \n              JOIN feeds ON blocks.feedPrivateId = feeds.feedPrivateId\n              WHERE feeds.spaceId = ${request.spaceId} AND feeds.feedNamespace = ${request.feedNamespace}\n            `;\n            maxPositions.set(request.feedNamespace, maxPosResult[0]?.maxPos ?? -1);\n          }\n\n          // 3. Insert all blocks and compute positions.\n          //\n          // Critical: when a block already exists (conflict on (feedPrivateId, sequence, actorId))\n          // we must (a) return the EXISTING position to the caller and (b) NOT advance the\n          // namespace position counter. Otherwise the response contains wasted slots that\n          // sync clients will try to UPDATE local rows to, tripping the\n          // (feedPrivateId, position) UNIQUE constraint and stalling sync indefinitely.\n          const positions: number[] = [];\n          for (const block of request.blocks) {\n            const key = block.feedId!;\n            const feedPrivateId = feedPrivateIds.get(key)!;\n\n            let positionToInsert: number | null = null;\n            if (this.#options.assignPositions) {\n              positionToInsert = maxPositions.get(request.feedNamespace)! + 1;\n            } else if (block.position != null) {\n              positionToInsert = block.position;\n            }\n\n            const inserted = yield* sql<{ position: number | null }>`\n              INSERT INTO blocks (\n                feedPrivateId, position, sequence, actorId,\n                prevSequence, prevActorId, timestamp, data\n              ) VALUES (\n                ${feedPrivateId}, ${positionToInsert}, ${block.sequence}, ${block.actorId},\n                ${block.prevSequence}, ${block.prevActorId}, ${block.timestamp}, ${block.data}\n              )\n              ON CONFLICT(feedPrivateId, sequence, actorId) DO NOTHING\n              RETURNING position\n            `;\n\n            if (!this.#options.assignPositions) {\n              continue;\n            }\n\n            if (inserted.length > 0) {\n              // New row written at the freshly allocated position.\n              positions.push(positionToInsert!);\n              maxPositions.set(request.feedNamespace, positionToInsert!);\n              continue;\n            }\n\n            // Duplicate Lamport tuple: return the stored position and leave maxPositions alone.\n            const existing = yield* sql<{ position: number | null }>`\n              SELECT position FROM blocks\n              WHERE feedPrivateId = ${feedPrivateId}\n                AND actorId = ${block.actorId}\n                AND sequence = ${block.sequence}\n            `;\n            const existingPosition = existing[0]?.position;\n            if (existingPosition != null) {\n              positions.push(existingPosition);\n              continue;\n            }\n\n            // Defensive: existing row carries no position (e.g. inserted via an earlier\n            // assignPositions=false path). Back-fill it so the caller can mark it positioned.\n            yield* sql`\n              UPDATE blocks SET position = ${positionToInsert}\n              WHERE feedPrivateId = ${feedPrivateId}\n                AND actorId = ${block.actorId}\n                AND sequence = ${block.sequence}\n            `;\n            positions.push(positionToInsert!);\n            maxPositions.set(request.feedNamespace, positionToInsert!);\n          }\n\n          return positions;\n        }),\n      );\n\n      this.onNewBlocks.emit();\n\n      return { requestId: request.requestId, positions };\n    }).pipe(Effect.withSpan('FeedStore.append'));\n\n  /**\n   * Creates local blocks with sequential predecessors and appends grouped batches.\n   *\n   * A block whose object id is later superseded by a newer same-id block (a live feed object's\n   * `Obj.update`, persisted as a whole-object re-append) is never reclaimed — the index collapses\n   * reads to the latest block by id, but old blocks stay on disk indefinitely.\n   * TODO(wittjosiah): Add compaction/retention driven by `Feed.RetentionOptions`.\n   */\n  appendLocal = Effect.fn('Feed.appendLocal')(\n    (\n      messages: { spaceId: string; feedId: string; feedNamespace: string; data: Uint8Array }[],\n    ): Effect.Effect<Block[], SqlError.SqlError, SqlClient.SqlClient | SqlTransaction.SqlTransaction> =>\n      Effect.gen(this, function* () {\n        const sql = yield* SqlClient.SqlClient;\n\n        // 1. Collect unique feeds and ensure they exist.\n        type FeedKey = string; // `${spaceId}|${feedNamespace}|${feedId}`\n        const feedKeys = new Map<FeedKey, { spaceId: string; feedId: string; feedNamespace: string }>();\n        for (const msg of messages) {\n          const key = `${msg.spaceId}|${msg.feedNamespace}|${msg.feedId}`;\n          if (!feedKeys.has(key)) {\n            feedKeys.set(key, { spaceId: msg.spaceId, feedId: msg.feedId, feedNamespace: msg.feedNamespace });\n          }\n        }\n\n        // Batch ensure feeds and get their private IDs.\n        const feedPrivateIds = new Map<FeedKey, number>();\n        yield* Effect.forEach(\n          [...feedKeys.entries()],\n          ([key, { spaceId, feedId, feedNamespace }]) =>\n            Effect.gen(this, function* () {\n              const id = yield* this.#ensureFeed(spaceId, feedId, feedNamespace);\n              feedPrivateIds.set(key, id);\n            }),\n          { concurrency: 'unbounded' },\n        );\n\n        // 2. Get last sequence for each unique feed (batch query).\n        const lastSeqs = new Map<FeedKey, { sequence: number; actorId: string } | null>();\n        for (const [key] of feedKeys) {\n          const feedPrivateId = feedPrivateIds.get(key)!;\n          const lastBlockResult = yield* sql<{ sequence: number; actorId: string }>`\n            SELECT sequence, actorId FROM blocks \n            WHERE feedPrivateId = ${feedPrivateId} \n            ORDER BY sequence DESC \n            LIMIT 1\n          `;\n          lastSeqs.set(key, lastBlockResult[0] ?? null);\n        }\n\n        // 3. Build all blocks with correct sequences.\n        // Track in-flight sequences per feed to handle multiple messages to same feed.\n        const currentSeqs = new Map<FeedKey, { sequence: number; actorId: string }>();\n        const blocks: Block[] = [];\n        const blocksBySpaceNamespace = new Map<string, { spaceId: string; feedNamespace: string; blocks: Block[] }>();\n\n        for (const msg of messages) {\n          const key = `${msg.spaceId}|${msg.feedNamespace}|${msg.feedId}`;\n\n          // Determine predecessor: either from in-flight blocks or from DB.\n          let sequence: number;\n          let prevSequence: number | null;\n          let prevActorId: string | null;\n\n          const inFlight = currentSeqs.get(key);\n          if (inFlight) {\n            // We've already added blocks for this feed - continue from last in-flight.\n            sequence = inFlight.sequence + 1;\n            prevSequence = inFlight.sequence;\n            prevActorId = inFlight.actorId;\n          } else {\n            // First block for this feed - use DB state.\n            const lastBlock = lastSeqs.get(key);\n            sequence = (lastBlock?.sequence ?? -1) + 1;\n            prevSequence = lastBlock?.sequence ?? null;\n            prevActorId = lastBlock?.actorId ?? null;\n          }\n\n          const block: Block = {\n            feedId: msg.feedId,\n            actorId: this.#options.localActorId,\n            sequence,\n            prevActorId,\n            prevSequence,\n            timestamp: Date.now(),\n            data: msg.data,\n            position: null, // Assigned by append.\n          };\n\n          blocks.push(block);\n\n          // Update in-flight tracking.\n          currentSeqs.set(key, { sequence, actorId: this.#options.localActorId });\n\n          // Group by (spaceId, feedNamespace).\n          const spaceNamespaceKey = `${msg.spaceId}|${msg.feedNamespace}`;\n          if (!blocksBySpaceNamespace.has(spaceNamespaceKey)) {\n            blocksBySpaceNamespace.set(spaceNamespaceKey, {\n              spaceId: msg.spaceId,\n              feedNamespace: msg.feedNamespace,\n              blocks: [],\n            });\n          }\n          blocksBySpaceNamespace.get(spaceNamespaceKey)!.blocks.push(block);\n        }\n\n        // 4. Call append once per (spaceId, namespace) batch and assign returned positions.\n        const positionByBlock = new Map<Block, number | null>();\n        for (const { spaceId, feedNamespace, blocks: batchBlocks } of blocksBySpaceNamespace.values()) {\n          const { positions } = yield* this.append({\n            requestId: 'local-append',\n            blocks: batchBlocks,\n            spaceId,\n            feedNamespace,\n          });\n          for (let i = 0; i < batchBlocks.length; i++) {\n            positionByBlock.set(batchBlocks[i], positions[i] ?? null);\n          }\n        }\n\n        return blocks.map((block) => ({\n          ...block,\n          position: positionByBlock.get(block) ?? block.position,\n        }));\n      }).pipe(Effect.withSpan('FeedStore.appendLocal')),\n  );\n\n  /**\n   * Sets positions for existing blocks while preventing conflicting reassignments.\n   */\n  setPosition = (request: {\n    spaceId: string;\n    blocks: (Pick<Block, 'feedId' | 'actorId' | 'sequence' | 'position'> & { feedNamespace: string })[];\n  }): Effect.Effect<void, SqlError.SqlError | PositionConflictError, SqlClient.SqlClient> =>\n    Effect.gen(this, function* () {\n      const sql = yield* SqlClient.SqlClient;\n      for (const block of request.blocks) {\n        // Fold the conflict check into the UPDATE itself: only write when the row is\n        // unset or already at the requested position. RETURNING tells us whether the\n        // write took effect, so we avoid the per-block SELECT round-trip on the\n        // common path. Conflicts (rare) take the slow path below.\n        const updated = yield* sql<{ position: number | null }>`\n          UPDATE blocks SET position = ${block.position}\n          WHERE feedPrivateId = (\n            SELECT feedPrivateId FROM feeds\n            WHERE spaceId = ${request.spaceId} AND feedId = ${block.feedId} AND feedNamespace = ${block.feedNamespace}\n          )\n          AND actorId = ${block.actorId} AND sequence = ${block.sequence}\n          AND (position IS NULL OR position = ${block.position})\n          RETURNING position\n        `;\n        if (updated.length > 0) {\n          continue;\n        }\n        const existing = yield* sql<{ position: number | null }>`\n          SELECT position FROM blocks\n          WHERE feedPrivateId = (\n            SELECT feedPrivateId FROM feeds\n            WHERE spaceId = ${request.spaceId} AND feedId = ${block.feedId} AND feedNamespace = ${block.feedNamespace}\n          )\n          AND actorId = ${block.actorId} AND sequence = ${block.sequence}\n        `;\n        const current = existing[0];\n        if (current?.position != null && current.position !== block.position) {\n          yield* Effect.fail(\n            new PositionConflictError({\n              feedId: block.feedId,\n              actorId: block.actorId,\n              sequence: block.sequence,\n              currentPosition: current.position,\n              requestedPosition: block.position,\n            }),\n          );\n        }\n      }\n    }).pipe(Effect.withSpan('FeedStore.setPosition'));\n\n  /**\n   * Gets all feeds and their blocks for a space, organized by feed ID and namespace.\n   * Used for space archive export.\n   */\n  getAllFeedsForSpace = (opts: {\n    spaceId: SpaceId;\n  }): Effect.Effect<\n    Array<{\n      feedId: string;\n      feedNamespace: string;\n      blocks: Block[];\n    }>,\n    SqlError.SqlError,\n    SqlClient.SqlClient\n  > =>\n    Effect.gen(this, function* () {\n      const sql = yield* SqlClient.SqlClient;\n\n      const feeds = yield* sql<{ feedId: string; feedNamespace: string }>`\n        SELECT DISTINCT feedId, feedNamespace FROM feeds WHERE spaceId = ${opts.spaceId}\n      `;\n\n      const result: Array<{ feedId: string; feedNamespace: string; blocks: Block[] }> = [];\n\n      for (const feed of feeds) {\n        const blocks = yield* sql<Block>`\n          SELECT blocks.*, feeds.feedId, feeds.feedNamespace\n          FROM blocks\n          JOIN feeds ON blocks.feedPrivateId = feeds.feedPrivateId\n          WHERE feeds.spaceId = ${opts.spaceId}\n            AND feeds.feedId = ${feed.feedId}\n            AND feeds.feedNamespace = ${feed.feedNamespace}\n          ORDER BY blocks.sequence ASC\n        `;\n\n        result.push({\n          feedId: feed.feedId,\n          feedNamespace: feed.feedNamespace,\n          blocks: blocks.map((row) => ({\n            ...row,\n            data: new Uint8Array(row.data),\n          })),\n        });\n      }\n\n      return result;\n    }).pipe(Effect.withSpan('FeedStore.getAllFeedsForSpace'));\n}\n\nconst encodeCursor = (token: string, insertionId: number) => FeedCursor.make(`${token}|${insertionId}`);\nconst decodeCursor = (cursor: FeedCursor) => {\n  const [token, insertionId] = cursor.split('|');\n  return { token, insertionId: Number(insertionId) };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport type * as SqlClient from '@effect/sql/SqlClient';\nimport * as Effect from 'effect/Effect';\n\nimport { Context } from '@dxos/context';\nimport { log } from '@dxos/log';\nimport { type FeedProtocol } from '@dxos/protocols';\nimport type { SqlTransaction } from '@dxos/sql-sqlite';\n\nimport type { FeedStore } from './feed-store';\n\ntype AppendRequest = FeedProtocol.AppendRequest;\ntype ProtocolMessage = FeedProtocol.ProtocolMessage;\ntype QueryRequest = FeedProtocol.QueryRequest;\ntype QueryResponse = FeedProtocol.QueryResponse;\n\nconst inboundSummary = (message: ProtocolMessage) => ({\n  tag: message._tag,\n  senderPeerId: message.senderPeerId,\n  recipientPeerId: message.recipientPeerId,\n  requestId: 'requestId' in message ? message.requestId : undefined,\n  spaceId: 'spaceId' in message ? message.spaceId : undefined,\n  feedNamespace: 'feedNamespace' in message ? message.feedNamespace : undefined,\n  blockCount: 'blocks' in message ? message.blocks.length : undefined,\n  position: 'position' in message ? message.position : undefined,\n  limit: 'limit' in message ? message.limit : undefined,\n});\n\nexport type SyncServerOptions = {\n  /** This server's peer id. Set as senderPeerId on all replies. */\n  peerId: string;\n  feedStore: FeedStore;\n  /** Send a protocol message to a client. Receives full message (recipient is set by server). Returns Effect. */\n  sendMessage: (ctx: Context, message: ProtocolMessage) => Effect.Effect<void, unknown, never>;\n};\n\n/**\n * Server-side sync: receives protocol messages, runs query/append against the store, sends responses.\n * Reusable for many peers: sets senderPeerId = peerId and recipientPeerId = incoming message's senderPeerId on replies.\n */\nexport class SyncServer {\n  readonly #peerId: string;\n  readonly #feedStore: FeedStore;\n  readonly #sendMessage: SyncServerOptions['sendMessage'];\n\n  constructor(options: SyncServerOptions) {\n    this.#peerId = options.peerId;\n    this.#feedStore = options.feedStore;\n    this.#sendMessage = options.sendMessage;\n  }\n\n  /**\n   * Receive a message from a client. Handles QueryRequest and AppendRequest; sends response via sendMessage with correct peer ids.\n   */\n  handleMessage(\n    ctx: Context,\n    message: ProtocolMessage,\n  ): Effect.Effect<void, unknown, SqlClient.SqlClient | SqlTransaction.SqlTransaction> {\n    const self = this;\n    log('feed sync server received message', {\n      peerId: self.#peerId,\n      ...inboundSummary(message),\n    });\n    const recipientPeerId = message.senderPeerId;\n    const withPeerIds = (payload: Omit<ProtocolMessage, 'senderPeerId' | 'recipientPeerId'>): ProtocolMessage =>\n      ({\n        ...payload,\n        senderPeerId: self.#peerId,\n        recipientPeerId,\n      }) as ProtocolMessage;\n    switch (message._tag) {\n      case 'QueryRequest': {\n        const req = message as QueryRequest;\n        return Effect.gen(function* () {\n          const response: QueryResponse = yield* self.#feedStore.query(req);\n          log('feed sync server query completed', {\n            peerId: self.#peerId,\n            recipientPeerId,\n            requestId: req.requestId,\n            spaceId: req.spaceId,\n            feedNamespace: req.feedNamespace,\n            returnedBlocks: response.blocks.length,\n            hasMore: response.hasMore,\n          });\n          yield* self.#sendMessage(ctx, withPeerIds({ _tag: 'QueryResponse', ...response }));\n        }).pipe(\n          Effect.catchAll((err: unknown) => {\n            log('feed sync server query failed', {\n              peerId: self.#peerId,\n              recipientPeerId,\n              requestId: req.requestId,\n              spaceId: req.spaceId,\n              feedNamespace: req.feedNamespace,\n              cause: err instanceof Error ? err.message : String(err),\n            });\n            return self.#sendMessage(\n              ctx,\n              withPeerIds({\n                _tag: 'Error',\n                message: err instanceof Error ? err.message : String(err),\n              } as Omit<ProtocolMessage, 'senderPeerId' | 'recipientPeerId'>),\n            );\n          }),\n        );\n      }\n      case 'AppendRequest': {\n        const req = message as AppendRequest;\n        return Effect.gen(function* () {\n          const response = yield* self.#feedStore.append(req);\n          log('feed sync server append completed', {\n            peerId: self.#peerId,\n            recipientPeerId,\n            requestId: req.requestId,\n            spaceId: req.spaceId,\n            feedNamespace: req.feedNamespace,\n            blockCount: req.blocks.length,\n            assignedPositions: response.positions.length,\n          });\n          yield* self.#sendMessage(ctx, withPeerIds({ _tag: 'AppendResponse', ...response }));\n        }).pipe(\n          Effect.catchAll((err: unknown) => {\n            log('feed sync server append failed', {\n              peerId: self.#peerId,\n              recipientPeerId,\n              requestId: req.requestId,\n              spaceId: req.spaceId,\n              feedNamespace: req.feedNamespace,\n              blockCount: req.blocks.length,\n              cause: err instanceof Error ? err.message : String(err),\n            });\n            return self.#sendMessage(\n              ctx,\n              withPeerIds({\n                _tag: 'Error',\n                message: err instanceof Error ? err.message : String(err),\n              } as Omit<ProtocolMessage, 'senderPeerId' | 'recipientPeerId'>),\n            );\n          }),\n        );\n      }\n      default:\n        log('feed sync server ignoring unsupported message', {\n          peerId: self.#peerId,\n          tag: message._tag,\n        });\n        return Effect.void;\n    }\n  }\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport type * as SqlClient from '@effect/sql/SqlClient';\nimport * as Array from 'effect/Array';\nimport * as Deferred from 'effect/Deferred';\nimport * as Effect from 'effect/Effect';\n\nimport { Context, ContextDisposedError } from '@dxos/context';\nimport type { SpaceId } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { type FeedProtocol } from '@dxos/protocols';\nimport type { SqlTransaction } from '@dxos/sql-sqlite';\n\nimport { SyncRpcTimeoutError } from './errors';\nimport type { FeedStore } from './feed-store';\n\n/** Default timeout for feed sync RPCs awaiting an edge response. */\nexport const DEFAULT_SYNC_RPC_TIMEOUT_MS = 30_000;\n\ntype AppendResponse = FeedProtocol.AppendResponse;\ntype ProtocolMessage = FeedProtocol.ProtocolMessage;\ntype QueryResponse = FeedProtocol.QueryResponse;\ntype QueryRequestMessage = Extract<ProtocolMessage, { _tag: 'QueryRequest' }>;\ntype AppendRequestMessage = Extract<ProtocolMessage, { _tag: 'AppendRequest' }>;\ntype RequestMessage = QueryRequestMessage | AppendRequestMessage;\ntype RequestPayload =\n  | Omit<QueryRequestMessage, 'senderPeerId' | 'recipientPeerId'>\n  | Omit<AppendRequestMessage, 'senderPeerId' | 'recipientPeerId'>;\n\nexport type SyncClientOptions = {\n  /** This client's peer id. Set as senderPeerId on all requests. */\n  peerId: string;\n  /** The server's peer id. Set as recipientPeerId on all requests. */\n  serverPeerId?: string;\n  feedStore: FeedStore;\n  /** Send a protocol message to the server. Returns Effect. */\n  sendMessage: (ctx: Context, message: RequestMessage) => Effect.Effect<void, unknown, never>;\n  /**\n   * Max time to wait for a matching protocol response after sending a request, in milliseconds.\n   * @default {@link DEFAULT_SYNC_RPC_TIMEOUT_MS}\n   */\n  rpcTimeoutMs?: number;\n};\n\n/**\n * Client-side sync: pull/push by sending protocol messages and handling responses.\n * Sets senderPeerId = peerId and recipientPeerId = serverPeerId on all requests.\n * Uses a map of requestId -> Deferred to match responses to in-flight requests.\n * handleMessage completes the Deferred for the requestId (if any) and removes the handler; no handler = noop.\n */\nexport class SyncClient {\n  readonly #peerId: string;\n  readonly #serverPeerId: string | undefined;\n  readonly #feedStore: FeedStore;\n  readonly #sendMessage: SyncClientOptions['sendMessage'];\n  readonly #rpcTimeoutMs: number;\n  readonly #handlers = new Map<string, Deferred.Deferred<ProtocolMessage, Error>>();\n\n  constructor(options: SyncClientOptions) {\n    this.#peerId = options.peerId;\n    this.#serverPeerId = options.serverPeerId;\n    this.#feedStore = options.feedStore;\n    this.#sendMessage = options.sendMessage;\n    this.#rpcTimeoutMs = options.rpcTimeoutMs ?? DEFAULT_SYNC_RPC_TIMEOUT_MS;\n  }\n\n  #withPeerIds(payload: RequestPayload): RequestMessage {\n    if (payload._tag === 'QueryRequest') {\n      return {\n        ...payload,\n        senderPeerId: this.#peerId,\n        recipientPeerId: this.#serverPeerId,\n      };\n    }\n    return {\n      ...payload,\n      senderPeerId: this.#peerId,\n      recipientPeerId: this.#serverPeerId,\n    };\n  }\n\n  /**\n   * Receive a message from the server. If a handler is registered for the message's requestId, complete it and remove; else noop.\n   */\n  handleMessage(message: ProtocolMessage): Effect.Effect<void, never, never> {\n    const requestId = 'requestId' in message && message.requestId != null ? String(message.requestId) : undefined;\n    if (requestId == null) {\n      log.trace('feed sync client response ignored (no request id)', {\n        tag: message._tag,\n        senderPeerId: message.senderPeerId,\n      });\n      return Effect.void;\n    }\n    const deferred = this.#handlers.get(requestId);\n    if (deferred == null) {\n      log.trace('feed sync client response ignored (no pending rpc)', {\n        requestId,\n        tag: message._tag,\n        senderPeerId: message.senderPeerId,\n      });\n      return Effect.void;\n    }\n    this.#handlers.delete(requestId);\n    log('feed sync client rpc completed', {\n      requestId,\n      tag: message._tag,\n      blockCount: 'blocks' in message ? message.blocks.length : undefined,\n      positionCount: 'positions' in message ? message.positions.length : undefined,\n      error: message._tag === 'Error' ? message.message : undefined,\n    });\n    if (message._tag === 'Error') {\n      return Effect.andThen(Deferred.fail(deferred, new Error(message.message)), () => Effect.void);\n    }\n    return Effect.andThen(Deferred.succeed(deferred, message), () => Effect.void);\n  }\n\n  /** Removes the pending handler entry and unregisters the ctx onDispose hook. Idempotent. */\n  #disposeHandler(requestId: string, cleanupDispose: () => void): void {\n    cleanupDispose();\n    this.#handlers.delete(requestId);\n  }\n\n  #awaitRpcResponse(\n    requestId: string,\n    deferred: Deferred.Deferred<ProtocolMessage, Error>,\n    cleanupDispose: () => void,\n    meta: { spaceId: SpaceId; feedNamespace: string; rpcTag: string },\n  ): Effect.Effect<ProtocolMessage, Error | SyncRpcTimeoutError, never> {\n    const self = this;\n    const timeoutMs = self.#rpcTimeoutMs;\n    return Effect.ensuring(\n      Deferred.await(deferred).pipe(\n        Effect.timeoutFail({\n          duration: timeoutMs,\n          onTimeout: () =>\n            new SyncRpcTimeoutError({\n              requestId,\n              spaceId: meta.spaceId,\n              feedNamespace: meta.feedNamespace,\n              rpcTag: meta.rpcTag,\n              timeoutMs,\n            }),\n        }),\n        Effect.tapError((cause) =>\n          Effect.sync(() => {\n            if (cause instanceof SyncRpcTimeoutError) {\n              log('feed sync client rpc timed out', {\n                requestId,\n                spaceId: meta.spaceId,\n                feedNamespace: meta.feedNamespace,\n                rpcTag: meta.rpcTag,\n                timeoutMs,\n              });\n            }\n          }),\n        ),\n      ),\n      Effect.sync(() => self.#disposeHandler(requestId, cleanupDispose)),\n    );\n  }\n\n  pull(\n    ctx: Context,\n    opts: {\n      spaceId: SpaceId;\n      feedNamespace: string;\n      limit?: number;\n    },\n  ): Effect.Effect<{ done: boolean }, unknown, SqlClient.SqlClient | SqlTransaction.SqlTransaction> {\n    const self = this;\n    return Effect.gen(function* () {\n      const lastPulledPosition = yield* self.#feedStore.getSyncState({\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n      });\n      const requestId = crypto.randomUUID();\n      const deferred = yield* Deferred.make<ProtocolMessage, Error>();\n      self.#handlers.set(requestId, deferred);\n      const cleanupDispose = ctx.disposed\n        ? () => {}\n        : ctx.onDispose(() => {\n            Effect.runFork(Deferred.fail(deferred, new ContextDisposedError()));\n          });\n      if (ctx.disposed) {\n        yield* Deferred.fail(deferred, new ContextDisposedError());\n      }\n      const request: RequestPayload = {\n        _tag: 'QueryRequest',\n        requestId,\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        position: lastPulledPosition,\n        limit: opts.limit,\n      };\n      log('feed sync client pull rpc sending', {\n        requestId,\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        afterPosition: lastPulledPosition,\n        limit: opts.limit,\n      });\n      yield* self.#sendMessage(ctx, self.#withPeerIds(request)).pipe(\n        Effect.tapErrorCause(() => Effect.sync(() => self.#disposeHandler(requestId, cleanupDispose))),\n      );\n      const message = yield* self.#awaitRpcResponse(requestId, deferred, cleanupDispose, {\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        rpcTag: 'QueryRequest',\n      });\n      const response = yield* self.#expectResponse<QueryResponse>(requestId, message, 'QueryResponse');\n      if (response.blocks.length === 0) {\n        log.trace('feed sync client pull done (empty batch)', {\n          requestId,\n          spaceId: opts.spaceId,\n          feedNamespace: opts.feedNamespace,\n        });\n        return { done: true };\n      }\n      yield* self.#feedStore.append({\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        blocks: response.blocks,\n      });\n\n      // Update sync state with the max position from the pulled batch.\n      const maxPulledPosition = response.blocks.reduce(\n        (max, block) => (block.position != null && block.position > max ? block.position : max),\n        lastPulledPosition,\n      );\n      yield* self.#feedStore.setSyncState({\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        lastPulledPosition: maxPulledPosition,\n      });\n\n      log('feed sync client pull applied batch', {\n        requestId,\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        batchSize: response.blocks.length,\n        hasMore: response.hasMore,\n        maxPulledPosition,\n      });\n      return { done: false };\n    });\n  }\n\n  /**\n   * Probes remote for blocks after the last pulled position without mutating local storage.\n   * Returns the number of blocks in the first batch (0 when caught up with remote).\n   */\n  peekPull(\n    ctx: Context,\n    opts: {\n      spaceId: SpaceId;\n      feedNamespace: string;\n      limit?: number;\n    },\n  ): Effect.Effect<{ blocksToPull: number }, unknown, SqlClient.SqlClient | SqlTransaction.SqlTransaction> {\n    const self = this;\n    return Effect.gen(function* () {\n      const lastPulledPosition = yield* self.#feedStore.getSyncState({\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n      });\n      const requestId = crypto.randomUUID();\n      const deferred = yield* Deferred.make<ProtocolMessage, Error>();\n      self.#handlers.set(requestId, deferred);\n      const cleanupDispose = ctx.disposed\n        ? () => {}\n        : ctx.onDispose(() => {\n            Effect.runFork(Deferred.fail(deferred, new ContextDisposedError()));\n          });\n      if (ctx.disposed) {\n        yield* Deferred.fail(deferred, new ContextDisposedError());\n      }\n      const request: RequestPayload = {\n        _tag: 'QueryRequest',\n        requestId,\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        position: lastPulledPosition,\n        limit: opts.limit,\n      };\n      yield* self.#sendMessage(ctx, self.#withPeerIds(request)).pipe(\n        Effect.tapErrorCause(() => Effect.sync(() => self.#disposeHandler(requestId, cleanupDispose))),\n      );\n      const message = yield* self.#awaitRpcResponse(requestId, deferred, cleanupDispose, {\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        rpcTag: 'QueryRequest',\n      });\n      const response = yield* self.#expectResponse<QueryResponse>(requestId, message, 'QueryResponse');\n      return { blocksToPull: response.blocks.length };\n    });\n  }\n\n  push(\n    ctx: Context,\n    opts: {\n      spaceId: SpaceId;\n      feedNamespace: string;\n      limit?: number;\n    },\n  ): Effect.Effect<{ done: boolean }, unknown, SqlClient.SqlClient | SqlTransaction.SqlTransaction> {\n    const self = this;\n    return Effect.gen(function* () {\n      const unpositioned = yield* self.#feedStore.query({\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        unpositionedOnly: true,\n        limit: opts.limit,\n      });\n      if (unpositioned.blocks.length === 0) {\n        log.trace('feed sync client push skipped (nothing to send)', {\n          spaceId: opts.spaceId,\n          feedNamespace: opts.feedNamespace,\n        });\n        return { done: true };\n      }\n      const requestId = crypto.randomUUID();\n      const deferred = yield* Deferred.make<ProtocolMessage, Error>();\n      self.#handlers.set(requestId, deferred);\n      const cleanupDispose = ctx.disposed\n        ? () => {}\n        : ctx.onDispose(() => {\n            Effect.runFork(Deferred.fail(deferred, new ContextDisposedError()));\n          });\n      if (ctx.disposed) {\n        yield* Deferred.fail(deferred, new ContextDisposedError());\n      }\n      const request: RequestPayload = {\n        _tag: 'AppendRequest',\n        requestId,\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        blocks: unpositioned.blocks,\n      };\n      log('feed sync client push rpc sending', {\n        requestId,\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        blockCount: unpositioned.blocks.length,\n      });\n      yield* self.#sendMessage(ctx, self.#withPeerIds(request)).pipe(\n        Effect.tapErrorCause(() => Effect.sync(() => self.#disposeHandler(requestId, cleanupDispose))),\n      );\n      const message = yield* self.#awaitRpcResponse(requestId, deferred, cleanupDispose, {\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        rpcTag: 'AppendRequest',\n      });\n      const response = yield* self.#expectResponse<AppendResponse>(requestId, message, 'AppendResponse');\n      yield* self.#feedStore.setPosition({\n        spaceId: opts.spaceId,\n        blocks: Array.zipWith(response.positions, unpositioned.blocks, (position, block) => ({\n          feedId: block.feedId,\n          feedNamespace: opts.feedNamespace,\n          actorId: block.actorId,\n          sequence: block.sequence,\n          position,\n        })),\n      });\n      log('feed sync client push positions applied', {\n        requestId,\n        spaceId: opts.spaceId,\n        feedNamespace: opts.feedNamespace,\n        positionCount: response.positions.length,\n      });\n      return { done: false };\n    });\n  }\n\n  #expectResponse<T>(requestId: string, message: ProtocolMessage, expectedTag: string): Effect.Effect<T, Error, never> {\n    if (message._tag === 'Error') {\n      return Effect.fail(new Error(message.message));\n    }\n    const requestIdMsg = 'requestId' in message ? String(message.requestId) : undefined;\n    if (message._tag !== expectedTag || requestIdMsg !== requestId) {\n      return Effect.fail(new Error(`Unexpected message: expected ${expectedTag} with requestId ${requestId}`));\n    }\n    return Effect.succeed(message as T);\n  }\n}\n"],"mappings":"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"}