@panagos/jojoscript 0.10.1 → 1.0.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 +677 -11
- package/bin/jojo.js +257 -23
- package/package.json +1 -1
- package/src/check.js +253 -0
- package/src/compiler.js +36 -37
- package/src/contracts.js +124 -0
- package/src/describe.js +63 -0
- package/src/effect-parser.js +4 -2
- package/src/formatter.js +105 -0
- package/src/index.js +31 -0
- package/src/match-parser.js +212 -14
- package/src/named-pipelines.js +87 -0
- package/src/parse.js +124 -0
- package/src/pipeline-explain.js +132 -0
- package/src/pipeline-ir.js +9 -7
- package/src/pipeline-parser.js +24 -2
- package/src/pipeline-plan.js +203 -0
- package/src/purity.js +101 -0
- package/src/runtime/checkpoint-store.js +5 -3
- package/src/runtime/checkpoint.js +3 -2
- package/src/runtime/effects.js +73 -3
- package/src/runtime/errors.js +14 -0
- package/src/runtime/index.js +8 -2
- package/src/runtime/mcp.js +1 -1
- package/src/runtime/serialization.js +97 -0
- package/src/runtime/tagged-unions.js +29 -0
- package/src/simulate.js +156 -0
- package/src/type-declarations.js +83 -0
- package/src/why.js +146 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# JojoScript 0.
|
|
1
|
+
# JojoScript 1.0.0
|
|
2
2
|
|
|
3
3
|
JojoScript is a lightweight language and runtime for data pipelines and agent
|
|
4
4
|
workflows, built on JavaScript.
|
|
@@ -22,8 +22,17 @@ npm install @panagos/jojoscript
|
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
jojo build <file.jojo|directory> [--out <directory>] [--watch]
|
|
25
|
-
jojo run <file.jojo> [--out <directory>]
|
|
26
|
-
jojo graph <file.jojo> [--format text|dot]
|
|
25
|
+
jojo run <file.jojo> [--out <directory>] [--deterministic [--seed <n>]]
|
|
26
|
+
jojo graph <file.jojo> [--format text|dot] [--pipeline <name>]
|
|
27
|
+
jojo explain <file.jojo> [--format text|json] [--pipeline <name>]
|
|
28
|
+
jojo plan <file.jojo> [--format json] [--pipeline <name>]
|
|
29
|
+
jojo generate <plan.json>
|
|
30
|
+
jojo check <file.jojo|plan.json> [--format text|json] [--machine]
|
|
31
|
+
jojo describe <file.jojo> [--pipeline <name>]
|
|
32
|
+
jojo simulate <file.jojo> [--input <records>] [--format text|json] [--pipeline <name>]
|
|
33
|
+
jojo why <file.jojo> [--format text|json] [--pipeline <name>]
|
|
34
|
+
jojo format <file.jojo|directory> [--check]
|
|
35
|
+
jojo parse <file.jojo> [--json]
|
|
27
36
|
jojo profile <file.jojo>
|
|
28
37
|
jojo resume <file.jojo> [--out <directory>]
|
|
29
38
|
jojo replay <file.jojo> --from <checkpoint> [--out <directory>]
|
|
@@ -35,7 +44,17 @@ jojo --help
|
|
|
35
44
|
- `jojo build src` compiles every `.jojo` file under `src` to `src/dist/*.js` (or `--out <dir>`).
|
|
36
45
|
- `jojo build src --watch` rebuilds whenever a `.jojo` file under `src` changes.
|
|
37
46
|
- `jojo run app.jojo` compiles a single file and immediately runs it with Node — useful for a fast dev loop.
|
|
47
|
+
- `jojo run app.jojo --deterministic --seed 42` runs the program in [deterministic execution mode](#deterministic-execution-mode): seeded `random()`, a fixed clock, sequential `parallel()`, and deterministic effect handlers only.
|
|
38
48
|
- `jojo graph app.jojo` prints a static pipeline graph; use `--format dot` for Graphviz output.
|
|
49
|
+
- `jojo explain app.jojo` describes what each pipeline means (stages, effects, properties) without running it; use `--format json` for tools and LLMs. See [Explaining pipelines](#explaining-pipelines).
|
|
50
|
+
- `jojo plan app.jojo` prints a canonical JSON execution plan, and `jojo generate plan.json` turns a plan back into validated JojoScript. See [Machine-readable execution plans](#machine-readable-execution-plans).
|
|
51
|
+
- `graph`, `explain`, and `plan` accept `--pipeline <name>` to select one pipeline by its (inferred or [declared](#named-pipelines)) name.
|
|
52
|
+
- `jojo check app.jojo` (or `plan.json`) reports structured diagnostics with codes, lines, and suggestions, and exits 1 on errors; `--machine` prints JSON. See [Checking programs](#checking-programs).
|
|
53
|
+
- `jojo describe app.jojo` prints one JSON document describing the pipelines, effects, functions, and types in a file. See [Describing programs](#describing-programs).
|
|
54
|
+
- `jojo simulate app.jojo --input 1000000` estimates item counts, effect calls, and risks for a given input size without running anything. See [Simulating pipelines](#simulating-pipelines).
|
|
55
|
+
- `jojo why app.jojo` answers questions such as "why is this pipeline not deterministic?" in plain language. See [Asking why](#asking-why).
|
|
56
|
+
- `jojo format src` reformats `.jojo` files in place; `--check` lists unformatted files and exits 1. See [Formatting](#formatting).
|
|
57
|
+
- `jojo parse app.jojo --json` lists every syntax construct (imports, effects, types, functions, pipelines, handle blocks, matches) with its line. See [Parsing programs](#parsing-programs).
|
|
39
58
|
- `jojo profile app.jojo` executes a pipeline and prints per-node item counts, duration, and throughput.
|
|
40
59
|
- `jojo resume app.jojo` reuses completed file-backed checkpoints; `jojo replay app.jojo --from name` starts from a selected checkpoint.
|
|
41
60
|
- `jojo test app.jojo` compiles the file and runs its co-located `<name>.test.js` test when present.
|
|
@@ -136,6 +155,69 @@ async fn loadUsers() {
|
|
|
136
155
|
}
|
|
137
156
|
```
|
|
138
157
|
|
|
158
|
+
### Pure and effectful functions
|
|
159
|
+
|
|
160
|
+
`pure` and `effectful` declare a function's role. They compile away to an
|
|
161
|
+
ordinary function, and may follow `export` or surround `async`:
|
|
162
|
+
|
|
163
|
+
```jojo
|
|
164
|
+
effect storeUser(user)
|
|
165
|
+
|
|
166
|
+
export pure fn normalizeUser(user) {
|
|
167
|
+
return { ...user, email: user.email.trim().toLowerCase() }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
effectful fn saveUser(user) {
|
|
171
|
+
return storeUser(user)
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The compiler rejects a `pure fn` that uses a declared `effect` or an
|
|
176
|
+
`effectful fn`, whether it calls it or passes it along (`map(saveUser)`):
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
pure fn "normalizeUser" uses effectful function "saveUser" (line 9)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The check is static and name-based. Strings, comments, property access
|
|
183
|
+
(`user.saveUser`), and recursion are ignored, and calls to unannotated
|
|
184
|
+
functions are allowed. `jojo explain` reports a stage that uses an
|
|
185
|
+
`effectful fn` as `effectful` and the pipeline as non-deterministic, exactly as
|
|
186
|
+
it does for declared effects. See
|
|
187
|
+
[example/src/application/purity-example.jojo](example/src/application/purity-example.jojo).
|
|
188
|
+
|
|
189
|
+
### Function contracts
|
|
190
|
+
|
|
191
|
+
A block function can state what it expects and what it guarantees. Each
|
|
192
|
+
clause goes on its own line between the parameter list and the opening `{`:
|
|
193
|
+
|
|
194
|
+
```jojo
|
|
195
|
+
fn applyDiscount(price, percent)
|
|
196
|
+
requires price >= 0
|
|
197
|
+
requires percent >= 0 && percent <= 100
|
|
198
|
+
ensures result >= 0 && result <= price
|
|
199
|
+
{
|
|
200
|
+
return price - price * percent / 100
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`requires` clauses are checked on entry, in order, before the body runs.
|
|
205
|
+
`ensures` clauses are checked after it, with the return value available as
|
|
206
|
+
`result` (async functions are awaited first). The body runs inside an arrow
|
|
207
|
+
function, so `this` and `arguments` keep referring to the declared function.
|
|
208
|
+
A broken clause throws `ContractError`:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
ContractError: Contract violated: applyDiscount requires percent >= 0 && percent <= 100
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The error carries `functionName`, `kind` (`"requires"` or `"ensures"`), and
|
|
215
|
+
`condition`, and is non-retryable, so `retry()` never repeats a call that
|
|
216
|
+
broke its contract. Contract clauses keep the source line count. Contracts
|
|
217
|
+
apply to block functions only, and a function using `ensures` should not
|
|
218
|
+
have a parameter named `result`. See
|
|
219
|
+
[example/src/application/contracts-example.jojo](example/src/application/contracts-example.jojo).
|
|
220
|
+
|
|
139
221
|
### Optional type annotations
|
|
140
222
|
|
|
141
223
|
Parameters and return values can be annotated. Annotations are **never
|
|
@@ -197,8 +279,77 @@ category := match true {
|
|
|
197
279
|
}
|
|
198
280
|
```
|
|
199
281
|
|
|
200
|
-
|
|
201
|
-
|
|
282
|
+
#### Structured patterns
|
|
283
|
+
|
|
284
|
+
Object patterns match on the shape of a value. Arms can be separated by
|
|
285
|
+
newlines instead of commas:
|
|
286
|
+
|
|
287
|
+
```jojo
|
|
288
|
+
message := match result {
|
|
289
|
+
{ status: "ok", value } => value
|
|
290
|
+
{ status: "error", error: { code: 404 } } => "not found"
|
|
291
|
+
{ status: "error", error } => handle(error)
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Inside an object pattern:
|
|
296
|
+
|
|
297
|
+
- a literal or expression (`status: "ok"`, `code: Status.OK`) is compared with `===`;
|
|
298
|
+
- a bare name (`value`) binds that field, and the field must be present;
|
|
299
|
+
- `key: name` binds the field under another name, and `key: { ... }` matches a nested object;
|
|
300
|
+
- quoted keys (`"content-type": "json"`) are supported.
|
|
301
|
+
|
|
302
|
+
A match with at least one object pattern may omit the `_` arm. If no arm
|
|
303
|
+
matches, it throws `TypeError: No match arm matched the value`, so an
|
|
304
|
+
unexpected shape fails loudly instead of returning `undefined`. A match whose
|
|
305
|
+
patterns are all plain expressions still requires a final `_` arm and
|
|
306
|
+
compiles exactly as before. See
|
|
307
|
+
[example/src/application/structured-match-example.jojo](example/src/application/structured-match-example.jojo).
|
|
308
|
+
|
|
309
|
+
### Tagged unions
|
|
310
|
+
|
|
311
|
+
`type` declares a tagged union: a value that is exactly one of a fixed set of
|
|
312
|
+
variants. Each variant has a constructor:
|
|
313
|
+
|
|
314
|
+
```jojo
|
|
315
|
+
type PaymentResult =
|
|
316
|
+
| Paid(amount)
|
|
317
|
+
| Declined(reason)
|
|
318
|
+
| Pending
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
becomes:
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
const PaymentResult = defineType("PaymentResult", { Paid: ["amount"], Declined: ["reason"], Pending: [] }); const { Paid, Declined, Pending } = PaymentResult
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
`Paid(40)` returns the frozen plain object `{ tag: "Paid", amount: 40 }`, so
|
|
328
|
+
values serialize to JSON unchanged. Constructors reject the wrong number of
|
|
329
|
+
arguments (`Paid expects 1 argument (amount), got 0`), and fieldless variants
|
|
330
|
+
are called with no arguments (`Pending()`). `export type` exports the type and
|
|
331
|
+
all of its constructors. Variant names must start with an uppercase letter,
|
|
332
|
+
variants and fields must be unique, and no field may be named `tag`.
|
|
333
|
+
|
|
334
|
+
Constructor patterns match a variant by its tag and bind its fields by
|
|
335
|
+
position, so they also match values decoded from JSON:
|
|
336
|
+
|
|
337
|
+
```jojo
|
|
338
|
+
message := match result {
|
|
339
|
+
Paid(amount) => `paid ${amount}`
|
|
340
|
+
Declined("insufficient funds") => "ask for another card"
|
|
341
|
+
Declined(reason) => `declined: ${reason}`
|
|
342
|
+
Pending() => "waiting for the bank"
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Pattern arguments can be literals, `_`, bindings, or nested object and
|
|
347
|
+
constructor patterns. Like object patterns, a match over constructor patterns
|
|
348
|
+
may omit `_` and throws `TypeError` when nothing matches. A capitalized call
|
|
349
|
+
such as `Ok(v)` inside a `match` is always a constructor pattern. Avoid naming
|
|
350
|
+
a variant `Error`, since its constructor would shadow JavaScript's `Error` in
|
|
351
|
+
that module. See
|
|
352
|
+
[example/src/application/tagged-unions-example.jojo](example/src/application/tagged-unions-example.jojo).
|
|
202
353
|
|
|
203
354
|
### Pipelines
|
|
204
355
|
|
|
@@ -221,6 +372,52 @@ A stage can have no arguments (`users |> unique`) or arguments
|
|
|
221
372
|
inputs, including `await` expressions and pipelines inside `return`
|
|
222
373
|
statements, function bodies, and object literals.
|
|
223
374
|
|
|
375
|
+
#### Named pipelines
|
|
376
|
+
|
|
377
|
+
A `pipeline` block declares a named, reusable pipeline. Its first line names
|
|
378
|
+
the source, and the remaining lines are stages:
|
|
379
|
+
|
|
380
|
+
```jojo
|
|
381
|
+
export pipeline enrichOrders {
|
|
382
|
+
source orders
|
|
383
|
+
|
|
384
|
+
|> filter(order => order.active)
|
|
385
|
+
|> map(addVat)
|
|
386
|
+
|> checkpoint("enriched")
|
|
387
|
+
|> toArrayAsync
|
|
388
|
+
|> await
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
It compiles to a function. An identifier source becomes its single parameter
|
|
393
|
+
(`enrichOrders(orders)`); any other source expression gives a function with
|
|
394
|
+
no parameters. A pipeline with a `|> await` stage (or an awaited source) becomes an
|
|
395
|
+
`async` function; an `await` inside a stage callback does not. Also,
|
|
396
|
+
blank lines inside the block are allowed. Tools see the pipeline under its
|
|
397
|
+
declared name, so `jojo explain app.jojo --pipeline enrichOrders` (and the same
|
|
398
|
+
option on `graph` and `plan`) selects it directly. See
|
|
399
|
+
[example/src/application/named-pipeline-example.jojo](example/src/application/named-pipeline-example.jojo).
|
|
400
|
+
|
|
401
|
+
#### Stage IDs
|
|
402
|
+
|
|
403
|
+
A stage can be labelled with a trailing `@id` (letters, digits, `_`, and `-`,
|
|
404
|
+
starting with a letter or `_`):
|
|
405
|
+
|
|
406
|
+
```jojo
|
|
407
|
+
saved := orders
|
|
408
|
+
|> filter(order => order.paid) @paid-only
|
|
409
|
+
|> map(assignCarrier) @pick-carrier
|
|
410
|
+
|> checkpoint("prepared")
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
The label is removed from the compiled code. It becomes the stage's node id in
|
|
414
|
+
runtime metadata (checkpoint, inspect, parallel, batch, and `jojo profile`),
|
|
415
|
+
in `jojo graph`, in `jojo explain` (`id` and `@pick-carrier` in the text), and
|
|
416
|
+
in `jojo plan` (an `id` field that `jojo generate` writes back). Tools and
|
|
417
|
+
LLMs can therefore refer to "the `@pick-carrier` stage" instead of a position
|
|
418
|
+
in the source. IDs must be unique within a pipeline. They are not part of the
|
|
419
|
+
pipeline's hash, so renaming a label never invalidates saved checkpoints.
|
|
420
|
+
|
|
224
421
|
#### Placeholder argument
|
|
225
422
|
|
|
226
423
|
By default the piped value is inserted as the stage's first argument. The
|
|
@@ -325,6 +522,381 @@ auto-imported):
|
|
|
325
522
|
See [ITERATOR_PROPOSAL.md](ITERATOR_PROPOSAL.md) and [ITERATORS.md](ITERATORS.md)
|
|
326
523
|
for the full laziness/memory model.
|
|
327
524
|
|
|
525
|
+
## Explaining pipelines
|
|
526
|
+
|
|
527
|
+
`jojo explain` lets a person, a tool, or an LLM ask JojoScript what a program
|
|
528
|
+
means instead of inferring it from source. It is static: nothing is executed.
|
|
529
|
+
|
|
530
|
+
```jojo
|
|
531
|
+
effect enrichOrder(order)
|
|
532
|
+
|
|
533
|
+
export async fn enrichPaidOrders(orders) {
|
|
534
|
+
return handle {
|
|
535
|
+
enrichOrder: order => ({ ...order, region: "EU" })
|
|
536
|
+
} {
|
|
537
|
+
return orders
|
|
538
|
+
|> filter(order => order.status == "paid")
|
|
539
|
+
|> parallel(4)
|
|
540
|
+
|> map(enrichOrder)
|
|
541
|
+
|> checkpoint("enriched-orders")
|
|
542
|
+
|> toArrayAsync
|
|
543
|
+
|> await
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
```
|
|
549
|
+
$ jojo explain orders.jojo
|
|
550
|
+
PIPELINE enrichPaidOrders
|
|
551
|
+
|
|
552
|
+
STAGES
|
|
553
|
+
1 source orders
|
|
554
|
+
2 filter pure
|
|
555
|
+
3 parallel concurrency=4
|
|
556
|
+
4 map effectful: enrichOrder
|
|
557
|
+
5 checkpoint enriched-orders
|
|
558
|
+
6 toArrayAsync pure
|
|
559
|
+
7 await control
|
|
560
|
+
|
|
561
|
+
EFFECTS
|
|
562
|
+
enrichOrder (handler declared)
|
|
563
|
+
|
|
564
|
+
PROPERTIES
|
|
565
|
+
lazy: true
|
|
566
|
+
parallel: true
|
|
567
|
+
resumable: true
|
|
568
|
+
deterministic: false
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
`jojo explain orders.jojo --format json` prints the same information as an
|
|
572
|
+
array of `{ name, id, stages, effects, properties }` objects, and
|
|
573
|
+
`explainPipelines(source)` / `renderExplanationText(explanation)` are exported
|
|
574
|
+
from `@panagos/jojoscript` for programmatic use.
|
|
575
|
+
|
|
576
|
+
- **name** is inferred: the variable or property a pipeline is assigned to
|
|
577
|
+
(`paid := orders |> ...`), the `fn` name of an expression function, or the
|
|
578
|
+
enclosing named function of a `return` pipeline; otherwise `pipeline-N`.
|
|
579
|
+
- **stages** list each stage's type, raw `args`, referenced `effects`, and a
|
|
580
|
+
`detail`: the source expression, `concurrency=N` for `parallel`, `size=N` for
|
|
581
|
+
`batch`, the checkpoint name, `control` for `await`/`catch`/`orElse`/`pass`,
|
|
582
|
+
or `pure`/`effectful: <effects>`.
|
|
583
|
+
- **effects** are the declared effects the pipeline references, each with the
|
|
584
|
+
same best-effort `staticallyHandled` signal as `jojo graph`.
|
|
585
|
+
- **properties**: `lazy` (no `sort`/`sortBy`/`groupBy`/`partition`
|
|
586
|
+
materialization boundary), `parallel` (has a `parallel` stage), `resumable`
|
|
587
|
+
(has a `checkpoint` stage), `deterministic` (references no declared effect).
|
|
588
|
+
|
|
589
|
+
Purity and determinism only reflect references to declared `effect`s and
|
|
590
|
+
`effectful fn`s (see [Pure and effectful functions](#pure-and-effectful-functions)): a stage
|
|
591
|
+
calling ordinary JavaScript such as `Math.random()` or `fetch()` directly is
|
|
592
|
+
still reported as `pure`. Route external work through effects to make it
|
|
593
|
+
visible. See
|
|
594
|
+
[example/src/application/explain-example.jojo](example/src/application/explain-example.jojo).
|
|
595
|
+
|
|
596
|
+
## Machine-readable execution plans
|
|
597
|
+
|
|
598
|
+
`jojo plan` gives every pipeline a stable, simple JSON representation, and
|
|
599
|
+
`jojo generate` turns such a plan back into JojoScript. A tool or an LLM can
|
|
600
|
+
read a program as data, or write a plan and have JojoScript validate it:
|
|
601
|
+
|
|
602
|
+
```
|
|
603
|
+
Jojo source -> jojo plan -> JSON plan -> LLM
|
|
604
|
+
LLM -> JSON plan -> jojo generate -> validated Jojo program
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
```jojo
|
|
608
|
+
export fn topCustomers(customers) {
|
|
609
|
+
return customers
|
|
610
|
+
|> filter(customer => customer.active)
|
|
611
|
+
|> sortBy(customer => -customer.spend)
|
|
612
|
+
|> take(2)
|
|
613
|
+
|> map(customer => customer.name)
|
|
614
|
+
|> toArray
|
|
615
|
+
}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
```
|
|
619
|
+
$ jojo plan customers.jojo --format=json
|
|
620
|
+
{
|
|
621
|
+
"version": 1,
|
|
622
|
+
"pipelines": [
|
|
623
|
+
{
|
|
624
|
+
"pipeline": "topCustomers",
|
|
625
|
+
"id": "…",
|
|
626
|
+
"stages": [
|
|
627
|
+
{ "type": "source", "expression": "customers" },
|
|
628
|
+
{ "type": "filter", "expression": "customer => customer.active" },
|
|
629
|
+
{ "type": "sortBy", "expression": "customer => -customer.spend" },
|
|
630
|
+
{ "type": "take", "args": ["2"] },
|
|
631
|
+
{ "type": "map", "expression": "customer => customer.name" },
|
|
632
|
+
{ "type": "toArray" }
|
|
633
|
+
]
|
|
634
|
+
}
|
|
635
|
+
]
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
(Printed with two-space indentation; condensed here.)
|
|
640
|
+
|
|
641
|
+
The plan format, version 1:
|
|
642
|
+
|
|
643
|
+
| Field | Meaning |
|
|
644
|
+
|---|---|
|
|
645
|
+
| `version` | Always `1` for this format |
|
|
646
|
+
| `pipelines[].pipeline` | Inferred name (same rules as `jojo explain`), or `null` for an unnamed pipeline |
|
|
647
|
+
| `pipelines[].id` | The pipeline's deterministic hash (informational, ignored by `generate`) |
|
|
648
|
+
| `pipelines[].stages[0]` | `{ "type": "source", "expression": "..." }` |
|
|
649
|
+
| `parallel` / `batch` | `concurrency` / `size` as a positive integer |
|
|
650
|
+
| `checkpoint` | `name` as a string |
|
|
651
|
+
| `map`, `filter`, `flatMap`, `tap`, `mapAsync`, `filterAsync`, `find`, `some`, `every`, `sortBy`, `distinctBy`, `groupBy`, `partition`, `catch` | `expression`: the single function argument as source text |
|
|
652
|
+
| any other stage | `args`: argument source strings, omitted when there are none |
|
|
653
|
+
| `.prop` / `.method()` | `{ "type": ".length" }` reads a property; `{ "type": ".trim", "args": [] }` calls it |
|
|
654
|
+
| `id` (any stage after the source) | Optional [stage ID](#stage-ids) such as `"pick-carrier"`, unique within the pipeline |
|
|
655
|
+
|
|
656
|
+
A stage whose setting is not a literal (`parallel(limit)`) keeps it in `args`
|
|
657
|
+
instead, so a plan is always lossless. Arguments stay source text: the plan
|
|
658
|
+
describes computation without evaluating it.
|
|
659
|
+
|
|
660
|
+
`jojo generate plan.json` (or `planToJojo(plan)`) emits one
|
|
661
|
+
`name := source |> ...` statement per pipeline. It rejects unknown fields,
|
|
662
|
+
wrong types, misplaced `source` stages, and duplicate names with the exact path
|
|
663
|
+
of the problem (for example
|
|
664
|
+
`Invalid plan: pipelines[0].stages[2].concurrency must be a positive integer`),
|
|
665
|
+
then compiles the program and checks that it parses as JavaScript, without
|
|
666
|
+
running it. The generated program contains only the pipelines: their source
|
|
667
|
+
variables, custom stage functions, and effects must be supplied by
|
|
668
|
+
surrounding code. Validation is not a sandbox: expressions are code, so
|
|
669
|
+
review a generated program as you would any code you did not write.
|
|
670
|
+
|
|
671
|
+
`planPipelines(source)` and `planToJojo(plan)` are exported from
|
|
672
|
+
`@panagos/jojoscript`. See
|
|
673
|
+
[example/src/application/plan-example.jojo](example/src/application/plan-example.jojo).
|
|
674
|
+
|
|
675
|
+
## Checking programs
|
|
676
|
+
|
|
677
|
+
`jojo check` validates a program, or a JSON plan, without running it. It is
|
|
678
|
+
designed for LLM-generated code: every problem is a structured diagnostic with
|
|
679
|
+
a stable code, a line, and a concrete suggestion, instead of a raw stack trace.
|
|
680
|
+
|
|
681
|
+
```
|
|
682
|
+
$ jojo check generated.jojo
|
|
683
|
+
ERROR E301
|
|
684
|
+
line 8
|
|
685
|
+
Call divide(1, 0) violates "requires b != 0"
|
|
686
|
+
suggestion: Pass arguments to "divide" that satisfy b != 0
|
|
687
|
+
|
|
688
|
+
1 error, 0 warnings
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
`--machine` (or `--format json`) prints the same result as JSON:
|
|
692
|
+
|
|
693
|
+
```json
|
|
694
|
+
{
|
|
695
|
+
"valid": false,
|
|
696
|
+
"errors": [
|
|
697
|
+
{
|
|
698
|
+
"code": "E301",
|
|
699
|
+
"severity": "error",
|
|
700
|
+
"line": 8,
|
|
701
|
+
"message": "Call divide(1, 0) violates \"requires b != 0\"",
|
|
702
|
+
"suggestion": "Pass arguments to \"divide\" that satisfy b != 0"
|
|
703
|
+
}
|
|
704
|
+
],
|
|
705
|
+
"warnings": []
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
The command exits with code 1 when there is at least one error, so a
|
|
710
|
+
generate-check-fix loop can rely on the exit code.
|
|
711
|
+
|
|
712
|
+
| Code | Severity | Meaning |
|
|
713
|
+
|---|---|---|
|
|
714
|
+
| `E100` | error | The compiler rejects the program (with its line when known) |
|
|
715
|
+
| `E101` | error | The compiled JavaScript does not parse (checked by Node's own parser) |
|
|
716
|
+
| `W102` | warning | A declared effect is used but no `handle { ... }` in the file handles it; a caller may still install one |
|
|
717
|
+
| `E201` | error | A `pure fn` uses a declared effect or an `effectful fn` |
|
|
718
|
+
| `E301` | error | A call with literal arguments breaks a `requires` clause |
|
|
719
|
+
| `E401` | error | A `match` over a declared tagged union misses a variant and has no `_` arm |
|
|
720
|
+
| `E402` | error | A constructor pattern has a different number of fields than its variant |
|
|
721
|
+
| `E700` | error | `jojo check plan.json`: the plan is invalid (message names the exact field) |
|
|
722
|
+
|
|
723
|
+
Contract clauses are only evaluated statically when they use nothing but the
|
|
724
|
+
function's parameters, literals, and operators, so `jojo check` never runs
|
|
725
|
+
program code. `checkSource(source)`, `checkPlan(plan)`, and
|
|
726
|
+
`renderCheckText(result)` are exported from `@panagos/jojoscript`. See
|
|
727
|
+
[example/src/application/check-example.jojo](example/src/application/check-example.jojo).
|
|
728
|
+
|
|
729
|
+
## Describing programs
|
|
730
|
+
|
|
731
|
+
`jojo describe` prints one self-describing JSON document for a file, so a tool
|
|
732
|
+
or an LLM can learn what a program contains without reading its source:
|
|
733
|
+
|
|
734
|
+
```json
|
|
735
|
+
{
|
|
736
|
+
"language": "jojo",
|
|
737
|
+
"version": "1",
|
|
738
|
+
"pipelines": [
|
|
739
|
+
{
|
|
740
|
+
"name": "openInvoices",
|
|
741
|
+
"inputs": ["records"],
|
|
742
|
+
"effects": [],
|
|
743
|
+
"stages": [
|
|
744
|
+
{ "id": "unpaid", "operation": "filter", "expression": "record => !record.paid" },
|
|
745
|
+
{ "id": "classify", "operation": "map", "expression": "toInvoice" },
|
|
746
|
+
{ "id": "node-3", "operation": "checkpoint", "name": "open-invoices" }
|
|
747
|
+
],
|
|
748
|
+
"properties": { "lazy": true, "parallel": false, "resumable": true, "deterministic": true }
|
|
749
|
+
}
|
|
750
|
+
],
|
|
751
|
+
"effects": [{ "name": "storeInvoice", "params": ["invoice"], "handled": true }],
|
|
752
|
+
"functions": [
|
|
753
|
+
{ "name": "toInvoice", "line": 12, "purity": "pure", "requires": ["record.amount >= 0"], "ensures": [] }
|
|
754
|
+
],
|
|
755
|
+
"types": [
|
|
756
|
+
{ "name": "Invoice", "variants": [{ "name": "Open", "fields": ["id", "amount"] }, { "name": "Settled", "fields": ["id"] }] }
|
|
757
|
+
]
|
|
758
|
+
}
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
- `pipelines` uses the same names, effects, and properties as `jojo explain`,
|
|
762
|
+
and the same stage fields as `jojo plan`. A pipeline whose source is an
|
|
763
|
+
identifier lists it in `inputs`. A stage's `id` is its
|
|
764
|
+
[stage ID](#stage-ids), or the runtime node id (`node-3`) used by
|
|
765
|
+
`jojo graph` and `jojo profile`.
|
|
766
|
+
- `effects` lists declared effects and whether a handler is statically
|
|
767
|
+
visible in the file.
|
|
768
|
+
- `functions` lists functions that declare a purity or contracts.
|
|
769
|
+
- `types` lists tagged unions with their variants and fields.
|
|
770
|
+
|
|
771
|
+
`--pipeline <name>` limits `pipelines` to one pipeline. `describeSource(source)`
|
|
772
|
+
is exported from `@panagos/jojoscript`. See
|
|
773
|
+
[example/src/application/describe-example.jojo](example/src/application/describe-example.jojo).
|
|
774
|
+
|
|
775
|
+
## Parsing programs
|
|
776
|
+
|
|
777
|
+
`jojo parse <file.jojo> --json` prints the syntax-level view of a file: every
|
|
778
|
+
JojoScript construct the compiler recognizes, with its line, ordered by line.
|
|
779
|
+
Where `jojo describe` summarizes what a program means, `jojo parse` lists what
|
|
780
|
+
it contains, so a tool or an LLM can locate and edit constructs precisely.
|
|
781
|
+
|
|
782
|
+
```json
|
|
783
|
+
{
|
|
784
|
+
"language": "jojo",
|
|
785
|
+
"version": "1",
|
|
786
|
+
"nodes": [
|
|
787
|
+
{ "kind": "type", "line": 6, "name": "Decision", "exported": true,
|
|
788
|
+
"variants": [{ "name": "Approved", "fields": ["id"] }, { "name": "Escalated", "fields": ["id", "team"] }] },
|
|
789
|
+
{ "kind": "function", "line": 10, "name": "decide", "params": ["refund"], "exported": false,
|
|
790
|
+
"async": false, "purity": null, "body": "expression", "requires": [], "ensures": [] },
|
|
791
|
+
{ "kind": "match", "line": 13, "patterns": ["Approved(id)", "Escalated(id, team)"] },
|
|
792
|
+
{ "kind": "pipeline", "line": 20, "name": "routeRefunds", "declared": false, "source": "refunds",
|
|
793
|
+
"stages": [{ "type": "map", "args": ["decide"], "id": null }] }
|
|
794
|
+
]
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
| Kind | Fields |
|
|
799
|
+
|---|---|
|
|
800
|
+
| `import` | `from`, `names` |
|
|
801
|
+
| `effect` | `name`, `params` |
|
|
802
|
+
| `type` | `name`, `exported`, `variants` (`name`, `fields`) |
|
|
803
|
+
| `function` | `name`, `params` (as written, including defaults), `exported`, `async`, `purity`, `body` (`"block"` or `"expression"`), `requires`, `ensures` |
|
|
804
|
+
| `pipeline` | `name`, `declared` (a `pipeline` block), `source`, `stages` (`type`, `args`, `id`) |
|
|
805
|
+
| `handle` | `handlers` |
|
|
806
|
+
| `match` | `patterns` |
|
|
807
|
+
|
|
808
|
+
JojoScript has no AST by design: nodes come from the same finders the compiler
|
|
809
|
+
uses, so `parse` never disagrees with what compiles. Invalid syntax is
|
|
810
|
+
reported as a single `jojo:` error line. `parseSource(source)` is exported
|
|
811
|
+
from `@panagos/jojoscript`. See
|
|
812
|
+
[example/src/application/parse-example.jojo](example/src/application/parse-example.jojo).
|
|
813
|
+
|
|
814
|
+
## Simulating pipelines
|
|
815
|
+
|
|
816
|
+
`jojo simulate` answers "what would this pipeline do with N records?" before
|
|
817
|
+
anything runs. No stage or effect is executed:
|
|
818
|
+
|
|
819
|
+
```
|
|
820
|
+
$ jojo simulate prices.jojo --input 250000 --pipeline pricedProducts
|
|
821
|
+
SIMULATION pricedProducts
|
|
822
|
+
|
|
823
|
+
INPUT
|
|
824
|
+
250,000 records
|
|
825
|
+
|
|
826
|
+
OPERATIONS
|
|
827
|
+
2 filter 250,000 items in
|
|
828
|
+
3 parallel ≤250,000 items in, 16 workers
|
|
829
|
+
4 map ≤250,000 items in, ≤250,000 calls to fetchPrice
|
|
830
|
+
5 checkpoint ≤250,000 items in, saved as "priced"
|
|
831
|
+
|
|
832
|
+
POTENTIAL PROBLEMS
|
|
833
|
+
⚠ stage 4: up to 250,000 calls to effect "fetchPrice"
|
|
834
|
+
⚠ stage 4: effect "fetchPrice" runs before checkpoint "priced": items processed before a failure are fetched again on retry (at-least-once)
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
Stages are numbered as in `jojo explain`, where the source is stage 1.
|
|
838
|
+
Item counts flow through the stages as upper bounds (`≤` once a filtering
|
|
839
|
+
stage such as `filter`, `unique`, or `distinctBy` has run). `take`, `skip`,
|
|
840
|
+
`chunk`, `batch`, and `window` change them exactly; `flatMap` and `groupBy`
|
|
841
|
+
make them unknown; `count`, `first`, `find`, `some`, `every`, and `reduce`
|
|
842
|
+
produce one result. A stage that uses a declared effect or an `effectful fn`
|
|
843
|
+
makes one call per item it receives.
|
|
844
|
+
|
|
845
|
+
Potential problems reported:
|
|
846
|
+
|
|
847
|
+
- 1,000 or more calls to one effect;
|
|
848
|
+
- an effect that runs before a checkpoint, and so may run again for items
|
|
849
|
+
processed before a failure (see
|
|
850
|
+
[Effects and checkpoint/resume](#effects-and-checkpointresume));
|
|
851
|
+
- a pipeline with effects but no checkpoint at all;
|
|
852
|
+
- `sort`, `sortBy`, `groupBy`, or `partition` holding 10,000 or more items in
|
|
853
|
+
memory.
|
|
854
|
+
|
|
855
|
+
The input defaults to 1,000 records. `--format json` prints the simulation as
|
|
856
|
+
data, and `--pipeline <name>` selects one pipeline. `simulatePipelines(source,
|
|
857
|
+
{ input })` and `renderSimulationText(simulation)` are exported from
|
|
858
|
+
`@panagos/jojoscript`. See
|
|
859
|
+
[example/src/application/simulate-example.jojo](example/src/application/simulate-example.jojo).
|
|
860
|
+
|
|
861
|
+
## Asking why
|
|
862
|
+
|
|
863
|
+
`jojo why` turns the static analysis into plain-language answers, numbered
|
|
864
|
+
like `jojo explain` (the source is stage 1):
|
|
865
|
+
|
|
866
|
+
```
|
|
867
|
+
$ jojo why onboarding.jojo --pipeline verifiedCustomers
|
|
868
|
+
WHY verifiedCustomers
|
|
869
|
+
|
|
870
|
+
Why is this pipeline not deterministic?
|
|
871
|
+
→ Stage 3 (mapAsync) uses effect verifyEmail; stage 5 (mapAsync) uses effectful function sendWelcome.
|
|
872
|
+
|
|
873
|
+
Why is this pipeline lazy?
|
|
874
|
+
→ Every stage passes items on one at a time, so no stage waits for the complete input.
|
|
875
|
+
|
|
876
|
+
Why is this pipeline resumable?
|
|
877
|
+
→ Checkpoint "verified" (stage 4) saves its items once they all complete; a rerun restores them instead of repeating stages 2-3.
|
|
878
|
+
|
|
879
|
+
Why can't stage 3 be moved before stage 2?
|
|
880
|
+
→ Stage 3 (mapAsync) uses effect verifyEmail and stage 2 (filter) decides which items reach it: moving it earlier would also run it for items that stage 2 drops.
|
|
881
|
+
|
|
882
|
+
Why does checkpoint "verified" not guarantee replay safety?
|
|
883
|
+
→ Effect verifyEmail runs before it (stage 3): if a run fails before "verified" completes, it runs again on retry (at-least-once, not exactly-once).
|
|
884
|
+
|
|
885
|
+
Why can stage 5 repeat after a failure?
|
|
886
|
+
→ Stage 5 (mapAsync) uses effectful function sendWelcome after the last checkpoint "verified": a rerun restores the checkpoint and runs it again for every restored item.
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
Every pipeline gets answers about determinism, laziness, and resumability.
|
|
890
|
+
Additional questions appear when they apply: an effectful stage behind a
|
|
891
|
+
filtering stage (`filter`, `filterAsync`, `unique`, `distinctBy`, `take`,
|
|
892
|
+
`skip`) cannot be moved before it, a `parallel` stage explains what it
|
|
893
|
+
parallelizes, and checkpoints explain which effects can run again on retry.
|
|
894
|
+
Answers only reflect declared effects and `effectful fn`s. `--format json`
|
|
895
|
+
prints `[{ name, answers: [{ question, answer }] }]`, and
|
|
896
|
+
`whyPipelines(source)` / `renderWhyText(why)` are exported from
|
|
897
|
+
`@panagos/jojoscript`. See
|
|
898
|
+
[example/src/application/why-example.jojo](example/src/application/why-example.jojo).
|
|
899
|
+
|
|
328
900
|
## Effect Handlers
|
|
329
901
|
|
|
330
902
|
> This is a deliberately simplified effect-handler model, not full
|
|
@@ -704,6 +1276,44 @@ interface (`record`/`replayNext`), mirroring `CheckpointStore` — an
|
|
|
704
1276
|
extension point for a future backend, not a full production-grade
|
|
705
1277
|
persistence system.
|
|
706
1278
|
|
|
1279
|
+
### Deterministic execution mode
|
|
1280
|
+
|
|
1281
|
+
`now()` and `random()` are built-in capabilities, like `parallel()`. Outside
|
|
1282
|
+
any handler they behave like `Date.now()` and `Math.random()`, and a
|
|
1283
|
+
`handle { TIME: ..., RANDOM: ... }` block can replace them:
|
|
1284
|
+
|
|
1285
|
+
```jojo
|
|
1286
|
+
import { now, random } from "@panagos/jojoscript/runtime"
|
|
1287
|
+
|
|
1288
|
+
order := { id: Math.round(random() * 1000), createdAt: new Date(now()) }
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
`deterministic(fn, { seed, startTime })` runs `fn` so that the same inputs
|
|
1292
|
+
always produce the same outputs:
|
|
1293
|
+
|
|
1294
|
+
- `random()` is a seeded generator (`seed` defaults to `1`);
|
|
1295
|
+
- `now()` starts at `startTime` (default `0`) and advances 1ms per read;
|
|
1296
|
+
- `parallel()` stages process one item at a time, in order;
|
|
1297
|
+
- every declared effect must resolve to a handler declared deterministic
|
|
1298
|
+
(`{ handle, deterministic: true }`, or `replayHandlers(...)`); any other
|
|
1299
|
+
handler throws a non-retryable `NondeterministicEffectError` before it runs;
|
|
1300
|
+
- checkpoints record `now()` as their `createdAt`, so reruns write identical
|
|
1301
|
+
checkpoints.
|
|
1302
|
+
|
|
1303
|
+
```js
|
|
1304
|
+
import { deterministic } from "@panagos/jojoscript/runtime"
|
|
1305
|
+
|
|
1306
|
+
const first = await deterministic(() => generateOrders(3), { seed: 2026 })
|
|
1307
|
+
const second = await deterministic(() => generateOrders(3), { seed: 2026 })
|
|
1308
|
+
// first and second are deep-equal
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
`jojo run app.jojo --deterministic [--seed <n>]` runs a whole program, top-level
|
|
1312
|
+
code included, in this mode. Determinism covers JojoScript's capabilities:
|
|
1313
|
+
direct calls to `Date.now()`, `Math.random()`, or I/O outside declared effects
|
|
1314
|
+
are not intercepted. See
|
|
1315
|
+
[example/src/application/deterministic-example.jojo](example/src/application/deterministic-example.jojo).
|
|
1316
|
+
|
|
707
1317
|
### JavaScript interoperability
|
|
708
1318
|
|
|
709
1319
|
A handler is just a normal JavaScript function — sync or async — and can
|
|
@@ -790,6 +1400,38 @@ never through `|>`) and also uses pipeline syntax with overlapping names,
|
|
|
790
1400
|
the compiler merges the auto-imported names into the existing import
|
|
791
1401
|
instead of emitting a second, colliding import declaration.
|
|
792
1402
|
|
|
1403
|
+
### Deterministic serialization
|
|
1404
|
+
|
|
1405
|
+
`toJson(value, { indent })` and `fromJson(text)` give JojoScript values one
|
|
1406
|
+
canonical JSON form:
|
|
1407
|
+
|
|
1408
|
+
```jojo
|
|
1409
|
+
import { toJson, fromJson } from "@panagos/jojoscript/runtime"
|
|
1410
|
+
|
|
1411
|
+
snapshot := toJson({ name: "John", id: 42, placedAt: new Date(0) })
|
|
1412
|
+
// {"id":42,"name":"John","placedAt":{"$type":"Date","value":"1970-01-01T00:00:00.000Z"}}
|
|
1413
|
+
|
|
1414
|
+
order := fromJson(snapshot) // order.placedAt is a Date again
|
|
1415
|
+
```
|
|
1416
|
+
|
|
1417
|
+
- Object keys are sorted recursively, so equal values always serialize to
|
|
1418
|
+
identical text, whatever order their properties were created in.
|
|
1419
|
+
- Values plain JSON would lose or change are written as `{ "$type": ... }`
|
|
1420
|
+
objects: `Date`, `Map`, `Set`, `BigInt`, `undefined`, `NaN`, `Infinity`,
|
|
1421
|
+
`-Infinity`, `-0`, and `Error` (name, message, own fields such as a
|
|
1422
|
+
`ContractError`'s `kind`, and `cause`). `fromJson` restores each exactly.
|
|
1423
|
+
- An ordinary object that has its own `$type` key is wrapped, so tags are
|
|
1424
|
+
never ambiguous. Tagged union values are plain objects and serialize as
|
|
1425
|
+
they are. Objects with a `toJSON` method serialize its result.
|
|
1426
|
+
- Functions, symbols, and circular structures throw a `TypeError`.
|
|
1427
|
+
|
|
1428
|
+
`FileCheckpointStore` writes and reads checkpoints in this format, so
|
|
1429
|
+
checkpointed dates, maps, and sets resume as themselves. Checkpoint files
|
|
1430
|
+
written as plain JSON before this change still load. Pipelines and execution
|
|
1431
|
+
plans have their own canonical form: the [`jojo plan`](#machine-readable-execution-plans)
|
|
1432
|
+
JSON. See
|
|
1433
|
+
[example/src/application/serialization-example.jojo](example/src/application/serialization-example.jojo).
|
|
1434
|
+
|
|
793
1435
|
### MCP support
|
|
794
1436
|
|
|
795
1437
|
JojoScript includes a small MCP-style JSON-RPC runtime for Node.js. It does
|
|
@@ -962,10 +1604,36 @@ const total = users
|
|
|
962
1604
|
boundaries because they need the complete input. See
|
|
963
1605
|
[ITERATORS.md](ITERATORS.md) for the complete proposal and memory model.
|
|
964
1606
|
|
|
1607
|
+
## Formatting
|
|
1608
|
+
|
|
1609
|
+
`jojo format <file.jojo|directory>` reformats files in place and prints each
|
|
1610
|
+
file it changed. `--check` changes nothing: it lists the files that need
|
|
1611
|
+
formatting and exits with code 1 if there are any, which suits CI and
|
|
1612
|
+
generate-format-check loops.
|
|
1613
|
+
|
|
1614
|
+
The formatter is line-based, like the compiler: it normalizes indentation and
|
|
1615
|
+
whitespace but never rewrites expressions.
|
|
1616
|
+
|
|
1617
|
+
- A line that opens brackets indents their contents one level, however many
|
|
1618
|
+
it opens (`posts.reduce((groups, post) => {`).
|
|
1619
|
+
- `|>` stages, `requires` / `ensures` clauses, `| Variant` lines, ternary
|
|
1620
|
+
`?` / `:` lines, leading `.method()` chains, and the line after a trailing
|
|
1621
|
+
`:=` or `=>` are indented one extra level. Stages inside a
|
|
1622
|
+
`pipeline name { ... }` block line up with its `source` line.
|
|
1623
|
+
- Trailing whitespace is removed, Windows line endings become `\n`, runs of
|
|
1624
|
+
blank lines collapse to one, and the file ends with a single newline.
|
|
1625
|
+
- Block comments and template literal text are left untouched.
|
|
1626
|
+
|
|
1627
|
+
`formatSource(source, { indent })` is exported from `@panagos/jojoscript`, and
|
|
1628
|
+
the VS Code extension's `Format Document` uses the same formatter. Every file
|
|
1629
|
+
under `example/src` is kept formatted by a test.
|
|
1630
|
+
|
|
965
1631
|
## Editor support
|
|
966
1632
|
|
|
967
1633
|
See [editors/vscode](editors/vscode) for a VS Code extension providing syntax
|
|
968
|
-
highlighting
|
|
1634
|
+
highlighting for all JojoScript syntax (pipelines, stage IDs, named
|
|
1635
|
+
pipelines, `match` patterns, tagged unions, effects, purity, and contracts)
|
|
1636
|
+
and a `Format Document` provider for `.jojo` files. It is not
|
|
969
1637
|
published to the marketplace; package it with `vsce package` and install the
|
|
970
1638
|
resulting `.vsix` locally.
|
|
971
1639
|
|
|
@@ -973,8 +1641,6 @@ resulting `.vsix` locally.
|
|
|
973
1641
|
|
|
974
1642
|
- **No source maps.** Stack traces from compiled `dist/*.js` point at the
|
|
975
1643
|
generated JavaScript, not the original `.jojo` source.
|
|
976
|
-
- **No `jojo fmt` CLI command.** Formatting is only available through the
|
|
977
|
-
VS Code extension's `Format Document` command, not the `jojo` CLI.
|
|
978
1644
|
- **No regex-literal awareness in the lexer.** A regex containing `{`, `}`,
|
|
979
1645
|
`(`, or `)` can confuse brace/paren-depth-sensitive transforms (pipelines,
|
|
980
1646
|
`match`, destructuring, type annotations). Prefer `new RegExp(...)` in code
|
|
@@ -1003,7 +1669,7 @@ resulting `.vsix` locally.
|
|
|
1003
1669
|
actually consumed.** Constructing a lazy pipeline inside a `handle` block
|
|
1004
1670
|
without consuming it there (no `toArray`/`toArrayAsync`/`reduce`/...) means
|
|
1005
1671
|
effects pulled later, outside the block, won't see its handlers.
|
|
1006
|
-
-
|
|
1007
|
-
mentioned as a long-term direction (`IO`, `HTTP`, `FILE`,
|
|
1008
|
-
`
|
|
1672
|
+
- **`PARALLEL`, `TIME`, and `RANDOM` are the only built-in effects.** The
|
|
1673
|
+
other capabilities mentioned as a long-term direction (`IO`, `HTTP`, `FILE`,
|
|
1674
|
+
`DATABASE`, `SLEEP`, `CHECKPOINT`) are not implemented; user-defined
|
|
1009
1675
|
`effect` declarations cover the same need today.
|