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

foundation-sdk

v0.3.6

Published

TypeScript SDK for Foundation

Downloads

330

Readme

foundation-sdk

TypeScript SDK for the Foundation platform. Handles authentication, data, files, integrations, and account management with one client.

Installation

npm install foundation-sdk

If you're using Cognito or Auth0 (most apps), install the corresponding peer dependency:

npm install aws-amplify              # for Cognito
npm install @auth0/auth0-spa-js      # for Auth0

Quick Start

import { createFoundation } from 'foundation-sdk'
import { cognitoAuth } from 'foundation-sdk/cognito'  // or 'foundation-sdk/auth0'

const foundation = await createFoundation({
  configUrl: 'https://backend.example.com/api/v1/public/init',
  tenantId: 'your-tenant-id',
  appId: 'your-app-id',
  auth: cognitoAuth
})

// Now use it
await foundation.auth.signIn('[email protected]', 'password')
const { items: projects } = await foundation.db.list('projects')

In production deployments, the SDK reads /foundation-env.json to discover the config URL and identifiers, so the call is just:

const foundation = await createFoundation({ auth: cognitoAuth })

Configuration

interface FoundationConfig {
  /** Public bootstrap endpoint URL. If omitted, reads from /foundation-env.json */
  configUrl?: string
  /** Tenant identifier */
  tenantId?: string
  /** Application identifier */
  appId?: string
  /** Override API base URL — useful with a dev proxy ('/api') */
  baseUrl?: string
  /** Auth provider factory or instance */
  auth?: AuthClient | AuthProvider
}

When /foundation-env.json is present, its values override configUrl, tenantId, appId, and clear baseUrl (the proxy is dev-only).

configUrl is fetched exactly as provided. It should already be the public bootstrap URL, usually https://.../api/v1/public/init. After auth, the SDK separately fetches authenticated backend config from the API base URL.

createFoundation(...) resolves with a fully initialized instance. You do not need to await foundation.ready separately.

Authentication

The SDK supports Cognito, Auth0, or no auth. Pick a provider entry point based on what your Foundation app uses:

// Cognito
import { cognitoAuth } from 'foundation-sdk/cognito'
const f = await createFoundation({ auth: cognitoAuth })

// Auth0
import { auth0Auth } from 'foundation-sdk/auth0'
const f = await createFoundation({ auth: auth0Auth })

Or auto-register via side-effect import:

import 'foundation-sdk/cognito'   // registers cognito as a known provider
const f = await createFoundation({})  // SDK reads provider from config and uses it

Sign in / sign up

signIn and signUp return result objects so you can branch on the outcome:

const result = await foundation.auth.signIn(email, password)

if (result.isSignedIn) {
  router.push('/dashboard')
} else if (result.nextStep?.signInStep === 'CONFIRM_SIGN_UP') {
  router.push({ path: '/confirm', query: { email } })
} else {
  console.error(`Sign-in needs additional step: ${result.nextStep?.signInStep}`)
}

const signUpResult = await foundation.auth.signUp(email, password, { name })
if (signUpResult.isSignUpComplete) {
  // verification email sent — user can sign in
} else if (signUpResult.nextStep?.signUpStep === 'CONFIRM_SIGN_UP') {
  // unconfigured Cognito — user needs to enter a code
  const confirmResult = await foundation.auth.confirmSignUp(email, code)
  if (confirmResult.isSignedIn) {
    router.push('/dashboard')   // already signed in — no trip back to the login form
  } else {
    router.push('/login')       // check confirmResult.nextStep for why (e.g. MFA)
  }
}

confirmSignUp signs the user in for you: the password passed to signUp is held in memory (never in storage) for 15 minutes so the confirmation code — which already proves the user owns the address — completes the session instead of dead-ending at a login form. The password is dropped as soon as it is used, and on signIn, logout, resetPassword, or TTL expiry. Pass autoSignInAfterConfirm: false to createFoundation to turn it off.

Result types:

interface SignInResult {
  isSignedIn: boolean
  nextStep?: { signInStep: 'DONE' | 'CONFIRM_SIGN_UP' | string; [key: string]: unknown }
}

interface SignUpResult {
  isSignUpComplete: boolean
  userId?: string
  nextStep?: { signUpStep: 'DONE' | 'CONFIRM_SIGN_UP' | string; [key: string]: unknown }
}

// confirmSignUp returns SignInResult: isSignedIn true means the session is ready.

Other auth methods

foundation.auth.user                  // User | null
foundation.auth.isAuthenticated       // boolean
foundation.auth.getToken()            // current JWT
foundation.auth.login()               // hosted login redirect
foundation.auth.logout()
foundation.auth.handleCallback()      // call on your /callback route after OAuth redirect
foundation.auth.confirmSignUp(email, code)   // -> SignInResult (auto signs in)
foundation.auth.resendSignUpCode(email)
foundation.auth.forgotPassword(email)
foundation.auth.resetPassword(email, code, newPassword)
foundation.auth.onChange(user => { ... })

