interface User { id: string; email: string; name?: string; picture?: string; [key: string]: unknown; } interface FileMetadata { id: string; name: string; type: string; size: number; url: string; folder?: string; metadata?: Record; createdAt: string; } interface EntityChangeEvent { /** 'created' | 'modified' | 'deleted' (wire types entity.created/modified/deleted) */ changeType: string; entityId?: string; /** Entity resource name (e.g. 'companies') */ resource?: string; id?: string; namespace?: string; timestamp?: number; } interface AuthProviderContext { /** API base URL for backend endpoints */ apiBaseUrl: string; /** Account service base URL */ accountBaseUrl: string; /** App ID for request headers */ appId: string; /** Tenant ID for request headers */ tenantId: string; /** App version for request headers */ version: string; } type AuthProvider = (config: Record, ctx: AuthProviderContext) => Promise; interface FoundationConfig { /** Backend config endpoint URL. If omitted, reads from /foundation-env.json */ configUrl?: string; tenantId?: string; appId?: string; /** Override API base URL (e.g. '/api' when using a dev proxy) */ baseUrl?: string; /** Auth client instance, or provider factory from foundation-sdk/cognito or foundation-sdk/auth0 */ auth?: AuthClient | AuthProvider; /** * Sign the user in automatically when `confirmSignUp` succeeds, reusing the password * they gave `signUp` (held in memory only, for 15 minutes, never written to storage). * Default true. Set false to require an explicit sign-in after confirmation. */ autoSignInAfterConfirm?: boolean; /** * App-level default redirect URLs for billing flows, sent with every * `billing.checkout()` / `billing.portal()` call unless overridden per call. * When omitted the SDK sends nothing and the backend applies its convention * (`{origin}/callback/stripe`, `{origin}/plan`, `{origin}/settings/profile`) — * the server stays the single source of truth for defaults. */ billing?: BillingDefaults; } interface FullConfig { readonly app: { id: string; name: string; version: string; environment: string; [key: string]: unknown; }; readonly features: Record; /** * Static plan definitions from the app config. Empty until authenticated — * the public bootstrap omits plans; they arrive with the authed * `/api/v1/config/init` merge. These carry Foundation plan ids and feature * limits, not Stripe prices: for purchase flows use `billing.plans()`. */ readonly plans: ConfigPlan[]; readonly theme: { colors: Record; dark: Record; defaultColorScheme?: string; }; readonly connectors: Record; readonly resources: Record; readonly auth: { provider: string; [key: string]: unknown; }; readonly raw: Record; } type SignInStep = 'DONE' | 'CONFIRM_SIGN_UP' | 'CONFIRM_SIGN_IN_WITH_SMS_CODE' | 'CONFIRM_SIGN_IN_WITH_TOTP_CODE' | 'CONTINUE_SIGN_IN_WITH_MFA_SELECTION' | 'RESET_PASSWORD' | 'CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED' | string; interface SignInResult { isSignedIn: boolean; nextStep?: { signInStep: SignInStep; [key: string]: unknown; }; } type SignUpStep = 'DONE' | 'CONFIRM_SIGN_UP' | string; interface SignUpResult { isSignUpComplete: boolean; userId?: string; nextStep?: { signUpStep: SignUpStep; [key: string]: unknown; }; } interface AuthClient { login(options?: Record): Promise; logout(options?: Record): void | Promise; getUser(): Promise; getTokenSilently(options?: Record): Promise; isAuthenticated(): Promise; handleCallback?(url?: string): Promise; signIn?(email: string, password: string): Promise; signUp?(email: string, password: string, metadata?: Record): Promise; confirmSignUp?(email: string, code: string): Promise; resendSignUpCode?(email: string): Promise; forgotPassword?(email: string): Promise; resetPassword?(email: string, code: string, newPassword: string): Promise; } interface AuthService { readonly user: User | null; readonly isAuthenticated: boolean; getToken(): Promise; /** Redirect-based login (Auth0 Universal Login / Cognito Hosted UI) */ login(options?: Record): Promise; logout(options?: Record): Promise; /** Direct sign in with credentials (for custom login forms). * Returns `{ isSignedIn, nextStep? }`. Check `isSignedIn` before navigating. */ signIn(email: string, password: string): Promise; /** Direct sign up (for custom registration forms). * Returns `{ isSignUpComplete, nextStep? }`. If configured for unified verification, * `isSignUpComplete` is true and the backend sends a verification email. */ signUp(email: string, password: string, metadata?: Record): Promise; /** Confirm sign up with verification code (Cognito email/SMS verification). * Returns `{ isSignedIn, nextStep? }`: when the password from `signUp` is still held * in memory the SDK signs the user in for you, so `isSignedIn: true` means the session * is ready and there is no need to send them to a login form. `isSignedIn: false` means * confirmation succeeded but the session did not — show sign-in (`nextStep` says why, * e.g. MFA). Disable with `autoSignInAfterConfirm: false`. */ confirmSignUp(email: string, code: string): Promise; /** Resend sign up verification code */ resendSignUpCode(email: string): Promise; /** Handle OAuth redirect callback — call on your /callback route */ handleCallback(url?: string): Promise; /** Initiate password reset */ forgotPassword(email: string): Promise; /** Complete password reset with the email/username used to request the code */ resetPassword(email: string, code: string, newPassword: string): Promise; onChange(callback: (user: User | null) => void): () => void; } interface DbService { list(entity: string, options?: { /** * Named list template to run (from the entity's `methods.list.templates`). * Serialized on the wire as the reserved `query` query-string param * (`GET /api/v1/core/{entity}?query={template}&...`); pass the template's * key fields via `filters`. If `filters` also contains a `query` key, * this option wins and the filter value is overwritten. */ template?: string; filters?: Record; limit?: number; cursor?: string; orderBy?: string; orderDir?: 'asc' | 'desc'; }): Promise<{ items: T[]; nextCursor?: string; }>; get(entity: string, id: string): Promise; create(entity: string, data: Partial): Promise; update(entity: string, id: string, updates: Partial): Promise; save(entity: string, data: Partial): Promise; delete(entity: string, id: string): Promise; } interface FilesService { initiate(options: { name: string; contentType: string; contentLength: number; metadata?: Record; /** * Base64-encoded (not hex) SHA-256 digest of the file contents. * When provided, S3 verifies the uploaded bytes against it. */ sha256?: string; }): Promise<{ id: string; name: string; signedUrl: string; signedData: Record; }>; upload(options: { name: string; contentType: string; file: ArrayBuffer | Blob | File; /** * Base64-encoded (not hex) SHA-256 digest of the file contents. * Computed automatically from `file` when omitted. */ sha256?: string; }): Promise<{ id: string; name: string; status: string; s3UploadComplete: boolean; }>; get(fileId: string): Promise; delete(fileId: string): Promise; list(options?: { limit?: number; cursor?: string; }): Promise<{ items: FileMetadata[]; nextCursor?: string; }>; } /** * A plan as defined in the app's static configuration. `id` here is the * Foundation plan id — the id space `account.planStatus` tracks and * `BillingPlan.metadata.planId` carries. It is NOT a Stripe price id; * `billing.checkout()` takes `BillingPlan.id` from `billing.plans()`. */ interface ConfigPlan { /** Foundation plan id (e.g. 'free', 'pro'). */ id: string; name: string; description: string; /** Stripe price lookup keys; null/absent for plans without a Stripe price. */ stripe?: { monthlyLookupKey: string; yearlyLookupKey: string; } | null; /** Feature flags and limits keyed by feature id. */ features: Record; trial?: { durationDays: number; expiringThreshold: number; requiresCreditCard: boolean; postTrialBehavior: 'hard-block' | 'free-tier'; }; /** Display prices from config (whole units, not cents) — informational only. */ pricing?: { monthly: number; yearly: number; currency: string; }; displayOrder?: number; [key: string]: unknown; } interface BillingPlan { /** Stripe price id ("price_…") — the value `billing.checkout()` takes. */ id: string; name: string; description: string; features: string[]; /** Unit amount in the smallest currency unit (e.g. cents). */ amount: number; /** Stripe product id. */ product: string; frequency: 'annual' | 'month' | string; order: number; position?: string; lookupKey: string | null; /** * Stripe price metadata. `metadata.planId` is the Foundation plan id that * `account.planStatus` tracks — a different id space from `BillingPlan.id`, * and NOT what `checkout()` takes. */ metadata: Record; productMetadata: Record; /** True for the account's current plan (`disabledReason: 'Current Plan'`). */ disabled: boolean; disabledReason?: string; checkoutLink?: string; } interface CheckoutSessionStatus { /** Stripe customer id the session belongs to. */ customer: string | null; /** Stripe payment status: 'paid' | 'unpaid' | 'no_payment_required'. */ payment_status: string; /** Stripe session status: 'complete' | 'open' | 'expired'. */ status: string | null; /** Total in the smallest currency unit (e.g. cents). */ amount_total: number | null; plan_name?: string; plan_description?: string; /** Present when the checkout was launched for a specific subject entity. */ subjectId?: string; } interface AccountSubscriptionSubject { /** The subject row id (e.g. a site id). */ id: string; /** The declaring entity; '' when no row was found. */ entityId: string; /** Display name of the subject row; '' when undeclared or unset. */ name: string; } interface AccountSubscription { /** Stripe subscription id ("sub_…"). */ id: string; /** Stripe subscription status, verbatim (e.g. 'active', 'past_due'). */ status: string; /** Default payment method summary ({ type, brand?, last4? }); {} when none. */ paymentMethod: Record; /** ISO date the subscription is scheduled to cancel; '' when none. */ cancelAt: string; cancelAtPeriodEnd: boolean; /** ISO renewal date; '' when unknown. */ currentPeriodEnd: string; /** The account's elected primary subscription. */ primary: boolean; /** Stripe's per-subscription item cap; free slots are `capacity - itemCount`. */ capacity: number; itemCount: number; subjectCount: number; subjects: AccountSubscriptionSubject[]; } interface BillingDefaults { /** * Where Stripe sends the user after a completed checkout. Must be an absolute * http(s) URL. Also used as the portal return URL when checkout turns into a * plan-change flow for an already-subscribed account. */ successUrl?: string; /** Where Stripe sends the user if they abandon checkout. Must be an absolute http(s) URL. */ cancelUrl?: string; /** Where the Stripe customer portal returns the user. Must be an absolute http(s) URL. */ returnUrl?: string; } interface BillingService { /** * List purchasable plans. Any authenticated user. The account's current plan * comes back `disabled: true, disabledReason: 'Current Plan'`. Pass a plan's * `id` (Stripe price id) to `checkout()`. */ plans(): Promise; /** * Start a subscription checkout for a plan (price id). If the account already * has a plan, the backend returns a customer-portal plan-change flow instead. * Redirect the user to the returned `url`. URL resolution: per-call option → * `billing` config default → backend convention. Account owner only — * other members get a 403 with `code: 'OWNER_REQUIRED'`. * * The backend appends `checkoutSessionId={CHECKOUT_SESSION_ID}` to a custom * successUrl unless you already placed the `{CHECKOUT_SESSION_ID}` placeholder. */ checkout(planId: string, options?: { successUrl?: string; cancelUrl?: string; }): Promise<{ url: string; }>; /** * Open the Stripe customer portal. Redirect the user to the returned `url`. * Account owner only — other members get a 403 with `code: 'OWNER_REQUIRED'`. */ portal(options?: { returnUrl?: string; flow?: 'subscription_update' | 'subscription_update_confirm'; /** Required when `flow` is 'subscription_update_confirm'. */ targetPriceId?: string; }): Promise<{ url: string; }>; /** * Resolve a checkout session after the success redirect (the success URL * carries `checkoutSessionId`). The redirect can beat the Stripe webhook, so * poll until `status === 'complete' && payment_status === 'paid'` before * treating the plan as active. Errors (including a session that belongs to a * different account) surface as a 500 with a message, not a clean error code. */ lookupSession(sessionId: string): Promise; /** * The account's subscriptions (status, card summary, renewal date, subjects). * Account owner only — other members get a 403 with `code: 'OWNER_REQUIRED'`. */ subscriptions(): Promise; } interface AccountService { get>(): Promise<{ user: TUser; account: TAccount; }>; update(data: Record): Promise; usage(): Promise>; resendVerification(): Promise; } interface Integration { id: string; name: string; description?: string; icon?: string; authType?: string; [key: string]: unknown; } interface IntegrationConnection { id: string; source: string; connected: boolean; [key: string]: unknown; } interface IntegrationDetail extends Integration { connections: IntegrationConnection[]; connected: boolean; connectionCount: number; } interface IntegrationService { list(): Promise; connections(): Promise; all(): Promise; status(source: string): Promise<{ connected: boolean; connections: IntegrationConnection[]; }>; connect(source: string): Promise<{ success: boolean; configuration?: unknown; message?: string; }>; disconnect(source: string, configurationId: string): Promise; } interface OAuthClient { id: string; name: string; description?: string; redirectUris: string[]; allowedScopes: string[]; grantTypes: string[]; status: string; [key: string]: unknown; } interface OAuthConsentParams { client_id: string; redirect_uri: string; scope: string; state: string; code_challenge: string; code_challenge_method: string; } interface OAuthService { /** Get an OAuth client's details (for consent screen) */ getClient(clientId: string): Promise; /** Grant consent and generate authorization code */ authorizeConsent(params: OAuthConsentParams): Promise<{ code: string; }>; } interface OpenApiService { get(): Promise>; } interface LogService { info(message: string, context?: Record): void; warn(message: string, context?: Record): void; error(message: string, context?: Record): void; event(event: string, data?: Record): void; } interface Foundation { readonly ready: Promise; readonly isReady: boolean; auth: AuthService; db: DbService; files: FilesService; integration: IntegrationService; account: AccountService; billing: BillingService; config: FullConfig; oauth: OAuthService; openapi: OpenApiService; log: LogService; on(event: string, callback: (event: EntityChangeEvent) => void): () => void; } export type { AuthProvider as A, BillingDefaults as B, CheckoutSessionStatus as C, DbService as D, EntityChangeEvent as E, FoundationConfig as F, IntegrationService as I, LogService as L, OAuthService as O, SignInResult as S, User as U, Foundation as a, FullConfig as b, FileMetadata as c, AuthClient as d, AuthService as e, SignInStep as f, SignUpResult as g, SignUpStep as h, FilesService as i, AccountService as j, BillingService as k, BillingPlan as l, AccountSubscription as m, AccountSubscriptionSubject as n, ConfigPlan as o, Integration as p, IntegrationConnection as q, IntegrationDetail as r, OAuthClient as s, OAuthConsentParams as t, OpenApiService as u, AuthProviderContext as v };