triplit icon indicating copy to clipboard operation
triplit copied to clipboard

SolidJS

Open Blankeos opened this issue 1 year ago • 1 comments

Hi! Awesome stuff. I've been pulling my hairs solving local-first. How are you guys seamlessly making this look easy. Any limitations encountered so far? What conflicts can Triplit not handle?

In any case... It would be great to have a Solid adapter for this. How much work would it take to port an adapter for SolidJS? (If it's not super difficult, I'd love to give it a shot)

Blankeos avatar Jul 11 '24 15:07 Blankeos

Glad to hear you're excited about Triplit!

re: Conflict Limitations: Currently, Triplit treats all attributes as a Last Writer Wins Registers so it can handle most situations that are primarily CRUD. However, there are cases where an app might need more complex resolution strategies that Triplit currently doesn't support. E.g. in the future, you'll be able to directly handle Collaborative Text, Distributed Counters, Reorderable Lists, etc. Some of that is still possible currently just requires more work, like here is an example of doing collaborative text with Triplit.

re: Solid support: I think adding it would be pretty straightforward to be honest, I think you should give it a shot! Since Solid components only run once it should be pretty straightforward to wire up Triplit Subscriptions to a signal if I had to guess. Don't hesitate to ask any questions in our Discord if you want faster collaboration on it!

matlin avatar Jul 11 '24 16:07 matlin

import { createSignal, createEffect, onCleanup, Accessor } from 'solid-js';


/**
 * A primitive that subscribes to a query.
 *
 * @param client - The client instance accessor to query with.
 * @param query - The query accessor to subscribe to.
 * @param options - Optional accessor for additional options for the subscription.
 * @param options.localOnly - If true, the subscription will only use the local cache. Defaults to false.
 * @param options.onRemoteFulfilled - An optional callback that is called when the remote query has been fulfilled.
 * @returns An object containing accessors for the fetching state, the result of the query, and any error that occurred.
 */
export function useQuery<T = any>(
    client: Accessor<Client>, 
    query: Accessor<Query>,   
    options?: Accessor<QueryOptions | undefined> 
) {
    const [results, setResults] = createSignal<T | undefined>(undefined);
    const [fetching, setFetching] = createSignal<boolean>(true);
    const [fetchingLocal, setFetchingLocal] = createSignal<boolean>(true);
    const [fetchingRemote, setFetchingRemote] = createSignal<boolean>(false);
    const [error, setError] = createSignal<Error | undefined>(undefined);

    createEffect(() => {

        const currentClient = client();
        const currentQuery = query();
        const currentOptions = options ? options() : undefined;

        // Reset state when query/client/options change before subscribing
        setResults(undefined);
        setFetching(true);
        setFetchingLocal(true);
        setFetchingRemote(true); 
        setError(undefined);

        const unsub = currentClient.subscribeWithStatus<T>(
            currentQuery,
            (newVal) => {
                setResults(newVal.results);
                setFetching(newVal.fetching);
                setFetchingLocal(newVal.fetchingLocal);
                setFetchingRemote(newVal.fetchingRemote);
                setError(newVal.error);
            },
            currentOptions
        );


        onCleanup(() => {
            unsub();
        });
    });


    return {
        results,
        fetching,
        fetchingLocal,
        fetchingRemote,
        error,
    };
}

/**
 * A primitive that subscribes to the connection status of a client with the server.
 *
 * @param client - The client instance accessor to get the connection status of.
 * @returns An object containing `status`, an accessor for the current connection status.
 */
export function useConnectionStatus(
    client: Accessor<Client> // Use Accessor if client can change
) {
    // Initialize with the current status
    const [status, setStatus] = createSignal<ConnectionStatus>(client().connectionStatus);

    createEffect(() => {
        const currentClient = client(); // Track client changes

        // Update signal if status changed between initial signal creation and effect run
        // Or if the client instance itself changed.
        const initialStatus = currentClient.connectionStatus;
        if (status() !== initialStatus) {
            setStatus(initialStatus);
        }


        const unsub = currentClient.onConnectionStatusChange(
            (newStatus) => {
                setStatus(newStatus);
            }, true
        );

        onCleanup(() => {
            unsub();
        });
    });


    return {
        status,
    };
}

doeixd avatar Mar 26 '25 16:03 doeixd

Solid bindings are now available with the @triplit/solid package -- thanks @doeixd!

Also there is a Solid.js template available with create-triplit-app e.g. yarn create triplit-app, etc

matlin avatar Apr 05 '25 01:04 matlin