Skip to content

Instantly share code, notes, and snippets.

@litewarp
Last active July 11, 2025 22:38
Show Gist options
  • Save litewarp/e4d1e82054298a17c923d6af3dbcca8b to your computer and use it in GitHub Desktop.
Save litewarp/e4d1e82054298a17c923d6af3dbcca8b to your computer and use it in GitHub Desktop.
Debugging Seroval in @tanstack/start
import type { User } from "@monolaw/auth";
import {
type RecordMap,
RecordSource,
type RelayEnvironment,
RelayEnvironmentProvider,
} from "@monolaw/relay-esm";
import type { AnyRouter } from "@tanstack/react-router";
import { Fragment } from "react";
import { createPreloader } from "./preloader.ts";
export interface RelayRouterContext {
environment: RelayEnvironment;
user: User | null;
preloadQuery: ReturnType<typeof createPreloader>;
}
type AdditionalOptions = {
WrapProvider?: (props: { children: any }) => React.JSX.Element;
/**
* If `true`, the QueryClient will handle errors thrown by `redirect()` inside of mutations and queries.
*
* @default true
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/api/router/redirectFunction)
*/
handleRedirects?: boolean;
};
export type ValidateRouter<TRouter extends AnyRouter> = NonNullable<
TRouter["options"]["context"]
> extends RelayRouterContext
? TRouter
: never;
interface DehydratedRouterQueryState<TOptions = Record<string, unknown>> {
dehydratedEnvironment?: {
options: TOptions;
recordSource: RecordMap;
};
// queryStream: ReadableStream<Record<string, unknown>>;
}
export function createRelayRouter<
TRouter extends AnyRouter,
TEnvironment extends RelayEnvironment,
>(
router: ValidateRouter<TRouter>,
environment: TEnvironment,
additionalOpts?: AdditionalOptions,
): TRouter {
// const seenQueryKeys = new Set<string>();
// const streamedQueryKeys = new Set<string>();
const ogOptions = router.options;
router.options = {
...router.options,
context: {
...ogOptions.context,
environment: environment,
preloadQuery: createPreloader(environment),
},
Wrap: ({ children }) => {
const OuterWrapper = additionalOpts?.WrapProvider || Fragment;
const OGWrap = ogOptions.Wrap || Fragment;
return (
<OuterWrapper>
<RelayEnvironmentProvider environment={environment}>
<OGWrap>{children}</OGWrap>
</RelayEnvironmentProvider>
</OuterWrapper>
);
},
};
if (router.isServer) {
// const queryStream = createPushableStream();
router.options.dehydrate = async (): Promise<DehydratedRouterQueryState> => {
const ogDehydrated = await ogOptions?.dehydrate?.();
const dehydratedEnvironment = {
options: environment.options,
recordSource: environment.getStore().getSource().toJSON(),
};
// router.serverSsr!.onRenderFinished(() => queryStream.close());
const dehydratedRouter = {
...ogDehydrated,
dehydratedEnvironment,
// queryStream,
};
return dehydratedRouter;
};
// add environment subscriber to push data
} else {
router.options.hydrate = async (dehydrated: DehydratedRouterQueryState) => {
await ogOptions.hydrate?.(dehydrated);
// on the clinet, hydrate the query client with the dehydrated data
if (dehydrated.dehydratedEnvironment?.recordSource) {
environment
.getStore()
.publish(new RecordSource(dehydrated.dehydratedEnvironment.recordSource));
}
// const reader = dehydrated.queryStream.getReader();
// reader
// .read()
// .then(async function handle({ done, value }) {
// // push to store?
// console.log(value);
// if (done) return;
// const result = await reader.read();
// return handle(result);
// })
// .catch((err) => {
// console.error(`Error reading query stream: ${err}`);
// });
};
}
return router;
}
interface PushableStream {
stream: ReadableStream;
enqueue: (chunk: unknown) => void;
close: () => void;
isClosed: () => boolean;
error: (err: unknown) => void;
}
function createPushableStream(): PushableStream {
let controllerRef: ReadableStreamDefaultController;
const stream = new ReadableStream({
start(controller) {
controllerRef = controller;
},
});
let _isClosed = false;
return {
stream,
enqueue: (chunk) => controllerRef.enqueue(chunk),
close: () => {
controllerRef.close();
_isClosed = true;
},
isClosed: () => _isClosed,
error: (err: unknown) => controllerRef.error(err),
};
}
[I] ~/projects/monolaw/apps/web discovery ❯ bun run dev                                                                                                                                                                              15:30:43
$ vite --port 3000
Generated route tree in 131ms
3:31:12 PM [vite] (client) Re-optimizing dependencies because vite config has changed

  VITE v7.0.4  ready in 1072 ms

  ➜  Local:   http://localhost:3000/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
