Verified Commit 2de249ab authored by Adam Parák's avatar Adam Parák 💬
Browse files

v9

parent 4b41bb2e
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
import {
  Button,
  ButtonGroup,
  Classes,
  Menu,
  MenuItem,
  Popover,
  Position,
  Tag,
} from '@blueprintjs/core'
import { cx } from '@emotion/css'
import { useAuthIdentity } from '@inject/graphql'
import { responsiveButtonGroup } from '@inject/shared'
import { useNavigate } from '@tanstack/react-router'
@@ -48,7 +50,13 @@ const UserTitle = () => {
    >
      <ButtonGroup className={responsiveButtonGroup(90)}>
        <Button icon='user' minimal>
          {vanityName}
          <span
            className={cx({
              [Classes.SKELETON]: !whoAmI,
            })}
          >
            {vanityName ? vanityName : 'Anonymous'}
          </span>
        </Button>
      </ButtonGroup>
    </Popover>
+2 −2
Original line number Diff line number Diff line
import { getHost, httpGraphql, notify, setSessionId } from '@inject/shared'
import pipeLogger from '@inject/webworker/pipeLogger'
import { devtoolsExchange } from '@urql/devtools'
import type { Exchange, OperationContext } from 'urql'
import { Client, mapExchange } from 'urql'
import { multitab } from './multitabController'
import pipeLogger from './pipeLogger'

export interface CustomOperationContext extends OperationContext {
  errorHandled?: boolean
@@ -46,7 +46,7 @@ export const constructClient = () =>
      // TODO: add proper typing for meta.env.mode from Vite and make it global

      // @ts-ignore
      import.meta.env.MODE !== 'production' ? pipeLogger('pre-cache') : null,
      import.meta.env.MODE !== 'production' ? pipeLogger('[pre-send]') : null,
      multitab,
    ].filter(exchange => exchange !== null) as Exchange[],
  })
+108 −136
Original line number Diff line number Diff line
@@ -2,7 +2,7 @@ import { getSessionId } from '@inject/shared'
import UrqlWorker from '@inject/webworker/worker?sharedworker&url'
import type { WorkerData } from '@inject/webworker/workerTyping'
import type { Exchange, Operation, OperationResult } from 'urql'
import { filter, make, map, merge, pipe, subscribe, tap } from 'wonka'
import { filter, make, merge, pipe, subscribe, tap } from 'wonka'
import { netvar } from './networkEventVar'

const generateTabNonce = () => {
@@ -27,14 +27,6 @@ bc.onmessage = (
  }
}

const keyMap = new Map<
  number,
  {
    result: OperationResult | undefined
    teardown: boolean
  }
>()

const operationTabNonce = generateTabNonce()
let initted = false

@@ -50,15 +42,20 @@ const awaitReadiness = () =>
  })

const receiverChannel = new BroadcastChannel('worker:urql')
const receiveMap = new Map<
  number,
  {
    objId: number
    result: OperationResult
  }
>()
const operationMap = new Map<number, Operation>()

export const multitab: Exchange = () => op$ => {
  const opBuffered = op$
  let isSessionIdReady = false
  const teardownCond = (op: Operation) => op.kind === 'teardown'
  const initConnCond = (op: Operation) =>
    !keyMap.has(op.key) || // always execute if op.key does not exist
    op.context.requestPolicy === 'network-only' || // network-only forces refetch
    op.context.requestPolicy === 'cache-and-network' // refetches always
  const queryCond = (op: Operation) => op.kind === 'query'
  const mutationCond = (op: Operation) => op.kind === 'mutation'
  const subscripCond = (op: Operation) => op.kind === 'subscription'
  const teardowns = pipe(
@@ -69,20 +66,16 @@ export const multitab: Exchange = () => op$ => {
        await awaitReadiness()
        isSessionIdReady = true
      }
      const get = keyMap.get(op.key)
      if (get) {
        if (get.teardown) return
        console.log('[teardown] sending notification: ', op.key)
      console.log(`{multitab/teardown} [teardown/${op.key}] - sending teardown`)
      const opNonce = Math.random()
      operationMap.set(opNonce, op)
      receiverChannel.postMessage({
        type: 'teardown',
        tabNonce: operationTabNonce,
        key: op.key,
        operation: op,
        opNonce,
      } as WorkerData)
        get.teardown = true
      } else {
        console.log('[teardown] no sub', op.key)
      }
    })
  )
  const mutations = pipe(
@@ -93,12 +86,15 @@ export const multitab: Exchange = () => op$ => {
        await awaitReadiness()
        isSessionIdReady = true
      }
      console.log(`[mutation] sending: `, op.key)
      console.log(`{multitab/mutation} [mutation/${op.key}] - sending`)
      const opNonce = Math.random()
      operationMap.set(opNonce, op)
      receiverChannel.postMessage({
        type: 'mutation',
        tabNonce: operationTabNonce,
        key: op.key,
        operation: op,
        opNonce,
      } as WorkerData)
    })
  )
