@jez.luka/iver-sdk
v0.1.9
Published
iVer SDK - Identity verification SDK for showcasing real world SDK architecture and integration.
Readme
IVer SDK
Identity verification SDK built with StencilJS for framework-agnostic web component integration.
Why StencilJS?
The goal was framework-agnostic components as close to vanilla JS as possible. While Svelte 5's minimal build size was attractive, it's designed for apps - using it would require wrapper containers and custom constructors around Svelte's rendering functions.
Web Components provide native browser support with excellent stability. After evaluating Lit and StencilJS, I chose StencilJS for superior DX and faster implementation within project constraints.
Key advantages:
- Tree shaking support: StencilJS generates optimized, individually importable component definitions. Consumers only bundle what they use.
- TypeScript-first: Native TypeScript support with auto-generated type definitions
(components.d.ts) - Build optimizations: Lazy loading, code splitting, and minimal runtime overhead
(~5KB per component) - Framework agnostic: with minimal setup, works seamlessly with React
Architecture
Challenge: Enable getIdentityData() to aggregate data from all 3 components without tight coupling.
Solution: Controller pattern with component registry instead of shared store. IVerComponentController
- Each component registers on init, providing its ID and
getData()reference - Controller instance passed from outside, giving consumers manual lifecycle control (inspired by React Hook Form)
- Handles multi-step forms where components mount/unmount frequently
- Components auto-unregister on unmount, but manual control available to preserve data flow across form steps
Shadow DOM vs Light DOM Decision
Why Light DOM?
This SDK uses Light DOM (shadow: false) rather than Shadow DOM encapsulation. This was a deliberate architectural choice: Rationale:
- Styling flexibility: Host applications can apply their design system (Tailwind, CSS modules, styled-components) directly to SDK components without piercing shadow boundaries
- Global styles integration: Components inherit CSS custom properties and theming variables from the parent application
- CSS framework compatibility: Works seamlessly with utility-first frameworks like Tailwind without requiring custom element targeting
- Simpler integration: No need for ::part() selectors or CSS custom properties workarounds
Trade-offs accepted:
- No style isolation: Host styles can potentially conflict with component internals (mitigated by BEM naming and scoped classes)
- DOM structure exposure: Component markup is accessible to parent selectors (acceptable for this use case where restyling is a feature, not a bug)
Data Aggregation Strategy
The getIdentityData() method uses a probabilistic verification scoring approach rather than deterministic state management.
Design rationale:
Probabilistic (current) vs Adaptive Deterministic:
- Probabilistic: Each method invocation will calculate failure/success probability at submission time. I was thinking about this being mock for actual API. This approach doesn't have state tracking overhead.
- Adaptive Deterministic (not implemented): Would maintain real-time validation state, track failure rate and adapt to it. More complex, requires state management and thorough testing.
Why probabilistic?
- Simpler mental model: Verification happens when needed (submission), not continuously
- Lower runtime cost: No state management and runtime complex calculations
Trade-off: No actual 30/70 percent split on small sample. From 100 times, this could theoretically fail 50% of the times.
Project Structure
src/
├── components/ # SDK Components
│ ├── address-form/
│ │ ├── components/ # Sub-components
│ │ ├── test/ # Component tests
│ │ ├── types/ # TypeScript type definitions
│ │ ├── address-form.configs.ts # Constants and config factory fns
│ │ ├── address-form.css
│ │ ├── address-form.helpers.ts # Business or component specific logic
│ │ └── address-form.tsx # entry file for SDK component
│ │
│ ├── phone-input/ # Phone number input component
│ │
│ └── selfie-capture/ # Selfie capture component
│
├── configs/ # Global configuration files
│ └── countries.json # Country data
│
├── global/ # Global styles and utilities
│ └── global.css # Application-wide styles
│
├── pages/ # Page components
│ ├── address-form.html
│ ├── phone.html
│ └── selfie.html
│
└── sdk/ # SDK utilities and types
├── types/ # Shared TypeScript definitions
│ └── sdk.types.ts
├── iver-component-controller.ts
├── components.d.ts # Auto-generated component types
├── global.ts # Global SDK configuration
└── index.html # SDK demo/test pageInstallation
npm i @jez.luka/iver-sdkSetup
1. TypeScript definitions (global.d.ts)
import { IVerComponentController } from '@jez.luka/iver-sdk'
type SDKComponent = React.DetailedHTMLProps
React.HTMLAttributes<HTMLElement> & { controller: IVerComponentController },
HTMLElement
>
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'address-form': SDKComponent
'phone-input': SDKComponent
'selfie-capture': SDKComponent
}
}
}
export {}2. Register components (iver-sdk.ts)
import { defineCustomElement as defineAddressForm } from '@jez.luka/iver-sdk/address-form'
import { defineCustomElement as definePhoneInput } from '@jez.luka/iver-sdk/phone-input'
import { defineCustomElement as defineSelfieCapture } from '@jez.luka/iver-sdk/selfie-capture'
import { IVerComponentController } from '@jez.luka/iver-sdk'
defineSelfieCapture()
definePhoneInput()
defineAddressForm()
export const iVerGlobalController = new IVerComponentController('global-controller')3. Import in main.tsx
import './toolkit/iver-sdk/iver-sdk.ts'Usage
Component usage (no imports needed)
<div className="flex flex-col mx-auto min-h-screen w-full gap-5">
<selfie-capture controller={iVerGlobalController} />
<div className="flex flex-col flex-1 gap-4 max-w-[350px] mx-auto w-full">
<phone-input controller={iVerGlobalController} />
<address-form controller={iVerGlobalController} />
<Button className="mt-4" onClick={handleSubmit}>Submit</Button>
</div>
</div>Controller usage
import { iVerGlobalController } from '@/toolkit/iver-sdk/iver-sdk.ts'
const data = iVerGlobalController.getIdentityData<VerificationResult>()Development
pnpm install
pnpm start
pnpm generate // to generate new SDK component