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 🙏

© 2026 – Pkg Stats / Ryan Hefner

sfgql

v0.1.3

Published

Developer-experience layer for Salesforce UI API GraphQL: React hooks, LWC helpers, ESLint rules, SOQL converter and codegen.

Readme

sfgql

Salesforce GraphQL, without the traps.

npm node license

Salesforce's UI API GraphQL is powerful, but the raw API makes you write the same boilerplate and hides a few nasty surprises. sfgql is one small package that fixes them, for React (Multi-Framework UI bundles) and LWC.

| Raw UI API GraphQL | With sfgql | |---|---| | Every value is wrapped: edges → node → Name → value | Plain objects: { Id, Name: 'Acme' } | | Forget first and the API silently returns 10 rows (10 of 17 in a real org) | A lint error (SFGQL001) before it ships | | Errors arrive as HTTP 200 with HTML-escaped messages | Per-field errors, ready for your form | | After a save, other screens go stale, or you refetch everything | Only affected queries refresh (12 refetches become 4) | | You rewrite every SOQL query by hand | sfgql convert "SELECT ..." | | Parent + 10 children = 11 requests | 1 request (with a small Apex service) |

See the difference

// Before: data.uiapi.query.Account.edges.map((e) => e.node.Name.value), plus paging state and cache refresh
// After:
const { records, loadMore, hasMore } = useSfConnection(ACCOUNTS);   // records = [{ Id, Name }]

Start in a minute

npm i sfgql @tanstack/react-query @salesforce/sdk-data

Then follow the 9-line React example, the LWC setup, or just try the converter with no setup:

npx sfgql convert "SELECT Name, Industry FROM Account WHERE Industry = 'Technology' LIMIT 20"

Honest limits: sfgql saves you requests and code, not CPU: a hand-written mapper flattens faster than flattenQuery. It is early (0.1.x), tested against responses recorded from a real org, and the Apex service is verified in one Developer Edition org. Numbers and caveats below.

Benchmarks

What you gain is fewer requests and less code, not faster in-memory work. Every number comes from a script in benchmarks/ run for this release; request counts are exact, times are simulated (120 ms per round trip), and the createTree row is a projection.

| Measured | Without sfgql | With sfgql | Result | |---|---|---|---| | Code to paginate (infinite scroll) | 40 lines | 9 lines | 78% fewer | | Rows returned if you forget first | 10 of 17, silently | lint error SFGQL001 | caught before production | | Refetches after one update (12 mounted queries) | 12 | 4 | 67% fewer (360 -> 240 ms simulated @120 ms) | | 20 components, same query | 20 requests | 1 request | same as plain react-query | | Create parent + 10 children | 11 requests | 1 request | 91% fewer (projection) | | Bundle, gzip (whole stack) | Apollo 63.2 kB | 18.9 kB | 3.3x smaller than Apollo; urql (11.1 kB) is smaller | | Flatten 10,000 records (CPU) | hand-written 2.7 ms | 22.8 ms | not faster: 8.5x slower than a hand-written mapper | | Mapping a validation error to fields | 13 lines | 1 line | 92% fewer |

Try it interactively: open docs/benchmarks-interactive.html (after install: node_modules/sfgql/docs/benchmarks-interactive.html). Sliders for latency, number of children and number of components recalculate every row from the measured data. Full tables: docs/benchmarks.md.

  • Machine: AMD Ryzen 5 7520U with Radeon Graphics, 8 threads, Node v22.18.0, 2026-10-15. Re-run with npm run bench; timing ratios vary by tens of percent between runs.
  • Baselines are competent hand-written versions; each was asserted to produce identical data before comparing.
  • Nothing here measures Salesforce server speed or a real network. Simulated time = one mutation round trip, then refetches in waves of up to 6 parallel requests.
  • The Apex tree service was verified separately in one Developer Edition org (3-level tree, rollback); the benchmark itself uses a fake endpoint, so the createTree row is a request-count projection.
  • Bundle sizes are esbuild minify + gzip of what a typical app imports; Apollo and urql would also need Salesforce-specific glue, which is not counted.

The GitHub repository is private. Links below point to the docs/ folder that ships inside the npm package.

Install

Requires Node 20+. The package is ESM-only (import, not require). Any package manager works:

npm i sfgql          # or: pnpm add sfgql   |   yarn add sfgql   |   bun add sfgql

Everything else is an optional peer, so install only what your use case needs:

| You are building | Install | Jump to | |---|---|---| | A React app inside Salesforce (Multi-Framework UI bundle) | npm i sfgql @tanstack/react-query @salesforce/sdk-data | React in Salesforce | | A React app outside Salesforce | npm i sfgql @tanstack/react-query | React elsewhere | | LWC components | npm i -D sfgql (nothing is installed in the org) | LWC | | A Node script, test or backend | npm i sfgql | Node | | Lint rules for your GraphQL | npm i -D sfgql eslint | ESLint | | Types generated on save | npm i -D sfgql vite | Vite | | Just the command line | npx sfgql ... or npm i -g sfgql | CLI | | Nested parent + children create | npx sfgql apex install ... | Nested create | | An AI coding assistant | copy one file | AI assistants |

Peer ranges: react >=18, @tanstack/react-query ^5, eslint >=9, vite >=5, @salesforce/sdk-data >=1.135.0 <2, esbuild (only to load a sfgql.config.ts). Every snippet below was run against an installed copy of the package.

React in Salesforce

npm i sfgql @tanstack/react-query @salesforce/sdk-data
import { createDataSDK } from '@salesforce/sdk-data';
import { gql, createSdkTransport } from 'sfgql/core';
import { SfgqlProvider, useSfConnection } from 'sfgql/react';

const ACCOUNTS = gql`query Accounts($first: Int = 25, $after: String) {
  uiapi { query { Account(first: $first, after: $after) {
    edges { node { Id Name { value } } } pageInfo { hasNextPage endCursor } } } } }`;

function List() {
  const { records, loadMore, hasMore } = useSfConnection<{ Id: string; Name: string }>(ACCOUNTS);
  return <>{records.map((a) => <p key={a.Id}>{a.Name}</p>)}{hasMore && <button onClick={() => loadMore()}>More</button>}</>;
}
export const App = () => <SfgqlProvider transport={createSdkTransport(createDataSDK())}><List /></SfgqlProvider>;

More: getting started (React), react module.

React outside Salesforce

Use createFetchTransport with an access token. Never ship a token in browser code; put your own server in between and hand the browser a transport that calls it.

import { createFetchTransport } from 'sfgql/core';
const transport = createFetchTransport({ instanceUrl, accessToken, apiVersion: '67.0' }); // server-side or tests
// <SfgqlProvider transport={transport}> ... same hooks as above

LWC

LWC cannot import npm packages, so the CLI exports a plain-JS service module into your SFDX project (nothing from npm runs in the org):

npm i -D sfgql
npx sfgql lwc export --out force-app/main/default/lwc/sfgqlCore    # writes sfgqlCore.js + .js-meta.xml
import { graphql, executeMutation } from 'lightning/graphql';
import { flattenQuery, trackWire, mutateAndRefresh } from 'c/sfgqlCore';

@wire(graphql, { query: ACCOUNTS })
wired(result) { trackWire(this, 'accounts', result); this.rows = flattenQuery(result.data)?.Account?.records ?? []; }
async save() { await mutateAndRefresh(this, executeMutation, { query: UPDATE, variables }); }

Re-run the export after upgrading sfgql. Auto-refresh works within one component; see LWC guide. --inline prints a paste-able snippet instead of files.

Node scripts, tests and backends

import { createFetchTransport, flattenQuery, gql } from 'sfgql/core';

const transport = createFetchTransport({ instanceUrl, accessToken });           // fetch is injectable for tests
const { data } = await transport.query({ query: gql`query { uiapi { query { Account(first: 5) {
  edges { node { Id Name { value } } } pageInfo { hasNextPage endCursor } } } } }` });
console.log(flattenQuery(data));   // { Account: { records: [{ Id, Name }], pageInfo } }

From CommonJS, use dynamic import: const core = await import('sfgql/core').

ESLint rules

npm i -D sfgql eslint
// eslint.config.js
import sfgql from 'sfgql/eslint';
export default [sfgql.configs.recommended];   // or sfgql.configs.strict

Catches the mistakes that fail silently, for example a missing first (SFGQL001: the API quietly returns 10 rows). Rules: docs/rules.

Vite plugin

npm i -D sfgql vite
// vite.config.ts
import sfgql from 'sfgql/vite';
export default defineConfig({ plugins: [react(), sfgql({ schema: 'schema.graphql', out: 'src/generated/sfgql-types.ts', validate: true })] });