@@ -110,111 +106,56 @@ export const multitab: Exchange = () => op$ => {
        await awaitReadiness()
        isSessionIdReady = true
      }
      if (!keyMap.has(op.key) || keyMap.get(op.key)?.teardown) {
        console.log(`[${op.kind}] subscribing: `, op.key)
      console.log(
        `{multitab/subscription} [subscription/${op.key}] - subscribing`
      )

      const opNonce = Math.random()
      operationMap.set(opNonce, op)
      receiverChannel.postMessage({
          type: 'input',
        type: 'subscription',
        tabNonce: operationTabNonce,
        key: op.key,
        operation: op,
        opNonce,
      } as WorkerData)
        keyMap.set(op.key, {
          result: undefined,
          teardown: false,
        })
      } else {
        if (keyMap.get(op.key)?.teardown) {
          throw Error(
            `Subscription with key ${op.key} is already marked for teardown`
          )
        }
        console.log(`[${op.kind}] reusing subscription: `, op.key)
      }
    })
  )
  const rest = pipe(
  const query = pipe(
    opBuffered,
    filter(op => !teardownCond(op) && !mutationCond(op) && initConnCond(op)),
    filter(op => !teardownCond(op) && queryCond(op)),
    tap(async op => {
      if (!isSessionIdReady) {
        await awaitReadiness()
        isSessionIdReady = true
      }
      console.log(`[${op.kind}] sending: `, op.key)
      const get = keyMap.get(op.key)
      if (!get) {
        receiverChannel.postMessage({
          type: 'input',
          tabNonce: operationTabNonce,
          key: op.key,
          operation: op,
        } as WorkerData)
        console.log(`[${op.kind}] marking for reuse: `, op.key)
        keyMap.set(op.key, {
          result: undefined,
          teardown: false,
        })
      } else {
        if (get.teardown) {
          console.log(`[${op.kind}] reusing cached entry: `, op.key, get)
          get.teardown = false
          receiverChannel.postMessage({
            type: 'input',
            tabNonce: operationTabNonce,
            key: op.key,
            operation: op,
          } as WorkerData)
        } else if (
          op.context.requestPolicy === 'cache-and-network' ||
          op.context.requestPolicy === 'network-only'
        ) {
          console.log(
            `[${op.kind}] cache policy set to fetch via network, requesting: `,
            op.key
          )
      console.log(`{multitab/query} [${op.kind}/${op.key}] - sending request`)
      const opNonce = Math.random()
      operationMap.set(opNonce, op)
      receiverChannel.postMessage({
        type: 'input',
        tabNonce: operationTabNonce,
        key: op.key,
        operation: op,
        opNonce,
      } as WorkerData)
        }
      }
    })
  )
  const reuse = pipe(
    opBuffered,
    filter(
      op =>
        !teardownCond(op) &&
        !initConnCond(op) &&
        !mutationCond(op) &&
        !subscripCond(op) &&
        keyMap.get(op.key)?.result !== undefined &&
        keyMap.get(op.key)?.teardown === false &&
        op.context.requestPolicy !== 'network-only'
    ),
    tap(op => {
      console.log(`[${op.kind}] reusing source from cache:`, keyMap.get(op.key))
    }),
    map(op => keyMap.get(op.key)!.result!)
  )
  const lost = pipe(
    opBuffered,
    filter(
      op =>
        !teardownCond(op) &&
        !initConnCond(op) &&
        !queryCond(op) &&
        !mutationCond(op) &&
        !subscripCond(op) &&
        keyMap.get(op.key) === undefined
        !subscripCond(op)
    ),
    tap(op => {
      console.warn('[fallthrough]:', op)
      console.log(`{multitab/lost} [${op.kind}/${op.key}] - not resolved`)
    })
  )
  pipe(
    merge([rest, teardowns, mutations, subscription, lost]),
    merge([query, teardowns, mutations, subscription, lost]),
    subscribe(() => {
      /*
       * This is just to trigger the subscription
@@ -237,44 +178,75 @@ export const multitab: Exchange = () => op$ => {
      if (type === 'connectionClose' || type === 'connectionInit') {
        throw Error('Incorrect protocol')
      }
      const { tabNonce } = msg.data
      const { tabNonce, key } = msg.data
      if (tabNonce !== operationTabNonce) {
        console.log(
          `[${type}] - received message for another tab: ${tabNonce} != ${operationTabNonce}`
          `{multitab/output} [unknown/${key}] - message from another tab`
        )
        return
      }
      if (type === 'ok') {
        initted = true
        console.log('received ok')
        console.log(
          `{multitab/output} [unknown/${key}] - connection established`
        )
        return
      }
      console.log(`[${msg.data.type}] received data: `, msg.data.key, msg.data)
      console.log(`{multitab/output} [${msg.data.type}/${key}] - received data`)

      switch (type) {
        case 'result':
          {
            const get = keyMap.get(msg.data.key)
            if (
              msg.data.result.operation.kind === 'query' &&
              !get?.teardown &&
              !msg.data.result.stale
            ) {
              get!.result = msg.data.result
            if (msg.data.objId === undefined) {
              throw Error(`No object id for result ${key}`)
            }
            const cachedEntry = receiveMap.get(key)
            const incomingResult = msg.data.result

            // Check if the worker sent back functionally identical data
            if (cachedEntry && cachedEntry.objId === msg.data.objId) {
              console.log(
                `{multitab/output} [result/${key}] - objectId matches. Preserving data reference.`
              )

              /*
               * The data is the same, but the operation state (fetching, stale) has changed.
               * Create a NEW result object to allow the state transition...
               */
              const operation = operationMap.get(msg.data.opNonce)
              const newResult = {
                ...incomingResult, // Use new properties like fetching: false
                ...(operation ? operation : {}),
                data: cachedEntry.result.data, // ...but reuse the OLD data object's reference.
              }
            if (!get) {
              throw Error('No entry in keyMap for result message')
              operationMap.delete(msg.data.opNonce)

              // Update our map with the newly constructed result
              receiveMap.set(key, {
                objId: msg.data.objId,
                result: newResult,
              })

              sink.next(newResult)
            } else {
              // Data is genuinely new or we've never seen it before.
              console.log(
                `{multitab/output} [result/${key}] - new data or objectId mismatch.`
              )

              receiveMap.set(key, {
                objId: msg.data.objId,
                result: incomingResult,
              })

              sink.next(incomingResult)
            }
            sink.next(msg.data.result)
          }
          break
        case 'teardown':
          {
            const get = keyMap.get(msg.data.key)
            if (get) {
              get.teardown = true
            }
          // garbage collection
          receiveMap.delete(key)
          sink.complete()
          }
          break
      }
    }
@@ -283,17 +255,17 @@ export const multitab: Exchange = () => op$ => {
      type: 'connectionInit',
      tabNonce: operationTabNonce,
    } as WorkerData)
    console.log(`[init] - sending init ${operationTabNonce}`)
    console.log(`{multitab/ws} [init]$${operationTabNonce} - sending init`)
    return () => {
      tabWorker.port.removeEventListener('message', fn)
      tabWorker.port.postMessage({
        type: 'connectionClose',
        tabNonce: operationTabNonce,
      } as WorkerData)
      console.log(`[close] - sending close ${operationTabNonce}`)
      console.log(`{multitab/ws} [close]$${operationTabNonce} - sending close `)
      tabWorker.port.close()
    }
  })

  return merge([outputLine, reuse])
  return outputLine
}

