rip-lang 3.17.2 → 3.17.4
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/README.md +1 -1
- package/docs/RIP-APP.md +1 -1
- package/docs/RIP-LANG.md +6 -6
- package/docs/RIP-SCHEMA.md +1 -1
- package/docs/RIP-TYPES.md +104 -104
- package/docs/dist/rip.js +10 -4
- package/docs/dist/rip.min.js +40 -33
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/extensions/vscode/print/index.html +2 -1
- package/docs/extensions/vscode/print/print-1.0.16.vsix +0 -0
- package/docs/extensions/vscode/print/print-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/index.html +2 -1
- package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/vscode-rip-0.8.0.vsix +0 -0
- package/package.json +1 -1
- package/src/lexer.js +5 -1
- package/src/schema/runtime-validate.js +7 -0
- package/src/schema/runtime.generated.js +7 -0
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="https://github.com/shreeve/rip-lang/commits/main"><img src="https://img.shields.io/badge/version-3.17.
|
|
12
|
+
<a href="https://github.com/shreeve/rip-lang/commits/main"><img src="https://img.shields.io/badge/version-3.17.4-blue.svg" alt="Version"></a>
|
|
13
13
|
<a href="#zero-dependencies"><img src="https://img.shields.io/badge/dependencies-ZERO-brightgreen.svg" alt="Dependencies"></a>
|
|
14
14
|
<a href="#"><img src="https://img.shields.io/badge/tests-1%2C436%2F1%2C436-brightgreen.svg" alt="Tests"></a>
|
|
15
15
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
|
package/docs/RIP-APP.md
CHANGED
|
@@ -246,7 +246,7 @@ export stash =
|
|
|
246
246
|
products: source # five minutes of freshness is plenty
|
|
247
247
|
fetch: -> Product.array.parse(api.get!('products').json!()) # validated Product[]
|
|
248
248
|
staleTime: '5 min'
|
|
249
|
-
order: source { fetch: (id
|
|
249
|
+
order: source { fetch: (id: string) -> Order.parse(api.get!("orders/#{id}").json!()) }
|
|
250
250
|
```
|
|
251
251
|
|
|
252
252
|
A source is **lazy** (loads on first touch, not at startup) and **not a
|
package/docs/RIP-LANG.md
CHANGED
|
@@ -673,8 +673,8 @@ String::shout = -> @toUpperCase() + "!"
|
|
|
673
673
|
"hello".shout() # "HELLO!"
|
|
674
674
|
|
|
675
675
|
# Type annotations (space after ::) — unaffected
|
|
676
|
-
name
|
|
677
|
-
def greet(name
|
|
676
|
+
name: string = "Alice"
|
|
677
|
+
def greet(name: string): string
|
|
678
678
|
"Hello, #{name}!"
|
|
679
679
|
```
|
|
680
680
|
|
|
@@ -1096,8 +1096,8 @@ Reads return the delayed value; writes update the source immediately. A drop-in
|
|
|
1096
1096
|
Reactive operators work with Rip's optional type system:
|
|
1097
1097
|
|
|
1098
1098
|
```coffee
|
|
1099
|
-
count
|
|
1100
|
-
doubled
|
|
1099
|
+
count: number := 0 # Typed state
|
|
1100
|
+
doubled: number ~= count * 2 # Typed computed
|
|
1101
1101
|
```
|
|
1102
1102
|
|
|
1103
1103
|
Type annotations are erased from `.js` output. In `.d.ts` output, reactive state emits `Signal<T>` and computed values emit `Computed<T>`:
|
|
@@ -1989,8 +1989,8 @@ Rip supports an optional, compile-time-only type system. Types are erased from
|
|
|
1989
1989
|
|
|
1990
1990
|
```coffee
|
|
1991
1991
|
# Type annotations (::)
|
|
1992
|
-
count
|
|
1993
|
-
def greet(name
|
|
1992
|
+
count: number = 0
|
|
1993
|
+
def greet(name: string): string
|
|
1994
1994
|
"Hello, #{name}!"
|
|
1995
1995
|
|
|
1996
1996
|
# Type aliases (type keyword)
|
package/docs/RIP-SCHEMA.md
CHANGED
|
@@ -761,7 +761,7 @@ signature (typed params, `this`, and the inferred return) instead of
|
|
|
761
761
|
```coffee
|
|
762
762
|
greet: -> "Hello, #{@name}!"
|
|
763
763
|
|
|
764
|
-
add: (other
|
|
764
|
+
add: (other: Money) ->
|
|
765
765
|
Money.parse amount: @amount + other.amount, currency: @currency
|
|
766
766
|
# shadow TS: add(this: Money, other: Money): Money
|
|
767
767
|
|
package/docs/RIP-TYPES.md
CHANGED
|
@@ -23,7 +23,7 @@ def greet(name)
|
|
|
23
23
|
"Hello, #{name}!"
|
|
24
24
|
|
|
25
25
|
# With types (also valid, same runtime behavior)
|
|
26
|
-
def greet(name
|
|
26
|
+
def greet(name: string): string
|
|
27
27
|
"Hello, #{name}!"
|
|
28
28
|
```
|
|
29
29
|
|
|
@@ -57,9 +57,9 @@ Both compile to identical JavaScript.
|
|
|
57
57
|
|
|
58
58
|
| Sigil | Meaning | Example |
|
|
59
59
|
|-------|---------|---------|
|
|
60
|
-
|
|
|
60
|
+
| `:` | Type annotation | `count: number = 0` |
|
|
61
61
|
| `type` | Type alias | `type ID = number` |
|
|
62
|
-
|
|
|
62
|
+
| `?:` | Optional parameter / field / prop | `email?: string` |
|
|
63
63
|
| `\|` | Union member | `"a" \| "b" \| "c"` |
|
|
64
64
|
| `=>` | Function type arrow | `(a: number) => string` |
|
|
65
65
|
| `<T>` | Generic parameter | `Container<T>` |
|
|
@@ -68,17 +68,17 @@ This is the complete Rip Types sigil vocabulary.
|
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
71
|
-
## Type Annotations (
|
|
71
|
+
## Type Annotations (`:`)
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
and properties.
|
|
73
|
+
A single colon `:` annotates types on variables, parameters, return values,
|
|
74
|
+
and properties. (`::` is prototype access only, e.g. `String::trim`.)
|
|
75
75
|
|
|
76
76
|
### Variables
|
|
77
77
|
|
|
78
78
|
```coffee
|
|
79
|
-
count
|
|
80
|
-
name
|
|
81
|
-
items
|
|
79
|
+
count: number = 0
|
|
80
|
+
name: string = "Rip"
|
|
81
|
+
items: string[] = []
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
**Emits:**
|
|
@@ -92,8 +92,8 @@ let items: string[]; items = [];
|
|
|
92
92
|
### Constants
|
|
93
93
|
|
|
94
94
|
```coffee
|
|
95
|
-
MAX_RETRIES
|
|
96
|
-
API_URL
|
|
95
|
+
MAX_RETRIES: number =! 3
|
|
96
|
+
API_URL: string =! "https://api.example.com"
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
**Emits:**
|
|
@@ -106,10 +106,10 @@ const API_URL: string = "https://api.example.com";
|
|
|
106
106
|
### Function Parameters
|
|
107
107
|
|
|
108
108
|
```coffee
|
|
109
|
-
def greet(name
|
|
109
|
+
def greet(name: string)
|
|
110
110
|
"Hello, #{name}!"
|
|
111
111
|
|
|
112
|
-
def add(a
|
|
112
|
+
def add(a: number, b: number)
|
|
113
113
|
a + b
|
|
114
114
|
```
|
|
115
115
|
|
|
@@ -123,10 +123,10 @@ function add(a: number, b: number) { ... }
|
|
|
123
123
|
### Return Types
|
|
124
124
|
|
|
125
125
|
```coffee
|
|
126
|
-
def getUser(id
|
|
126
|
+
def getUser(id: number): User
|
|
127
127
|
db.find!(id)
|
|
128
128
|
|
|
129
|
-
def fetchData()
|
|
129
|
+
def fetchData(): Promise<Data>
|
|
130
130
|
fetch!("/api/data")
|
|
131
131
|
```
|
|
132
132
|
|
|
@@ -140,7 +140,7 @@ async function fetchData(): Promise<Data> { ... }
|
|
|
140
140
|
### Combined
|
|
141
141
|
|
|
142
142
|
```coffee
|
|
143
|
-
def processUser(id
|
|
143
|
+
def processUser(id: number, options: Options): Result
|
|
144
144
|
user = getUser!(id)
|
|
145
145
|
transform(user, options)
|
|
146
146
|
```
|
|
@@ -156,8 +156,8 @@ function processUser(id: number, options: Options): Result { ... }
|
|
|
156
156
|
Types work with Rip's reactive operators:
|
|
157
157
|
|
|
158
158
|
```coffee
|
|
159
|
-
count
|
|
160
|
-
doubled
|
|
159
|
+
count: number := 0
|
|
160
|
+
doubled: number ~= count * 2
|
|
161
161
|
```
|
|
162
162
|
|
|
163
163
|
**Emits:**
|
|
@@ -181,8 +181,8 @@ type Name = string
|
|
|
181
181
|
|
|
182
182
|
# Complex types
|
|
183
183
|
type UserID = number | string
|
|
184
|
-
type Callback = (error
|
|
185
|
-
type Handler = (req
|
|
184
|
+
type Callback = (error: Error?, data: any) => void
|
|
185
|
+
type Handler = (req: Request, res: Response) => Promise<void>
|
|
186
186
|
```
|
|
187
187
|
|
|
188
188
|
**Emits:**
|
|
@@ -287,16 +287,16 @@ Rip uses a single optionality marker: `?` placed on the **name**
|
|
|
287
287
|
type-suffix operators — write `T | undefined`, `T | null | undefined`,
|
|
288
288
|
or `NonNullable<T>` directly when you need them.
|
|
289
289
|
|
|
290
|
-
### Optional Parameter / Field: `name
|
|
290
|
+
### Optional Parameter / Field: `name?: T`
|
|
291
291
|
|
|
292
292
|
```coffee
|
|
293
|
-
def greet(name
|
|
293
|
+
def greet(name?: string): void
|
|
294
294
|
console.log "hello #{name ?? 'world'}"
|
|
295
295
|
|
|
296
296
|
type User =
|
|
297
|
-
id
|
|
298
|
-
name
|
|
299
|
-
email
|
|
297
|
+
id: number
|
|
298
|
+
name: string
|
|
299
|
+
email?: string # Optional field — may be absent
|
|
300
300
|
```
|
|
301
301
|
|
|
302
302
|
**Emits:**
|
|
@@ -311,7 +311,7 @@ type User = {
|
|
|
311
311
|
};
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
-
### Component Props: `@prop
|
|
314
|
+
### Component Props: `@prop?: T [:= default]`
|
|
315
315
|
|
|
316
316
|
A `?` on a component prop name marks it optional in the generated
|
|
317
317
|
constructor type. A `:=` default is purely a runtime initializer — it
|
|
@@ -319,9 +319,9 @@ does NOT make the prop optional on its own. The three canonical shapes:
|
|
|
319
319
|
|
|
320
320
|
```coffee
|
|
321
321
|
export Counter = component
|
|
322
|
-
@value
|
|
323
|
-
@step
|
|
324
|
-
@label
|
|
322
|
+
@value: number # required, no default
|
|
323
|
+
@step?: number # optional, no default
|
|
324
|
+
@label?: string := "Count" # optional with default
|
|
325
325
|
```
|
|
326
326
|
|
|
327
327
|
**Emits:**
|
|
@@ -336,7 +336,7 @@ export declare class Counter {
|
|
|
336
336
|
}
|
|
337
337
|
```
|
|
338
338
|
|
|
339
|
-
Writing `@prop
|
|
339
|
+
Writing `@prop: T := V` (typed, defaulted, no `?`) declares a
|
|
340
340
|
**required prop with a default** — the parent must still pass it. To
|
|
341
341
|
make a prop optional, add `?` to the name.
|
|
342
342
|
|
|
@@ -346,9 +346,9 @@ When the **value** itself needs to permit `undefined` or `null`, write
|
|
|
346
346
|
the union explicitly:
|
|
347
347
|
|
|
348
348
|
```coffee
|
|
349
|
-
email
|
|
350
|
-
middle
|
|
351
|
-
id
|
|
349
|
+
email: string | undefined
|
|
350
|
+
middle: string | null | undefined
|
|
351
|
+
id: NonNullable<ID>
|
|
352
352
|
```
|
|
353
353
|
|
|
354
354
|
This is the same form TypeScript uses, so the emitted DTS is a
|
|
@@ -406,23 +406,23 @@ numeric values.
|
|
|
406
406
|
|
|
407
407
|
```coffee
|
|
408
408
|
# Type aliases for function signatures
|
|
409
|
-
type Comparator = (a
|
|
410
|
-
type AsyncFetcher = (url
|
|
409
|
+
type Comparator = (a: any, b: any) => number
|
|
410
|
+
type AsyncFetcher = (url: string) => Promise<Response>
|
|
411
411
|
|
|
412
412
|
# Overloads
|
|
413
|
-
def toHtml(content
|
|
414
|
-
def toHtml(nodes
|
|
415
|
-
def toHtml(input
|
|
413
|
+
def toHtml(content: string): string
|
|
414
|
+
def toHtml(nodes: Element[]): string
|
|
415
|
+
def toHtml(input: any): string
|
|
416
416
|
switch typeof input
|
|
417
417
|
when "string" then escapeHtml(input)
|
|
418
418
|
else renderNodes(input)
|
|
419
419
|
|
|
420
420
|
# Method signatures in classes
|
|
421
421
|
class UserService
|
|
422
|
-
find: (id
|
|
422
|
+
find: (id: number): User? ->
|
|
423
423
|
@db.find(id)
|
|
424
424
|
|
|
425
|
-
create: (data
|
|
425
|
+
create: (data: CreateUserInput): User ->
|
|
426
426
|
@db.create(data)
|
|
427
427
|
```
|
|
428
428
|
|
|
@@ -461,16 +461,16 @@ type Pair<K, V> =
|
|
|
461
461
|
# With constraints
|
|
462
462
|
type Comparable<T extends Ordered> =
|
|
463
463
|
value: T
|
|
464
|
-
compareTo: (other
|
|
464
|
+
compareTo: (other: T) => number
|
|
465
465
|
|
|
466
466
|
# Generic functions
|
|
467
|
-
def identity<T>(value
|
|
467
|
+
def identity<T>(value: T): T
|
|
468
468
|
value
|
|
469
469
|
|
|
470
|
-
def map<T, U>(items
|
|
470
|
+
def map<T, U>(items: T[], fn: (item: T) => U): U[]
|
|
471
471
|
items.map(fn)
|
|
472
472
|
|
|
473
|
-
def merge<T extends object, U extends object>(a
|
|
473
|
+
def merge<T extends object, U extends object>(a: T, b: U): T & U
|
|
474
474
|
{...a, ...b}
|
|
475
475
|
```
|
|
476
476
|
|
|
@@ -586,14 +586,14 @@ Types should be explicit at boundaries, optional elsewhere:
|
|
|
586
586
|
|
|
587
587
|
```coffee
|
|
588
588
|
# Explicit — function signatures, exports, class properties
|
|
589
|
-
def processUser(id
|
|
589
|
+
def processUser(id: number, options: Options): Result
|
|
590
590
|
...
|
|
591
591
|
|
|
592
|
-
export config
|
|
592
|
+
export config: Config = loadConfig()
|
|
593
593
|
|
|
594
594
|
class UserService
|
|
595
|
-
db
|
|
596
|
-
cache
|
|
595
|
+
db: Database
|
|
596
|
+
cache: Map<string, User>
|
|
597
597
|
|
|
598
598
|
# Inferred — local variables
|
|
599
599
|
user = getUser!(id) # Inferred from return type
|
|
@@ -668,7 +668,7 @@ export type User =
|
|
|
668
668
|
id: number
|
|
669
669
|
name: string
|
|
670
670
|
|
|
671
|
-
export def getUser(id
|
|
671
|
+
export def getUser(id: number): User?
|
|
672
672
|
db.find(id)
|
|
673
673
|
```
|
|
674
674
|
|
|
@@ -705,7 +705,7 @@ User = schema :input
|
|
|
705
705
|
email! email
|
|
706
706
|
|
|
707
707
|
# Validate at an API boundary
|
|
708
|
-
def createUser(req
|
|
708
|
+
def createUser(req: Request): User
|
|
709
709
|
User.parse req.json! # throws SchemaError on invalid
|
|
710
710
|
|
|
711
711
|
# Or non-throwing for structured error responses
|
|
@@ -907,7 +907,7 @@ rewriteTypes() {
|
|
|
907
907
|
this.scanTokens((token, i, tokens) => {
|
|
908
908
|
let tag = token[0];
|
|
909
909
|
|
|
910
|
-
// --- Handle
|
|
910
|
+
// --- Handle : (type annotations) ---
|
|
911
911
|
if (tag === 'TYPE_ANNOTATION') {
|
|
912
912
|
let prevToken = tokens[i - 1];
|
|
913
913
|
if (!prevToken) return 1;
|
|
@@ -998,7 +998,7 @@ rewriteTypes() {
|
|
|
998
998
|
if (k >= 0) target = tokens[k];
|
|
999
999
|
propName = 'returnType';
|
|
1000
1000
|
} else if (prevToken[0] === 'PARAM_END') {
|
|
1001
|
-
// Return type on arrow function: (x
|
|
1001
|
+
// Return type on arrow function: (x: number): string -> ...
|
|
1002
1002
|
// Scan forward past the type tokens to find the -> token.
|
|
1003
1003
|
let arrowIdx = i + 1 + typeTokens.length;
|
|
1004
1004
|
let arrowToken = tokens[arrowIdx];
|
|
@@ -1008,15 +1008,15 @@ rewriteTypes() {
|
|
|
1008
1008
|
propName = 'returnType';
|
|
1009
1009
|
} else if (prevToken[0] === 'IDENTIFIER' && i >= 2 &&
|
|
1010
1010
|
tokens[i - 2]?.[0] === 'DEF') {
|
|
1011
|
-
// Return type on parameterless function: def foo
|
|
1011
|
+
// Return type on parameterless function: def foo: string
|
|
1012
1012
|
propName = 'returnType';
|
|
1013
1013
|
}
|
|
1014
1014
|
|
|
1015
1015
|
if (!target.data) target.data = {};
|
|
1016
1016
|
target.data[propName] = typeStr;
|
|
1017
1017
|
|
|
1018
|
-
// Remove
|
|
1019
|
-
let removeCount = 1 + typeTokens.length; //
|
|
1018
|
+
// Remove : and type tokens from stream
|
|
1019
|
+
let removeCount = 1 + typeTokens.length; // : + type tokens
|
|
1020
1020
|
tokens.splice(i, removeCount);
|
|
1021
1021
|
return 0; // Re-examine current position
|
|
1022
1022
|
}
|
|
@@ -1062,14 +1062,14 @@ always ends a type expression.
|
|
|
1062
1062
|
**Worked examples:**
|
|
1063
1063
|
|
|
1064
1064
|
```
|
|
1065
|
-
INPUT: count
|
|
1066
|
-
SCAN:
|
|
1065
|
+
INPUT: count: number = 0
|
|
1066
|
+
SCAN: : → start collecting
|
|
1067
1067
|
number → depth=0, type token
|
|
1068
1068
|
= → depth=0, assignment delimiter, STOP
|
|
1069
1069
|
RESULT: type = "number"
|
|
1070
1070
|
|
|
1071
|
-
INPUT: items
|
|
1072
|
-
SCAN:
|
|
1071
|
+
INPUT: items: Map<string, number> = x
|
|
1072
|
+
SCAN: : → start collecting
|
|
1073
1073
|
Map → depth=0, type token
|
|
1074
1074
|
< → depth=1, type token
|
|
1075
1075
|
string → depth=1, type token
|
|
@@ -1079,18 +1079,18 @@ SCAN: :: → start collecting
|
|
|
1079
1079
|
= → depth=0, assignment delimiter, STOP
|
|
1080
1080
|
RESULT: type = "Map<string, number>"
|
|
1081
1081
|
|
|
1082
|
-
INPUT: def f(a
|
|
1083
|
-
SCAN:
|
|
1082
|
+
INPUT: def f(a: number, b: string)
|
|
1083
|
+
SCAN: : (after a) → start collecting
|
|
1084
1084
|
number → depth=0, type token
|
|
1085
1085
|
, → depth=0, parameter delimiter, STOP
|
|
1086
1086
|
RESULT: type on a = "number"
|
|
1087
|
-
SCAN:
|
|
1087
|
+
SCAN: : (after b) → start collecting
|
|
1088
1088
|
string → depth=0, type token
|
|
1089
1089
|
) → depth=0, close paren, STOP
|
|
1090
1090
|
RESULT: type on b = "string"
|
|
1091
1091
|
|
|
1092
|
-
INPUT: fn
|
|
1093
|
-
SCAN:
|
|
1092
|
+
INPUT: fn: (a: number, b: string) => void = ...
|
|
1093
|
+
SCAN: : → start collecting
|
|
1094
1094
|
( → depth=1, type token
|
|
1095
1095
|
a → depth=1, type token
|
|
1096
1096
|
: → depth=1, type token
|
|
@@ -1105,8 +1105,8 @@ SCAN: :: → start collecting
|
|
|
1105
1105
|
= → depth=0, assignment delimiter, STOP
|
|
1106
1106
|
RESULT: type = "(a: number, b: string) => void"
|
|
1107
1107
|
|
|
1108
|
-
INPUT: (name
|
|
1109
|
-
SCAN:
|
|
1108
|
+
INPUT: (name: string): string -> "Hello!"
|
|
1109
|
+
SCAN: : (after PARAM_END) → start collecting return type
|
|
1110
1110
|
string → depth=0, type token
|
|
1111
1111
|
-> → depth=0, code arrow delimiter, STOP
|
|
1112
1112
|
RESULT: returnType = "string"
|
|
@@ -1137,7 +1137,7 @@ collection — the token itself is the answer:
|
|
|
1137
1137
|
Return types appear after the parameter list close:
|
|
1138
1138
|
|
|
1139
1139
|
```coffee
|
|
1140
|
-
def greet(name
|
|
1140
|
+
def greet(name: string): string
|
|
1141
1141
|
```
|
|
1142
1142
|
|
|
1143
1143
|
After `rewriteTypes()` processes the `::` after `)`, the return type must be
|
|
@@ -1169,7 +1169,7 @@ Each example shows: Rip source → tokens after rewrite → s-expression → out
|
|
|
1169
1169
|
**Typed variable:**
|
|
1170
1170
|
|
|
1171
1171
|
```coffee
|
|
1172
|
-
count
|
|
1172
|
+
count: number = 0
|
|
1173
1173
|
```
|
|
1174
1174
|
|
|
1175
1175
|
Tokens after `rewriteTypes()`:
|
|
@@ -1195,7 +1195,7 @@ let count: number;
|
|
|
1195
1195
|
**Typed constant:**
|
|
1196
1196
|
|
|
1197
1197
|
```coffee
|
|
1198
|
-
MAX
|
|
1198
|
+
MAX: number =! 100
|
|
1199
1199
|
```
|
|
1200
1200
|
|
|
1201
1201
|
Tokens: `IDENTIFIER("MAX", {type: "number"}), READONLY_ASSIGN, NUMBER(100)`
|
|
@@ -1209,8 +1209,8 @@ S-expression: `["readonly", MAX, 100]` — MAX carries `.data.type = "number"`
|
|
|
1209
1209
|
**Typed reactive state:**
|
|
1210
1210
|
|
|
1211
1211
|
```coffee
|
|
1212
|
-
count
|
|
1213
|
-
doubled
|
|
1212
|
+
count: number := 0
|
|
1213
|
+
doubled: number ~= count * 2
|
|
1214
1214
|
```
|
|
1215
1215
|
|
|
1216
1216
|
S-expressions:
|
|
@@ -1228,7 +1228,7 @@ declare const doubled: Computed<number>;
|
|
|
1228
1228
|
**Typed function:**
|
|
1229
1229
|
|
|
1230
1230
|
```coffee
|
|
1231
|
-
def getUser(id
|
|
1231
|
+
def getUser(id: number): User
|
|
1232
1232
|
db.find!(id)
|
|
1233
1233
|
```
|
|
1234
1234
|
|
|
@@ -1250,7 +1250,7 @@ S-expression: `["def", getUser, [id], body]`
|
|
|
1250
1250
|
**Typed arrow function:**
|
|
1251
1251
|
|
|
1252
1252
|
```coffee
|
|
1253
|
-
greet = (name
|
|
1253
|
+
greet = (name: string): string -> "Hello, #{name}!"
|
|
1254
1254
|
```
|
|
1255
1255
|
|
|
1256
1256
|
Tokens after rewrite:
|
|
@@ -1264,8 +1264,8 @@ PARAM_END, ->({returnType: "string"}), ...
|
|
|
1264
1264
|
|
|
1265
1265
|
```coffee
|
|
1266
1266
|
class UserService
|
|
1267
|
-
db
|
|
1268
|
-
cache
|
|
1267
|
+
db: Database
|
|
1268
|
+
cache: Map<string, User>
|
|
1269
1269
|
```
|
|
1270
1270
|
|
|
1271
1271
|
Tokens: identifiers carry `.data.type`, grammar sees normal class body.
|
|
@@ -1592,10 +1592,10 @@ the rewriter and removed before parsing.
|
|
|
1592
1592
|
#### 2.6 Generic Type Parameters
|
|
1593
1593
|
|
|
1594
1594
|
```coffee
|
|
1595
|
-
def identity<T>(value
|
|
1595
|
+
def identity<T>(value: T): T
|
|
1596
1596
|
value
|
|
1597
1597
|
|
|
1598
|
-
def map<T, U>(items
|
|
1598
|
+
def map<T, U>(items: T[], fn: (item: T) => U): U[]
|
|
1599
1599
|
items.map(fn)
|
|
1600
1600
|
```
|
|
1601
1601
|
|
|
@@ -1752,12 +1752,12 @@ A `?` placed unspaced **before** the `::` annotation on a parameter,
|
|
|
1752
1752
|
field, or component prop name marks the name as optional:
|
|
1753
1753
|
|
|
1754
1754
|
```coffee
|
|
1755
|
-
def fetch(url
|
|
1755
|
+
def fetch(url: string, opts?: RequestInit): Response
|
|
1756
1756
|
...
|
|
1757
1757
|
|
|
1758
1758
|
type User =
|
|
1759
|
-
id
|
|
1760
|
-
email
|
|
1759
|
+
id: number
|
|
1760
|
+
email?: string # optional field
|
|
1761
1761
|
```
|
|
1762
1762
|
|
|
1763
1763
|
In the lexer, when `?` appears immediately before `::`, it is stripped
|
|
@@ -1781,7 +1781,7 @@ export type User =
|
|
|
1781
1781
|
id: number
|
|
1782
1782
|
name: string
|
|
1783
1783
|
|
|
1784
|
-
export def getUser(id
|
|
1784
|
+
export def getUser(id: number): User?
|
|
1785
1785
|
db.find(id)
|
|
1786
1786
|
```
|
|
1787
1787
|
|
|
@@ -1812,23 +1812,23 @@ Each row is a test case. Verify both .js and .d.ts output.
|
|
|
1812
1812
|
|
|
1813
1813
|
| # | Rip Input | .js Output | .d.ts Output |
|
|
1814
1814
|
|---|-----------|-----------|-------------|
|
|
1815
|
-
| 1 | `count
|
|
1816
|
-
| 2 | `MAX
|
|
1817
|
-
| 3 | `count
|
|
1818
|
-
| 4 | `doubled
|
|
1819
|
-
| 5 | `def f(a
|
|
1820
|
-
| 6 | `(x
|
|
1815
|
+
| 1 | `count: number = 0` | `count = 0` | `let count: number;` |
|
|
1816
|
+
| 2 | `MAX: number =! 100` | `const MAX = 100` | `declare const MAX: number;` |
|
|
1817
|
+
| 3 | `count: number := 0` | `const count = __state(0)` | `declare const count: Signal<number>;` |
|
|
1818
|
+
| 4 | `doubled: number ~= x * 2` | `const doubled = __computed(...)` | `declare const doubled: Computed<number>;` |
|
|
1819
|
+
| 5 | `def f(a: number): string` | `function f(a) { ... }` | `declare function f(a: number): string;` |
|
|
1820
|
+
| 6 | `(x: number): number -> x + 1` | `(x) => x + 1` | `(x: number) => number` |
|
|
1821
1821
|
| 7 | `type ID = number` | *(empty)* | `type ID = number;` |
|
|
1822
1822
|
| 8 | `type User =` (+ block) | *(empty)* | `type User = { id: number; ... };` |
|
|
1823
1823
|
| 9 | `type Status = \| "a" \| "b"` | *(empty)* | `type Status = "a" \| "b";` |
|
|
1824
1824
|
| 10 | `interface Foo` (+ block) | *(empty)* | `interface Foo { ... }` |
|
|
1825
1825
|
| 11 | `enum Code` (+ block) | `const Code = { ... }` | `enum Code { ... }` |
|
|
1826
|
-
| 12 | `items
|
|
1827
|
-
| 13 | `email
|
|
1828
|
-
| 14 | `id
|
|
1829
|
-
| 15 | `def identity<T>(v
|
|
1826
|
+
| 12 | `items: Map<string, number> = x` | `items = x` | `let items: Map<string, number>;` |
|
|
1827
|
+
| 13 | `email: string?` | — | `let email: string \| undefined;` |
|
|
1828
|
+
| 14 | `id: ID!` | — | `let id: NonNullable<ID>;` |
|
|
1829
|
+
| 15 | `def identity<T>(v: T): T` | `function identity(v) { ... }` | `declare function identity<T>(v: T): T;` |
|
|
1830
1830
|
| 16 | `export type User =` (+ block) | *(empty)* | `export type User = { ... };` |
|
|
1831
|
-
| 17 | `export def f(x
|
|
1831
|
+
| 17 | `export def f(x: number): number` | `export function f(x) { ... }` | `export function f(x: number): number;` |
|
|
1832
1832
|
|
|
1833
1833
|
---
|
|
1834
1834
|
|
|
@@ -1859,21 +1859,21 @@ The type system uses a two-phase approach:
|
|
|
1859
1859
|
|
|
1860
1860
|
| Rip Input | .d.ts Output |
|
|
1861
1861
|
|-----------|-------------|
|
|
1862
|
-
| `x
|
|
1863
|
-
| `x
|
|
1864
|
-
| `x
|
|
1865
|
-
| `x
|
|
1866
|
-
| `x
|
|
1862
|
+
| `x: number = 0` | `let x: number;` |
|
|
1863
|
+
| `x: number =! 100` | `declare const x: number;` |
|
|
1864
|
+
| `x: number := 0` | `declare const x: Signal<number>;` |
|
|
1865
|
+
| `x: number ~= y * 2` | `declare const x: Computed<number>;` |
|
|
1866
|
+
| `x: Function ~> ...` | `declare const x: () => void;` |
|
|
1867
1867
|
| `export` variants of all above | `export const/let ...` |
|
|
1868
1868
|
|
|
1869
1869
|
**Functions:**
|
|
1870
1870
|
|
|
1871
1871
|
| Rip Input | .d.ts Output |
|
|
1872
1872
|
|-----------|-------------|
|
|
1873
|
-
| `def f(a
|
|
1874
|
-
| `def f<T>(a
|
|
1873
|
+
| `def f(a: T): R` | `declare function f(a: T): R;` |
|
|
1874
|
+
| `def f<T>(a: T): T` | `declare function f<T>(a: T): T;` |
|
|
1875
1875
|
| `def f<T extends Base>(...)` | `declare function f<T extends Base>(...);` |
|
|
1876
|
-
| `f = (a
|
|
1876
|
+
| `f = (a: T) -> ...` | `declare function f(a: T);` |
|
|
1877
1877
|
| `export def ...` | `export function ...;` |
|
|
1878
1878
|
|
|
1879
1879
|
**Types, Interfaces, and Enums:**
|
|
@@ -1892,9 +1892,9 @@ The type system uses a two-phase approach:
|
|
|
1892
1892
|
|
|
1893
1893
|
| Rip Input | .d.ts Output |
|
|
1894
1894
|
|-----------|-------------|
|
|
1895
|
-
| `class X { prop
|
|
1896
|
-
| `constructor: (@name
|
|
1897
|
-
| `method: (a
|
|
1895
|
+
| `class X { prop: T = val }` | `declare class X { prop: T; }` |
|
|
1896
|
+
| `constructor: (@name: T) ->` | `constructor(name: T);` |
|
|
1897
|
+
| `method: (a: T) ->` | `method(a: T);` |
|
|
1898
1898
|
| `class X extends Y` | `declare class X extends Y { ... }` |
|
|
1899
1899
|
|
|
1900
1900
|
**Components:**
|
|
@@ -1902,7 +1902,7 @@ The type system uses a two-phase approach:
|
|
|
1902
1902
|
| Rip Input | .d.ts Output |
|
|
1903
1903
|
|-----------|-------------|
|
|
1904
1904
|
| `Name = component { ... }` | `declare class Name { ... }` |
|
|
1905
|
-
| Reactive state `count
|
|
1905
|
+
| Reactive state `count: number := 0` | `count: number;` |
|
|
1906
1906
|
| Computed `doubled ~= ...` | `readonly doubled: any;` |
|
|
1907
1907
|
| Methods | Method declarations |
|
|
1908
1908
|
| Auto-generated | `constructor`, `mount()`, `unmount()` |
|
package/docs/dist/rip.js
CHANGED
|
@@ -1036,6 +1036,13 @@ class __SchemaDef {
|
|
|
1036
1036
|
// a non-enumerable accessor so order.user_id and order.userId read
|
|
1037
1037
|
// the same slot — useful when DB column names leak into user code
|
|
1038
1038
|
// via raw SQL helpers.
|
|
1039
|
+
//
|
|
1040
|
+
// Temporal values arrive already decoded by the adapter: @rip-lang/db
|
|
1041
|
+
// decodes date/datetime columns to real \`Date\` at the wire boundary
|
|
1042
|
+
// (naive TIMESTAMP is read as UTC), so hydrate stores them verbatim —
|
|
1043
|
+
// do NOT add a decode here. That keeps a single decode seam and honors
|
|
1044
|
+
// the \`date\`/\`datetime\` -> \`Date\` .d.ts contract. (\`_coerceDates\` below
|
|
1045
|
+
// is parse-only, for JSON/HTTP input — it never runs on this path.)
|
|
1039
1046
|
const data = {};
|
|
1040
1047
|
for (let i = 0; i < columns.length; i++) {
|
|
1041
1048
|
data[__schemaCamel(columns[i].name)] = row[i];
|
|
@@ -7303,8 +7310,7 @@ Expecting ${expected.join(", ")}, got '${this.tokenNames[symbol] || symbol}'`;
|
|
|
7303
7310
|
this.emit(".", ".");
|
|
7304
7311
|
return 2;
|
|
7305
7312
|
} else if (val === "::") {
|
|
7306
|
-
|
|
7307
|
-
this.inTypeAnnotation = true;
|
|
7313
|
+
syntaxError("type annotations use a single ':' (e.g. `x: number`), not '::'", { row: this.row, col: this.col, len: 2 });
|
|
7308
7314
|
} else if (val === "~=")
|
|
7309
7315
|
tag = "COMPUTED_ASSIGN";
|
|
7310
7316
|
else if (val === ":=")
|
|
@@ -16938,8 +16944,8 @@ if (typeof globalThis !== 'undefined') {
|
|
|
16938
16944
|
return new CodeEmitter({}).getComponentRuntime();
|
|
16939
16945
|
}
|
|
16940
16946
|
// src/browser.js
|
|
16941
|
-
var VERSION = "3.17.
|
|
16942
|
-
var BUILD_DATE = "2026-06-28@
|
|
16947
|
+
var VERSION = "3.17.4";
|
|
16948
|
+
var BUILD_DATE = "2026-06-28@05:09:42GMT";
|
|
16943
16949
|
if (typeof globalThis !== "undefined") {
|
|
16944
16950
|
if (!globalThis.__rip)
|
|
16945
16951
|
new Function(getReactiveRuntime())();
|