TypeScript OSDK: Type-Safe Ontology SDK for Frontend Applications
The coomia-dip TypeScript OSDK provides a fully type-safe Ontology SDK for frontend and Node.js applications. Built on code generation from the Ontology Schema, it delivers compile-time type checking, IntelliSense-driven development, and seamless React/Vue integration. The SDK communicates with the platform through a gRPC-Web gateway with automatic serialization, retry logic, and optimistic updates. This article covers the complete design from TypeScript type generation, query builder patterns, React hooks integration, real-time subscriptions, to bundle optimization.
“Series: S6 Platform Engineering · Article 15 | Level: Advanced | Reading Time: 18 min
TypeScript OSDK: Type-Safe Ontology SDK for Frontend Applications
#TL;DR
The coomia-dip TypeScript OSDK provides a fully type-safe Ontology SDK for frontend and Node.js applications. Built on code generation from the Ontology Schema, it delivers compile-time type checking, IntelliSense-driven development, and seamless React/Vue integration. The SDK communicates with the platform through a gRPC-Web gateway with automatic serialization, retry logic, and optimistic updates. This article covers the complete design from TypeScript type generation, query builder patterns, React hooks integration, real-time subscriptions, to bundle optimization.
#1. Why a TypeScript OSDK
#1.1 Frontend Developer Experience
Frontend developers need a first-class SDK that integrates naturally with their tooling:
- IntelliSense: Auto-completion for Ontology object types, properties, and actions
- Type checking: Compile-time validation of property names and types
- Framework integration: Native hooks for React, composables for Vue
- Bundle efficiency: Tree-shakeable, minimal bundle impact
#1.2 Comparison with Palantir OSDK
| Capability | Palantir OSDK | coomia-dip OSDK |
|---|---|---|
| Type generation | Full TypeScript | Full TypeScript |
| Framework support | React | React + Vue |
| Communication | REST/JSON | gRPC-Web |
| Real-time | WebSocket | gRPC streaming |
| Code gen CLI | @osdk/cli | onto-codegen |
| Bundle size | ~50KB | ~35KB |
#2. Type Generation Architecture
#2.1 Generation Pipeline
Schema Registry → onto-codegen CLI → TypeScript Source
│ │ │
Fetch schemas Generate types Compile & bundle
via gRPC via templates via tsc + rollup
#2.2 Generated Type Examples
// Auto-generated: Employee.ts
import { OntologyObject, PropertyFilter, QueryBuilder } from "@coomia-dip/osdk-core";
export interface EmployeeProperties {
readonly name: string;
readonly department: string;
readonly salary: number;
readonly hireDate: Date;
readonly email: string | null;
readonly managerId: string | null;
}
export interface Employee extends OntologyObject<EmployeeProperties> {
readonly __objectType: "Employee";
readonly name: string;
readonly department: string;
readonly salary: number;
readonly hireDate: Date;
readonly email: string | null;
readonly managerId: string | null;
// Type-safe link traversal
manager(): Promise<Employee | null>;
reports(): Promise<Employee[]>;
}
// Type-safe filters
export interface EmployeeFilters {
name: PropertyFilter<string>;
department: PropertyFilter<string>;
salary: PropertyFilter<number>;
hireDate: PropertyFilter<Date>;
email: PropertyFilter<string | null>;
}
// Type-safe query builder
export interface EmployeeQueryBuilder
extends QueryBuilder<Employee, EmployeeFilters> {
where(filter: (f: EmployeeFilters) => FilterExpression): this;
select<K extends keyof EmployeeProperties>(
...fields: K[]
): QueryBuilder<Pick<Employee, K>>;
orderBy(field: keyof EmployeeProperties, direction?: "asc" | "desc"): this;
}
#2.3 Action Type Generation
// Auto-generated: actions.ts
export interface CreateEmployeeParams {
name: string;
department: string;
salary: number;
hireDate: Date;
email?: string;
managerId?: string;
}
export interface PromoteEmployeeParams {
employeeId: string;
newTitle: string;
salaryIncrease: number;
effectiveDate: Date;
}
export interface EmployeeActions {
createEmployee(params: CreateEmployeeParams): Promise<ActionResult<Employee>>;
promoteEmployee(params: PromoteEmployeeParams): Promise<ActionResult<Employee>>;
}
#3. Core SDK Implementation
#3.1 Client Initialization
import { OntoPlatform } from "@coomia-dip/osdk";
// Type-safe client with full IntelliSense
const client = OntoPlatform.init({
baseUrl: "https://platform.example.com",
auth: {
type: "bearer",
token: "your-token-here",
},
options: {
retries: 3,
timeout: 30000,
enableCaching: true,
cacheMaxAge: 60000,
},
});
// Full type-safety
const employee = await client.objects.Employee.get("emp-001");
// employee.name -> string (IntelliSense)
// employee.salary -> number (IntelliSense)
#3.2 Query Builder
class TypeSafeQueryBuilder<T extends OntologyObject> {
private filters: FilterExpression[] = [];
private selectedFields: string[] = [];
private ordering: OrderSpec[] = [];
private pageSize: number = 100;
where(buildFilter: (f: Filters<T>) => FilterExpression): this {
const filter = buildFilter(this.filterProxy);
this.filters.push(filter);
return this;
}
select<K extends keyof T["properties"]>(...fields: K[]): QueryBuilder<Pick<T, K>> {
this.selectedFields = fields as string[];
return this as any;
}
orderBy(field: keyof T["properties"], direction: "asc" | "desc" = "asc"): this {
this.ordering.push({ field: field as string, direction });
return this;
}
limit(n: number): this {
this.pageSize = n;
return this;
}
async execute(): Promise<T[]> {
const request = this.buildRequest();
return await this.client.executeQuery(request);
}
async *stream(): AsyncGenerator<T> {
const request = this.buildRequest();
yield* this.client.streamQuery(request);
}
}
// Usage
const engineers = await client.objects.Employee
.where(f => f.department.eq("Engineering"))
.where(f => f.salary.gt(80000))
.select("name", "salary", "department")
.orderBy("salary", "desc")
.limit(50)
.execute();
#3.3 gRPC-Web Transport
class GrpcWebTransport {
private readonly channel: grpc.Channel;
private readonly interceptors: Interceptor[];
constructor(config: TransportConfig) {
this.channel = new grpc.Channel(config.baseUrl);
this.interceptors = [
new AuthInterceptor(config.auth),
new RetryInterceptor(config.retries),
new TracingInterceptor(),
];
}
async unary<Req, Res>(
method: MethodDescriptor<Req, Res>,
request: Req,
): Promise<Res> {
const pipeline = this.buildPipeline(this.interceptors);
return await pipeline.execute(this.channel, method, request);
}
stream<Req, Res>(
method: MethodDescriptor<Req, Res>,
request: Req,
): AsyncIterable<Res> {
return this.channel.serverStream(method, request);
}
}
#4. React Integration
#4.1 React Hooks
import { useOntologyObject, useOntologyQuery, useAction } from "@coomia-dip/osdk-react";
// Single object hook
function EmployeeProfile({ id }: { id: string }) {
const { data: employee, loading, error, refetch } = useOntologyObject(
client.objects.Employee,
id,
);
if (loading) return <Spinner />;
if (error) return <ErrorBanner error={error} />;
return (
<div>
<h1>{employee.name}</h1>
<p>Department: {employee.department}</p>
<p>Salary: ${employee.salary.toLocaleString()}</p>
</div>
);
}
// Query hook with filters
function EngineeringTeam() {
const { data: engineers, loading, hasMore, loadMore } = useOntologyQuery(
client.objects.Employee
.where(f => f.department.eq("Engineering"))
.orderBy("name", "asc"),
{ pageSize: 20 },
);
return (
<div>
{engineers.map(emp => (
<EmployeeCard key={emp.id} employee={emp} />
))}
{hasMore && <Button onClick={loadMore}>Load More</Button>}
</div>
);
}
// Action hook
function PromoteButton({ employeeId }: { employeeId: string }) {
const { execute, loading, error } = useAction(
client.actions.promoteEmployee,
);
const handlePromote = async () => {
await execute({
employeeId,
newTitle: "Senior Engineer",
salaryIncrease: 10000,
effectiveDate: new Date(),
});
};
return (
<Button onClick={handlePromote} loading={loading}>
Promote
</Button>
);
}
#4.2 Optimistic Updates
const { execute } = useAction(client.actions.updateEmployee, {
optimistic: true,
onOptimisticUpdate: (params, cache) => {
// Immediately update the cache before server confirms
cache.update("Employee", params.employeeId, {
salary: params.newSalary,
});
},
onError: (error, cache) => {
// Rollback on error
cache.rollback();
toast.error("Update failed: " + error.message);
},
});
#4.3 Real-Time Subscriptions
function LiveDashboard() {
const { data: employees } = useOntologySubscription(
client.objects.Employee
.where(f => f.department.eq("Engineering")),
{
onUpdate: (updated) => {
console.log("Employee updated:", updated.id);
},
onDelete: (deleted) => {
console.log("Employee removed:", deleted.id);
},
},
);
return <EmployeeGrid employees={employees} />;
}
#5. Vue Integration
#5.1 Vue Composables
import { useOntologyObject, useOntologyQuery } from "@coomia-dip/osdk-vue";
// Composition API
export default defineComponent({
setup() {
const { data: employee, loading } = useOntologyObject(
client.objects.Employee,
"emp-001",
);
const { data: team, loadMore } = useOntologyQuery(
client.objects.Employee
.where(f => f.department.eq("Engineering")),
);
return { employee, loading, team, loadMore };
},
});
#6. Bundle Optimization
#6.1 Tree Shaking
// Only import what you need - unused object types are tree-shaken
import { Employee } from "@coomia-dip/osdk/generated/Employee";
import { useOntologyObject } from "@coomia-dip/osdk-react";
// Bundle only includes Employee type, not all 100+ object types
#6.2 Code Splitting
// Lazy-load object types
const EmployeeModule = lazy(() => import("@coomia-dip/osdk/generated/Employee"));
// Dynamic import for large schemas
const client = OntoPlatform.init({
...config,
lazyLoading: true, // Object type modules loaded on first access
});
#6.3 Bundle Size Analysis
| Component | Size (gzipped) |
|---|---|
| Core SDK | ~12KB |
| gRPC-Web transport | ~8KB |
| React hooks | ~5KB |
| Vue composables | ~4KB |
| Per object type | ~0.5KB |
| Total (10 types) | ~30KB |
#7. Testing
describe("TypeScript OSDK", () => {
it("provides type-safe object access", async () => {
const employee = await client.objects.Employee.get("emp-001");
expect(employee.name).toBeTypeOf("string");
expect(employee.salary).toBeTypeOf("number");
});
it("supports type-safe queries", async () => {
const results = await client.objects.Employee
.where(f => f.salary.gt(50000))
.select("name", "salary")
.execute();
expect(results.length).toBeGreaterThan(0);
results.forEach(emp => {
expect(emp.salary).toBeGreaterThan(50000);
});
});
it("validates action parameters", async () => {
// @ts-expect-error - Missing required field
await expect(client.actions.createEmployee({ name: "John" }))
.rejects.toThrow("Missing required field");
});
it("handles errors gracefully", async () => {
await expect(client.objects.Employee.get("nonexistent"))
.rejects.toThrow(ObjectNotFoundError);
});
});
#8. Production Best Practices
#8.1 Code Generation CI/CD
# .gitlab-ci.yml
generate-osdk:
stage: codegen
script:
- npx onto-codegen generate
--schema-url $PLATFORM_URL
--output ./src/generated
--language typescript
- npm run typecheck
- npm run test
artifacts:
paths:
- src/generated/
#8.2 Performance Recommendations
- Enable response caching for read-heavy workloads
- Use streaming queries for large result sets
- Implement pagination with cursor-based page tokens
- Use optimistic updates for responsive UIs
- Lazy-load object type modules for large schemas
#8.3 Security Considerations
- Never embed tokens in client-side code; use secure token exchange
- Enable CSRF protection on the gRPC-Web gateway
- Validate all user inputs before sending to the SDK
- Use Content Security Policy headers
#9. Summary
The coomia-dip TypeScript OSDK delivers a premium developer experience for frontend applications through type-safe code generation and framework-native integration. Key design highlights:
- Full type safety: Compile-time checking for all Ontology operations
- Framework integration: Native React hooks and Vue composables
- gRPC-Web transport: Efficient binary protocol with streaming support
- Optimistic updates: Responsive UIs with automatic rollback on error
- Bundle optimized: Tree-shakeable with per-object-type code splitting
The next article will explore the coomia-dip async SDK design.