@panagos/jojoscript 0.7.0 → 0.8.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 CHANGED
@@ -1,418 +1,429 @@
1
- # JojoScript 0.7.0
2
-
3
- JojoScript is a deliberately small source-to-source language that adds a
4
- handful of ergonomic constructs on top of JavaScript while keeping ordinary
5
- JavaScript valid. The compiler is a regex/token-based transpiler (not a full
6
- AST), so it stays tiny and every feature composes with plain JavaScript
7
- around it.
8
-
9
- See [CHANGELOG.md](CHANGELOG.md) for release history and
10
- [example/](example/) for a complete runnable application that exercises
11
- every feature described below.
12
-
13
- ## Install
14
-
15
- ```bash
16
- npm install jojoscript
17
- ```
18
-
19
- ## CLI
20
-
21
- ```bash
22
- jojo build <file.jojo|directory> [--out <directory>] [--watch]
23
- jojo run <file.jojo> [--out <directory>]
24
- jojo --version
25
- jojo --help
26
- ```
27
-
28
- - `jojo build src` compiles every `.jojo` file under `src` to `src/dist/*.js` (or `--out <dir>`).
29
- - `jojo build src --watch` rebuilds whenever a `.jojo` file under `src` changes.
30
- - `jojo run app.jojo` compiles a single file and immediately runs it with Node — useful for a fast dev loop.
31
- - Compiler and filesystem errors are reported as a single friendly line (`jojo: <message>`, exit code 1) instead of a raw stack trace. Unknown flags are rejected.
32
-
33
- ## Language features
34
-
35
- ### Immutable declaration
36
-
37
- ```jojo
38
- users := getUsers()
39
- ```
40
-
41
- becomes:
42
-
43
- ```js
44
- const users = getUsers()
45
- ```
46
-
47
- ### Mutable declaration
48
-
49
- ```jojo
50
- mutable count := 0
51
- count++
52
- ```
53
-
54
- becomes:
55
-
56
- ```js
57
- let count = 0
58
- count++
59
- ```
60
-
61
- Existing JavaScript `const`, `let`, and `var` declarations are untouched.
62
-
63
- ### Destructuring declarations
64
-
65
- Object and array patterns are supported with `:=` and `mutable :=`. Only the
66
- declaration head is rewritten — the right-hand side is left completely
67
- untouched and may span multiple lines.
68
-
69
- ```jojo
70
- { id, name } := getUser()
71
- [head, ...rest] := items
72
- mutable { count } := state
73
- ```
74
-
75
- becomes:
76
-
77
- ```js
78
- const { id, name } = getUser()
79
- const [head, ...rest] = items
80
- let { count } = state
81
- ```
82
-
83
- ### Multi-binding (tuple) declarations
84
-
85
- A comma-separated list of names before `:=` destructures a comma-separated
86
- list of values on the right-hand side (single line only):
87
-
88
- ```jojo
89
- a, b := 1, 2
90
- mutable x, y := 10, 20
91
- ```
92
-
93
- becomes:
94
-
95
- ```js
96
- const [a, b] = [1, 2]
97
- let [x, y] = [10, 20]
98
- ```
99
-
100
- This is a distinct construct from bracket destructuring: use `a, b := 1, 2`
101
- to bind a fixed list of values, and `[a, b] := getPair()` to destructure a
102
- single array-producing expression.
103
-
104
- ### Functions
105
-
106
- Block functions:
107
-
108
- ```jojo
109
- fn add(a, b) {
110
- return a + b
111
- }
112
- ```
113
-
114
- Expression functions:
115
-
116
- ```jojo
117
- fn double(value) = value * 2
118
- ```
119
-
120
- Async functions:
121
-
122
- ```jojo
123
- async fn loadUsers() {
124
- return await getUsers()
125
- }
126
- ```
127
-
128
- ### Optional type annotations
129
-
130
- Parameters and return values can be annotated. Annotations are **never
131
- checked** — they are stripped from the emitted JavaScript and re-expressed
132
- as a JSDoc block above the function, so editors/tools that understand JSDoc
133
- get parameter and return type information without JojoScript needing its own
134
- type checker.
135
-
136
- ```jojo
137
- fn add(a: number, b: number): number { return a + b }
138
- ```
139
-
140
- becomes:
141
-
142
- ```js
143
- /**
144
- * @param {number} a
145
- * @param {number} b
146
- * @returns {number}
147
- */
148
- function add(a, b) { return a + b; }
149
- ```
150
-
151
- Functions with no annotations are left completely untouched.
152
-
153
- ### `match` expression
154
-
155
- `match` compares a subject against each pattern with `===`, in order, and
156
- requires a default `_` arm as the last arm. Arms are single expressions
157
- (not statement blocks).
158
-
159
- ```jojo
160
- label := match status {
161
- 200 => "OK",
162
- 404 => "Not Found",
163
- _ => "Unknown"
164
- }
165
- ```
166
-
167
- becomes:
168
-
169
- ```js
170
- const label = (() => {
171
- const __match = status;
172
- if (__match === (200)) return "OK";
173
- if (__match === (404)) return "Not Found";
174
- return "Unknown";
175
- })()
176
- ```
177
-
178
- Patterns can be arbitrary expressions, which enables range-style matching
179
- against `true`:
180
-
181
- ```jojo
182
- category := match true {
183
- value > 100 => "large",
184
- value > 10 => "medium",
185
- _ => "small"
186
- }
187
- ```
188
-
189
- `match` is a value-equality construct, not structural pattern matching:
190
- patterns are plain expressions compared with `===`.
191
-
192
- ### Pipelines
193
-
194
- ```jojo
195
- users
196
- |> filter(isAdult)
197
- |> map(normalize)
198
- |> sort(byName)
199
- |> take(10)
200
- ```
201
-
202
- becomes nested JavaScript calls:
203
-
204
- ```js
205
- take(sort(map(filter(users, isAdult), normalize), byName), 10)
206
- ```
207
-
208
- A stage can have no arguments (`users |> unique`) or arguments
209
- (`users |> take(10)`), and normal JavaScript expressions can be pipeline
210
- inputs, including `await` expressions and pipelines inside `return`
211
- statements, function bodies, and object literals.
212
-
213
- #### Placeholder argument
214
-
215
- By default the piped value is inserted as the stage's first argument. The
216
- `_` placeholder lets you put it anywhere in the argument list instead:
217
-
218
- ```jojo
219
- value |> divide(100, _)
220
- ```
221
-
222
- becomes:
223
-
224
- ```js
225
- divide(100, value)
226
- ```
227
-
228
- #### `catch` / `orElse` control stages
229
-
230
- `catch` and `orElse` provide inline error handling without leaving pipeline
231
- syntax. Both wrap the preceding pipeline in a try/catch, compiled as an
232
- immediately-invoked arrow function:
233
-
234
- ```jojo
235
- risky() |> catch(error => fallbackFor(error))
236
- risky() |> orElse(0)
237
- ```
238
-
239
- `catch(handler)` calls `handler(error)` and returns its result; `orElse(fallback)`
240
- returns `fallback` directly. Both require exactly one argument.
241
-
242
- #### `await` control stage
243
-
244
- `|> await` unwraps a promise at the end of a pipeline, so an async pipeline
245
- still reads as a single chain:
246
-
247
- ```jojo
248
- return ids
249
- |> mapAsync(id => repository.findById(id))
250
- |> toArrayAsync
251
- |> await
252
- ```
253
-
254
- `await`, `catch`, and `orElse` are resolved at compile time and are never
255
- added to the automatic `jojoscript/runtime` import.
256
-
257
- ### Standard pipeline runtime
258
-
259
- The following pipeline stages are supplied by `jojoscript/runtime`. The
260
- compiler automatically generates only the runtime imports that are actually
261
- used through pipeline syntax (custom/user-defined stages are never
262
- auto-imported):
263
-
264
- | Stage | Kind | Description |
265
- |---|---|---|
266
- | `map` | lazy | Transform each item |
267
- | `filter` | lazy | Keep items matching a predicate |
268
- | `take` | lazy | Keep the first N items |
269
- | `skip` | lazy | Discard the first N items |
270
- | `unique` | lazy | Remove duplicates (by `===`) |
271
- | `flatMap` | lazy | Map then flatten one level |
272
- | `distinctBy` | lazy | Remove duplicates by a derived key |
273
- | `tap` | lazy | Observe each item without changing the pipeline (side effects/debugging) |
274
- | `chunk` | lazy | Group items into fixed-size arrays |
275
- | `window` | lazy | Yield a sliding view of size N |
276
- | `zip` | lazy | Pair items from two iterables, stopping at the shorter one |
277
- | `zipWith` | lazy | Combine items from two iterables with an arity-2 combining function, stopping at the shorter one |
278
- | `scan` | lazy | Yield a running accumulation (like `reduce`, but yields every step) |
279
- | `mapAsync` | lazy, async | Like `map`, awaiting an async transform |
280
- | `filterAsync` | lazy, async | Like `filter`, awaiting an async predicate |
281
- | `sort` | eager | Materializes the input; sorting needs the complete input |
282
- | `sortBy` | eager | Like `sort`, ordered by a derived key |
283
- | `groupBy` | eager | Groups items into a `Map` keyed by a derived key |
284
- | `partition` | eager | Splits into `[matching, nonMatching]` arrays |
285
- | `reduce` | terminal | Accumulate to a single value |
286
- | `toArray` | terminal | Materialize an iterator into an array |
287
- | `toArrayAsync` | terminal, async | Materialize an async iterator into an array |
288
- | `count` | terminal | Count items |
289
- | `first` | terminal | First item, or `undefined` |
290
- | `find` | terminal | First matching item, or `undefined` |
291
- | `some` | terminal | Whether any item matches (short-circuits) |
292
- | `every` | terminal | Whether every item matches (short-circuits) |
293
-
294
- See [ITERATOR_PROPOSAL.md](ITERATOR_PROPOSAL.md) and [ITERATORS.md](ITERATORS.md)
295
- for the full laziness/memory model.
296
-
297
- ## JavaScript compatibility
298
-
299
- JojoScript intentionally leaves ordinary JavaScript alone. You can use:
300
-
301
- - normal `const`, `let`, and `var`
302
- - arrow functions
303
- - classes
304
- - destructuring (as plain JS assignment, not a `:=` declaration)
305
- - spread/rest
306
- - optional chaining
307
- - nullish coalescing
308
- - template literals
309
- - loops
310
- - try/catch
311
- - array methods
312
- - async/await
313
- - built-in Node modules
314
- - npm/bare imports
315
-
316
- Local relative imports receive `.js` after compilation:
317
-
318
- ```jojo
319
- import { users } from "./users"
320
- ```
321
-
322
- becomes:
323
-
324
- ```js
325
- import { users } from "./users.js"
326
- ```
327
-
328
- Bare imports remain untouched:
329
-
330
- ```jojo
331
- import axios from "axios"
332
- ```
333
-
334
- ## Runtime package
335
-
336
- Generated JavaScript imports the runtime using the package subpath:
337
-
338
- ```js
339
- import { map, take } from "jojoscript/runtime"
340
- ```
341
-
342
- The package exposes this through `package.json` `exports`, so consumers
343
- install `jojoscript` as a normal dependency. If a file both manually imports
344
- from `jojoscript/runtime` (for a stage that's only ever called directly,
345
- never through `|>`) and also uses pipeline syntax with overlapping names,
346
- the compiler merges the auto-imported names into the existing import
347
- instead of emitting a second, colliding import declaration.
348
-
349
- ## Example
350
-
351
- See [example/](example/). It is a complete runnable project covering every
352
- language feature above plus JavaScript compatibility: a Node/SQLite
353
- application that fetches posts from a public API, stores them, and prints a
354
- report, alongside one small focused module (and test) per feature. Every
355
- module is imported and invoked from `example/src/main.jojo`, so both
356
- `npm start` and `npm test` exercise the whole example.
357
-
358
- ```bash
359
- cd example
360
- npm install
361
- npm run build
362
- npm start
363
- npm test
364
- ```
365
-
366
- ## Iterator pipelines
367
-
368
- Collection pipeline stages are lazy by default. `map`, `filter`, `take`,
369
- `skip`, `unique`, `flatMap`, `distinctBy`, `tap`, `chunk`, `window`, `zip`,
370
- `zipWith`, `scan`, `mapAsync`, and `filterAsync` return iterators (or async iterators)
371
- instead of allocating an intermediate array. Use `toArray`/`toArrayAsync`
372
- when an array is actually required.
373
-
374
- ```jojo
375
- const total = users
376
- |> filter(user => user.active)
377
- |> map(user => user.age)
378
- |> reduce((sum, age) => sum + age, 0)
379
- ```
380
-
381
- `sort`, `sortBy`, `groupBy`, and `partition` are intentionally materialization
382
- boundaries because they need the complete input. See
383
- [ITERATORS.md](ITERATORS.md) for the complete proposal and memory model.
384
-
385
- ## Editor support
386
-
387
- See [editors/vscode](editors/vscode) for a VS Code extension providing syntax
388
- highlighting and a `Format Document` provider for `.jojo` files. It is not
389
- published to the marketplace; package it with `vsce package` and install the
390
- resulting `.vsix` locally.
391
-
392
- ## Known limitations
393
-
394
- - **No source maps.** Stack traces from compiled `dist/*.js` point at the
395
- generated JavaScript, not the original `.jojo` source.
396
- - **No `jojo fmt` CLI command.** Formatting is only available through the
397
- VS Code extension's `Format Document` command, not the `jojo` CLI.
398
- - **No regex-literal awareness in the lexer.** A regex containing `{`, `}`,
399
- `(`, or `)` can confuse brace/paren-depth-sensitive transforms (pipelines,
400
- `match`, destructuring, type annotations). Prefer `new RegExp(...)` in code
401
- paths that also use these constructs on the same line, or keep such regexes
402
- simple.
403
- - **Pipeline statement boundaries need a blank line (or a semicolon) between
404
- consecutive `:=` statements that each end in a bare pipeline stage**, e.g.:
405
-
406
- ```jojo
407
- a := x |> map(f)
408
- b := y |> map(g) // may be parsed as a continuation of the line above
409
- ```
410
-
411
- Add a blank line (or a trailing `;`) between them.
412
- - **`match` subject expressions should be simple** (a single identifier,
413
- member access, or a fully-parenthesized expression) and appear on the same
414
- statement as the `{ ... }` block.
415
- - **Multi-binding declarations (`a, b := 1, 2`) are single-line only** and
416
- always wrap the right-hand side in `[...]`; use `[a, b] := expr()` to
417
- destructure a single expression instead.
418
-
1
+ # JojoScript 0.8.0
2
+
3
+ JojoScript is a small JavaScript-compatible language and runtime for
4
+ expressive, composable pipelines. It adds lightweight syntax for declarations,
5
+ functions, pattern matching, type annotations, and pipelines while preserving
6
+ ordinary JavaScript. Its compiler uses a lightweight lexer and source
7
+ transformations rather than a full AST, keeping the toolchain compact. The
8
+ project also provides lazy and async collection operators, checkpointing,
9
+ retries, metrics, pipeline graphing, CLI workflows, and VS Code editor support.
10
+
11
+ See [CHANGELOG.md](CHANGELOG.md) for release history and
12
+ [example/](example/) for a complete runnable application that exercises
13
+ every feature described below.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @panagos/jojoscript
19
+ ```
20
+
21
+ ## CLI
22
+
23
+ ```bash
24
+ jojo build <file.jojo|directory> [--out <directory>] [--watch]
25
+ jojo run <file.jojo> [--out <directory>]
26
+ jojo graph <file.jojo> [--format text|dot]
27
+ jojo profile <file.jojo>
28
+ jojo resume <file.jojo> [--out <directory>]
29
+ jojo replay <file.jojo> --from <checkpoint> [--out <directory>]
30
+ jojo test <file.jojo>
31
+ jojo --version
32
+ jojo --help
33
+ ```
34
+
35
+ - `jojo build src` compiles every `.jojo` file under `src` to `src/dist/*.js` (or `--out <dir>`).
36
+ - `jojo build src --watch` rebuilds whenever a `.jojo` file under `src` changes.
37
+ - `jojo run app.jojo` compiles a single file and immediately runs it with Node — useful for a fast dev loop.
38
+ - `jojo graph app.jojo` prints a static pipeline graph; use `--format dot` for Graphviz output.
39
+ - `jojo profile app.jojo` executes a pipeline and prints per-node item counts, duration, and throughput.
40
+ - `jojo resume app.jojo` reuses completed file-backed checkpoints; `jojo replay app.jojo --from name` starts from a selected checkpoint.
41
+ - `jojo test app.jojo` compiles the file and runs its co-located `<name>.test.js` test when present.
42
+ - Compiler and filesystem errors are reported as a single friendly line (`jojo: <message>`, exit code 1) instead of a raw stack trace. Unknown flags are rejected.
43
+
44
+ ## Language features
45
+
46
+ ### Immutable declaration
47
+
48
+ ```jojo
49
+ users := getUsers()
50
+ ```
51
+
52
+ becomes:
53
+
54
+ ```js
55
+ const users = getUsers()
56
+ ```
57
+
58
+ ### Mutable declaration
59
+
60
+ ```jojo
61
+ mutable count := 0
62
+ count++
63
+ ```
64
+
65
+ becomes:
66
+
67
+ ```js
68
+ let count = 0
69
+ count++
70
+ ```
71
+
72
+ Existing JavaScript `const`, `let`, and `var` declarations are untouched.
73
+
74
+ ### Destructuring declarations
75
+
76
+ Object and array patterns are supported with `:=` and `mutable :=`. Only the
77
+ declaration head is rewritten — the right-hand side is left completely
78
+ untouched and may span multiple lines.
79
+
80
+ ```jojo
81
+ { id, name } := getUser()
82
+ [head, ...rest] := items
83
+ mutable { count } := state
84
+ ```
85
+
86
+ becomes:
87
+
88
+ ```js
89
+ const { id, name } = getUser()
90
+ const [head, ...rest] = items
91
+ let { count } = state
92
+ ```
93
+
94
+ ### Multi-binding (tuple) declarations
95
+
96
+ A comma-separated list of names before `:=` destructures a comma-separated
97
+ list of values on the right-hand side (single line only):
98
+
99
+ ```jojo
100
+ a, b := 1, 2
101
+ mutable x, y := 10, 20
102
+ ```
103
+
104
+ becomes:
105
+
106
+ ```js
107
+ const [a, b] = [1, 2]
108
+ let [x, y] = [10, 20]
109
+ ```
110
+
111
+ This is a distinct construct from bracket destructuring: use `a, b := 1, 2`
112
+ to bind a fixed list of values, and `[a, b] := getPair()` to destructure a
113
+ single array-producing expression.
114
+
115
+ ### Functions
116
+
117
+ Block functions:
118
+
119
+ ```jojo
120
+ fn add(a, b) {
121
+ return a + b
122
+ }
123
+ ```
124
+
125
+ Expression functions:
126
+
127
+ ```jojo
128
+ fn double(value) = value * 2
129
+ ```
130
+
131
+ Async functions:
132
+
133
+ ```jojo
134
+ async fn loadUsers() {
135
+ return await getUsers()
136
+ }
137
+ ```
138
+
139
+ ### Optional type annotations
140
+
141
+ Parameters and return values can be annotated. Annotations are **never
142
+ checked** — they are stripped from the emitted JavaScript and re-expressed
143
+ as a JSDoc block above the function, so editors/tools that understand JSDoc
144
+ get parameter and return type information without JojoScript needing its own
145
+ type checker.
146
+
147
+ ```jojo
148
+ fn add(a: number, b: number): number { return a + b }
149
+ ```
150
+
151
+ becomes:
152
+
153
+ ```js
154
+ /**
155
+ * @param {number} a
156
+ * @param {number} b
157
+ * @returns {number}
158
+ */
159
+ function add(a, b) { return a + b; }
160
+ ```
161
+
162
+ Functions with no annotations are left completely untouched.
163
+
164
+ ### `match` expression
165
+
166
+ `match` compares a subject against each pattern with `===`, in order, and
167
+ requires a default `_` arm as the last arm. Arms are single expressions
168
+ (not statement blocks).
169
+
170
+ ```jojo
171
+ label := match status {
172
+ 200 => "OK",
173
+ 404 => "Not Found",
174
+ _ => "Unknown"
175
+ }
176
+ ```
177
+
178
+ becomes:
179
+
180
+ ```js
181
+ const label = (() => {
182
+ const __match = status;
183
+ if (__match === (200)) return "OK";
184
+ if (__match === (404)) return "Not Found";
185
+ return "Unknown";
186
+ })()
187
+ ```
188
+
189
+ Patterns can be arbitrary expressions, which enables range-style matching
190
+ against `true`:
191
+
192
+ ```jojo
193
+ category := match true {
194
+ value > 100 => "large",
195
+ value > 10 => "medium",
196
+ _ => "small"
197
+ }
198
+ ```
199
+
200
+ `match` is a value-equality construct, not structural pattern matching:
201
+ patterns are plain expressions compared with `===`.
202
+
203
+ ### Pipelines
204
+
205
+ ```jojo
206
+ users
207
+ |> filter(isAdult)
208
+ |> map(normalize)
209
+ |> sort(byName)
210
+ |> take(10)
211
+ ```
212
+
213
+ becomes nested JavaScript calls:
214
+
215
+ ```js
216
+ take(sort(map(filter(users, isAdult), normalize), byName), 10)
217
+ ```
218
+
219
+ A stage can have no arguments (`users |> unique`) or arguments
220
+ (`users |> take(10)`), and normal JavaScript expressions can be pipeline
221
+ inputs, including `await` expressions and pipelines inside `return`
222
+ statements, function bodies, and object literals.
223
+
224
+ #### Placeholder argument
225
+
226
+ By default the piped value is inserted as the stage's first argument. The
227
+ `_` placeholder lets you put it anywhere in the argument list instead:
228
+
229
+ ```jojo
230
+ value |> divide(100, _)
231
+ ```
232
+
233
+ becomes:
234
+
235
+ ```js
236
+ divide(100, value)
237
+ ```
238
+
239
+ #### `catch` / `orElse` control stages
240
+
241
+ `catch` and `orElse` provide inline error handling without leaving pipeline
242
+ syntax. Both wrap the preceding pipeline in a try/catch, compiled as an
243
+ immediately-invoked arrow function:
244
+
245
+ ```jojo
246
+ risky() |> catch(error => fallbackFor(error))
247
+ risky() |> orElse(0)
248
+ ```
249
+
250
+ `catch(handler)` calls `handler(error)` and returns its result; `orElse(fallback)`
251
+ returns `fallback` directly. Both require exactly one argument.
252
+
253
+ #### `await` control stage
254
+
255
+ `|> await` unwraps a promise at the end of a pipeline, so an async pipeline
256
+ still reads as a single chain:
257
+
258
+ ```jojo
259
+ return ids
260
+ |> mapAsync(id => repository.findById(id))
261
+ |> toArrayAsync
262
+ |> await
263
+ ```
264
+
265
+ `await`, `catch`, and `orElse` are resolved at compile time and are never
266
+ added to the automatic `@panagos/jojoscript/runtime` import.
267
+
268
+ ### Standard pipeline runtime
269
+
270
+ The following pipeline stages are supplied by `@panagos/jojoscript/runtime`. The
271
+ compiler automatically generates only the runtime imports that are actually
272
+ used through pipeline syntax (custom/user-defined stages are never
273
+ auto-imported):
274
+
275
+ | Stage | Kind | Description |
276
+ |---|---|---|
277
+ | `map` | lazy | Transform each item |
278
+ | `filter` | lazy | Keep items matching a predicate |
279
+ | `take` | lazy | Keep the first N items |
280
+ | `skip` | lazy | Discard the first N items |
281
+ | `unique` | lazy | Remove duplicates (by `===`) |
282
+ | `flatMap` | lazy | Map then flatten one level |
283
+ | `distinctBy` | lazy | Remove duplicates by a derived key |
284
+ | `tap` | lazy | Observe each item without changing the pipeline (side effects/debugging) |
285
+ | `chunk` | lazy | Group items into fixed-size arrays |
286
+ | `window` | lazy | Yield a sliding view of size N |
287
+ | `zip` | lazy | Pair items from two iterables, stopping at the shorter one |
288
+ | `zipWith` | lazy | Combine items from two iterables with an arity-2 combining function, stopping at the shorter one |
289
+ | `scan` | lazy | Yield a running accumulation (like `reduce`, but yields every step) |
290
+ | `mapAsync` | lazy, async | Like `map`, awaiting an async transform |
291
+ | `filterAsync` | lazy, async | Like `filter`, awaiting an async predicate |
292
+ | `sort` | eager | Materializes the input; sorting needs the complete input |
293
+ | `sortBy` | eager | Like `sort`, ordered by a derived key |
294
+ | `groupBy` | eager | Groups items into a `Map` keyed by a derived key |
295
+ | `partition` | eager | Splits into `[matching, nonMatching]` arrays |
296
+ | `reduce` | terminal | Accumulate to a single value |
297
+ | `toArray` | terminal | Materialize an iterator into an array |
298
+ | `toArrayAsync` | terminal, async | Materialize an async iterator into an array |
299
+ | `count` | terminal | Count items |
300
+ | `first` | terminal | First item, or `undefined` |
301
+ | `find` | terminal | First matching item, or `undefined` |
302
+ | `some` | terminal | Whether any item matches (short-circuits) |
303
+ | `every` | terminal | Whether every item matches (short-circuits) |
304
+
305
+ See [ITERATOR_PROPOSAL.md](ITERATOR_PROPOSAL.md) and [ITERATORS.md](ITERATORS.md)
306
+ for the full laziness/memory model.
307
+
308
+ ## JavaScript compatibility
309
+
310
+ JojoScript intentionally leaves ordinary JavaScript alone. You can use:
311
+
312
+ - normal `const`, `let`, and `var`
313
+ - arrow functions
314
+ - classes
315
+ - destructuring (as plain JS assignment, not a `:=` declaration)
316
+ - spread/rest
317
+ - optional chaining
318
+ - nullish coalescing
319
+ - template literals
320
+ - loops
321
+ - try/catch
322
+ - array methods
323
+ - async/await
324
+ - built-in Node modules
325
+ - npm/bare imports
326
+
327
+ Local relative imports receive `.js` after compilation:
328
+
329
+ ```jojo
330
+ import { users } from "./users"
331
+ ```
332
+
333
+ becomes:
334
+
335
+ ```js
336
+ import { users } from "./users.js"
337
+ ```
338
+
339
+ Bare imports remain untouched:
340
+
341
+ ```jojo
342
+ import axios from "axios"
343
+ ```
344
+
345
+ ## Runtime package
346
+
347
+ Generated JavaScript imports the runtime using the package subpath:
348
+
349
+ ```js
350
+ import { map, take } from "@panagos/jojoscript/runtime"
351
+ ```
352
+
353
+ The package exposes this through `package.json` `exports`, so consumers
354
+ install `jojoscript` as a normal dependency. If a file both manually imports
355
+ from `@panagos/jojoscript/runtime` (for a stage that's only ever called directly,
356
+ never through `|>`) and also uses pipeline syntax with overlapping names,
357
+ the compiler merges the auto-imported names into the existing import
358
+ instead of emitting a second, colliding import declaration.
359
+
360
+ ## Example
361
+
362
+ See [example/](example/). It is a complete runnable project covering every
363
+ language feature above plus JavaScript compatibility: a Node/SQLite
364
+ application that fetches posts from a public API, stores them, and prints a
365
+ report, alongside one small focused module (and test) per feature. Every
366
+ module is imported and invoked from `example/src/main.jojo`, so both
367
+ `npm start` and `npm test` exercise the whole example.
368
+
369
+ ```bash
370
+ cd example
371
+ npm install
372
+ npm run build
373
+ npm start
374
+ npm test
375
+ ```
376
+
377
+ ## Iterator pipelines
378
+
379
+ Collection pipeline stages are lazy by default. `map`, `filter`, `take`,
380
+ `skip`, `unique`, `flatMap`, `distinctBy`, `tap`, `chunk`, `window`, `zip`,
381
+ `zipWith`, `scan`, `mapAsync`, and `filterAsync` return iterators (or async iterators)
382
+ instead of allocating an intermediate array. Use `toArray`/`toArrayAsync`
383
+ when an array is actually required.
384
+
385
+ ```jojo
386
+ const total = users
387
+ |> filter(user => user.active)
388
+ |> map(user => user.age)
389
+ |> reduce((sum, age) => sum + age, 0)
390
+ ```
391
+
392
+ `sort`, `sortBy`, `groupBy`, and `partition` are intentionally materialization
393
+ boundaries because they need the complete input. See
394
+ [ITERATORS.md](ITERATORS.md) for the complete proposal and memory model.
395
+
396
+ ## Editor support
397
+
398
+ See [editors/vscode](editors/vscode) for a VS Code extension providing syntax
399
+ highlighting and a `Format Document` provider for `.jojo` files. It is not
400
+ published to the marketplace; package it with `vsce package` and install the
401
+ resulting `.vsix` locally.
402
+
403
+ ## Known limitations
404
+
405
+ - **No source maps.** Stack traces from compiled `dist/*.js` point at the
406
+ generated JavaScript, not the original `.jojo` source.
407
+ - **No `jojo fmt` CLI command.** Formatting is only available through the
408
+ VS Code extension's `Format Document` command, not the `jojo` CLI.
409
+ - **No regex-literal awareness in the lexer.** A regex containing `{`, `}`,
410
+ `(`, or `)` can confuse brace/paren-depth-sensitive transforms (pipelines,
411
+ `match`, destructuring, type annotations). Prefer `new RegExp(...)` in code
412
+ paths that also use these constructs on the same line, or keep such regexes
413
+ simple.
414
+ - **Pipeline statement boundaries need a blank line (or a semicolon) between
415
+ consecutive `:=` statements that each end in a bare pipeline stage**, e.g.:
416
+
417
+ ```jojo
418
+ a := x |> map(f)
419
+ b := y |> map(g) // may be parsed as a continuation of the line above
420
+ ```
421
+
422
+ Add a blank line (or a trailing `;`) between them.
423
+ - **`match` subject expressions should be simple** (a single identifier,
424
+ member access, or a fully-parenthesized expression) and appear on the same
425
+ statement as the `{ ... }` block.
426
+ - **Multi-binding declarations (`a, b := 1, 2`) are single-line only** and
427
+ always wrap the right-hand side in `[...]`; use `[a, b] := expr()` to
428
+ destructure a single expression instead.
429
+