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.6.5
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
await base44.entities.Invoice.filter({
  amount: { op: 'gt', value: 1000 },
  customer_name: { op: 'ilike', value: '%co.%' },
});

Operators: eq, neq, gt, gte, lt, lte, like, ilike, in.

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() overlay the signed-in user's own row on top of user_metadata, so me().role is the same role the database's RLS checks — including after an admin changes it. auth.updateMe() updates user_metadata and mirrors the keys that are columns of the row (never id, email, role or system columns). On an older target where core isn't reachable, both fall back to user_metadata alone. Pass authOptions: { usersTable: null } to opt out.

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's hosted agents (WhatsApp / Telegram) have no self-host equivalent. The agents namespace exposes a stub so base44.agents.getWhatsAppConnectURL(name) doesn't crash — it returns null by default, which lets <a href={...}> render as an inert link (feature visibly disabled). Wire your own bridge with:

createClient({
  ...,
  agents: {
    whatsappUrls: { SubdomainReviewer: 'https://wa.example.com/connect' },
  },
});

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.
  • 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 24 Sep 2026

Related posts