auth.resendSignUpCode is provider-aware: Cognito sends a new code; Auth0 hits the backend resend-verification endpoint to retrigger the email link.

Data (foundation.db)

Generic CRUD for any entity defined in your Foundation app:

const { items, nextCursor } = await foundation.db.list<Project>('projects', {
  filters: { status: 'active' },
  limit: 20,
  orderBy: 'createdAt',
  orderDir: 'desc'
})

// Run a named list template (from the entity's methods.list.templates).
// Sent on the wire as the reserved `query` param; pass the template's key
// fields via `filters`. If `filters` also contains a `query` key, the
// explicit `template` option wins.
const { items: matches } = await foundation.db.list<Company>('companies', {
  template: 'by-domain',
  filters: { domain: 'acme.com' },
  limit: 1
})
// GET /api/v1/core/companies?domain=acme.com&query=by-domain&limit=1

const project = await foundation.db.get<Project>('projects', id)
const created = await foundation.db.create<Project>('projects', { name: 'New' })
const updated = await foundation.db.update<Project>('projects', id, { name: 'Renamed' })
await foundation.db.save<Project>('projects', data)   // create-or-update
await foundation.db.delete('projects', id)

Realtime (foundation.on)

Subscribe to entity-change events pushed over WebSocket:

const off = foundation.on('entity.changed', (event) => {
  // event: EntityChangeEvent
  // {
  //   changeType: 'created' | 'modified' | 'deleted'
  //   entityId?: string    // entity record id
  //   resource?: string    // entity resource name, e.g. 'companies'
  //   id?: string
  //   namespace?: string
  //   timestamp?: number
  // }
})

off() // unsubscribe

No client-side configuration is needed — the SDK discovers the realtime endpoint from the auth token. The socket opens lazily on the first subscriber, is shared by all subscribers, heartbeats and reconnects automatically (server-side idle timeouts are normal), and closes on the last unsubscribe.

Backend prerequisite: events fire only for entities whose backend definition enables them — websocket: { enabled: true } with scope namespace or user. If the backend has no realtime support (or there is no WebSocket global, e.g. SSR), foundation.on logs a single warning and the listener simply never fires — it does not throw.

Files (foundation.files)

// Upload (handles initiate + S3 PUT; sha256 is computed automatically)
const { id, name } = await foundation.files.upload({
  name: file.name,
  contentType: file.type,
  file                               // ArrayBuffer | Blob | File
})

// Or get a presigned URL yourself. sha256 is optional here; if you pass it,
// it must be the BASE64-encoded digest (not hex) and S3 will verify the
// uploaded bytes against it.
const { signedUrl, signedData } = await foundation.files.initiate({ ... })

const file = await foundation.files.get(id)
const { items } = await foundation.files.list({ limit: 20 })
await foundation.files.delete(id)

Integrations (foundation.integration)

// Get available connectors merged with the user's current connections
const all = await foundation.integration.all()
// [{ id, name, connections: [...], connected: true, connectionCount: 2, ... }]

// Or fetch them separately
const catalog = await foundation.integration.list()
const connections = await foundation.integration.connections()

// Status for a single source
const { connected, connections } = await foundation.integration.status('github-oauth')

// Connect (OAuth flow returns auth URL — open it in a popup or redirect)
const result = await foundation.integration.connect('github-oauth')
if (result.url) window.open(result.url, 'oauth')

await foundation.integration.disconnect('github-oauth', configurationId)

Account (foundation.account)

const { user, account } = await foundation.account.get()
await foundation.account.update({ name: 'New Name' })
const usage = await foundation.account.usage()
await foundation.account.resendVerification()

Pass type parameters for extended user/account shapes:

const { user, account } = await foundation.account.get<MyUser, MyAccount>()

Billing (foundation.billing)

// List purchasable plans. plan.id is the Stripe price id checkout() takes;
// the account's current plan comes back disabled: true ("Current Plan").
const plans = await foundation.billing.plans()

// Start a subscription checkout (returns a plan-change portal flow instead
// when the account already has a plan). Redirect the user to the URL.
const { url } = await foundation.billing.checkout('price_pro_monthly')
window.location.href = url

// Open the Stripe customer portal
const { url: portalUrl } = await foundation.billing.portal()

// On your success page: the redirect can beat the Stripe webhook, so poll
// until the session is complete and paid before treating the plan as active.
const sessionId = new URLSearchParams(location.search).get('checkoutSessionId')!
const session = await foundation.billing.lookupSession(sessionId)
const paid = session.status === 'complete' && session.payment_status === 'paid'

// The account's subscriptions: status, card summary, renewal date, subjects
const subscriptions = await foundation.billing.subscriptions()

checkout, portal, and subscriptions are account-owner only — other members get a 403 with code: 'OWNER_REQUIRED'. plans and lookupSession work for any authenticated user.

By default the backend derives redirect URLs from your app's origin ({origin}/callback/stripe, {origin}/plan, {origin}/settings/profile). Apps with different routes override them — once at init, or per call:

const foundation = await createFoundation({
  configUrl: '...',
  billing: {
    successUrl: 'https://app.example.com/billing/done', // after completed checkout
    cancelUrl: 'https://app.example.com/pricing',       // abandoned checkout
    returnUrl: 'https://app.example.com/settings'       // customer portal return
  }
})

// Per-call options win over the init defaults, field by field
await foundation.billing.checkout('price_pro_monthly', {
  successUrl: 'https://app.example.com/onboarding/complete'
})

URLs must be absolute http(s). The backend appends checkoutSessionId={CHECKOUT_SESSION_ID} to a custom successUrl unless you already placed the {CHECKOUT_SESSION_ID} placeholder yourself.

OAuth (foundation.oauth)

For building consent screens when your app acts as an OAuth authorization server:

// Read OAuth params from URL (set by /oauth/authorize redirect)
const params = new URLSearchParams(location.search)
const clientId = params.get('client_id')!

// Get client info to display
const client = await foundation.oauth.getClient(clientId)
// { id, name, description, redirectUris, allowedScopes, ... }

// User clicks Allow → generate code, redirect back
const { code } = await foundation.oauth.authorizeConsent({
  client_id: clientId,
  redirect_uri: params.get('redirect_uri')!,
  scope: params.get('scope')!,
  state: params.get('state')!,
  code_challenge: params.get('code_challenge')!,
  code_challenge_method: params.get('code_challenge_method')!
})

const url = new URL(params.get('redirect_uri')!)
url.searchParams.set('code', code)
url.searchParams.set('state', params.get('state')!)
window.location.href = url.toString()

Config (foundation.config)

The full config from your Foundation backend:

foundation.config.app          // { id, name, version, environment }
foundation.config.features     // feature flag definitions
foundation.config.plans        // ConfigPlan[] — static plan defs (Foundation plan ids); empty pre-auth; billing.plans() has the Stripe prices
foundation.config.theme        // { colors, dark, defaultColorScheme }
foundation.config.connectors   // available integration connectors
foundation.config.resources    // service endpoints
foundation.config.auth         // auth provider config
foundation.config.raw          // full config object for anything not surfaced above

Logging (foundation.log)

foundation.log.info('Page loaded', { path: '/dashboard' })
foundation.log.warn('Slow query', { duration: 1234 })
foundation.log.error('Failed to save', { error })
foundation.log.event('button_clicked', { id: 'submit' })

OpenAPI (foundation.openapi)

const spec = await foundation.openapi.get()

Web Components

The SDK ships pre-built custom elements that handle complex flows. They work in any framework.

import 'foundation-sdk/components'   // registers all custom elements
<!-- OAuth integration connect modal — handles the popup, completion state, errors -->
<foundation-connect integration-id="github-oauth"></foundation-connect>

The component reads the SDK instance from window.__foundation, so set it after creating:

const foundation = await createFoundation({ ... })
;(window as any).__foundation = foundation

If your framework warns about unknown elements, configure it to recognize foundation-* tags. For Vue:

// vite.config.ts
vue({
  template: {
    compilerOptions: {
      isCustomElement: tag => tag.startsWith('foundation-')
    }
  }
})

Browser Builds (Script Tag)

For apps without a bundler, two pre-bundled IIFE files are available:

<!-- Cognito auth bundled in -->
<script src="https://unpkg.com/foundation-sdk/dist/foundation-sdk.browser.global.js"></script>

<!-- Auth0 auth bundled in -->
<script src="https://unpkg.com/foundation-sdk/dist/foundation-sdk.browser.auth0.global.js"></script>

<script>
  FoundationSDK.createFoundation({
    configUrl: '...',
    tenantId: '...',
    appId: '...'
  }).then(f => {
    window.__foundation = f
  })
</script>

<!-- Optional: web components -->
<script src="https://unpkg.com/foundation-sdk/dist/foundation-sdk.components.global.js"></script>

Type Exports

import type {
  Foundation,
  FoundationConfig,
  FullConfig,
  User,
  AuthClient,
  AuthService,
  AuthProvider,
  SignInResult,
  SignUpResult,
  DbService,
  FilesService,
  FileMetadata,
  AccountService,
  BillingService,
  BillingDefaults,
  IntegrationService,
  Integration,
  IntegrationConnection,
  IntegrationDetail,
  OAuthService,
  OAuthClient,
  OAuthConsentParams,
  OpenApiService,
  LogService,
  EntityChangeEvent
} from 'foundation-sdk'

Dev Tip — local proxy

When developing against a remote Foundation backend, configure a Vite proxy and point baseUrl at it. This avoids CORS and gives you the network panel:

// vite.config.ts
server: {
  proxy: {
    '/api': { target: 'https://backend.your-app.com', changeOrigin: true, secure: true }
  }
}
const foundation = await createFoundation({
  configUrl: '/api/v1/public/init',
  baseUrl: '/api',
  tenantId: '...',
  appId: '...',
  auth: cognitoAuth
})

baseUrl is automatically ignored when /foundation-env.json is present, so this only takes effect in dev.

License

MIT