graphql/urql/pipeLogger.ts

deleted100644 → 0
+0 −30
Original line number Diff line number Diff line
import type { Exchange } from 'urql'
import { map, pipe } from 'wonka'

const pipeLogger = (letter: string): Exchange => {
  const left = `<=${letter}`
  const right = `${letter}=>`
  return ({ forward }) =>
    op$ =>
      pipe(
        forward(
          pipe(
            op$,
            map(x => {
              new Promise(() => {
                console.log(right, x)
              })
              return x
            })
          )
        ),
        map(x => {
          new Promise(() => {
            console.log(left, x)
          })
          return x
        })
      )
}

export default pipeLogger
+20 −19
Original line number Diff line number Diff line
import type { Exchange } from 'urql'
import { pipe, tap } from 'wonka'

const pipeLogger = (letter: string): Exchange => {
  const left = `<=${letter}`
  const right = `${letter}=>`
  return ({ forward }) =>
const pipeLogger =
  (letter: string): Exchange =>
  ({ forward }) =>
  op$ =>
    pipe(
      forward(
        pipe(
          op$,
          tap(x => {
              console.log(right, x)
            console.log(`[${x.kind}]/${x.key} - ${letter}=>:`, x)
          })
        )
      ),
      tap(x => {
          console.log(left, x)
        console.log(
          `[${x.operation.kind}]/${x.operation.key} - <=${letter}:`,
          x
        )
      })
    )
}

export default pipeLogger
Loading