JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 22
  • Score
    100M100P100Q79583F
  • License FSL-1.1-Apache-2.0

Package Exports

  • @geenius/adapters
  • @geenius/adapters/auth
  • @geenius/adapters/cache
  • @geenius/adapters/cloudflareKV
  • @geenius/adapters/convex
  • @geenius/adapters/convex/api
  • @geenius/adapters/convex/convex.config
  • @geenius/adapters/deploy
  • @geenius/adapters/deploy/cloudflare
  • @geenius/adapters/deploy/heroku
  • @geenius/adapters/deploy/netlify
  • @geenius/adapters/deploy/vercel
  • @geenius/adapters/events
  • @geenius/adapters/memory
  • @geenius/adapters/neon
  • @geenius/adapters/payments
  • @geenius/adapters/queue
  • @geenius/adapters/react
  • @geenius/adapters/react-ant
  • @geenius/adapters/react-chakra
  • @geenius/adapters/react-css
  • @geenius/adapters/react-css/styles.css
  • @geenius/adapters/react-daisyui
  • @geenius/adapters/react-heroui
  • @geenius/adapters/react-mantine
  • @geenius/adapters/react-mui
  • @geenius/adapters/react-native
  • @geenius/adapters/react-shadcn
  • @geenius/adapters/solidjs
  • @geenius/adapters/solidjs-ark
  • @geenius/adapters/solidjs-css
  • @geenius/adapters/solidjs-css/styles.css
  • @geenius/adapters/solidjs-kobalte
  • @geenius/adapters/solidjs-solidui
  • @geenius/adapters/storage

Readme

@geenius/adapters

Typed adapter contracts for the Geenius ecosystem. The current package surface is a contract layer for infrastructure domains and launch DB backends: interfaces, Zod schemas, typed errors, SDK-free DB binding helpers, memory/local dev adapters, and retained UI compatibility subpaths.

Install

pnpm add @geenius/adapters

Install only the peers for the subpaths you import:

Subpath Styling/runtime Required peers
@geenius/adapters Framework-agnostic contracts None
@geenius/adapters/storage Object/blob storage contract None
@geenius/adapters/cache Key/value cache contract None
@geenius/adapters/queue Job queue and scheduled-work contract None
@geenius/adapters/events Pub/sub and webhook contract None
@geenius/adapters/auth Session and identity-provider contract None
@geenius/adapters/payments Payment-provider contract None
@geenius/adapters/deploy Deployment-platform contract None
@geenius/adapters/react React, Tailwind CSS v4 react, react-dom
@geenius/adapters/react-css React, Vanilla CSS react, react-dom
@geenius/adapters/react-shadcn React, shadcn/Radix-compatible bridge react, react-dom
@geenius/adapters/react-ant React, Ant Design bridge react, react-dom, antd, @ant-design/icons
@geenius/adapters/react-chakra React, Chakra-compatible bridge react, react-dom
@geenius/adapters/react-mui React, MUI-compatible bridge react, react-dom
@geenius/adapters/react-mantine React, Mantine-compatible bridge react, react-dom
@geenius/adapters/react-heroui React, HeroUI-compatible bridge react, react-dom
@geenius/adapters/react-daisyui React, DaisyUI-compatible bridge react, react-dom
@geenius/adapters/react-native React Native bridge react, react-native
@geenius/adapters/solidjs SolidJS, Tailwind CSS v4 solid-js
@geenius/adapters/solidjs-css SolidJS, Vanilla CSS solid-js
@geenius/adapters/solidjs-ark SolidJS, Ark-compatible bridge solid-js
@geenius/adapters/solidjs-kobalte SolidJS, Kobalte-compatible bridge solid-js
@geenius/adapters/solidjs-solidui SolidJS, SolidUI-compatible bridge solid-js
@geenius/adapters/convex Convex DB binding helpers convex plus the downstream @geenius/db Convex adapter you bind
@geenius/adapters/neon Neon DB binding helpers The downstream @geenius/db Neon adapter you bind
@geenius/adapters/cloudflareKV Cloudflare Workers KV binding helpers A Workers KV namespace
@geenius/adapters/memory In-process DB adapter None

Quick Start

Use domain subpaths when a consumer only needs a contract:

