rip-lang 2.8.9 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/TYPES.md ADDED
@@ -0,0 +1,1132 @@
1
+ # Optional Types in Rip
2
+
3
+ > **Type-Driven Development Without the Overhead**
4
+
5
+ This specification defines Rip's optional, lightweight type system — a thin, compile-time-only layer that enables TypeScript-level type-driven development while preserving Rip's core philosophy: minimal syntax, high readability, zero runtime overhead.
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ 1. [Philosophy](#1-philosophy)
12
+ 2. [Type Annotations](#2-type-annotations)
13
+ 3. [Type Aliases](#3-type-aliases)
14
+ 4. [Optionality Modifiers](#4-optionality-modifiers)
15
+ 5. [Structural Types](#5-structural-types)
16
+ 6. [Union Types](#6-union-types)
17
+ 7. [Function Types](#7-function-types)
18
+ 8. [Generic Types](#8-generic-types)
19
+ 9. [Type Inference](#9-type-inference)
20
+ 10. [Adoption Model](#10-adoption-model)
21
+ 11. [Emission Strategy](#11-emission-strategy)
22
+ 12. [Interfaces](#12-interfaces)
23
+ 13. [Enums](#13-enums)
24
+ 14. [Boundary Validation](#14-boundary-validation)
25
+ 15. [Implementation Notes](#15-implementation-notes)
26
+ 16. [Quick Reference](#16-quick-reference)
27
+
28
+ ---
29
+
30
+ # 1. Philosophy
31
+
32
+ ## Core Principles
33
+
34
+ 1. **Types are additive, never required** — Rip code without types is valid Rip code
35
+ 2. **All type syntax erases at runtime** — Zero performance cost
36
+ 3. **Type syntax decorates existing constructs** — No new control flow or semantics
37
+ 4. **No typechecker required in Rip** — Parse and preserve, don't enforce
38
+ 5. **Emit TypeScript-compatible types** — Leverage existing tooling
39
+ 6. **Prefer zero-runtime representations** — Unions over enums, types over classes
40
+
41
+ ## The Rip Way
42
+
43
+ Types in Rip should feel like documentation that happens to be machine-readable:
44
+
45
+ ```coffee
46
+ # Without types (valid)
47
+ def greet(name)
48
+ "Hello, #{name}!"
49
+
50
+ # With types (also valid, same runtime behavior)
51
+ def greet(name:: string):: string
52
+ "Hello, #{name}!"
53
+ ```
54
+
55
+ Both compile to identical JavaScript. Types exist for developers and tools, not for the runtime.
56
+
57
+ ## Static Typing as a Discipline, Not a Mandate
58
+
59
+ Rip's type system is designed for teams and projects that want:
60
+
61
+ - **IDE intelligence** — Autocompletion, hover info, refactoring support
62
+ - **Documentation** — Self-documenting function signatures
63
+ - **Boundary safety** — Confidence at API and module boundaries
64
+ - **Gradual adoption** — Add types where they matter, skip where they don't
65
+
66
+ ---
67
+
68
+ # 2. Type Annotations
69
+
70
+ ## Syntax: `::`
71
+
72
+ The double-colon `::` annotates types on variables, parameters, return values, and properties.
73
+
74
+ ### Variables
75
+
76
+ ```coffee
77
+ count:: number = 0
78
+ name:: string = "Rip"
79
+ active:: boolean = true
80
+ items:: string[] = []
81
+ ```
82
+
83
+ **Emits to TypeScript:**
84
+
85
+ ```ts
86
+ let count: number = 0;
87
+ let name: string = "Rip";
88
+ let active: boolean = true;
89
+ let items: string[] = [];
90
+ ```
91
+
92
+ ### Function Parameters
93
+
94
+ ```coffee
95
+ def greet(name:: string)
96
+ "Hello, #{name}!"
97
+
98
+ def add(a:: number, b:: number)
99
+ a + b
100
+ ```
101
+
102
+ **Emits:**
103
+
104
+ ```ts
105
+ function greet(name: string) { ... }
106
+ function add(a: number, b: number) { ... }
107
+ ```
108
+
109
+ ### Return Types
110
+
111
+ ```coffee
112
+ def getUser(id:: number):: User
113
+ db.find!(id)
114
+
115
+ def fetchData():: Promise<Data>
116
+ fetch!("/api/data")
117
+ ```
118
+
119
+ **Emits:**
120
+
121
+ ```ts
122
+ function getUser(id: number): User { ... }
123
+ async function fetchData(): Promise<Data> { ... }
124
+ ```
125
+
126
+ ### Combined
127
+
128
+ ```coffee
129
+ def processUser(id:: number, options:: Options):: Result
130
+ user = getUser!(id)
131
+ transform(user, options)
132
+ ```
133
+
134
+ ### Constants
135
+
136
+ ```coffee
137
+ MAX_RETRIES:: number =! 3
138
+ API_URL:: string =! "https://api.example.com"
139
+ ```
140
+
141
+ **Emits:**
142
+
143
+ ```ts
144
+ const MAX_RETRIES: number = 3;
145
+ const API_URL: string = "https://api.example.com";
146
+ ```
147
+
148
+ ### Reactive State
149
+
150
+ Types work with Rip's reactive operators:
151
+
152
+ ```coffee
153
+ count:: number := 0 # Reactive state with type
154
+ doubled:: number ~= count * 2 # Computed with type
155
+ ```
156
+
157
+ ---
158
+
159
+ # 3. Type Aliases
160
+
161
+ ## Syntax: `::=`
162
+
163
+ The `::=` operator declares a named type alias, mapping directly to TypeScript's `type X = ...`.
164
+
165
+ ### Simple Aliases
166
+
167
+ ```coffee
168
+ ID ::= number
169
+ Name ::= string
170
+ Timestamp ::= number
171
+ ```
172
+
173
+ **Emits:**
174
+
175
+ ```ts
176
+ type ID = number;
177
+ type Name = string;
178
+ type Timestamp = number;
179
+ ```
180
+
181
+ ### Complex Types
182
+
183
+ ```coffee
184
+ UserID ::= number | string
185
+ Callback ::= (error:: Error?, data:: any) -> void
186
+ Handler ::= (req:: Request, res:: Response) -> Promise<void>
187
+ ```
188
+
189
+ **Emits:**
190
+
191
+ ```ts
192
+ type UserID = number | string;
193
+ type Callback = (error: Error | undefined, data: any) => void;
194
+ type Handler = (req: Request, res: Response) => Promise<void>;
195
+ ```
196
+
197
+ ---
198
+
199
+ # 4. Optionality Modifiers
200
+
201
+ Lightweight suffix operators that map directly to TypeScript unions.
202
+
203
+ ## Optional: `T?`
204
+
205
+ Indicates a value may be undefined.
206
+
207
+ ```coffee
208
+ email:: string?
209
+ callback:: Function?
210
+ ```
211
+
212
+ **Emits:**
213
+
214
+ ```ts
215
+ email: string | undefined
216
+ callback: Function | undefined
217
+ ```
218
+
219
+ ## Nullable Optional: `T??`
220
+
221
+ Indicates a value may be null or undefined.
222
+
223
+ ```coffee
224
+ middle:: string??
225
+ cache:: Map<string, any>??
226
+ ```
227
+
228
+ **Emits:**
229
+
230
+ ```ts
231
+ middle: string | null | undefined
232
+ cache: Map<string, any> | null | undefined
233
+ ```
234
+
235
+ ## Non-Nullable: `T!`
236
+
237
+ Asserts a value is never null or undefined.
238
+
239
+ ```coffee
240
+ id:: ID!
241
+ user:: User!
242
+ ```
243
+
244
+ **Emits:**
245
+
246
+ ```ts
247
+ id: NonNullable<ID>
248
+ user: NonNullable<User>
249
+ ```
250
+
251
+ ## In Function Signatures
252
+
253
+ ```coffee
254
+ def findUser(id:: number):: User?
255
+ db.find(id) or undefined
256
+
257
+ def getUser(id:: number):: User!
258
+ db.find(id) ?? throw new Error "Not found"
259
+
260
+ def updateUser(id:: number, email:: string??):: boolean
261
+ ...
262
+ ```
263
+
264
+ ## Optional Properties
265
+
266
+ In object types, `?` after the property name makes it optional:
267
+
268
+ ```coffee
269
+ User ::= type
270
+ id: number
271
+ name: string
272
+ email?: string # Optional property
273
+ phone?: string? # Optional property that can also be undefined when present
274
+ ```
275
+
276
+ ---
277
+
278
+ # 5. Structural Types
279
+
280
+ ## Object Types with `type` Block
281
+
282
+ Define structural types using the `type` keyword followed by a block:
283
+
284
+ ```coffee
285
+ User ::= type
286
+ id: number
287
+ name: string
288
+ email?: string
289
+ createdAt: Date
290
+
291
+ Config ::= type
292
+ host: string
293
+ port: number
294
+ ssl?: boolean
295
+ timeout?: number
296
+ ```
297
+
298
+ **Emits:**
299
+
300
+ ```ts
301
+ type User = {
302
+ id: number;
303
+ name: string;
304
+ email?: string;
305
+ createdAt: Date;
306
+ };
307
+
308
+ type Config = {
309
+ host: string;
310
+ port: number;
311
+ ssl?: boolean;
312
+ timeout?: number;
313
+ };
314
+ ```
315
+
316
+ ## Nested Types
317
+
318
+ ```coffee
319
+ Response ::= type
320
+ data: type
321
+ users: User[]
322
+ total: number
323
+ meta: type
324
+ page: number
325
+ limit: number
326
+ ```
327
+
328
+ **Emits:**
329
+
330
+ ```ts
331
+ type Response = {
332
+ data: {
333
+ users: User[];
334
+ total: number;
335
+ };
336
+ meta: {
337
+ page: number;
338
+ limit: number;
339
+ };
340
+ };
341
+ ```
342
+
343
+ ## Array Properties
344
+
345
+ ```coffee
346
+ Collection ::= type
347
+ items: Item[]
348
+ tags: string[]
349
+ matrix: number[][]
350
+ ```
351
+
352
+ ## Readonly Properties
353
+
354
+ ```coffee
355
+ ImmutableConfig ::= type
356
+ readonly host: string
357
+ readonly port: number
358
+ ```
359
+
360
+ ## Index Signatures
361
+
362
+ ```coffee
363
+ Dictionary ::= type
364
+ [key: string]: any
365
+
366
+ StringMap ::= type
367
+ [key: string]: string
368
+ ```
369
+
370
+ ---
371
+
372
+ # 6. Union Types
373
+
374
+ ## Inline Unions
375
+
376
+ ```coffee
377
+ Status ::= "pending" | "active" | "done"
378
+ Result ::= Success | Error
379
+ ID ::= number | string
380
+ ```
381
+
382
+ ## Block Unions (Preferred)
383
+
384
+ For readability and diff-friendliness, use the block form with leading `|`:
385
+
386
+ ```coffee
387
+ Status ::=
388
+ | "pending"
389
+ | "active"
390
+ | "done"
391
+
392
+ HttpMethod ::=
393
+ | "GET"
394
+ | "POST"
395
+ | "PUT"
396
+ | "PATCH"
397
+ | "DELETE"
398
+
399
+ Result ::=
400
+ | { success: true, data: Data }
401
+ | { success: false, error: Error }
402
+ ```
403
+
404
+ **Emits:**
405
+
406
+ ```ts
407
+ type Status = "pending" | "active" | "done";
408
+
409
+ type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
410
+
411
+ type Result =
412
+ | { success: true; data: Data }
413
+ | { success: false; error: Error };
414
+ ```
415
+
416
+ ## Why Block Unions Over Enums
417
+
418
+ Block unions have **zero runtime cost** — they exist only at compile time. Traditional enums generate runtime code:
419
+
420
+ ```coffee
421
+ # Preferred: Zero runtime cost
422
+ Size ::=
423
+ | "xs"
424
+ | "sm"
425
+ | "md"
426
+ | "lg"
427
+
428
+ # Avoid: Generates runtime object
429
+ enum Size
430
+ xs
431
+ sm
432
+ md
433
+ lg
434
+ ```
435
+
436
+ Use enums only when you need:
437
+ - Reverse mapping (value → name)
438
+ - Runtime iteration over values
439
+ - Explicit numeric values
440
+
441
+ ---
442
+
443
+ # 7. Function Types
444
+
445
+ ## Arrow Function Types
446
+
447
+ ```coffee
448
+ Comparator ::= (a:: any, b:: any) -> number
449
+ AsyncFetcher ::= (url:: string) -> Promise<Response>
450
+ Callback ::= (err:: Error?, data:: any?) -> void
451
+ ```
452
+
453
+ **Emits:**
454
+
455
+ ```ts
456
+ type Comparator = (a: any, b: any) => number;
457
+ type AsyncFetcher = (url: string) => Promise<Response>;
458
+ type Callback = (err: Error | undefined, data: any | undefined) => void;
459
+ ```
460
+
461
+ ## Function Overloads
462
+
463
+ Multiple signatures for a single implementation:
464
+
465
+ ```coffee
466
+ # Overload signatures
467
+ def toHtml(content:: string):: string
468
+ def toHtml(nodes:: Element[]):: string
469
+ def toHtml(fragment:: DocumentFragment):: string
470
+
471
+ # Implementation (matches last signature or uses widest type)
472
+ def toHtml(input:: any):: string
473
+ switch typeof input
474
+ when "string" then escapeHtml(input)
475
+ else renderNodes(input)
476
+ ```
477
+
478
+ **Emits:**
479
+
480
+ ```ts
481
+ function toHtml(content: string): string;
482
+ function toHtml(nodes: Element[]): string;
483
+ function toHtml(fragment: DocumentFragment): string;
484
+ function toHtml(input: any): string {
485
+ // implementation
486
+ }
487
+ ```
488
+
489
+ ## Method Signatures in Classes
490
+
491
+ ```coffee
492
+ class UserService
493
+ find: (id:: number):: User? ->
494
+ @db.find(id)
495
+
496
+ create: (data:: CreateUserInput):: User ->
497
+ @db.create(data)
498
+
499
+ update: (id:: number, data:: UpdateUserInput):: User? ->
500
+ @db.update(id, data)
501
+ ```
502
+
503
+ ---
504
+
505
+ # 8. Generic Types
506
+
507
+ ## Generic Type Parameters
508
+
509
+ ```coffee
510
+ # Simple generic
511
+ Container<T> ::= type
512
+ value: T
513
+
514
+ # Multiple type parameters
515
+ Pair<K, V> ::= type
516
+ key: K
517
+ value: V
518
+
519
+ # With constraints
520
+ Comparable<T extends Ordered> ::= type
521
+ value: T
522
+ compareTo: (other:: T) -> number
523
+ ```
524
+
525
+ **Emits:**
526
+
527
+ ```ts
528
+ type Container<T> = {
529
+ value: T;
530
+ };
531
+
532
+ type Pair<K, V> = {
533
+ key: K;
534
+ value: V;
535
+ };
536
+
537
+ type Comparable<T extends Ordered> = {
538
+ value: T;
539
+ compareTo: (other: T) => number;
540
+ };
541
+ ```
542
+
543
+ ## Generic Functions
544
+
545
+ ```coffee
546
+ def identity<T>(value:: T):: T
547
+ value
548
+
549
+ def map<T, U>(items:: T[], fn:: (item:: T) -> U):: U[]
550
+ items.map(fn)
551
+
552
+ def first<T>(items:: T[]):: T?
553
+ items[0]
554
+ ```
555
+
556
+ ## Generic Constraints
557
+
558
+ ```coffee
559
+ def merge<T extends object, U extends object>(a:: T, b:: U):: T & U
560
+ {...a, ...b}
561
+
562
+ def stringify<T extends { toString: () -> string }>(value:: T):: string
563
+ value.toString()
564
+ ```
565
+
566
+ ---
567
+
568
+ # 9. Type Inference
569
+
570
+ ## When to Annotate
571
+
572
+ Rip's type system uses **TypeScript-compatible inference**. Types should be explicit at boundaries, optional elsewhere:
573
+
574
+ ### Explicit (Recommended)
575
+
576
+ ```coffee
577
+ # Function signatures — always annotate
578
+ def processUser(id:: number, options:: ProcessOptions):: Result
579
+ ...
580
+
581
+ # Exported values — always annotate
582
+ export config:: Config = loadConfig()
583
+
584
+ # Class properties — annotate for clarity
585
+ class UserService
586
+ db:: Database
587
+ cache:: Map<string, User>
588
+ ```
589
+
590
+ ### Inferred (Acceptable)
591
+
592
+ ```coffee
593
+ # Local variables — let TypeScript infer
594
+ user = getUser!(id) # Inferred from getUser's return type
595
+ items = users.map (u) -> u.name # Inferred as string[]
596
+ count = 0 # Inferred as number
597
+ ```
598
+
599
+ ## Inference Rules
600
+
601
+ 1. **Function returns** — Inferred from body if not annotated
602
+ 2. **Variables** — Inferred from initializer
603
+ 3. **Parameters** — Should be explicitly annotated
604
+ 4. **Generic type args** — Often inferred from usage
605
+
606
+ ---
607
+
608
+ # 10. Adoption Model
609
+
610
+ Types are optional at three levels: project, file, and line.
611
+
612
+ ## Project Level
613
+
614
+ Configure in `package.json` or `rip.config.json`:
615
+
616
+ ```json
617
+ {
618
+ "rip": {
619
+ "types": "emit"
620
+ }
621
+ }
622
+ ```
623
+
624
+ | Mode | Behavior |
625
+ |------|----------|
626
+ | `"off"` | Ignore all type syntax (strip during parse) |
627
+ | `"emit"` | Parse types, emit `.d.ts` files |
628
+ | `"check"` | Parse types, emit `.d.ts`, run `tsc --noEmit` |
629
+
630
+ ## File Level
631
+
632
+ Override project settings per-file with a directive comment:
633
+
634
+ ```coffee
635
+ # @types off
636
+ # This file has no type checking
637
+
638
+ # @types emit
639
+ # Types parsed and emitted for this file
640
+
641
+ # @types check
642
+ # Types checked via TypeScript for this file
643
+ ```
644
+
645
+ ## Line Level
646
+
647
+ All type syntax is simply ignored if types are disabled. No special syntax needed — annotations silently disappear:
648
+
649
+ ```coffee
650
+ # With types enabled:
651
+ count:: number = 0 # Type preserved
652
+
653
+ # With types disabled:
654
+ count:: number = 0 # Parses as: count = 0
655
+ ```
656
+
657
+ ## Gradual Adoption Path
658
+
659
+ 1. **Start with `"off"`** — Write normal Rip code
660
+ 2. **Enable `"emit"`** — Add types where helpful, get `.d.ts` for tooling
661
+ 3. **Move to `"check"`** — Enforce type safety via TypeScript
662
+
663
+ ---
664
+
665
+ # 11. Emission Strategy
666
+
667
+ ## Compilation Outputs
668
+
669
+ | Input | Output | Purpose |
670
+ |-------|--------|---------|
671
+ | `file.rip` | `file.js` | Runtime code (always) |
672
+ | `file.rip` | `file.d.ts` | Type declarations (when `emit` or `check`) |
673
+
674
+ ## Type Declaration Files
675
+
676
+ When `types: "emit"` or `types: "check"`, generate `.d.ts` files:
677
+
678
+ ```coffee
679
+ # user.rip
680
+ export User ::= type
681
+ id: number
682
+ name: string
683
+
684
+ export def getUser(id:: number):: User?
685
+ db.find(id)
686
+ ```
687
+
688
+ **Generates `user.d.ts`:**
689
+
690
+ ```ts
691
+ export type User = {
692
+ id: number;
693
+ name: string;
694
+ };
695
+
696
+ export function getUser(id: number): User | undefined;
697
+ ```
698
+
699
+ **Generates `user.js`:**
700
+
701
+ ```js
702
+ export function getUser(id) {
703
+ return db.find(id);
704
+ }
705
+ ```
706
+
707
+ ## Type-Only Exports
708
+
709
+ Type aliases that have no runtime representation are only emitted to `.d.ts`:
710
+
711
+ ```coffee
712
+ # These only appear in .d.ts, not .js
713
+ Status ::= "pending" | "active" | "done"
714
+ UserID ::= number
715
+ ```
716
+
717
+ ## Integration with TypeScript
718
+
719
+ When `types: "check"`:
720
+
721
+ 1. Compile `.rip` → `.js` + `.d.ts`
722
+ 2. Run `tsc --noEmit` to validate types
723
+ 3. Report TypeScript errors
724
+
725
+ This leverages TypeScript's mature type checker without reimplementing it.
726
+
727
+ ---
728
+
729
+ # 12. Interfaces
730
+
731
+ ## Syntax: `interface`
732
+
733
+ For TypeScript compatibility, Rip also supports the `interface` keyword for object types:
734
+
735
+ ```coffee
736
+ interface User
737
+ id: number
738
+ name: string
739
+ email?: string
740
+ ```
741
+
742
+ **Emits:**
743
+
744
+ ```ts
745
+ interface User {
746
+ id: number;
747
+ name: string;
748
+ email?: string;
749
+ }
750
+ ```
751
+
752
+ ## Type vs Interface
753
+
754
+ | Feature | `::= type` | `interface` |
755
+ |---------|------------|-------------|
756
+ | Declaration merging | No | Yes |
757
+ | Extends | Via `&` intersection | Via `extends` |
758
+ | Computed properties | Yes | No |
759
+ | Mapped types | Yes | No |
760
+
761
+ **Recommendation:** Use `::= type` by default. Use `interface` when you need declaration merging or prefer the interface aesthetic.
762
+
763
+ ## Interface Extension
764
+
765
+ ```coffee
766
+ interface Animal
767
+ name: string
768
+
769
+ interface Dog extends Animal
770
+ breed: string
771
+ bark: () -> void
772
+ ```
773
+
774
+ ---
775
+
776
+ # 13. Enums
777
+
778
+ ## Zero-Runtime Enums (Preferred)
779
+
780
+ Use string literal unions for zero runtime cost:
781
+
782
+ ```coffee
783
+ Size ::=
784
+ | "xs"
785
+ | "sm"
786
+ | "md"
787
+ | "lg"
788
+ | "xl"
789
+
790
+ Direction ::=
791
+ | "north"
792
+ | "south"
793
+ | "east"
794
+ | "west"
795
+ ```
796
+
797
+ **Emits only to `.d.ts`:**
798
+
799
+ ```ts
800
+ type Size = "xs" | "sm" | "md" | "lg" | "xl";
801
+ type Direction = "north" | "south" | "east" | "west";
802
+ ```
803
+
804
+ ## Runtime Enums (When Needed)
805
+
806
+ When you need runtime access to enum values, use the `enum` keyword:
807
+
808
+ ```coffee
809
+ enum Status
810
+ pending
811
+ active
812
+ completed
813
+ cancelled
814
+
815
+ enum HttpCode
816
+ ok = 200
817
+ created = 201
818
+ badRequest = 400
819
+ notFound = 404
820
+ serverError = 500
821
+ ```
822
+
823
+ **Emits to both `.js` and `.d.ts`:**
824
+
825
+ ```ts
826
+ // .d.ts
827
+ enum Status {
828
+ pending,
829
+ active,
830
+ completed,
831
+ cancelled
832
+ }
833
+
834
+ // .js (runtime object generated)
835
+ const Status = {
836
+ pending: 0,
837
+ active: 1,
838
+ completed: 2,
839
+ cancelled: 3,
840
+ 0: "pending",
841
+ 1: "active",
842
+ 2: "completed",
843
+ 3: "cancelled"
844
+ };
845
+ ```
846
+
847
+ ## When to Use Runtime Enums
848
+
849
+ Use runtime enums only when you need:
850
+
851
+ - **Reverse mapping** — Get name from value: `Status[0]` → `"pending"`
852
+ - **Iteration** — Loop over all values at runtime
853
+ - **Explicit numeric values** — `HttpCode.ok === 200`
854
+
855
+ For all other cases, prefer zero-runtime union types.
856
+
857
+ ---
858
+
859
+ # 14. Boundary Validation
860
+
861
+ ## Philosophy
862
+
863
+ Types are compile-time contracts. For data entering your system (user input, API responses, file contents), **validate at boundaries**:
864
+
865
+ ```coffee
866
+ # Type declares the shape
867
+ User ::= type
868
+ id: number
869
+ name: string
870
+ email: string
871
+
872
+ # Validation enforces at runtime
873
+ def parseUser(input:: unknown):: User
874
+ UserSchema.parse(input) # Throws if invalid
875
+ ```
876
+
877
+ ## Recommended Pattern
878
+
879
+ Use a validation library (like Zod) at IO boundaries:
880
+
881
+ ```coffee
882
+ import { z } from "zod"
883
+
884
+ # Define schema with runtime validation
885
+ UserSchema = z.object
886
+ id: z.number()
887
+ name: z.string()
888
+ email: z.string().email()
889
+
890
+ # Derive type from schema (single source of truth)
891
+ User ::= z.infer<typeof UserSchema>
892
+
893
+ # Validate at API boundary
894
+ def createUser(req:: Request):: User
895
+ data = req.json!
896
+ UserSchema.parse(data) # Validates and returns typed data
897
+ ```
898
+
899
+ ## Trust Internally
900
+
901
+ Once data passes boundary validation, trust the types internally:
902
+
903
+ ```coffee
904
+ def processUser(user:: User):: void
905
+ # No need to re-validate — type guarantees shape
906
+ sendWelcomeEmail(user.email)
907
+ createProfile(user.id, user.name)
908
+ ```
909
+
910
+ ---
911
+
912
+ # 15. Implementation Notes
913
+
914
+ ## Parser Changes
915
+
916
+ The parser must recognize and preserve:
917
+
918
+ 1. **`::` annotations** — After identifiers, parameters, before return
919
+ 2. **`::=` declarations** — Type alias definitions
920
+ 3. **Type modifiers** — `?`, `??`, `!` suffixes
921
+ 4. **`type` blocks** — Structural type definitions
922
+ 5. **Generic syntax** — `<T>`, `<T extends U>`, etc.
923
+ 6. **Block unions** — Leading `|` for union members
924
+
925
+ ## AST Representation
926
+
927
+ Type information should be stored as AST metadata:
928
+
929
+ ```coffee
930
+ # Source
931
+ count:: number = 0
932
+ ```
933
+
934
+ ```javascript
935
+ // AST (conceptual)
936
+ ["=", "count", 0, { type: "number" }]
937
+ ```
938
+
939
+ ## Code Generation
940
+
941
+ Type annotations should:
942
+
943
+ 1. **Strip from runtime** — Never appear in `.js` output
944
+ 2. **Preserve for `.d.ts`** — Emit valid TypeScript declarations
945
+ 3. **Maintain source order** — Types in `.d.ts` match source file order
946
+
947
+ ## Scope and Validation
948
+
949
+ Rip does **not** need to:
950
+
951
+ - Evaluate type expressions
952
+ - Prove type soundness
953
+ - Check type compatibility
954
+ - Resolve type references
955
+
956
+ Rip only needs to:
957
+
958
+ - **Parse** type syntax correctly
959
+ - **Preserve** type information in AST
960
+ - **Emit** valid TypeScript type syntax
961
+
962
+ All actual type checking is delegated to TypeScript when `types: "check"`.
963
+
964
+ ---
965
+
966
+ ## Summary
967
+
968
+ Rip's optional type system provides:
969
+
970
+ | Feature | Description |
971
+ |---------|-------------|
972
+ | **Type Annotations** | `::` for variables, parameters, returns |
973
+ | **Type Aliases** | `::=` for named types |
974
+ | **Optionality** | `T?` optional, `T??` nullable, `T!` non-null |
975
+ | **Structural Types** | `type` blocks for object shapes |
976
+ | **Union Types** | Inline or block form with `\|` |
977
+ | **Function Types** | Arrow syntax, overloads supported |
978
+ | **Generics** | Full generic support with constraints |
979
+ | **Adoption Levels** | Project, file, and line granularity |
980
+ | **Emission** | `.js` runtime + `.d.ts` declarations |
981
+
982
+ **The result:**
983
+
984
+ - Type-driven development with TypeScript ecosystem compatibility
985
+ - Optional adoption — add types where they help
986
+ - Zero runtime cost — types are compile-time only
987
+ - Minimal syntax — Rip stays Rip
988
+
989
+ > **Static typing as a discipline, not a mandate.**
990
+
991
+ ---
992
+
993
+ # 16. Quick Reference
994
+
995
+ ## Syntax Cheat Sheet
996
+
997
+ ```coffee
998
+ # ═══════════════════════════════════════════════════════════
999
+ # TYPE ANNOTATIONS (::)
1000
+ # ═══════════════════════════════════════════════════════════
1001
+
1002
+ # Variables
1003
+ count:: number = 0
1004
+ name:: string = "Rip"
1005
+ items:: string[] = []
1006
+
1007
+ # Constants
1008
+ MAX:: number =! 100
1009
+
1010
+ # Reactive state
1011
+ count:: number := 0
1012
+ doubled:: number ~= count * 2
1013
+
1014
+ # Function parameters and return
1015
+ def greet(name:: string):: string
1016
+ "Hello, #{name}!"
1017
+
1018
+ # ═══════════════════════════════════════════════════════════
1019
+ # TYPE ALIASES (::=)
1020
+ # ═══════════════════════════════════════════════════════════
1021
+
1022
+ # Simple alias
1023
+ ID ::= number
1024
+
1025
+ # Union (inline)
1026
+ Status ::= "pending" | "active" | "done"
1027
+
1028
+ # Union (block - preferred)
1029
+ HttpMethod ::=
1030
+ | "GET"
1031
+ | "POST"
1032
+ | "PUT"
1033
+ | "DELETE"
1034
+
1035
+ # Structural type
1036
+ User ::= type
1037
+ id: number
1038
+ name: string
1039
+ email?: string
1040
+
1041
+ # Function type
1042
+ Handler ::= (req:: Request) -> Response
1043
+
1044
+ # Generic type
1045
+ Container<T> ::= type
1046
+ value: T
1047
+
1048
+ # ═══════════════════════════════════════════════════════════
1049
+ # OPTIONALITY MODIFIERS
1050
+ # ═══════════════════════════════════════════════════════════
1051
+
1052
+ email:: string? # T | undefined
1053
+ middle:: string?? # T | null | undefined
1054
+ id:: ID! # NonNullable<T>
1055
+
1056
+ # ═══════════════════════════════════════════════════════════
1057
+ # GENERICS
1058
+ # ═══════════════════════════════════════════════════════════
1059
+
1060
+ def identity<T>(x:: T):: T
1061
+ x
1062
+
1063
+ def map<T, U>(arr:: T[], fn:: (x:: T) -> U):: U[]
1064
+ arr.map(fn)
1065
+
1066
+ # With constraints
1067
+ def merge<T extends object>(a:: T, b:: T):: T
1068
+ {...a, ...b}
1069
+
1070
+ # ═══════════════════════════════════════════════════════════
1071
+ # INTERFACES
1072
+ # ═══════════════════════════════════════════════════════════
1073
+
1074
+ interface Animal
1075
+ name: string
1076
+
1077
+ interface Dog extends Animal
1078
+ breed: string
1079
+
1080
+ # ═══════════════════════════════════════════════════════════
1081
+ # ENUMS (use sparingly)
1082
+ # ═══════════════════════════════════════════════════════════
1083
+
1084
+ enum Status
1085
+ pending
1086
+ active
1087
+ done
1088
+
1089
+ enum HttpCode
1090
+ ok = 200
1091
+ notFound = 404
1092
+ ```
1093
+
1094
+ ## Comparison: Rip vs TypeScript vs CoffeeScript
1095
+
1096
+ | Feature | Rip | TypeScript | CoffeeScript |
1097
+ |---------|-----|------------|--------------|
1098
+ | Type annotations | `x:: T` | `x: T` | N/A |
1099
+ | Type aliases | `T ::= ...` | `type T = ...` | N/A |
1100
+ | Optional type | `T?` | `T \| undefined` | N/A |
1101
+ | Nullable | `T??` | `T \| null \| undefined` | N/A |
1102
+ | Non-nullable | `T!` | `NonNullable<T>` | N/A |
1103
+ | Structural types | `type` block | `{ ... }` | N/A |
1104
+ | Block unions | `\| "a" \| "b"` | Same | N/A |
1105
+ | Types required | No | Configurable | N/A |
1106
+ | Runtime cost | Zero | Zero | N/A |
1107
+
1108
+ ## Project Configuration
1109
+
1110
+ ```json
1111
+ // package.json
1112
+ {
1113
+ "rip": {
1114
+ "types": "emit" // "off" | "emit" | "check"
1115
+ }
1116
+ }
1117
+ ```
1118
+
1119
+ ## File Directives
1120
+
1121
+ ```coffee
1122
+ # @types off — Ignore types in this file
1123
+ # @types emit — Parse and emit .d.ts
1124
+ # @types check — Full TypeScript checking
1125
+ ```
1126
+
1127
+ ---
1128
+
1129
+ **See Also:**
1130
+ - [README.md](README.md) — Language overview
1131
+ - [docs/GUIDE.md](docs/GUIDE.md) — Language guide
1132
+ - [docs/REACTIVITY.md](docs/REACTIVITY.md) — Reactive system details