[vite] connected.
Error reading routerStream: g [Error]: The value [object Object] of type "object" cannot be parsed/serialized.

There are few workarounds for this problem:
- Transform the value in a way that it can be serialized.
- If the reference is present on multiple runtimes (isomorphic), you can use the Reference API to map the references.
    at G.parseObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:31059)
    at G.parse (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:31430)
    at G.parseProperties (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:32428)
    at G.parsePlainObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:28700)
    at G.parseObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:29897)
    at G.parse (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:31430)
    at G.parseProperties (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:32428)
    at G.parsePlainObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:28700)
    at G.parseObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:29897)
    at G.parse (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:31430)
    at G.parseItems (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:27974)
    at G.parseArray (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:28031)
    at G.parseObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:29705)
    at G.parse (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:31430)
    at G.parseProperties (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:32428)
    at G.parsePlainObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:28700)
    at G.parseObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:29897)
    at G.parse (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:31430)
    at G.parseWithError (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:34024)
    at G.start (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:34088)
    at gr (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:35486)
    at Object.dehydrate (file:///Users/litewarp/projects/monolaw/node_modules/@tanstack/router-core/dist/esm/ssr/ssr-server.js:76:7)
    at async executeRouter (/Users/litewarp/projects/monolaw/node_modules/@tanstack/start-server-core/src/createStartHandler.ts:206:19)
    at async eval (/Users/litewarp/projects/monolaw/node_modules/@tanstack/start-server-core/src/createStartHandler.ts:432:22)
    at async next (/Users/litewarp/projects/monolaw/node_modules/@tanstack/start-server-core/src/createStartHandler.ts:448:20)
    at async handleServerRoutes (/Users/litewarp/projects/monolaw/node_modules/@tanstack/start-server-core/src/createStartHandler.ts:406:17)
    at async eval (/Users/litewarp/projects/monolaw/node_modules/@tanstack/start-server-core/src/createStartHandler.ts:221:48)
    at async startRequestResolver (/Users/litewarp/projects/monolaw/node_modules/@tanstack/start-server-core/src/createStartHandler.ts:126:24)
    at async file:///Users/litewarp/projects/monolaw/node_modules/@tanstack/start-plugin-core/dist/esm/dev-server-plugin/plugin.js:47:30 {
  value: RelayModernEnvironment {
    configName: undefined,
    _treatMissingFieldsAsNull: false,
    __log: [Function: emptyFunction],
    relayFieldLogger: [Function: defaultRelayFieldLogger],
    _defaultRenderPolicy: 'partial',
    _operationLoader: undefined,
    _operationExecutions: Map(0) {},
    _network: { execute: [Function: execute] },
    _getDataID: [Function: defaultGetDataID],
    _missingFieldHandlers: [],
    _publishQueue: RelayPublishQueue {
      _hasStoreSnapshot: false,
      _handlerProvider: [Function: RelayDefaultHandlerProvider],
      _pendingBackupRebase: false,
      _pendingData: Set(0) {},
      _pendingOptimisticUpdates: Set(0) {},
      _store: [RelayModernStore],
      _appliedOptimisticUpdates: Set(0) {},
      _gcHold: null,
      _getDataID: [Function: defaultGetDataID],
      _missingFieldHandlers: [],
      _log: [Function: emptyFunction]
    },
    _scheduler: null,
    _store: RelayModernStore {
      _gcStep: [Function (anonymous)],
      _currentWriteEpoch: 0,
      _gcHoldCounter: 0,
      _gcReleaseBufferSize: 10,
      _shouldRetainWithinTTL_EXPERIMENTAL: false,
      _gcRun: null,
      _gcScheduler: [Function: resolveImmediate],
      _getDataID: [Function: defaultGetDataID],
      _globalInvalidationEpoch: null,
      _invalidationSubscriptions: Set(0) {},
      _invalidatedRecordIDs: Set(0) {},
      __log: null,
      _queryCacheExpirationTime: undefined,
      _operationLoader: null,
      _optimisticSource: null,
      _recordSource: [RelayRecordSource],
      _releaseBuffer: [],
      _roots: [Map],
      _shouldScheduleGC: false,
      _resolverCache: [LiveResolverCache],
      _resolverContext: undefined,
      _storeSubscriptions: [RelayStoreSubscriptions],
      _updatedRecordIDs: Set(0) {},
      _shouldProcessClientComponents: false,
      _treatMissingFieldsAsNull: false,
      _actorIdentifier: undefined
    },
    options: undefined,
    _isServer: false,
    _normalizeResponse: [Function: normalizeResponse],
    __setNet: [Function (anonymous)],
    DEBUG_inspect: [Function (anonymous)],
    _operationTracker: RelayOperationTracker {
      _ownersToPendingOperations: Map(0) {},
      _pendingOperationsToOwners: Map(0) {},
      _ownersToPendingPromise: Map(0) {}
    },
    _shouldProcessClientComponents: undefined
  }
}
node:internal/process/promises:394
    triggerUncaughtException(err, true /* fromPromise */);
    ^