import type {
  AuthAdapter,
  AuthProviderAdapter,
} from '@geenius/adapters/auth'
import type { CacheAdapter } from '@geenius/adapters/cache'
import type { DeployAdapter } from '@geenius/adapters/deploy'
import type {
  EventEnvelope,
  EventPublishInput,
  EventsAdapter,
} from '@geenius/adapters/events'
import type { PaymentsProviderAdapter } from '@geenius/adapters/payments'
import type { QueueAdapter } from '@geenius/adapters/queue'
import type { AdapterHealth, StorageAdapter } from '@geenius/adapters/storage'
import {
  AuthContractError,
  AuthIdentitySchema,
} from '@geenius/adapters/auth'
import {
  CacheEntrySchema,
  CacheContractError,
  CacheSetOptionsSchema,
} from '@geenius/adapters/cache'
import {
  DeployContractError,
  DeploymentResultSchema,
  DeploymentTargetSchema,
} from '@geenius/adapters/deploy'
import {
  AdapterHealthSchema as EventsHealthSchema,
  EventEnvelopeSchema,
  EventPublishInputSchema,
  EventSubscribeInputSchema,
  EventsContractError,
  WebhookDeliverySchema,
  WebhookEmitInputSchema,
} from '@geenius/adapters/events'
import {
  PaymentCheckoutInputSchema,
  PaymentCheckoutSessionSchema,
  PaymentPlanContractSchema,
  PaymentSubscriptionContractSchema,
  PaymentsContractError,
} from '@geenius/adapters/payments'
import {
  QueueContractError,
  QueueJobInputSchema,
} from '@geenius/adapters/queue'
import {
  StorageContractError,
  StorageError,
  StorageObjectSchema,
  StorageWriteInputSchema,
} from '@geenius/adapters/storage'

export interface AppAdapters {
  auth: AuthAdapter
  authProvider: AuthProviderAdapter
  cache: CacheAdapter
  deploy: DeployAdapter
  events: EventsAdapter
  paymentsProvider: PaymentsProviderAdapter
  queue: QueueAdapter
  storage: StorageAdapter
}

export function parseCacheEntry(input: unknown) {
  const result = CacheEntrySchema.safeParse(input)

  if (!result.success) {
    throw new CacheContractError('Invalid cache entry', result.error)
  }

  return result.data
}

export async function setCachedValue(
  cache: CacheAdapter,
  key: string,
  value: unknown,
  ttlSeconds: number,
) {
  const result = CacheSetOptionsSchema.safeParse({ ttlSeconds })

  if (!result.success) {
    throw new CacheContractError('Invalid cache set options', result.error)
  }

  await cache.set(key, value, result.data)
}

export async function deployValidatedTarget(
  deploy: DeployAdapter,
  input: unknown,
) {
  const result = DeploymentTargetSchema.safeParse(input)

  if (!result.success) {
    throw new DeployContractError('Invalid deployment target', result.error)
  }

  const deployment = await deploy.deploy(result.data)
  const deploymentResult = DeploymentResultSchema.safeParse(deployment)

  if (!deploymentResult.success) {
    throw new DeployContractError(
      'Invalid deployment result',
      deploymentResult.error,
    )
  }

  return deploymentResult.data
}

export async function putValidatedObject(
  storage: StorageAdapter,
  input: unknown,
) {
  const result = StorageWriteInputSchema.safeParse(input)

  if (!result.success) {
    throw new StorageContractError('Invalid storage write input', result.error)
  }

  const object = await storage.putObject(result.data)
  return StorageObjectSchema.parse(object)
}

export async function listValidatedObjects(storage: StorageAdapter) {
  const objects = await storage.listObjects({ prefix: 'uploads/', limit: 25 })
  return objects.map((object) => StorageObjectSchema.parse(object))
}

export async function requireStorageObject(
  storage: StorageAdapter,
  key: string,
) {
  const object = await storage.getObject(key)

  if (!object) {
    throw new StorageContractError(`Missing storage object: ${key}`)
  }

  return StorageObjectSchema.parse(object)
}

export async function deleteStoredObject(storage: StorageAdapter, key: string) {
  await storage.deleteObject(key)
}

export async function requireStorageHealth(
  storage: StorageAdapter,
): Promise<AdapterHealth> {
  const health = await storage.getHealth()

  if (!health.ok) {
    throw new StorageError(
      'Storage provider health check failed',
      'SERVICE_ERROR',
    )
  }

  return health
}

export async function listValidatedPlans(payments: PaymentsProviderAdapter) {
  const plans = await payments.listPlans()

  return plans.map((plan) => {
    const result = PaymentPlanContractSchema.safeParse(plan)

    if (!result.success) {
      throw new PaymentsContractError('Invalid payment plan', result.error)
    }

    return result.data
  })
}

export async function createValidatedCheckout(
  payments: PaymentsProviderAdapter,
  input: unknown,
) {
  const inputResult = PaymentCheckoutInputSchema.safeParse(input)

  if (!inputResult.success) {
    throw new PaymentsContractError('Invalid checkout input', inputResult.error)
  }

  const session = await payments.createCheckout(inputResult.data)
  const result = PaymentCheckoutSessionSchema.safeParse(session)

  if (!result.success) {
    throw new PaymentsContractError('Invalid checkout session', result.error)
  }

  return result.data
}

export async function getValidatedSubscription(
  payments: PaymentsProviderAdapter,
  identityId: string,
) {
  const subscription = await payments.getSubscription(identityId)

  if (!subscription) {
    return null
  }

  const result = PaymentSubscriptionContractSchema.safeParse(subscription)

  if (!result.success) {
    throw new PaymentsContractError('Invalid subscription', result.error)
  }

  return result.data
}

export async function cancelValidatedSubscription(
  payments: PaymentsProviderAdapter,
  identityId: string,
) {
  await payments.cancelSubscription(identityId)
  return getValidatedSubscription(payments, identityId)
}

export function requireFeatureEntitlement(
  payments: PaymentsProviderAdapter,
  identityId: string,
  feature: string,
) {
  return payments.isFeatureEnabled(identityId, feature)
}

export async function submitQueueJob(
  queue: QueueAdapter,
  input: unknown,
  runAt?: string,
) {
  const result = QueueJobInputSchema.safeParse(input)

  if (!result.success) {
    throw new QueueContractError('Invalid queue job input', result.error)
  }

  return runAt
    ? queue.schedule({ ...result.data, runAt })
    : queue.enqueue(result.data)
}

export async function publishValidatedEvent(
  events: EventsAdapter,
  input: unknown,
) {
  const result = EventPublishInputSchema.safeParse(input)

  if (!result.success) {
    throw new EventsContractError('Invalid event publish input', result.error)
  }

  const event = await events.publish(result.data)
  return EventEnvelopeSchema.parse(event)
}

export async function emitValidatedWebhook(
  events: EventsAdapter,
  input: EventPublishInput,
  url: string,
) {
  const event = await publishValidatedEvent(events, input)
  const webhookInput = WebhookEmitInputSchema.parse({ url, event })
  const delivery = await events.emitWebhook(webhookInput)

  return WebhookDeliverySchema.parse(delivery)
}

export async function subscribeToValidatedEvents(
  events: EventsAdapter,
  topic: string,
  handler: (event: EventEnvelope) => void | Promise<void>,
) {
  const subscribeInput = EventSubscribeInputSchema.parse({ topic })
  const subscription = await events.subscribe(subscribeInput, (event) => {
    return handler(EventEnvelopeSchema.parse(event))
  })

  return {
    unsubscribe: () => subscription.unsubscribe(),
  }
}

export async function listValidatedEvents(
  events: EventsAdapter,
  prefix?: string,
) {
  const envelopes = await events.listEvents({ prefix, limit: 25 })
  return envelopes.map((event) => EventEnvelopeSchema.parse(event))
}

export async function requireEventsHealth(events: EventsAdapter) {
  const health = EventsHealthSchema.parse(await events.getHealth())

  if (!health.ok) {
    throw new EventsContractError(health.message ?? 'Events adapter unhealthy')
  }

  return health
}

export async function requireIdentity(
  auth: AuthProviderAdapter,
  identityId: string,
) {
  const identity = await auth.getIdentity(identityId)

  if (!identity) {
    throw new AuthContractError(`Unknown identity: ${identityId}`)
  }

  return AuthIdentitySchema.parse(identity)
}

Create the shared adapter set once, then pass it to the framework provider.

import {
  configureAdapters,
  createConsoleLoggerAdapter,
  createLocalStorageAuthAdapter,
  createLocalStorageDbAdapter,
} from '@geenius/adapters'

configureAdapters({
  db: { provider: 'localStorage' },
  auth: { provider: 'localStorage' },
  logger: { provider: 'console' },
})

export const adapters = {
  db: createLocalStorageDbAdapter(),
  auth: createLocalStorageAuthAdapter(),
  logger: createConsoleLoggerAdapter(),
}

React:

import {
  AdapterList,
  AdapterProvider,
  useAdapterStatuses,
  useDb,
} from '@geenius/adapters/react'
import { adapters } from './adapters'

function Dashboard() {
  const db = useDb()
  const statuses = useAdapterStatuses()

  void db.list('users')

  return (
    <>
      <p>Database adapter: {statuses.db.status}</p>
      <AdapterList />
    </>
  )
}

export function App() {
  return (
    <AdapterProvider adapters={adapters} healthCheck>
      <Dashboard />
    </AdapterProvider>
  )
}

SolidJS:

import {
  AdapterProvider,
  createAuthAdapter,
  createDb,
} from '@geenius/adapters/solidjs'
import { adapters } from './adapters'

function Dashboard() {
  const auth = createAuthAdapter()
  const db = createDb()

  void auth.getSession()
  void db.list('users')

  return null
}

export function App() {
  return (
    <AdapterProvider adapters={adapters} healthCheck>
      <Dashboard />
    </AdapterProvider>
  )
}

For Vanilla CSS variants, import the stylesheet next to the component subpath:

import { AdapterList } from '@geenius/adapters/react-css'
import '@geenius/adapters/react-css/styles.css'

Configuration Patterns

@geenius/adapters exposes configureAdapters() rather than an exported config builder. Keep app-level presets as plain objects and validate them through the package API:

import type { AdapterConfig } from '@geenius/adapters'
import { configureAdapters, LAUNCH_DB_PROVIDERS } from '@geenius/adapters'

const simple: AdapterConfig = {
  db: { provider: 'localStorage' },
  auth: { provider: 'localStorage' },
  logger: { provider: 'console' },
}

const production: AdapterConfig = {
  db: { provider: LAUNCH_DB_PROVIDERS[0] },
  auth: { provider: 'better-auth' },
  payments: { provider: 'stripe' },
  ai: { provider: 'openai' },
  storage: { provider: 'r2' },
  logger: { provider: 'console' },
}

const isDevelopment = true

configureAdapters(isDevelopment ? simple : production)

Use registerResolver(domain, provider, factory) when the concrete provider factory lives in a downstream package such as @geenius/db, @geenius/auth, @geenius/payments, @geenius/ai, or @geenius/file-storage.

Provider Registry

@geenius/adapters owns two related contract surfaces:

  • Infrastructure domain contracts: storage, cache, queue, events, auth, payments, and deploy.
  • Launch DB backend subpaths: convex, neon, cloudflareKV, and memory.

The older root registry for db, auth, payments, ai, storage, admin, and logger remains exported for UI-provider compatibility. New persistence runtime implementations belong in @geenius/db; new concrete domain provider SDKs belong in their owning domain package.

The DB provider registry exports current launch ids and future metadata from constants.ts:

Scope Providers
Launch convex, neon, cloudflareKV, memory
Future metadata supabase, mongodb
Platform-native metadata launch convex, launch neon, future supabase, future mongodb
Edge KV metadata launch cloudflareKV
In-process metadata launch memory
BYOD ORM metadata drizzle, prisma, kysely

Use LAUNCH_DB_PROVIDERS, FUTURE_DB_PROVIDERS, ECOSYSTEM_DB_PROVIDERS, PLATFORM_NATIVE_DB_PROVIDERS, BYOD_ORM_DB_PROVIDERS, getEcosystemDbProviderMeta(), getEcosystemDbProvidersByCategory(), and getEcosystemDbProvidersByStage() for setup UIs and scaffold tooling.

Only the launch row maps to v1 public provider subpaths. Future and BYOD ORM entries are root metadata/resolver ids for setup UIs and downstream wiring; a coordinated ecosystem-axis update is still required before any @geenius/adapters/<provider> import path exists for those ids.

The public Cloudflare KV subpath and provider id are @geenius/adapters/cloudflareKV and cloudflareKV.

neon is reserved for the native Neon/serverless PostgreSQL adapter contract. Downstream apps that use Drizzle should bind it behind their Neon adapter module; drizzle is metadata only and is not a current @geenius/adapters DB provider subpath.

Errors

Adapter failures are surfaced as typed error classes:

  • AdapterError
  • DbError
  • AuthError
  • AiError
  • StorageError
  • PaymentsError
  • AdminError
  • AdapterConfigurationError
  • AdapterResolutionError
  • AdapterContextError
  • AdapterInvariantError

Consumers should catch these exported classes instead of vendor-specific exceptions. Domain subpaths also expose SDK-free contract errors for validation boundaries: PaymentsContractError from @geenius/adapters/payments represents invalid payments contract payloads before a concrete provider runs, while PaymentsError represents root/local runtime payment adapter failures. DeployContractError from @geenius/adapters/deploy has the same role for deployment target and deployment result validation.

