Comprehensive guide to using i45 with TypeScript for maximum type safety.
- Getting Started
- Type Parameters
- Interface Definitions
- Type Safety Examples
- Type Inference
- Advanced Patterns
- TSConfig Recommendations
- Common TypeScript Patterns
- Troubleshooting
i45 is built with TypeScript and provides full type definitions out of the box.
npm install i45No need for @types/i45 - types are included!
import { DataContext, StorageLocations, Logger } from "i45";
// Define your data type
interface User {
id: number;
name: string;
email: string;
}
// Create type-safe context
const context = new DataContext<User>({
storageKey: "Users",
storageLocation: StorageLocations.LocalStorage,
});
// TypeScript knows the types!
await context.store([{ id: 1, name: "Alice", email: "alice@example.com" }]);
// users is typed as User[]
const users = await context.retrieve();DataContext uses a generic type parameter to provide type safety.
class DataContext<T = any> { ... }Default: any (if not specified)
interface Product {
id: string;
name: string;
price: number;
}
const context = new DataContext<Product>();const context = new DataContext(); // T = any
await context.store([1, 2, 3]); // Works, but no type safetyconst products: Product[] = [...];
const context = new DataContext<Product>();
await context.store(products); // Type-safe!
// TypeScript error - wrong type!
await context.store([{ id: 1 }]); // Error: missing 'name' and 'price'interface User {
id: number;
name: string;
email: string;
}
const context = new DataContext<User>();interface Address {
street: string;
city: string;
zipCode: string;
}
interface User {
id: number;
name: string;
address: Address;
}
const context = new DataContext<User>();
await context.store([
{
id: 1,
name: "Alice",
address: {
street: "123 Main St",
city: "Springfield",
zipCode: "12345",
},
},
]);interface User {
id: number;
name: string;
email?: string; // Optional
phone?: string; // Optional
}
const context = new DataContext<User>();
// Valid - email is optional
await context.store([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob", email: "bob@example.com" },
]);type Status = "active" | "inactive" | "pending";
interface User {
id: number;
name: string;
status: Status;
}
const context = new DataContext<User>();
await context.store([{ id: 1, name: "Alice", status: "active" }]);
// TypeScript error!
await context.store([
{ id: 2, name: "Bob", status: "invalid" }, // Error: "invalid" not in union
]);import { StorageLocations, type StorageLocation } from "i45";
interface DataItem {
id: number;
data: string;
}
// Type-safe storage location selection
function createStorageContext<T>(
key: string,
location: StorageLocation
): DataContext<T> {
return new DataContext<T>({
storageKey: key,
storageLocation: location,
});
}
// Usage with IndexedDB
const largeDataContext = createStorageContext<DataItem>(
"LargeData",
StorageLocations.IndexedDB // Type-safe!
);
// TypeScript error for invalid location
const invalid = createStorageContext<DataItem>(
"Data",
"invalidStorage" as any // Error!
);enum UserRole {
Admin = "admin",
User = "user",
Guest = "guest",
}
interface User {
id: number;
name: string;
role: UserRole;
}
const context = new DataContext<User>();
await context.store([{ id: 1, name: "Alice", role: UserRole.Admin }]);TypeScript catches errors at compile time:
interface User {
id: number;
name: string;
}
const context = new DataContext<User>();
// ✅ Valid
await context.store([{ id: 1, name: "Alice" }]);
// ❌ TypeScript Error: missing 'name'
await context.store([{ id: 1 }]);
// ❌ TypeScript Error: wrong type for 'id'
await context.store([{ id: "1", name: "Alice" }]);
// ❌ TypeScript Error: extra property
await context.store([{ id: 1, name: "Alice", extra: "value" }]);interface Product {
id: string;
name: string;
price: number;
}
const context = new DataContext<Product>();
// products is typed as Product[]
const products = await context.retrieve();
// TypeScript knows the type!
products.forEach((product) => {
console.log(product.name); // ✅ Valid
console.log(product.price); // ✅ Valid
console.log(product.invalid); // ❌ Error: property doesn't exist
});
// Type-safe operations
const total = products.reduce((sum, p) => sum + p.price, 0);TypeScript can infer types from context.
interface User {
id: number;
name: string;
}
const users: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
const context = new DataContext<User>();
await context.store(users); // Type inferred from 'users'function getUsers(): User[] {
return [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
}
const context = new DataContext<User>();
await context.store(getUsers()); // Type inferred from functionimport { StateType } from "i45-sample-data";
const context = new DataContext<StateType>();
// TypeScript knows the structure of StateTypeinterface User {
readonly id: number;
name: string;
}
const context = new DataContext<User>();
const users = await context.retrieve();
// TypeScript error - id is readonly!
users[0].id = 999; // Errorinterface User {
id: number;
name: string;
email: string;
}
// Store partial updates
type PartialUser = Partial<User> & { id: number };
const context = new DataContext<PartialUser>();
await context.store([
{ id: 1, name: "Alice" }, // email is optional
{ id: 2, email: "bob@example.com" }, // name is optional
]);interface User {
id: number;
name: string;
email: string;
password: string;
}
// Store only public fields
type PublicUser = Omit<User, "password">;
const context = new DataContext<PublicUser>();
await context.store([
{ id: 1, name: "Alice", email: "alice@example.com" },
// password is not included
]);
// Or pick specific fields
type UserSummary = Pick<User, "id" | "name">;
const summaryContext = new DataContext<UserSummary>();
await summaryContext.store([{ id: 1, name: "Alice" }]);interface HasId {
id: string | number;
}
function createContext<T extends HasId>(key: string) {
return new DataContext<T>({ storageKey: key });
}
interface User {
id: number;
name: string;
}
// ✅ Valid - User has 'id'
const userContext = createContext<User>("Users");
interface NoId {
name: string;
}
// ❌ Error - NoId doesn't have 'id'
const noIdContext = createContext<NoId>("NoId");interface User {
id: number;
name: string;
}
function isUser(obj: any): obj is User {
return typeof obj.id === "number" && typeof obj.name === "string";
}
const context = new DataContext<User>();
const data = await context.retrieve();
data.forEach((item) => {
if (isUser(item)) {
console.log(item.name); // TypeScript knows it's a User
}
});{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}strict: true- Enables all strict type checkingesModuleInterop: true- Enables ES module interopmoduleResolution: "bundler"- Modern module resolutionlib: ["DOM"]- Required for localStorage/sessionStorage types
import { DataContext, DataContextConfig, StorageLocations } from "i45";
interface User {
id: number;
name: string;
}
const config: DataContextConfig = {
storageKey: "Users",
storageLocation: StorageLocations.SessionStorage,
loggingEnabled: true,
};
const context = new DataContext<User>(config);import {
DataContext,
StorageKeyError,
StorageQuotaError,
DataRetrievalError,
} from "i45";
const context = new DataContext<User>();
try {
await context.store(users);
} catch (error) {
if (error instanceof StorageKeyError) {
console.error("Invalid key:", error.key);
} else if (error instanceof StorageQuotaError) {
console.error("Quota exceeded:", error.storageType);
} else if (error instanceof DataRetrievalError) {
console.error("Retrieval failed:", error.key);
}
}function createTypedContext<T>(
key: string,
useSession: boolean = false
): DataContext<T> {
return new DataContext<T>({
storageKey: key,
storageLocation: useSession
? StorageLocations.SessionStorage
: StorageLocations.LocalStorage,
});
}
// Usage
const userContext = createTypedContext<User>("Users");
const sessionContext = createTypedContext<Session>("Sessions", true);interface User {
id: number;
name: string;
}
async function initUserContext(): Promise<DataContext<User>> {
const context = new DataContext<User>({ storageKey: "Users" });
// Load initial data
try {
await context.retrieve();
} catch (error) {
// Initialize with empty array if no data
await context.store([]);
}
return context;
}
// Usage
const context = await initUserContext();interface User {
id: number;
name: string;
}
interface Settings {
theme: "light" | "dark";
notifications: boolean;
}
class AppStorage {
private userContext: DataContext<User>;
private settingsContext: DataContext<Settings>;
constructor() {
this.userContext = new DataContext<User>({ storageKey: "Users" });
this.settingsContext = new DataContext<Settings>({
storageKey: "Settings",
});
}
async saveUser(user: User): Promise<void> {
const users = await this.userContext.retrieve();
users.push(user);
await this.userContext.store(users);
}
async getSettings(): Promise<Settings | null> {
const settings = await this.settingsContext.retrieve();
return settings[0] || null;
}
}Solution: Ensure i45 is installed:
npm install i45Problem: Not providing a generic type parameter.
Solution: Specify the type:
// ❌ Bad
const context = new DataContext();
// ✅ Good
const context = new DataContext<User>();Problem: TypeScript doesn't know the structure.
Solution: Define an interface:
interface MyData {
id: number;
name: string;
}
const context = new DataContext<MyData>();
const data = await context.retrieve();
console.log(data[0].name); // ✅ TypeScript knows the typeProblem: Type mismatch.
Solution: Check your data structure matches the interface:
interface User {
id: number;
name: string;
}
const context = new DataContext<User>();
// ❌ Wrong type
await context.store([
{ id: "1", name: "Alice" }, // id should be number
]);
// ✅ Correct
await context.store([{ id: 1, name: "Alice" }]);Problem: Strict null checks enabled.
Solution: Handle null cases:
const users = await context.retrieve();
// ❌ May be null
console.log(users[0].name);
// ✅ Safe
if (users && users.length > 0) {
console.log(users[0].name);
}
// ✅ Using optional chaining
console.log(users?.[0]?.name);- api.md - Complete API reference
- README.md - Getting started
- examples.md - More examples
- migration.md - Migration guide