@slim-lang/core 1.2.0
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 +666 -0
- package/package.json +55 -0
- package/packages/slim/.spm +7 -0
- package/packages/slim/converters/main.slim +106 -0
- package/packages/slim/helpers/array.slim +25 -0
- package/packages/slim/helpers/path.slim +3 -0
- package/packages/slim/helpers/request.slim +102 -0
- package/packages/slim/helpers/string.slim +27 -0
- package/packages/slim/main.slim +42 -0
- package/packages/slim/parse/main.slim +25 -0
- package/packages/slim/server/main.slim +423 -0
- package/packages/slim/time/main.slim +66 -0
- package/packages/slim/types/common.slim +6 -0
- package/packages/slim/types/formats.slim +23 -0
- package/packages/slim/types/hash.slim +6 -0
- package/packages/slim/types/mails.slim +3 -0
- package/packages/slim/types/numerical.slim +9 -0
- package/packages/slim/types/time.slim +3 -0
- package/run-dev-slim.js +133 -0
- package/run-slim.js +20 -0
- package/src/bin/api/github_auth.js +89 -0
- package/src/bin/api/github_get.js +139 -0
- package/src/bin/api/github_req.js +455 -0
- package/src/bin/api/lock.js +37 -0
- package/src/bin/api/spm.js +103 -0
- package/src/bin/api/storage.js +30 -0
- package/src/bin/cli.js +404 -0
- package/src/bin/config.default.json +5 -0
- package/src/bin/helpers.js +147 -0
- package/src/bin/parsers/spm.js +174 -0
- package/src/bin/spm.js +519 -0
- package/src/checker.js +926 -0
- package/src/compile.js +230 -0
- package/src/external/classErrors.js +202 -0
- package/src/external/client.js +38 -0
- package/src/external/core.js +861 -0
- package/src/external/defaults.js +25 -0
- package/src/external/helpers.js +541 -0
- package/src/external/slim-globals.d.ts +65 -0
- package/src/external/types.js +38 -0
- package/src/format.js +81 -0
- package/src/handlers/errorHandler.js +43 -0
- package/src/handlers/parser/components.js +250 -0
- package/src/handlers/parserHandler.js +793 -0
- package/src/jsdoc.js +273 -0
- package/src/lexer.js +174 -0
- package/src/modulePaths.js +74 -0
- package/src/parser.js +818 -0
- package/src/repl.js +32 -0
- package/src/sourcemap.js +0 -0
- package/src/test-runner.js +62 -0
- package/src/transform.js +765 -0
package/README.md
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img width="800" src="https://github.com/user-attachments/assets/29849f19-9e0f-49d0-a188-74714c14b0a4" />
|
|
3
|
+
<h1>Slim</h1>
|
|
4
|
+
<h4 align="center">Slim extends JavaScript with runtime types, structs, operators, and components, compiling to plain Javascript</h4>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div align="center">
|
|
8
|
+
<a href="https://codemotion.yurba.one/github">CodeMotion IDE</a>
|
|
9
|
+
⋅
|
|
10
|
+
<a href="https://codemotion.yurba.one/telegram">Telegram (News on russian)</a>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<br>
|
|
14
|
+
|
|
15
|
+
> [!IMPORTANT]
|
|
16
|
+
> Slim is still early in development. Please report bugs in Issues and send pull requests for improvements.
|
|
17
|
+
|
|
18
|
+
## Why Slim?
|
|
19
|
+
|
|
20
|
+
Slim is for projects where data validation needs to stay in the running program. It compiles to JavaScript and works directly with npm packages and the JavaScript ecosystem.
|
|
21
|
+
|
|
22
|
+
| | JavaScript | TypeScript | Slim |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| Static checks | No | Yes | Yes, where possible |
|
|
25
|
+
| Runtime validation | Manual | Manual or libraries | Built in for typed values and structs |
|
|
26
|
+
| Data modelling | Objects and classes | Interfaces and types | Structs, enums, defaults, inheritance, validators |
|
|
27
|
+
| API and external data | Trust it or validate manually | Types are erased at runtime | Validate at the boundary with a `struct` or `type` |
|
|
28
|
+
| Components | Native APIs or a framework | Native APIs or a framework | Components and custom elements |
|
|
29
|
+
| Tooling | Depends on the project | Depends on the project | Compiler, formatter, test runner, REPL, dev server, and package manager |
|
|
30
|
+
|
|
31
|
+
Slim does not aim to replace TypeScript everywhere. It is useful when runtime guarantees and built-in language tools matter.
|
|
32
|
+
|
|
33
|
+
## Where Can Slim Be Useful?
|
|
34
|
+
|
|
35
|
+
Slim fits JavaScript projects that need runtime validation at data boundaries: API clients and servers, CLI tools, scripts, UI forms, libraries, and embedded JavaScript environments. It adds structs, validators, and language features that otherwise require separate libraries or conventions.
|
|
36
|
+
|
|
37
|
+
## How To Install?
|
|
38
|
+
|
|
39
|
+
Requirements:
|
|
40
|
+
- Node.js 22+
|
|
41
|
+
- Git
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
Create a project folder, then run:
|
|
45
|
+
|
|
46
|
+
- Clone project
|
|
47
|
+
|
|
48
|
+
```console
|
|
49
|
+
git clone https://github.com/cdmtn/slim-lang.git
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- Install all deps
|
|
53
|
+
|
|
54
|
+
```console
|
|
55
|
+
npm i
|
|
56
|
+
npm link
|
|
57
|
+
```
|
|
58
|
+
On mac/linux this may cause a permission error, try using `sudo npm link`
|
|
59
|
+
|
|
60
|
+
- Check if all Slim CLI Installed
|
|
61
|
+
|
|
62
|
+
```console
|
|
63
|
+
slmc --version
|
|
64
|
+
spm --version
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- Create Slim config:
|
|
68
|
+
|
|
69
|
+
```console
|
|
70
|
+
slmc create --config
|
|
71
|
+
slmc config -S main=index
|
|
72
|
+
```
|
|
73
|
+
- Create `.slim` file:
|
|
74
|
+
|
|
75
|
+
```console
|
|
76
|
+
slmc create --file index
|
|
77
|
+
slmc run
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This creates `slimconfig.json`, sets `index` as the entry point, then compiles and runs `index.slim`.
|
|
81
|
+
|
|
82
|
+
Or scaffold everything at once — `slimconfig.json` plus a starter `index.slim`:
|
|
83
|
+
|
|
84
|
+
```console
|
|
85
|
+
slmc init
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Examples
|
|
89
|
+
|
|
90
|
+
Slim keeps type checks at runtime; they are not erased during compilation.
|
|
91
|
+
|
|
92
|
+
**Examples of runtime data validation structures:**
|
|
93
|
+
```cpp
|
|
94
|
+
struct User {
|
|
95
|
+
name: string | any
|
|
96
|
+
id: int
|
|
97
|
+
roles: string[] // string array (string[])
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Let's assume that the data came from an API
|
|
101
|
+
const user = {
|
|
102
|
+
name: "John",
|
|
103
|
+
id: 3,
|
|
104
|
+
roles: [] // null array (null[])
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
User.verify(user) // ❌ StructError: "User.roles" expected string[], got null[]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Built-in Operators**:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// sizeof
|
|
114
|
+
|
|
115
|
+
log(sizeof [1, 2, 3]) // 3
|
|
116
|
+
log(sizeof { key: "value" }) // 1
|
|
117
|
+
|
|
118
|
+
// empty
|
|
119
|
+
|
|
120
|
+
log(empty []) // true
|
|
121
|
+
log(empty {}) // true
|
|
122
|
+
log(empty null) // true
|
|
123
|
+
log(empty [1, 2, 3]) // false
|
|
124
|
+
|
|
125
|
+
// kindof
|
|
126
|
+
|
|
127
|
+
log(kindof []) // null[]
|
|
128
|
+
log(kindof [1, 2, 3]) // int[]
|
|
129
|
+
log(kindof null) // null
|
|
130
|
+
log(kindof [1, "hello"]) // array
|
|
131
|
+
log(kindof 1.5) // float
|
|
132
|
+
|
|
133
|
+
// or / and (lower into || and &&)
|
|
134
|
+
|
|
135
|
+
log(true or false) // true
|
|
136
|
+
log(true and false) // false
|
|
137
|
+
|
|
138
|
+
// lock
|
|
139
|
+
|
|
140
|
+
const a = { name: "John" }
|
|
141
|
+
lock a;
|
|
142
|
+
|
|
143
|
+
a.name = "Arthur" // ❌ Error: Cannot assign to read only property 'name' of object '#<Object>'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Basic enum example:**
|
|
147
|
+
|
|
148
|
+
```cpp
|
|
149
|
+
enum Role {
|
|
150
|
+
Member: 0
|
|
151
|
+
Helper: 1
|
|
152
|
+
Admin: 2
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const user = {
|
|
156
|
+
name: "John",
|
|
157
|
+
role: 0
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if(user.role == Role.Member) log(true) // true
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Enum members can also be used in struct annotations:
|
|
164
|
+
|
|
165
|
+
```cpp
|
|
166
|
+
enum Role {
|
|
167
|
+
Member: 0
|
|
168
|
+
Helper: 1
|
|
169
|
+
Admin: 2
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const user = {
|
|
173
|
+
name: "John",
|
|
174
|
+
role: Role.Admin
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
struct User {
|
|
178
|
+
name: string
|
|
179
|
+
role: Role::Helper
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
User.verify(user) // ❌ StructError: "User.role" expected Role::Helper, got int
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Struct fields may use enums and other structs as types.
|
|
186
|
+
|
|
187
|
+
A trailing `?` marks a type nullable (`T?` means `T | null | undefined`):
|
|
188
|
+
|
|
189
|
+
```cpp
|
|
190
|
+
let name: string? = null // ok
|
|
191
|
+
name = "Slim" // ok
|
|
192
|
+
name = 42 // ❌ TypeError
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Tuple types validate a fixed-length array element by element:
|
|
196
|
+
|
|
197
|
+
```cpp
|
|
198
|
+
let pair: [int, string] = [1, "a"] // ok
|
|
199
|
+
let bad: [int, string] = [1] // ❌ wrong length
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
You can destructure a typed value; the source is validated before it is unpacked:
|
|
203
|
+
|
|
204
|
+
```cpp
|
|
205
|
+
struct User { name: string
|
|
206
|
+
id: int }
|
|
207
|
+
|
|
208
|
+
const { name, id }: User = payload // throws if payload is not a valid User
|
|
209
|
+
const [x, y]: [int, int] = point
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Types combine with `|` (union — matches any) and `&` (intersection — matches all):
|
|
213
|
+
|
|
214
|
+
```cpp
|
|
215
|
+
type Positive(v) { return v > 0 }
|
|
216
|
+
type Even(v) { return v % 2 == 0 }
|
|
217
|
+
|
|
218
|
+
let n: Positive & Even = 4 // ok
|
|
219
|
+
let m: Positive & Even = 3 // ❌ TypeError: expected Positive & Even
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Built-in Runtime Validators
|
|
223
|
+
|
|
224
|
+
Slim ships common validator types you can use in any annotation: `email`, `url`, `uuid`, `positive`, `negative`, `natural`, `nonempty`.
|
|
225
|
+
|
|
226
|
+
```cpp
|
|
227
|
+
struct Account {
|
|
228
|
+
email: email
|
|
229
|
+
balance: positive
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let id: uuid = "550e8400-e29b-41d4-a716-446655440000"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Struct Defaults, Constructors, and Inheritance
|
|
236
|
+
|
|
237
|
+
Struct fields can declare defaults. `Struct.new(...)` fills them in and validates the result:
|
|
238
|
+
|
|
239
|
+
```cpp
|
|
240
|
+
struct User {
|
|
241
|
+
name: string
|
|
242
|
+
role: string = "member"
|
|
243
|
+
active: bool = true
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const u = User.new({ name: "Alice" }) // { name: "Alice", role: "member", active: true }
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
A struct can `extend` another, inheriting its fields and defaults:
|
|
250
|
+
|
|
251
|
+
```cpp
|
|
252
|
+
struct Admin extends User {
|
|
253
|
+
level: int
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const a = Admin.new({ name: "Bob", level: 9 }) // role defaults to "member"
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Structs can also carry methods (available on `Struct.new(...)` instances, inherited through `extends`):
|
|
260
|
+
|
|
261
|
+
```cpp
|
|
262
|
+
struct Greeter {
|
|
263
|
+
name: string
|
|
264
|
+
greet() { return "Hi " + this.name }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
Greeter.new({ name: "Ada" }).greet() // "Hi Ada"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Return Types
|
|
271
|
+
|
|
272
|
+
A function can declare its return type with `-> Type`. Slim checks each `return` and verifies the value at runtime:
|
|
273
|
+
|
|
274
|
+
```cpp
|
|
275
|
+
func parse(raw: string) -> int {
|
|
276
|
+
return JSON.parse(raw).value
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
parse(`{ "value": 7 }`) // 7
|
|
280
|
+
parse(`{ "value": "seven" }`) // ❌ function "parse" must return int, got string: "seven"
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
This works with `func`, methods, arrows, and `async` functions. Nested functions keep their own return type:
|
|
284
|
+
|
|
285
|
+
```cpp
|
|
286
|
+
const double = (n: int) -> int => n * 2
|
|
287
|
+
|
|
288
|
+
async func load(id: int) -> User {
|
|
289
|
+
return await fetchUser(id)
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
With JSDoc or declarations enabled, the return type is also available to TypeScript (`async` becomes `Promise<T>`).
|
|
294
|
+
|
|
295
|
+
## Generic Containers
|
|
296
|
+
|
|
297
|
+
`Array<T>`, `Set<T>`, and `Map<K, V>` validate their contents. `Array<T>` and `T[]` are equivalent:
|
|
298
|
+
|
|
299
|
+
```cpp
|
|
300
|
+
let ids: Array<int> = [1, 2] // ok
|
|
301
|
+
let names: Set<string> = new Set(["a"]) // ok
|
|
302
|
+
let ages: Map<string, int> = new Map([["a", 1]]) // ok
|
|
303
|
+
|
|
304
|
+
let mixed: Array<int> = JSON.parse(`[1, "a"]`) // ❌ rejected: "a" is not an int
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`Promise<T>` and bare built-ins (`Map`, `Set`, `Date`, `RegExp`, `Error`) are matched by constructor. A promise resolves too late for its contents to be checked.
|
|
308
|
+
|
|
309
|
+
## Match Expressions
|
|
310
|
+
|
|
311
|
+
`match` returns the branch whose pattern matches the subject. `_` is the fallback; without it, an unmatched value returns `undefined`. Enum members compare by value.
|
|
312
|
+
|
|
313
|
+
```cpp
|
|
314
|
+
enum Role {
|
|
315
|
+
Member: 0
|
|
316
|
+
Admin: 2
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const label = match (user.role) {
|
|
320
|
+
Role.Admin => "administrator",
|
|
321
|
+
Role.Member => "member",
|
|
322
|
+
_ => "guest"
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
A case can bind the subject and add a `when` guard:
|
|
327
|
+
|
|
328
|
+
```cpp
|
|
329
|
+
const size = match (n) {
|
|
330
|
+
x when x > 10 => "big",
|
|
331
|
+
x when x > 0 => "small",
|
|
332
|
+
_ => "nonpositive"
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
A `match` over an enum has to handle every member, or say it does not with `_`:
|
|
337
|
+
|
|
338
|
+
```cpp
|
|
339
|
+
enum Role { Member: 0
|
|
340
|
+
Admin: 2 }
|
|
341
|
+
|
|
342
|
+
match (role) {
|
|
343
|
+
Role.Admin => "administrator"
|
|
344
|
+
} // ❌ match on "Role" does not handle Role.Member — add the missing case or a "_" fallback
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Slim skips this check for guarded cases and non-enum subjects.
|
|
348
|
+
|
|
349
|
+
## Error Handling
|
|
350
|
+
|
|
351
|
+
An uncaught top-level error is reported and exits the process. Use a handler when the process should stay alive:
|
|
352
|
+
|
|
353
|
+
```cpp
|
|
354
|
+
onError((err) => {
|
|
355
|
+
log("handled:", err.message)
|
|
356
|
+
})
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Testing
|
|
360
|
+
|
|
361
|
+
Write tests in `.slim` files named `*.test.slim` using the built-in `test`, `assert`, and `assertEqual`:
|
|
362
|
+
|
|
363
|
+
```cpp
|
|
364
|
+
test("addition works", () => {
|
|
365
|
+
assertEqual(2 + 2, 4)
|
|
366
|
+
})
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Run them all with:
|
|
370
|
+
|
|
371
|
+
```console
|
|
372
|
+
slmc test
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Formatting
|
|
376
|
+
|
|
377
|
+
Reindent Slim source (strings, templates, and comments are left untouched):
|
|
378
|
+
|
|
379
|
+
```console
|
|
380
|
+
slmc fmt index
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## REPL
|
|
384
|
+
|
|
385
|
+
Bindings persist between REPL lines:
|
|
386
|
+
|
|
387
|
+
```console
|
|
388
|
+
slmc repl
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
slim> struct User { name: string }
|
|
393
|
+
slim> log(User.new({ name: "Ada" }).name)
|
|
394
|
+
Ada
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Static Type Checking
|
|
398
|
+
|
|
399
|
+
Before building, Slim checks literals, arithmetic, arrays, struct instances, and typed parameters. A definite conflict is a compile error, so no output is written to `dist`:
|
|
400
|
+
|
|
401
|
+
```console
|
|
402
|
+
TypeError: "User.age" expects int, got string
|
|
403
|
+
at index.slim:10:37
|
|
404
|
+
|
|
405
|
+
const u: User = { name: "Ada", age: "old" }
|
|
406
|
+
^
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
It catches invalid literals and reassignments, invalid struct fields, missing field access, array or tuple element errors, incompatible returns, incomplete enum matches, and calls with the wrong arguments:
|
|
410
|
+
|
|
411
|
+
```cpp
|
|
412
|
+
struct User {
|
|
413
|
+
name: string
|
|
414
|
+
age: int
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
func greet(user: User, times: int) { return user.name }
|
|
418
|
+
|
|
419
|
+
let n: int = 1.5 // ❌ expects int, got float
|
|
420
|
+
const u: User = { name: "Ada", age: "old" } // ❌ "User.age" expects int, got string
|
|
421
|
+
greet(u) // ❌ expects 2 arguments, got 1
|
|
422
|
+
log(u.nmae) // ❌ "User" has no field "nmae"
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Inference is intentionally conservative. Calls into npm packages, unknown imports, and custom `type` validators remain unknown and are checked at runtime. Slim reports only conflicts it can prove, so normal narrowing with unions remains valid:
|
|
426
|
+
|
|
427
|
+
```cpp
|
|
428
|
+
let v: int | string = 1
|
|
429
|
+
|
|
430
|
+
if (kindof v == "int") {
|
|
431
|
+
let n: int = v // fine — the runtime confirms it
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
let b: bool = v // ❌ no arm of int | string can ever be a bool
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Checks follow `use` across files, so imported structs, enums, custom types, and function signatures are checked at their call sites:
|
|
438
|
+
|
|
439
|
+
```cpp
|
|
440
|
+
// models.slim
|
|
441
|
+
export struct User {
|
|
442
|
+
name: string
|
|
443
|
+
role: Role
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export func greet(user: User) -> string { return user.name }
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
```cpp
|
|
450
|
+
// index.slim
|
|
451
|
+
use { User, greet } from "./models"
|
|
452
|
+
|
|
453
|
+
const u: User = { name: 1 } // ❌ "User.name" expects string, got int
|
|
454
|
+
let n: int = greet(u) // ❌ "n" expects int, got string
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Inherited struct fields and types used only inside an imported module are also resolved. A local declaration takes precedence over an imported name.
|
|
458
|
+
|
|
459
|
+
Static checks run before runtime validation. Disable them with `slmc build --no-check` or in the config:
|
|
460
|
+
|
|
461
|
+
```json
|
|
462
|
+
{
|
|
463
|
+
"main": "index",
|
|
464
|
+
"check": false
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Release Mode
|
|
469
|
+
|
|
470
|
+
Typed variables, parameters, and struct fields are validated at runtime by default. Production builds can remove those implicit checks while keeping explicit `struct.verify()` calls, operators, and `lock`:
|
|
471
|
+
|
|
472
|
+
```console
|
|
473
|
+
slmc run --release
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
This sets `SLIM_RELEASE=1`. You can use the same variable when running compiled output directly:
|
|
477
|
+
|
|
478
|
+
```console
|
|
479
|
+
SLIM_RELEASE=1 node dist/index.js
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## TypeScript-Compatible Types (JSDoc)
|
|
483
|
+
|
|
484
|
+
Slim can add JSDoc to compiled JavaScript so editors and `tsc --checkJs` understand Slim types. Enable it in `slimconfig.json`:
|
|
485
|
+
|
|
486
|
+
```json
|
|
487
|
+
{
|
|
488
|
+
"main": "index",
|
|
489
|
+
"jsdoc": true
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Structs, typed functions, and typed variables then compile to plain JS with JSDoc:
|
|
494
|
+
|
|
495
|
+
```js
|
|
496
|
+
/**
|
|
497
|
+
* @typedef {{ name: string, id: number, roles: string[] }} User
|
|
498
|
+
*/
|
|
499
|
+
|
|
500
|
+
/** @param {User} user */
|
|
501
|
+
function greet(user) { /* runtime check + return */ }
|
|
502
|
+
|
|
503
|
+
/** @type {number} */
|
|
504
|
+
let count = 0
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Each `struct` provides a runtime validator and a static type. `int` and `float` map to `number`, `bool` to `boolean`, and optional fields to `field?`. Enums and custom types emit typedefs too.
|
|
508
|
+
|
|
509
|
+
With `jsdoc` enabled, Slim adds `// @ts-check`, writes `dist/external/slim-globals.d.ts`, and creates `jsconfig.json` when one does not exist:
|
|
510
|
+
|
|
511
|
+
```console
|
|
512
|
+
npx tsc -p jsconfig.json
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
TypeScript flags invalid arguments and property access. Slim continues to validate typed variable initializers and reassignments at runtime.
|
|
516
|
+
|
|
517
|
+
### Publishing a Slim library for TypeScript
|
|
518
|
+
|
|
519
|
+
For a Slim library consumed from TypeScript, enable declaration files. Each compiled module receives a `.d.ts` sidecar for exported structs, enums, custom types, and typed functions:
|
|
520
|
+
|
|
521
|
+
```json
|
|
522
|
+
{
|
|
523
|
+
"main": "index",
|
|
524
|
+
"declarations": true
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Point `package.json` at the generated entry declaration so consumers pick it up:
|
|
529
|
+
|
|
530
|
+
```json
|
|
531
|
+
{
|
|
532
|
+
"types": "dist/index.d.ts"
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
A TypeScript project can then import `User` and `greet` from the package with type checking intact.
|
|
537
|
+
|
|
538
|
+
## Custom Elements
|
|
539
|
+
|
|
540
|
+
Mark a component as `element` to compile it as both a custom element and a function. `Tab` becomes `slim-tab`, `TabBar` becomes `slim-tab-bar`, and the tag is available as `Tab.tag`:
|
|
541
|
+
|
|
542
|
+
```cpp
|
|
543
|
+
element component Tab(props) {
|
|
544
|
+
onMount((host) => {
|
|
545
|
+
host.setLabel = (text) => { host.querySelector(".label").textContent = text }
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
onConnect((host) => { log("in the document") })
|
|
549
|
+
onUnmount((host) => { log("removed") })
|
|
550
|
+
|
|
551
|
+
return <div class="tab"><span class="label">${props.label}</span></div>
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
```html
|
|
556
|
+
<slim-tab label="Overview"></slim-tab>
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
The host element is the mount target, and `onMount` receives it. In this example, `host.setLabel` becomes a method of `<slim-tab>`:
|
|
560
|
+
`document.querySelector("slim-tab").setLabel("…")`.
|
|
561
|
+
|
|
562
|
+
`element` also keeps the component callable as a regular function:
|
|
563
|
+
|
|
564
|
+
```js
|
|
565
|
+
EmptyState({ icon: "inbox" }) // the rendered element, as before
|
|
566
|
+
document.createElement("empty-state") // the same component, as a tag
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
`onConnect` maps to `connectedCallback`; `onUnmount` maps to `disconnectedCallback`.
|
|
570
|
+
|
|
571
|
+
Interpolating a component preserves the same DOM node, including its methods and listeners:
|
|
572
|
+
|
|
573
|
+
```cpp
|
|
574
|
+
component SideLeft(props) {
|
|
575
|
+
onMount((el) => { el.setActive = (id) => { /* … */ } })
|
|
576
|
+
return <nav class="side-left">${props.items}</nav>
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
element("music-left") component MusicLeft() {
|
|
580
|
+
return ${SideLeft({ items: playlists() })}
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
`document.querySelector("music-left").setActive(…)` reaches the inner component method.
|
|
585
|
+
|
|
586
|
+
Props can come from attributes or the `props` property. Properties take precedence, accept any value, and trigger a re-render when reassigned. Values set before the element upgrades are preserved:
|
|
587
|
+
|
|
588
|
+
```js
|
|
589
|
+
const tab = document.createElement("slim-tab")
|
|
590
|
+
tab.props = { label: "Overview", items: [1, 2, 3] }
|
|
591
|
+
document.body.append(tab)
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
A tag renders on connection and when `props` changes. There is no hydration; server output is markup, not state. On the server or in an embedded engine, registration is skipped and the component remains a function.
|
|
595
|
+
|
|
596
|
+
## Embedding Slim in a Host Engine
|
|
597
|
+
|
|
598
|
+
Slim uses one of two runtimes per file:
|
|
599
|
+
|
|
600
|
+
- `external/core.js` — the type system, structs, enums, operators, validators and
|
|
601
|
+
test helpers. It imports no Node built-in, touches no filesystem, and assumes no
|
|
602
|
+
DOM. Every `process` reference is guarded, so the module loads where `process`
|
|
603
|
+
does not exist at all.
|
|
604
|
+
- `external/defaults.js` — the core plus the DOM layer components render through
|
|
605
|
+
(linkedom on the server) and the file reading that puts a source line in an error.
|
|
606
|
+
|
|
607
|
+
Files without components use `core.js`, which also works in hosts such as QuickJS:
|
|
608
|
+
|
|
609
|
+
```cpp
|
|
610
|
+
struct Enemy {
|
|
611
|
+
name: string
|
|
612
|
+
hp: positive
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
func spawn(payload: string) -> Enemy {
|
|
616
|
+
return JSON.parse(payload) // ❌ throws if the host sent a bad shape
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
The output is plain ES modules. A host with module support (for QuickJS, `JS_SetModuleLoaderFunc`) needs:
|
|
621
|
+
|
|
622
|
+
- **`console.log`**, which `log()` and friends call. QuickJS's `qjs` has it; an
|
|
623
|
+
embedder linking `libquickjs` usually defines it.
|
|
624
|
+
- **A module loader**, resolving `./external/core.js` relative to the entry.
|
|
625
|
+
|
|
626
|
+
Data from the host is validated when it reaches a Slim struct. Where performance matters, use release mode and keep explicit `verify()` calls.
|
|
627
|
+
|
|
628
|
+
## Dev Server
|
|
629
|
+
|
|
630
|
+
`slmc server` watches the project and rebuilds on `.slim` changes (`node_modules`, `dist`, and `.git` are ignored). Point the watcher at a specific folder with the `watch` key in `slimconfig.json`:
|
|
631
|
+
|
|
632
|
+
```json
|
|
633
|
+
{
|
|
634
|
+
"main": "index",
|
|
635
|
+
"watch": "src"
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
## Using npm Packages
|
|
640
|
+
|
|
641
|
+
Slim compiles to plain ES modules, so npm packages work directly. `use` resolves a bare specifier to an installed package when no local `.slim` file has that name:
|
|
642
|
+
|
|
643
|
+
```
|
|
644
|
+
use { Command } from "commander" // → import { Command } from "commander"
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
A plain JavaScript `import` also passes through. Use a `struct` or `type` to validate data returned by an external library:
|
|
648
|
+
|
|
649
|
+
```
|
|
650
|
+
use { fetchUser } from "some-api-client"
|
|
651
|
+
|
|
652
|
+
struct User {
|
|
653
|
+
name: string
|
|
654
|
+
id: int
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const user: User = await fetchUser(3) // ❌ throws if the payload is not a valid User
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
## Package Versions
|
|
661
|
+
|
|
662
|
+
`spm i <name>` records each installed package's resolved version and repository in `spm.lock.json`. Inspect them with:
|
|
663
|
+
|
|
664
|
+
```console
|
|
665
|
+
spm lock
|
|
666
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@slim-lang/core",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Slim extends JavaScript with runtime types, structs, operators, and components, compiling to plain Javascript",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/cdmtn/slim-lang.git"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"slim:build": "node src/compile.js",
|
|
13
|
+
"slim": "node src/compile.js && node run-slim.js",
|
|
14
|
+
"slim:server": "node run-dev-slim.js",
|
|
15
|
+
"release:patch": "npm version patch && git push --follow-tags && npm publish",
|
|
16
|
+
"release:minor": "npm version minor && git push --follow-tags && npm publish",
|
|
17
|
+
"release:major": "npm version major && git push --follow-tags && npm publish",
|
|
18
|
+
"test": "node --test"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"packages",
|
|
22
|
+
"src",
|
|
23
|
+
"run-dev-slim.js",
|
|
24
|
+
"run-slim.js",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"bin": {
|
|
28
|
+
"slmc": "./src/bin/cli.js",
|
|
29
|
+
"spm": "./src/bin/spm.js"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [],
|
|
32
|
+
"author": "",
|
|
33
|
+
"license": "ISC",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@babel/generator": "^7.29.7",
|
|
36
|
+
"@babel/parser": "^7.29.7",
|
|
37
|
+
"@babel/traverse": "^7.29.7",
|
|
38
|
+
"@babel/types": "^7.29.7",
|
|
39
|
+
"@jridgewell/gen-mapping": "^0.3.13",
|
|
40
|
+
"@jridgewell/remapping": "^2.3.5",
|
|
41
|
+
"chalk": "^6.0.0",
|
|
42
|
+
"chokidar": "^5.0.0",
|
|
43
|
+
"commander": "^15.0.0",
|
|
44
|
+
"keytar": "^7.9.0",
|
|
45
|
+
"linkedom": "^0.18.13",
|
|
46
|
+
"magic-string": "^0.30.21",
|
|
47
|
+
"open": "^11.0.1",
|
|
48
|
+
"source-map": "^0.7.6",
|
|
49
|
+
"tar": "^7.5.22"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@babel/plugin-transform-typescript": "^7.29.7",
|
|
53
|
+
"typescript": "^5.9.3"
|
|
54
|
+
}
|
|
55
|
+
}
|