FluxyChat

Guides

Chat Singleton for Thread Deserialization

FluxyChat's chat singleton provides a global registry so ThreadRef.fromJSON() can resolve adapters without passing a Chat instance through every call.

Chat Singleton for Thread Deserialization

FluxyChat's chat singleton provides a global registry so that ThreadRef.fromJSON() can resolve adapters without an explicit Chat instance being passed through every call.

Overview

When serialized threads are stored in D1, KV, or passed through workflow engines, deserializing them back into usable ThreadRef objects requires access to the adapter registry. The singleton pattern avoids threading a Chat instance through every serialization boundary.

Source: apps/worker/src/lib/chat-singleton.js

Registration

Register a Chat instance at application startup. The first registered instance becomes the default.

import { registerChatInstance } from "@fluxy-chat/sdk";

// During worker initialization
const api = createChatApi({ env, db, roomStub });
registerChatInstance("default", api);

// Or register multiple instances (e.g., multi-tenant)
registerChatInstance("project-123", projectApi);
registerChatInstance("project-456", otherProjectApi);

Resolution

Resolve an adapter

import { resolveAdapter } from "@fluxy-chat/sdk";

// Uses the default instance
const slack = resolveAdapter("slack");
await slack.postMessage(threadId, "Hello!");

Get a specific instance

import { getChatInstance } from "@fluxy-chat/sdk";

const api = getChatInstance("project-123");
const thread = api.thread("slack\:C123:1234567890.123456");

Get the default instance

import { getChatInstance } from "@fluxy-chat/sdk";

const api = getChatInstance(); // no name → default instance

Introspection

import {
  hasChatInstance,
  listChatInstances,
  getDefaultChatInstanceName,
} from "@fluxy-chat/sdk";

console.log(hasChatInstance());                    // true
console.log(listChatInstances());                  // ["default", "project-123"]
console.log(getDefaultChatInstanceName());         // "default"

Changing the Default

import { setDefaultChatInstance } from "@fluxy-chat/sdk";

// Switch the default to a different instance
setDefaultChatInstance("project-456");

Cleanup

import { unregisterChatInstance, clearChatInstances } from "@fluxy-chat/sdk";

// Remove one instance
unregisterChatInstance("project-123");
// If it was the default, the next remaining instance becomes default

// Clear all (useful in tests)
clearChatInstances();

Use Case: D1 Round-Trip with Reviver

The singleton enables the reviver() to deserialize threads from JSON stored in D1:

import { registerChatInstance, parseChatJSON } from "@fluxy-chat/sdk";

// 1. Register at startup
registerChatInstance("default", createChatApi({ env, db }));

// 2. Store a thread in D1
const thread = chat.thread("slack\:C123:1234567890.123456");
await db.prepare("INSERT INTO workflow_state (id, data) VALUES (?, ?)")
  .bind("wf-1", JSON.stringify(thread))
  .run();

// 3. Retrieve and deserialize — no explicit Chat instance needed
const row = await db.prepare("SELECT data FROM workflow_state WHERE id = ?").bind("wf-1").first();
const data = parseChatJSON(row.data);
// data is a ThreadRef with adapterSlug resolved via the singleton

Multi-Tenant Pattern

For multi-tenant deployments, register one instance per project:

// On each request, set the default to the current project
setDefaultChatInstance(`project-${request.projectId}`);
// Now all singleton calls resolve to this project's adapters

See Also

On this page