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

target-supabase-sdk

v0.3.4

Published

Supabase client initializer and data access APIs

Readme

target-supabase-sdk

Supabase client initializer and data access APIs for Target-based backends.

Install

pnpm add target-supabase-sdk

This package declares @supabase/supabase-js, @supabase/postgrest-js, and lodash-es as peer dependencies — your app must have them available at runtime, but they are not bundled into the SDK itself. This avoids duplicate Supabase client instances and lets you control dependency versions.

pnpm 8+ and npm 7+ usually auto-install missing peer dependencies, so the command above is often enough.

If you see peer dependency warnings or Cannot find module errors at runtime, install them explicitly:

pnpm add target-supabase-sdk @supabase/supabase-js @supabase/postgrest-js lodash-es

@supabase/postgrest-js is also pulled in transitively by @supabase/supabase-js; it is listed here because the SDK uses its types directly (e.g. PostgrestFilterBuilder).

Environment Variables

Copy .env.example to .env.local and fill in your Supabase project values:

cp .env.example .env.local

| Variable | Required | Description | |----------|----------|-------------| | SUPABASE_URL | Yes | Main Supabase project URL | | SUPABASE_ANON_KEY | Yes | Main project anon (public) key | | SUPABASE_NEED_AUTH_URL | No | Separate auth project URL | | SUPABASE_NEED_AUTH_ANON_KEY | No | Separate auth project anon key |

Used by scripts/ (pnpm worker:trigger) via .env.local. Browser apps pass the same values to supabase.initialize() (e.g. import.meta.env.VITE_SUPABASE_URL if your bundler uses a VITE_ prefix).

Security

  • Only use the anon (public) key in client or script env — it is designed to be public when protected by Row Level Security (RLS).
  • Never put the service_role key in env files or frontend code. Use it only in trusted server-side environments.
  • Do not commit .env or .env.local. They are gitignored; only .env.example (placeholders) should be tracked.
  • Auth APIs return session tokens. Store them securely and never log them.

Usage

Browser / bundlers (default)

Chrome extensions, React, Vite — import from the main entry (no Node built-ins):

import { supabase, getTarget, postLinkCreate, postTask } from "target-supabase-sdk";

postTask enqueues a task row (Zod payload only). Repo and params validation run at worker prepareTask, not at publish time.

Node.js workers / CLI

Local task registry, repo script loading, TaskNode — use the /node entry:

import { TaskManager, RepoManager, TaskNode, postTaskWithValidation } from "target-supabase-sdk/node";

Consumer services (e.g. ../download-service, ../watch-service) run TaskNode / TriggerNode with their own config/task.config.js and tasks/ packages.

Breaking change in 0.2.0: TaskManager, RepoManager, and postTaskWithValidation are no longer the only task publish path on /node. postTask (no Repo validation) is on the default browser entry and re-exported from /node.

Browser vs Node (package layout)

Since 0.2.0, the package ships two compiled entries:

| Import | Built file | Runtime | |--------|------------|---------| | target-supabase-sdk | dist/browser.js | Chrome extension, React, Vite — no Node built-ins | | target-supabase-sdk/node | dist/node.js | Workers, CLI, TaskNode — includes browser API + Node-only code | | target-supabase-sdk/browser | dist/browser.js | Explicit alias of default |

src/browser.ts is the curated browser public surface (includes postTask). src/node.ts re-exports browser and adds Node-only symbols (TaskManager, postTaskWithValidation, RepoManager, …).

Initialize Supabase

import { supabase, getTarget, loginUser } from "target-supabase-sdk";

await supabase.initialize({
  supabaseUrl: import.meta.env.VITE_SUPABASE_URL,
  supabaseAnonKey: import.meta.env.VITE_SUPABASE_ANON_KEY,
  supabaseNeedAuthUrl: import.meta.env.VITE_SUPABASE_NEED_AUTH_URL,
  supabaseNeedAuthAnonKey: import.meta.env.VITE_SUPABASE_NEED_AUTH_ANON_KEY,
});

const target = await getTarget({ id: "..." });

Service catalog

Register services and Api capabilities in Supabase Target rows. L3 startup uses claimServiceRegistrySlot (see l3-service-host) — each process creates a new Service instance and claims a registry slot.

import {
  postApi,
  postService,
  getService,
  getApi,
  claimServiceRegistrySlot,
  ApiMethod,
  ServiceLifecycleStatus,
} from "target-supabase-sdk";

// Register an Api capability
await postApi({
  name: "Upload Chunk",
  value: "storage.post.chunk.0",
  tagList: [],
  details: {
    method: ApiMethod.POST,
    path: "/v0/chunks",
    endpoint: "http://localhost:3100/v0/chunks",
    request: { query: [] },
    response: { "200": [] },
    manifestVersion: 0,
    lifecycle: {
      status: ServiceLifecycleStatus.ACTIVE,
      activeSince: "2026-01-01",
      deprecatedAt: null,
      sunsetAt: null,
    },
  },
});

