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/adaptersInstall 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, anddeploy. - Launch DB backend subpaths:
convex,neon,cloudflareKV, andmemory.
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:
AdapterErrorDbErrorAuthErrorAiErrorStorageErrorPaymentsErrorAdminErrorAdapterConfigurationErrorAdapterResolutionErrorAdapterContextErrorAdapterInvariantError
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.
- React review app
- React CSS review app
- React shadcn/Radix review app
- React Ant Design review app
- React Chakra review app
- React MUI review app
- React Mantine review app
- React HeroUI review app
- React DaisyUI review app
- React Native review app
- SolidJS review app
- SolidJS CSS review app
- SolidJS Ark review app
- SolidJS Kobalte review app
- SolidJS SolidUI review app
pnpm run test:storybook:build
pnpm run test:storybookContributing 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-snapshotstest: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)