import {
  AdapterConfigurationError,
  AdapterResolutionError,
  configureAdapters,
  isAdapterError,
  withRetry,
} from '@geenius/adapters'

const db = {
  list: async (_collection: string) => [],
}

function reportConfigurationProblem(message: string) {
  console.warn(message)
}

function reportAdapterProblem(domain: string, code: string) {
  console.warn(`${domain}:${code}`)
}

try {
  await withRetry(() => db.list('users'), { maxAttempts: 3 })
} catch (error) {
  if (error instanceof AdapterResolutionError) {
    configureAdapters({ db: { provider: 'localStorage' } })
  } else if (error instanceof AdapterConfigurationError) {
    reportConfigurationProblem(error.message)
  } else if (isAdapterError(error)) {
    reportAdapterProblem(error.domain, error.code)
  } else {
    throw error
  }
}

Convex, Neon, Cloudflare KV, And Memory

The Convex and Neon subpaths do not import provider SDKs. They validate and bind DbAdapter instances produced downstream, usually by @geenius/db, and expose the same SDK-free domain contract exports as the other backend subpaths. Cloudflare KV stays limited to a Workers KV namespace contract. Memory is the local/dev implementation surface and also implements every new domain contract with localStorage-compatible mocks.

import type { DbAdapter } from '@geenius/adapters'
import { configureAdapters } from '@geenius/adapters'
import { createCloudflareKvDbAdapter } from '@geenius/adapters/cloudflareKV'
import { createMemoryDbAdapter } from '@geenius/adapters/memory'
import { type ConvexProviderContract, withConvexDb } from '@geenius/adapters/convex'
import { type CreateNeonDbAdapterOptions, withNeonDb } from '@geenius/adapters/neon'

interface Env {
  ADAPTERS_KV: KVNamespace
}

declare function createConvexDbAdapter(options: unknown): DbAdapter
declare const neonAdapterFromGeeniusDb: DbAdapter
declare const neonFactoryOptions: CreateNeonDbAdapterOptions
declare const env: Env

const convexOptions = {}

const convexContract: ConvexProviderContract = {
  provider: 'convex',
  stage: 'launch',
  sdk: 'convex',
  schema: 'ConvexDB schema',
  factory: 'createConvexDbAdapter',
  tables: ['users', 'sessions', 'audit_events'],
}
const convexDb = withConvexDb({
  adapter: createConvexDbAdapter(convexOptions),
  provider: 'convex',
  contract: convexContract,
})
const neonDb = withNeonDb({
  adapter: neonAdapterFromGeeniusDb,
  provider: 'neon',
  source: { factoryOptions: neonFactoryOptions },
})
const memoryDb = createMemoryDbAdapter({ environment: 'development' })
const edgeCacheDb = createCloudflareKvDbAdapter({ namespace: env.ADAPTERS_KV })

configureAdapters({
  db: { provider: 'convex' },
})

Storybook Review Apps

The repository retains one review app for each legacy UI variant. For the current contract-layer mission, variants.json marks the five launch review surfaces as in scope: react, react-css, solidjs, solidjs-css, and react-native. The root Storybook build/test scripts intentionally enumerate every retained review app with a storybook entry so backward-compatible UI subpaths keep review coverage, even when their package source has inScope: false.

pnpm run test:storybook:build
pnpm run test:storybook

Contributing Tests

Variant and provider coverage is driven from variants.json. When adding a new launch variant, add its metadata there first, then add the matching packages/<variant>/ source, Storybook app, e2e harness coverage, and bundle budget. Scripts and tests that enumerate variants should import from @geenius/release-toolkit (packageVariants, uiVariants, dbProviderVariants, …); pnpm fan-out uses geenius-release pnpm-filters <task> [--sequential …].

Use these package-level gates before opening a PR:

pnpm test:gauntlet
pnpm test:all
pnpm test:visual --update-snapshots

test:gauntlet is the PR-blocking suite for lint, public package checks, type-check, unit/root contract tests, bundle budgets, supply-chain audit, and license validation. test:all adds Storybook, DB conformance/migrations, Playwright e2e, accessibility, visual regression, performance smoke, and coverage aggregation. Mutation testing runs separately with pnpm test:mutation.

License

FSL-1.1-Apache-2.0 (free-tier) / Proprietary (commercial)