Regenerates operation types on save from your local schema (npx sfgql codegen creates schema.graphql).

CLI

Use it without installing, per project, or globally:

npx sfgql --help                                  # no install
npm i -D sfgql && npx sfgql ...                   # pinned per project
npm i -g sfgql && sfgql ...                       # global
sfgql convert "SELECT Name FROM Account WHERE Industry = 'Technology' LIMIT 20"   # SOQL -> GraphQL
sfgql explain query.graphql          # plain-English description, cost, lint findings, equivalent SOQL
sfgql codegen --target-org my-org    # introspect the org and generate TypeScript types
sfgql validate --as [email protected] --target-org my-org   # check queries against a user's permissions
sfgql doctor                         # check your setup
sfgql init                           # write config, ESLint setup and npm scripts (--dry-run to preview)

Org commands use your sf CLI login (sf org login web); tokens and instance URLs are never printed.

Nested create (Apex)

createTree creates a parent and its children in one request through a small Apex REST service. Deploy it to your org first (preview with --dry-run; production orgs need --allow-production):

npx sfgql apex install --target-org my-org --dry-run
npx sfgql apex install --target-org my-org
import { createTree } from 'sfgql/nested';
await createTree(transport, { object: 'Account', fields: { Name: 'Acme' },
  children: { Contacts: [{ object: 'Contact', fields: { LastName: 'Doe' } }] } });

Limits: 200 records, depth 3; all-or-nothing by default. Verified in a Developer Edition org; see apex-verification.

AI coding assistants

The package ships a skill and an API index so assistants write correct sfgql code:

mkdir -p .claude/skills/sfgql && cp node_modules/sfgql/skills/sfgql/SKILL.md .claude/skills/sfgql/
# llms.txt (concise API index): node_modules/sfgql/llms.txt

Modules

| Import | What | Docs | |---|---|---| | sfgql/core | transports, flatten, errors, record-Id keys, cost, gql | core | | sfgql/react | provider and hooks on TanStack Query v5, cache sync | react | | sfgql/lwc | LWC helpers, exported to the org as c/sfgqlCore | lwc | | sfgql/realtime | CDC, polling and focus invalidation | realtime | | sfgql/forms | headless FLS-aware form model | forms | | sfgql/nested | parent + children create (bundled Apex) | nested | | sfgql/eslint | flat-config plugin, 10 rules | eslint | | sfgql/vite | codegen on save, operation validation | vite | | sfgql/devtools | event bridge for a browser panel | devtools | | sfgql (bin) | convert codegen validate doctor apex lwc explain init | cli |

Start here: React, LWC. Learn: benchmarks, SOQL to GraphQL, v1 vs v2, gotchas, personas and FLS, recipes, error codes, rules. AI assistants: skills/sfgql/SKILL.md, llms.txt.

Requirements: Node >= 20, ESM only, API v67.0+. Peers (all optional): react >= 18, @tanstack/react-query ^5, eslint >= 9, vite >= 5, @salesforce/sdk-data >=1.135.0 <2.

Status and limits

Verified against a real org (Developer Edition, API v67.0): the transport endpoint, introspection, query/mutation shapes, error shapes, date literals/ranges, semi-joins, aggregates, no-offset, silent 10-row default, IdOrRef!. The SOQL converter is golden-tested (99 cases) against the schema snapshot.

Not verified (treat as experimental):

  • Anything that runs inside an org: the React hooks in a deployed UI bundle, LWC refresh() (A5), importing c/sfgqlCore (A6), lightning/empApi CDC (A8), streaming in a UI bundle (A9), Apex REST from a UI bundle (A11).
  • The Apex tree service compiled and passed its 25 tests in a Developer Edition org (coverage 95% / 100% / 100%), and a real createTree call created a 3-level tree, rolled back a bad one and rejected an unknown relationship. It has not been run in a scratch org or from a UI bundle (A11). Details: docs/reports/apex-verification.md.
  • Persona validation: introspection as another user (A4) is untested; there are no validation-rule, duplicate-rule, trigger or FLS error fixtures, so those error kinds are classified from documented wording.
  • The devtools panel is not part of this package.
  • Maximum page size and upperBound semantics.
  • Both examples are unverified scaffolds (react, lwc).

Differences from the original design are listed in ADR 0002. Non-goals: no UI components, no Aura, no LDS replacement in LWC, no LLM calls.

License

MIT