New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

@staticbot/base44-supabase-shim

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@staticbot/base44-supabase-shim

Drop-in shim mimicking @base44/sdk API but routing to a Supabase backend. Vendored into migrated apps by Staticbot's Base44 native migration so the customer's build doesn't need a live npm connection.

Source
npmnpm
Version
0.8.1
Version published
Weekly downloads
580
-38.88%
Maintainers
1
Weekly downloads
 
Created
Source

@staticbot/base44-supabase-shim

Drop-in shim that exposes the same surface as @base44/sdk but routes every call to a Supabase backend (Postgres + GoTrue + Storage + Edge Functions). Built so you swap one import in src/api/base44Client.js and your hundreds of pages keep working.

This is the runtime shim Staticbot's Base44 → Supabase migration vendors into your repo as the final step of the migration. You can also use it standalone if you're doing the migration by hand.

Install (automated path — done for you by Staticbot)

Staticbot's Base44 native migration vendors the prebuilt dist/ directly into your repo at vendor/base44-supabase-shim/ and patches package.json to point at it. You don't need to do anything manually — just merge the PR that lands on the staticbot/base44-switchover-<id> branch.

Install (manual)

In each consuming app, vendor the prebuilt tarball or add as a git submodule (so builds don't depend on an npm registry):

# Option A: git submodule (you'll need to build it once locally)
git submodule add https://github.com/staticbot/staticbot-base44-supabase-shim.git vendor/base44-supabase-shim
cd vendor/base44-supabase-shim && npm i && npm run build

# Option B: vendor the prebuilt tarball straight from the npm registry
#   (same URL shape staticbot-app's Base44ShimProperties.getDistTarballUrl builds)
curl -L https://registry.npmjs.org/@staticbot/base44-supabase-shim/-/base44-supabase-shim-0.6.1.tgz \
    | tar -xz -C vendor/base44-supabase-shim --strip-components=1

In package.json:

{
  "dependencies": {
    "@staticbot/base44-supabase-shim": "file:./vendor/base44-supabase-shim",
    "@supabase/supabase-js": "^2.45.0"
  }
}

Use (browser / Vite app)

// src/api/base44Client.js  — replace @base44/sdk import
import { createClient } from '@staticbot/base44-supabase-shim';

export const base44 = createClient({
  supabaseUrl: import.meta.env.VITE_SUPABASE_URL,
  supabaseAnonKey: import.meta.env.VITE_SUPABASE_ANON_KEY,
  schemaPrefix: 'propertyflow',          // app-specific schema
  sharedSchema: 'core',                  // shared entities live here
  // sharedEntities default: ['Customer','Company','User','Role','Department','Notification','AuditLog']
});

// Then everything in your existing pages keeps working:
const customers = await base44.entities.Customer.list('-created_date', 50);
const c = await base44.entities.Customer.get('uuid');
await base44.entities.Customer.update('uuid', { phone: '081...' });
await base44.entities.Unit.create({ unit_no: 'A-101' });

Use (Supabase Edge Function — Deno)

import { createClientFromRequest } from 'npm:@staticbot/base44-supabase-shim/server';

Deno.serve(async (req) => {
  const base44 = createClientFromRequest(req, {
    supabaseUrl: Deno.env.get('SUPABASE_URL')!,
    supabaseAnonKey: Deno.env.get('SUPABASE_ANON_KEY')!,
    supabaseServiceRoleKey: Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,
    schemaPrefix: 'propertyflow',
  });

  const customers = await base44.asServiceRole.entities.Customer.filter(
    {},
    '-created_date',
    50,
  );
  return new Response(JSON.stringify({ data: customers }));
});

Entity → table mapping

Default rule: PascalCase → snake_case + plural.

  • Customer → customers
  • ChartOfAccount → chart_of_accounts
  • MeetingMinute → meeting_minutes

Override via entityMap if a real table doesn't follow the rule:

createClient({
  ...,
  entityMap: {
    Customer: { schema: 'core', table: 'customers' },        // explicit
    Job: { schema: 'construction', table: 'project_jobs' },  // non-default name
  },
});

Filter syntax

// Equality (default)
await base44.entities.Customer.filter({ status: 'active', vip: true });

// IN (pass array)
await base44.entities.Customer.filter({ id: ['a', 'b', 'c'] });

// Operators (Mongo-style, as Base44 apps write them)
await base44.entities.Invoice.filter({
  amount: { $gt: 1000 },
  customer_name: { $ilike: '%co.%' },
});

// Case-insensitive regex search (common in migrated server functions)
await base44.entities.Provider.filter({ name: { $regex: '^acme', $options: 'i' } });

// Presence check
await base44.entities.Profile.filter({ avatar_url: { $exists: true } });

// Boolean OR across sub-filters
await base44.entities.Argument.filter({
  $or: [{ created_by_id: userId }, { author_id: userId }],
});

Field operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $like, $ilike, $regex (+ $options: 'i'), $exists. Top-level combinators: $or, $and (over an array of sub-filters; multiple fields within one sub-filter are AND-ed). The legacy { op, value } form is still accepted. $nor/$not and regex/like inside a $or group throw a clear error rather than silently returning wrong rows.

Order by

  • String form (Base44 convention): 'name' ascending, '-created_date' descending.
  • Object form: { field: 'name', ascending: false }.

The User entity and auth.me()

Base44's User entity maps to the shared core.users table (Staticbot's migration exposes the core schema and gives it Base44's User rules: a user sees and edits their own row, an admin all rows, and only an admin can change a role). auth.me() / getUser() build the user from, in rising precedence: user_metadata → app_metadata → the signed-in user's own row. The row is authoritative for every column it has, NULL included, so me().role is the same role the database's RLS checks — including after an admin changes it. user_metadata is writable by the user (signUp({options: {data}})), so role and the other protected keys are never taken from it; app_metadata (service-role only) is the trusted fallback when the row isn't reachable. auth.updateMe() updates user_metadata and mirrors the keys that are columns of the row (never id, email, role or system columns). Pass authOptions: { usersTable: null } to opt out of the row overlay.

Creating records

Base44 fills id, created_by_id and created_by on its servers and returns the created record whatever the entity's read rules say. create() / bulkCreate() keep that contract:

  • The plain insert runs first. Tables created by Staticbot's DDL carry column defaults for all three, so it normally succeeds as is.
  • On an older target (no defaults), a row-level-security refusal is retried with created_by_id / created_by taken from the signed-in session, and a not-null error on id with a Base44-shaped 24-hex id.
  • If the new row can't be read back (INSERT … RETURNING applies the SELECT policy — e.g. an admin-only activity log), the insert is retried without RETURNING and the submitted row is returned with a client-side id.

A failed insert writes nothing, so retries never duplicate. A refusal that survives every step throws the original error. Without a session (service role, anonymous) nothing is stamped.

Auth surface

Base44 alias names are supported alongside Supabase-idiomatic ones so migrated apps compile unchanged:

  • auth.signIn({email, password}) / auth.loginViaEmailPassword(email, password)
  • auth.signUp({email, password, metadata?}) / auth.register({email, password, metadata?})
  • auth.loginWithProvider(provider, returnPath?) — Google / Microsoft / Facebook / Apple; builds an absolute redirectTo from window.location.origin.
  • auth.verifyOtp({email, otpCode}) — email OTP after register(). Returns {access_token, session, user}; the Supabase session is installed as a side-effect so a follow-up setToken() is a no-op.
  • auth.resendOtp(email)
  • auth.setToken(accessToken) — kept as a warn-once no-op so migrated code doesn't TypeError; the session is already installed by verifyOtp / signInWithPassword.
  • auth.resetPasswordRequest(email, {redirectTo?}) — Supabase resetPasswordForEmail.
  • auth.resetPassword({resetToken, newPassword}) — resetToken is ignored (Supabase already exchanged the link fragment for a session before you got here).
  • auth.isAuthenticated(): Promise<boolean>
  • auth.logout(returnUrl?) — after signOut resolves, navigates to returnUrl if provided.
  • auth.me() / auth.updateMe(metadata) / auth.getUser() / auth.getSession() / auth.redirectToLogin(returnUrl?) / auth.onAuthStateChange(cb)

Agents

base44.agents.* (Superagents) runs on the Staticbot AI agent runtime in your own Supabase project: conversations and messages live in the staticbot_ai schema (each user sees only their own), and the staticbot-agents edge function runs the agent's tool loop against any OpenAI-compatible AI provider. Tools run with the signed-in user's permissions, so row-level security applies to everything an agent reads or writes.

Supported, in the SDK's shape so existing UI code works unchanged: listConversations({ agent_name }), getConversations(), getConversation(id), createConversation({ agent_name, metadata }), addMessage(conversation, { role, content }), subscribeToConversation(id, onUpdate) (Supabase Realtime; replies stream in as the agent works).

addMessage resolves once the message is stored; the reply arrives through subscribeToConversation (or the next getConversation).

WhatsApp / Telegram bridges aren't part of the runtime. Their connect URLs return null unless configured, so <a href={...}> renders as an inert link:

createClient({
  ...,
  agents: {
    functionName: 'staticbot-agents', // default
    schema: 'staticbot_ai',           // default
    whatsappUrls: { SubdomainReviewer: 'https://wa.example.com/connect' },
    telegramUrls: { Hilton: 'https://t.me/hilton_bot' },
  },
});

What's NOT covered

  • Stripe / payments — Base44 had managed integration; here, wire Stripe into edge functions yourself.
  • Email — air-gapped LAN can't send mail by default; supply an internal SMTP relay or capture via inbucket. Enable via integrations.sendEmailFunction.
  • AI / InvokeLLM / GenerateImage — opt-in only. Wire your own endpoint through an edge function and pass its name via integrations.invokeLlmFunction / integrations.generateImageFunction.
  • SendSMS / ExtractDataFromUploadedFile — opt-in edge functions (integrations.sendSmsFunction / integrations.extractDataFunction). Both are implemented as methods that throw a clear "not configured" error when unset, so a migrated app that calls them fails legibly instead of hitting an undefined TypeError.
  • Schema discovery — entity definitions must exist in Postgres first (separate migrations). The shim assumes tables exist with conventional names.

Test

npm i
npm test       # vitest, mocked SupabaseClient + real postgrest-js over a fake fetch
npm run build  # tsup → dist/

tests/entities.e2e.test.ts runs create() against a real Postgres + PostgREST (RLS ordering, RETURNING vs the SELECT policy and missing=default are server behaviour no mock can prove). It is skipped unless pointed at one:

docker network create shimnet
docker run -d --name shimpg --network shimnet -e POSTGRES_PASSWORD=pw \
  -v "$PWD/tests/e2e/init.sql:/docker-entrypoint-initdb.d/init.sql" postgres:16
docker run -d --name shimrest --network shimnet -p 3999:3000 \
  -e PGRST_DB_URI=postgres://authenticator:pw@shimpg:5432/postgres -e PGRST_DB_SCHEMAS=public \
  -e PGRST_DB_ANON_ROLE=anon -e PGRST_JWT_SECRET=a-string-secret-at-least-32-bytes-long-xx \
  postgrest/postgrest:v12.2.3
SHIM_E2E_PGRST_URL=http://localhost:3999 SHIM_E2E_JWT_SECRET=a-string-secret-at-least-32-bytes-long-xx \
  npx vitest run tests/entities.e2e.test.ts

FAQs

Package last updated on 30 Sep 2026

Related posts