An educational pure functional programming library in TypeScript
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add data/entity module with typestate pattern

+129
+128
src/data/entity.ts
··· 1 + /** 2 + * Entity module - Typestate pattern for state machines. 3 + * 4 + * Typestate encodes state transitions at the type level, ensuring 5 + * only valid transitions are possible at compile time. 6 + * 7 + * @example 8 + * ```typescript 9 + * // Define states 10 + * type Draft = "draft" 11 + * type Review = "review" 12 + * type Published = "published" 13 + * 14 + * // Define document type 15 + * interface Document { 16 + * title: string 17 + * content: string 18 + * } 19 + * 20 + * // Create entity in draft state 21 + * const draft = entity<Document, Draft>({ title: "Hello", content: "" }) 22 + * 23 + * // Transition from draft to review 24 + * const toReview = transition<Document, Draft, Review>(doc => ({ 25 + * ...doc, 26 + * content: doc.content.trim() 27 + * })) 28 + * 29 + * const inReview = toReview(draft) // Entity<Document, "review"> 30 + * ``` 31 + * 32 + * @module data/entity 33 + */ 34 + 35 + // State phantom type 36 + declare const __state: unique symbol 37 + type State<S> = { [__state]: S } 38 + 39 + /** 40 + * Entity with typestate - tracks valid state transitions at compile time. 41 + * 42 + * The phantom type parameter S represents the current state of the entity. 43 + * This enables compile-time enforcement of state machine rules. 44 + * 45 + * @template T - The underlying data type 46 + * @template S - The current state (phantom type) 47 + * 48 + * @example 49 + * ```typescript 50 + * type OrderState = "pending" | "confirmed" | "shipped" | "delivered" 51 + * 52 + * interface Order { 53 + * id: string 54 + * items: string[] 55 + * } 56 + * 57 + * // Only pending orders can be confirmed 58 + * const confirm = transition<Order, "pending", "confirmed">() 59 + * 60 + * // Only confirmed orders can be shipped 61 + * const ship = transition<Order, "confirmed", "shipped">() 62 + * ``` 63 + */ 64 + export type Entity<T, S extends string> = T & State<S> 65 + 66 + /** 67 + * Create an entity in initial state. 68 + * 69 + * @template T - The underlying data type 70 + * @template S - The initial state 71 + * @param data - The entity data 72 + * @returns Entity in the specified state 73 + * 74 + * @example 75 + * ```typescript 76 + * interface User { 77 + * email: string 78 + * verified: boolean 79 + * } 80 + * 81 + * const unverifiedUser = entity<User, "unverified">({ 82 + * email: "user@example.com", 83 + * verified: false 84 + * }) 85 + * ``` 86 + */ 87 + export const entity = <T, S extends string>(data: T): Entity<T, S> => 88 + data as Entity<T, S> 89 + 90 + /** 91 + * Transition entity to new state with optional transformation. 92 + * 93 + * Type parameters enforce valid From -> To transitions at compile time. 94 + * The caller specifies which transitions are allowed by the type parameters. 95 + * 96 + * @template T - The underlying data type 97 + * @template From - The required current state 98 + * @template To - The target state after transition 99 + * @param transform - Optional transformation function to apply during transition 100 + * @returns A function that transitions an entity from From state to To state 101 + * 102 + * @example 103 + * ```typescript 104 + * // Document workflow: draft -> review -> published 105 + * interface Document { 106 + * content: string 107 + * reviewedAt?: Date 108 + * publishedAt?: Date 109 + * } 110 + * 111 + * const submitForReview = transition<Document, "draft", "review">(doc => ({ 112 + * ...doc, 113 + * reviewedAt: new Date() 114 + * })) 115 + * 116 + * const publish = transition<Document, "review", "published">(doc => ({ 117 + * ...doc, 118 + * publishedAt: new Date() 119 + * })) 120 + * 121 + * // Type error: Cannot transition from draft directly to published 122 + * // const invalid = publish(draftDoc) 123 + * ``` 124 + */ 125 + export const transition = 126 + <T, From extends string, To extends string>(transform?: (t: T) => T) => 127 + (e: Entity<T, From>): Entity<T, To> => 128 + (transform ? transform(e) : e) as unknown as Entity<T, To>
+1
src/data/index.ts
··· 7 7 export * from "./guards" 8 8 export * from "./array" 9 9 export * from "./units" 10 + export * from "./entity"