// L3 startup: new instance + registry slot claim
const session = await claimServiceRegistrySlot({
  serviceValue: "storage-service",
  createInstance: () =>
    postService({
      name: "Storage Service",
      value: "storage-service",
      tagList: [],
      details: {
        manifestVersion: 0,
        apiKeys: ["storage.post.chunk.0"],
        dependencies: [],
        lifecycle: { /* same shape as Api */ },
      },
    }).then((r) => {
      if (!r.success || r.data == null) throw new Error(r.error?.message ?? "postService failed");
      return r.data;
    }),
});
// session.release() on graceful shutdown

| API | Description | |-----|-------------| | postApi / getApi | Create or fetch Api Target rows | | postService / getService | Create or fetch Service rows (getService({ id }) for a specific instance) | | claimServiceRegistrySlot | Preflight + register slot for a new instance (createServiceHost wraps this) | | getTargetSystemRegistry | Read slot layout and which instance ids are ACTIVE |

Types: Service, Api, ApiDetails (method, path, endpoint, request, response), FieldDefinition for inline schemas.

Example consumers: ../storage-service (chunk upload/download), ../watch-service (site scan scheduler), ../download-service (Chrome-backed auto-chrome executor).

TriggerNode (local interval runners)

TriggerNode registers with Supabase (heartbeat, commands) and runs in-process interval runners registered in code (TriggerManager.registerRunner). There is no category=trigger Target type in the SDK.

Register runners in code before start() (registration closes on bootstrap):

import {
  TriggerManager,
  TriggerNode,
  LOG_TOPIC_TRIGGER,
  postTask,
} from "target-supabase-sdk/node";

await supabase.initialize({ supabaseUrl, supabaseAnonKey });

TriggerManager.registerRunner({
  key: "daily-weather",
  intervalMs: 24 * 60 * 60 * 1000,
  retryCount: 3,
  retryDelayMs: 5_000,
  timeoutMs: 120_000,
  fn: async (ctx) => {
    const { error } = await postTask({ name: "weather", value: "weather", params: { city: "Tokyo" } });
    if (error) throw new Error(error.message);
    ctx.logger.success("task posted", { topic: LOG_TOPIC_TRIGGER });
  },
});

await new TriggerNode({ requireRunners: true }).start();

| Concept | Behavior | |---------|----------| | Main loop | Fixed 60s (TRIGGER_LOOP_INTERVAL_MS) | | intervalMs | Per-runner spacing; values < 60s are warned — effective precision is loop-bound | | Registration | registerRunner before start(); hasRunner / unregisterRunner for lifecycle | | Retries | 1 + retryCount per tick; optional retryDelayMs between attempts | | timeoutMs | Optional per-attempt limit; does not cancel in-flight work | | Overlap | Running fn when due → skip tick (no backlog) | | Parallel | Due runners in one round → Promise.all | | Zero runners | Warn by default; requireRunners: true aborts bootstrap | | Multi-instance | Run one TriggerNode per scheduler — duplicates fire the same runners |

CLI: pnpm worker:trigger (scripts/run-trigger-node.ts — log + postTask examples).

See .cursor/skills/trigger-local-runners/SKILL.md.

Adding new modules (contributors)

When adding a domain, API, or Manager, classify browser vs Node before registering exports. The default entry must stay bundler-safe (Chrome extensions, Webpack, Vite).