g [Error]: The value [object Object] of type "object" cannot be parsed/serialized.

There are few workarounds for this problem:
- Transform the value in a way that it can be serialized.
- If the reference is present on multiple runtimes (isomorphic), you can use the Reference API to map the references.
    at G.parseObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:31059)
    at G.parse (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:31430)
    at G.parseProperties (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:32428)
    at G.parsePlainObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:28700)
    at G.parseObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:29897)
    at G.parse (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:31430)
    at G.parseProperties (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:32428)
    at G.parsePlainObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:28700)
    at G.parseObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:29897)
    at G.parse (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:31430)
    at G.parseItems (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:27974)
    at G.parseArray (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:28031)
    at G.parseObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:29705)
    at G.parse (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:31430)
    at G.parseProperties (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:32428)
    at G.parsePlainObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:28700)
    at G.parseObject (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:29897)
    at G.parse (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:31430)
    at G.parseWithError (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:34024)
    at G.start (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:34088)
    at gr (file:///Users/litewarp/projects/monolaw/node_modules/seroval/dist/esm/production/index.mjs:17:35486)
    at Object.dehydrate (file:///Users/litewarp/projects/monolaw/node_modules/@tanstack/router-core/dist/esm/ssr/ssr-server.js:76:7)
    at async executeRouter (/Users/litewarp/projects/monolaw/node_modules/@tanstack/start-server-core/src/createStartHandler.ts:206:19)
    at async eval (/Users/litewarp/projects/monolaw/node_modules/@tanstack/start-server-core/src/createStartHandler.ts:432:22)
    at async next (/Users/litewarp/projects/monolaw/node_modules/@tanstack/start-server-core/src/createStartHandler.ts:448:20)
    at async handleServerRoutes (/Users/litewarp/projects/monolaw/node_modules/@tanstack/start-server-core/src/createStartHandler.ts:406:17)
    at async eval (/Users/litewarp/projects/monolaw/node_modules/@tanstack/start-server-core/src/createStartHandler.ts:221:48)
    at async startRequestResolver (/Users/litewarp/projects/monolaw/node_modules/@tanstack/start-server-core/src/createStartHandler.ts:126:24)
    at async file:///Users/litewarp/projects/monolaw/node_modules/@tanstack/start-plugin-core/dist/esm/dev-server-plugin/plugin.js:47:30 {
  value: RelayModernEnvironment {
    configName: undefined,
    _treatMissingFieldsAsNull: false,
    __log: [Function: emptyFunction],
    relayFieldLogger: [Function: defaultRelayFieldLogger],
    _defaultRenderPolicy: 'partial',
    _operationLoader: undefined,
    _operationExecutions: Map(0) {},
    _network: { execute: [Function: execute] },
    _getDataID: [Function: defaultGetDataID],
    _missingFieldHandlers: [],
    _publishQueue: RelayPublishQueue {
      _hasStoreSnapshot: false,
      _handlerProvider: [Function: RelayDefaultHandlerProvider],
      _pendingBackupRebase: false,
      _pendingData: Set(0) {},
      _pendingOptimisticUpdates: Set(0) {},
      _store: <ref *1> RelayModernStore {
        _gcStep: [Function (anonymous)],
        _currentWriteEpoch: 0,
        _gcHoldCounter: 0,
        _gcReleaseBufferSize: 10,
        _shouldRetainWithinTTL_EXPERIMENTAL: false,
        _gcRun: null,
        _gcScheduler: [Function: resolveImmediate],
        _getDataID: [Function: defaultGetDataID],
        _globalInvalidationEpoch: null,
        _invalidationSubscriptions: Set(0) {},
        _invalidatedRecordIDs: Set(0) {},
        __log: null,
        _queryCacheExpirationTime: undefined,
        _operationLoader: null,
        _optimisticSource: null,
        _recordSource: RelayRecordSource {
          _records: Map(1) { 'client:root' => [Object] }
        },
        _releaseBuffer: [],
        _roots: Map(2) {
          'fe6e1c0b4dcd701df1db506c58582afa{}' => {
            operation: [Object],
            refCount: 1,
            epoch: null,
            fetchTime: null
          },
          '676406b84ab53ec0f13ad7ce05cfa13a{"filter":{"storageBucket":{"equalToInsensitive":"uploads"}},"first":20,"offset":0,"orderBy":["CREATED_AT_ASC"]}' => {
            operation: [Object],
            refCount: 1,
            epoch: null,
            fetchTime: null
          }
        },
        _shouldScheduleGC: false,
        _resolverCache: LiveResolverCache {
          _resolverIDToRecordIDs: Map(0) {},
          _recordIDToResolverIDs: Map(0) {},
          _getRecordSource: [Function (anonymous)],
          _store: [Circular *1],
          _handlingBatch: false,
          _liveResolverBatchRecordSource: null
        },
        _resolverContext: undefined,
        _storeSubscriptions: RelayStoreSubscriptions {
          _subscriptions: Set(0) {},
          __log: undefined,
          _resolverCache: LiveResolverCache {
            _resolverIDToRecordIDs: Map(0) {},
            _recordIDToResolverIDs: Map(0) {},
            _getRecordSource: [Function (anonymous)],
            _store: [Circular *1],
            _handlingBatch: false,
            _liveResolverBatchRecordSource: null
          },
          _resolverContext: undefined
        },
        _updatedRecordIDs: Set(0) {},
        _shouldProcessClientComponents: false,
        _treatMissingFieldsAsNull: false,
        _actorIdentifier: undefined
      },
      _appliedOptimisticUpdates: Set(0) {},
      _gcHold: null,
      _getDataID: [Function: defaultGetDataID],
      _missingFieldHandlers: [],
      _log: [Function: emptyFunction]
    },
    _scheduler: null,
    _store: <ref *1> RelayModernStore {
      _gcStep: [Function (anonymous)],
      _currentWriteEpoch: 0,
      _gcHoldCounter: 0,
      _gcReleaseBufferSize: 10,
      _shouldRetainWithinTTL_EXPERIMENTAL: false,
      _gcRun: null,
      _gcScheduler: [Function: resolveImmediate],
      _getDataID: [Function: defaultGetDataID],
      _globalInvalidationEpoch: null,
      _invalidationSubscriptions: Set(0) {},
      _invalidatedRecordIDs: Set(0) {},
      __log: null,
      _queryCacheExpirationTime: undefined,
      _operationLoader: null,
      _optimisticSource: null,
      _recordSource: RelayRecordSource {
        _records: Map(1) {
          'client:root' => { __id: 'client:root', __typename: '__Root' }
        }
      },
      _releaseBuffer: [],
      _roots: Map(2) {
        'fe6e1c0b4dcd701df1db506c58582afa{}' => {
          operation: { fragment: [Object], request: [Object], root: [Object] },
          refCount: 1,
          epoch: null,
          fetchTime: null
        },
        '676406b84ab53ec0f13ad7ce05cfa13a{"filter":{"storageBucket":{"equalToInsensitive":"uploads"}},"first":20,"offset":0,"orderBy":["CREATED_AT_ASC"]}' => {
          operation: { fragment: [Object], request: [Object], root: [Object] },
          refCount: 1,
          epoch: null,
          fetchTime: null
        }
      },
      _shouldScheduleGC: false,
      _resolverCache: LiveResolverCache {
        _resolverIDToRecordIDs: Map(0) {},
        _recordIDToResolverIDs: Map(0) {},
        _getRecordSource: [Function (anonymous)],
        _store: [Circular *1],
        _handlingBatch: false,
        _liveResolverBatchRecordSource: null
      },
      _resolverContext: undefined,
      _storeSubscriptions: RelayStoreSubscriptions {
        _subscriptions: Set(0) {},
        __log: undefined,
        _resolverCache: LiveResolverCache {
          _resolverIDToRecordIDs: Map(0) {},
          _recordIDToResolverIDs: Map(0) {},
          _getRecordSource: [Function (anonymous)],
          _store: [Circular *1],
          _handlingBatch: false,
          _liveResolverBatchRecordSource: null
        },
        _resolverContext: undefined
      },
      _updatedRecordIDs: Set(0) {},
      _shouldProcessClientComponents: false,
      _treatMissingFieldsAsNull: false,
      _actorIdentifier: undefined
    },
    options: undefined,
    _isServer: false,
    _normalizeResponse: [Function: normalizeResponse],
    __setNet: [Function (anonymous)],
    DEBUG_inspect: [Function (anonymous)],
    _operationTracker: RelayOperationTracker {
      _ownersToPendingOperations: Map(0) {},
      _pendingOperationsToOwners: Map(0) {},
      _ownersToPendingPromise: Map(0) {}
    },
    _shouldProcessClientComponents: undefined
  }
}

Node.js v22.13.1
error: script "dev" exited with code 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment