npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

supastruct

v0.1.3

Published

Parse Supabase.js queries into mutable objects, and vice versa.

Downloads

55

Readme

Supastruct

Supastruct lets you parse/convert supabase-js queries into mutable objects, and back again. This enables powerful abstractions to be built on top of supabase-js -- a real-world example is SupaQuery.

Install

npm install supastruct

How does it work?

Imagine you have the following Supabase query:

const query = supabase
  .from("todos")
  .select("*")
  .eq("project", 1234)
  .eq("status", "in_progress");

Let's imagine you want the ability to toggle on a "debug" mode for all your app's queries while in development -- you simply want to console-log information about each query as they are executed (I'm being purposely trivial). You want the log to output something like this for the above query:

> QUERY EXECUTION:
> {
>   query: {
>     from: "todos",
>     filters: {
>       eq: [
>         ["project", 1234],
>         ["status", "in_progress"]
>       ]
>     }
>     modifiers: {
>       select: "*"
>     }
>   },
>   result: {
>     data: [ ... ],
>     error: null,
>   }
> }

So, we build a runQuery function which will "wrap" all our Supabase-js queries throughout our app; we'll set an ENV variable DEBUG_QUERIES to true or false, which runQuery will read to determine whether to log what we'll call "query meta". Let's stub this out:

// in `.env.local`:
DEBUG_QUERIES=true

// in createClient.js:
import { createClient } from '@supabase/supabase-js';
export const db = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY);

// somewhere in app:
import { db } from "./createClient";
const { data } = runQuery(
  db.from('todos').select('*').eq('project', 1234).eq('status', 'in_progress')
);

// in `runQuery.js`:
export function runQuery(query) {
  const queryMeta = { ... }; // TODO: parse query into object representation
  const result = await query;

  if (process.env.DEBUG_QUERIES) {
    console.log("QUERY EXECUTION:", { query: queryMeta, result })
  }

  return result;
}

So, the question is: in runQuery, how do we parse the Supabase query into object format? It's a tough problem, but Supastruct makes it dead-simple -- here's how:

// in createClient.js:
import { createClient } from '@supabase/supabase-js';
+ import { SupastructClient } from 'supastruct';
- export const db = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY);
+ export const db = new SupastructClient(createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY));

// use `db` exactly the same as before to create Supabase-js queries

// in `runQuery.js`:
export function runQuery(query) {
- const queryMeta = { ... }; // TODO: parse query into object representation
+ const queryMeta = query.getQueryMeta(); // getQueryMeta is available on Supastruct clients
  const result = await query;

  if (process.env.DEBUG_QUERIES) {
    console.log("QUERY EXECUTION:", { query: queryMeta, result })
  }

  return result;
}

This is a simple example of how Supastruct is useful for writing abstractions around Supabase-js. It gets more interesting when you start to do things like: parse a query into a queryMeta object (i.e. using the getQueryMeta() method), programmatically modify the queryMeta (which is simple when it's in object format), and then construct a new Supabase-js query from the modified queryMeta using the supastruct function:

import { createClient } from "@supabase/supabase-js";
import { SupastructClient } from "supastruct";

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY
);
const db = new SupastructClient(supabase);

const query = db()
  .from("todos")
  .select("*")
  .eq("project", 1234)
  .eq("status", "in_progress");

// let's mark all of the above Todos as "done", without respecifying all those same query/filter/modifier methods & args:
const queryMeta = query.getQueryMeta();

const updateMeta = {
  ...queryMeta,
  mutation: "update",
  values: { status: "done" },
};

const { data, error } = await supastruct(supabase, updateMeta);

If I wanted to, I could have called supastruct above without awaiting it, which wouldn't execute the query and instead would return a Supabase query/filter builder that I could continue chaining methods onto.

Having the ability to programmatically read/modify Supabase-js queries opens up a world of possibilities; for example, check out how Supaquery uses it to integrate Supabase with React Query, providing a dead-simple API for "coupled mutations" that enable automatic, zero-config optimistic updates, resulting in a super snappy UI/UX -- all possible thanks to Supastruct.

Query Hooks

Supastruct also enables you to hook into the Supabase query execution lifecycle with various filter/action hooks, so you can programmatically modify records before they're mutated, and/or define side-effects/actions to run after a query is executed... all at a global level -- i.e. you define these hooks when instantiating your SupastructClient, and then any queries using that client will run those hooks at the appropriate time. Example:

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY
);
const db = new SupastructClient(supabase, {
  filters: {
    recordForUpdate: (record) => {
      // transform record here
      return record;
    }
    recordsForInsert: (records) => {
      // transform records here
      return records;
    }
    recordsForUpsert: (records) => {
      // transform records here
      return records;
    }
  },
  actions: {
    onUpdate: (res) => ...,
    onInsert: (res) => ...,
    onUpsert: (res) => ...,
    onDelete: (res) => ...
  }
});

FAQ

Have more questions? Drop an issue.

Feedback

This package is still very young, not well tested, and is likely to have breaking changes in the coming versions. That said, it is successfully being used in production environments, and will only get better with usage & feedback -- please don't hesitate to post GitHub issues, email me at [email protected], or DM me on Twitter @kaelancsmith


Made by Kaelan Smith

kaelansmith.com