Decision flow

  1. Scan static dependencies — grep the new code and everything it imports for node:fs, node:crypto, node:path, etc.
  2. Choose entry:
    • No node:* in the chain → add to src/browser.ts (and domain index.ts)
    • Uses Node built-ins → add only to src/node.ts (consumers use target-supabase-sdk/node)
    • Same file mixes both → split into separate leaf modules (example: task.api.ts vs task-post.api.ts)
  3. Build — pnpm build runs Rollup (dual entry browser.js + node.js, shared chunks), tsc --emitDeclarationOnly for types, then scripts/verify-browser-entry.mjs, which scans dist/browser.js and every ./chunks/* file it statically imports and fails if any contain node:… imports (Rollup keeps node:* as external in shared chunks — Webpack MV3 cannot resolve them).
pnpm build
# [verify:browser] OK — dist/browser.js bundle has no node: imports.

Node entry (/node) is bundled by the same Rollup build (peers and node:* stay external); there is no separate verify script for it.

Rules of thumb

| Safe on default entry . | Node entry /node only | |---------------------------|-------------------------| | *.interface.ts, Supabase RPC *.api.ts | TaskManager, RepoManager, TaskNode, TriggerNode, TriggerManager | | createTarget, postLinkCreate, task patch* APIs, postTask | postTaskWithValidation, TaskRepoValidation | | repo.api (remote) | local-task-registry, repo.script-loader |

Do not rely on dynamic import() to hide Node code: if browser.ts statically re-exports a file, bundlers include the whole module. Keep Node-only code in separate files that browser.ts never touches.

Internal imports: within a domain, import leaf paths (from "./task.api") not the domain barrel (from "./index") to avoid cycles.

Detailed conventions: .cursor/skills/browser-node-exports/SKILL.md and .cursor/skills/library-exports/SKILL.md.

Media Links (video / audio / image / image-list)

Convention on Link (same category=link): value === details.loaderKey. Browser-safe draft/mime helpers on the default entry; register / availability on /node only.

import { buildMediaLinkDraft, buildMediaNameFromLocator } from "target-supabase-sdk";
import { registerMediaLink } from "target-supabase-sdk/node";

await registerMediaLink({
  value: "video",
  name: "my-key",
  storageProvider: process.env.LOCAL_STORAGE_PROVIDER!,
  locator: "D:\\\\data\\\\clip.mp4",
  localStorageProvider: process.env.LOCAL_STORAGE_PROVIDER!,
  tagList: ["cv-service", "media-asr"],
});

Details: src/shared/media/README.md and .cursor/skills/link-conventions/SKILL.md.

Feed Links (feed.image / feed.audio / feed.video / feed.image-list)

Convention on Link (same category=link): value === details.loaderKey. Platform key name = {platformName}.{feedKey}. System lifecycle tags (.public, .local, .oss) use a leading dot. Draft on the default entry; ingest .public → .local/.oss on /node via registerFeed (locator validation + optimistic lock).

import { buildFeedLinkDraft } from "target-supabase-sdk";
import { registerFeed } from "target-supabase-sdk/node";

const draft = buildFeedLinkDraft({
  value: "feed.video",
  platformName: "bilibili",
  feedKey: "12345",
  source: ".public",
  locator: "https://example.com/feed/12345",
  tagList: ["creator-x"],
});

await registerFeed({
  id: feedRowId,
  source: ".local",
  storageProvider: process.env.LOCAL_STORAGE_PROVIDER!,
  locator: "D:\\\\data\\\\bilibili.12345.mp4",
  localStorageProvider: process.env.LOCAL_STORAGE_PROVIDER!,
});

Details: src/shared/feed/README.md and .cursor/skills/link-conventions/SKILL.md.

PostgREST filter helpers (0.2.15+)

When using supabase.client directly (e.g. ilike search or text[] tag filters):

| Export | Use | |--------|-----| | escapeIlikePattern | User input for .ilike() / .or("col.ilike.%…%") — escapes % _ \ and strips , | | toPostgrestTextArrayLiteral | .overlaps("tagList", …) and .filter("tagList", "not.ov", …) — never .not("tagList", "ov", jsArray) |

Pitfalls and examples: .cursor/skills/target-list-query/SKILL.md.

Development

pnpm install
pnpm typecheck
pnpm typecheck:scripts
pnpm format        # Biome: format + lint + organize imports (write)
pnpm format:check  # CI gate (read-only)
pnpm verify        # typecheck + typecheck:scripts + format:check (same as pre-push)
pnpm build
pnpm dev

pnpm install sets core.hooksPath to .githooks/. Every git push (including git push --tags) runs pnpm verify first; fix with pnpm format then commit if format fails. Bypass only when intentional: git push --no-verify.

Verify the publishable tarball locally (runs prepack → full build):

pnpm pack:check

CI runs pnpm build then npm pack --dry-run --ignore-scripts so the tarball check does not rebuild.

Release

Publishing is automated via GitHub Actions when a version tag is pushed.

One-time setup

  1. Create an npm Granular Access Token with Read and Write permission and Bypass 2FA for automation enabled.
  2. Add it to the GitHub repository as a secret named NPM_TOKEN:
    • Repository → Settings → Secrets and variables → Actions → New repository secret

Publish a new version

npm version patch
git push
git push --tags
  • npm version patch — bump version (0.1.0 → 0.1.1), commit, and create git tag v0.1.1. Use minor or major when needed.
  • git push — push the version commit to GitHub
  • git push --tags — push the tag; triggers .github/workflows/release.yml to publish to npm

First push on a new machine (set upstream once):

git push -u origin main
git push --tags

PowerShell 5.x does not support &&. Run each command on its own line, or use git push; git push --tags.

The release workflow will:

  1. Verify the git tag matches package.json version (e.g. tag v0.1.1 ↔ version 0.1.1)
  2. Build the package
  3. Publish to npm with provenance

Manual publish (fallback)

pnpm pack:check
npm publish --otp=YOUR_2FA_CODE

License

MIT