TypeScript Style Guide
Google TypeScript Style standards for Fulcrum dashboard and SDKs.
Overview
This guide summarizes key rules from the Google TypeScript Style Guide, enforced by gts. It applies to:
- Dashboard (Next.js)
- TypeScript SDK
- Build tooling
Language Features
Variable Declarations
// Use const by default
const config = loadConfig();
// Use let only when reassignment is needed
let count = 0;
count++;
// NEVER use var
var x = 5; // FORBIDDEN
Modules
// Use ES6 modules
import { PolicyEngine } from './policy-engine';
export { evaluatePolicy, PolicyResult };
// NEVER use namespace
namespace MyApp { // FORBIDDEN
// ...
}
Exports
// Use named exports
export { PolicyEngine };
export { helper1, helper2 };
// DO NOT use default exports
export default PolicyEngine; // BAD
Classes
Visibility Modifiers
class PolicyService {
// NEVER use public (it's the default)
public name: string; // BAD
name: string; // GOOD
// Use private/protected to restrict access
private cache: Map<string, Policy>;
protected logger: Logger;
// Mark readonly for properties set only in constructor
readonly id: string;
constructor(id: string) {
this.id = id;
}
}
Private Fields
// DO NOT use #private fields
class Service {
#secret: string; // BAD - ECMAScript private field
}
// Use TypeScript's private modifier
class Service {
private secret: string; // GOOD
}
Functions
Function Declarations vs Expressions
// Prefer declarations for named functions
function evaluatePolicy(policyId: string): PolicyResult {
// ...
}
// Use arrow functions for anonymous/callbacks
const policies = items.filter((item) => item.type === 'policy');
const handler = async (req: Request) => {
// ...
};
Strings
// Use single quotes
const name = 'fulcrum';
// Use template literals for interpolation
const message = `Hello, ${name}!`;
// Use template literals for multi-line
const query = `
SELECT *
FROM policies
WHERE tenant_id = $1
`;
Type System
Type Assertions
// AVOID type assertions
const x = value as SomeType; // Avoid unless necessary
const y = value!; // Avoid non-null assertions
// If you must use them, justify with a comment
// Type assertion is safe here because we validated above
const config = data as Config;
Avoid any
// AVOID any
function process(data: any) { } // BAD
// Prefer unknown
function process(data: unknown) {
if (isPolicy(data)) {
// Now TypeScript knows it's a Policy
}
}
// Or use a specific type
function process(data: Policy | PolicyGroup) { }
Optional vs undefined
// Prefer optional parameters
function greet(name?: string) { }
// Over union with undefined
function greet(name: string | undefined) { } // Less preferred
Array Types
// Use T[] for simple types
const policies: Policy[] = [];
const names: string[] = [];
// Use Array<T> for complex unions
const items: Array<string | number> = [];
const results: Array<{ id: string; value: number }> = [];
Avoid {}
// DO NOT use {}
const data: {} = getValue(); // BAD
// Use appropriate alternatives
const data: unknown = getValue();
const data: Record<string, unknown> = getValue();
const data: object = getValue();
Equality
// ALWAYS use triple equals
if (a === b) { }
if (a !== b) { }
// NEVER use loose equality
if (a == b) { } // BAD
if (a != b) { } // BAD
Disallowed Features
| Feature | Alternative |
|---|---|
any type |
unknown or specific types |
var |
const or let |
namespace |
ES6 modules |
| default exports | named exports |
#private fields |
private modifier |
const enum |
plain enum |
eval() |
(none - forbidden) |
| Wrapper objects | primitives |
| ASI reliance | explicit semicolons |
Naming Conventions
| Type | Style | Example |
|---|---|---|
| Classes | UpperCamelCase |
PolicyEngine |
| Interfaces | UpperCamelCase |
PolicyResult |
| Types | UpperCamelCase |
EvaluationStatus |
| Enums | UpperCamelCase |
Decision |
| Enum values | CONSTANT_CASE |
ALLOW, DENY |
| Functions | lowerCamelCase |
evaluatePolicy |
| Methods | lowerCamelCase |
getConfig |
| Variables | lowerCamelCase |
policyResult |
| Constants | CONSTANT_CASE |
MAX_RETRIES |
No Underscores
// DO NOT use _ prefix/suffix
private _name: string; // BAD
const name_: string; // BAD
// Use clear names
private name: string; // GOOD
const userName: string; // GOOD
Comments and Documentation
JSDoc
/**
* Evaluates a policy against the given request.
*
* @param policyId - The policy identifier
* @param request - The request to evaluate
* @returns The evaluation result
* @throws {PolicyNotFoundError} When policy doesn't exist
*/
function evaluatePolicy(policyId: string, request: Request): PolicyResult {
// ...
}
Type Redundancy
// DO NOT declare types in JSDoc (TypeScript has them)
/**
* @param {string} name - The name // BAD - redundant
*/
function greet(name: string) { }
// Just describe the parameter
/**
* @param name - The user's display name
*/
function greet(name: string) { }
Comments Must Add Value
// BAD - restates the code
// Increment counter
counter++;
// GOOD - explains why
// Rate limit requires tracking requests per second
counter++;
React/Next.js Patterns (Dashboard)
Component Structure
// Named export for components
export function PolicyList({ policies }: PolicyListProps) {
return (
<ul>
{policies.map((policy) => (
<PolicyItem key={policy.id} policy={policy} />
))}
</ul>
);
}
// Interface for props
interface PolicyListProps {
policies: Policy[];
onSelect?: (policy: Policy) => void;
}
Hooks
// Custom hooks start with "use"
function usePolicies(tenantId: string) {
const [policies, setPolicies] = useState<Policy[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchPolicies(tenantId).then(setPolicies);
setLoading(false);
}, [tenantId]);
return { policies, loading };
}
See Also
- Google TypeScript Style Guide
- JavaScript Style Guide - For plain JS files
- Code Style Index - All language guides
Document Version: 1.0 Last Updated: January 20, 2026