rip-lang 3.14.0 → 3.14.2

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
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="https://github.com/shreeve/rip-lang/commits/main"><img src="https://img.shields.io/badge/version-3.14.0-blue.svg" alt="Version"></a>
12
+ <a href="https://github.com/shreeve/rip-lang/commits/main"><img src="https://img.shields.io/badge/version-3.14.2-blue.svg" alt="Version"></a>
13
13
  <a href="#zero-dependencies"><img src="https://img.shields.io/badge/dependencies-ZERO-brightgreen.svg" alt="Dependencies"></a>
14
14
  <a href="#"><img src="https://img.shields.io/badge/tests-1%2C436%2F1%2C436-brightgreen.svg" alt="Tests"></a>
15
15
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
@@ -60,6 +60,34 @@ rip -c file.rip # Compile to JavaScript
60
60
 
61
61
  ---
62
62
 
63
+ ## Extensions
64
+
65
+ Pre-built binaries and VS Code / Cursor extensions are published via GitHub Pages — see the [extensions hub](https://shreeve.github.io/rip-lang/extensions/) for details.
66
+
67
+ **`ripdb`** — DuckDB extension that exposes a rip-db server as a first-class attached database:
68
+
69
+ ```sql
70
+ SET allow_unsigned_extensions = true;
71
+ INSTALL ripdb FROM 'https://shreeve.github.io/rip-lang/extensions/duckdb';
72
+ LOAD ripdb;
73
+ ```
74
+
75
+ **`rip-lang.print`** — syntax-highlighted source printer for VS Code / Cursor:
76
+
77
+ ```bash
78
+ curl -LO https://shreeve.github.io/rip-lang/extensions/vscode/print/print-latest.vsix
79
+ cursor --install-extension ./print-latest.vsix
80
+ ```
81
+
82
+ **`rip-lang.rip`** — Rip language support for VS Code / Cursor:
83
+
84
+ ```bash
85
+ curl -LO https://shreeve.github.io/rip-lang/extensions/vscode/rip/rip-latest.vsix
86
+ cursor --install-extension ./rip-latest.vsix
87
+ ```
88
+
89
+ ---
90
+
63
91
  ## Language
64
92
 
65
93
  ### Functions & Classes
@@ -405,9 +433,6 @@ The UI framework is built into rip-lang: file-based router, reactive stash, comp
405
433
  | **Reactivity** | None | Built-in |
406
434
  | **Dependencies** | Multiple | Zero |
407
435
  | **Self-hosting** | No | Yes |
408
- | **Lexer** | 3,558 LOC | 2,024 LOC |
409
- | **Compiler** | 10,346 LOC | 3,293 LOC |
410
- | **Total** | 17,760 LOC | ~11,890 LOC |
411
436
 
412
437
  Smaller codebase, modern output, built-in reactivity.
413
438
 
@@ -442,25 +467,10 @@ await rip("res = fetch! 'https://api.example.com/todos/1'; res.json!") // → {
442
467
 
443
468
  ```
444
469
  Source -> Lexer -> emitTypes -> Parser -> S-Expressions -> Codegen -> JavaScript
445
- (1,778) (types.js) (359) ["=", "x", 42] (3,334) + source map
470
+ (types.js) ["=", "x", 42] + source map
446
471
  ```
447
472
 
448
- Simple arrays (with `.loc`) instead of AST node classes. The compiler is self-hosting — `bun run parser` rebuilds from source.
449
-
450
- | Component | File | Lines |
451
- |-----------|------|-------|
452
- | Lexer + Rewriter | `src/lexer.js` | 1,778 |
453
- | Grammar | `src/grammar/grammar.rip` | 948 |
454
- | Parser Generator | `src/grammar/solar.rip` | 929 |
455
- | Parser (generated) | `src/parser.js` | 359 |
456
- | Compiler + Codegen | `src/compiler.js` | 3,334 |
457
- | Component System | `src/components.js` | 2,026 |
458
- | Browser Engine | `src/browser.js` | 194 |
459
- | Source Maps | `src/sourcemaps.js` | 189 |
460
- | Type System | `src/types.js` | 1,091 |
461
- | Type Checking | `src/typecheck.js` | 442 |
462
- | REPL | `src/repl.js` | 600 |
463
- | **Total** | | **~11,890** |
473
+ Simple arrays (with `.loc`) instead of AST node classes. The compiler is self-hosting — `bun run parser` rebuilds from source. The implementation lives across a handful of focused modules under `src/` — lexer, compiler, schema, types, typecheck, components, parser, browser, REPL — plus the grammar sources under `src/grammar/`. Run `wc -l src/*.js` for current sizes.
464
474
 
465
475
  ---
466
476
 
@@ -468,16 +478,16 @@ Simple arrays (with `.loc`) instead of AST node classes. The compiler is self-ho
468
478
 
469
479
  Rip includes optional packages for full-stack development:
470
480
 
471
- | Package | Version | Purpose |
472
- |---------|---------|---------|
473
- | [rip-lang](https://www.npmjs.com/package/rip-lang) | 3.13.62 | Core language compiler |
474
- | [@rip-lang/server](packages/server/) | 1.3.12 | Multi-worker app server (web framework, hot reload, HTTPS, mDNS) |
475
- | [@rip-lang/db](packages/db/) | 1.3.15 | DuckDB server with official UI + ActiveRecord-style client |
476
- | [@rip-lang/ui](packages/ui/) | — | Unified UI system — browser widgets, email components, shared helpers, Tailwind integration |
477
- | [@rip-lang/swarm](packages/swarm/) | 1.2.18 | Parallel job runner with worker pool |
478
- | [@rip-lang/csv](packages/csv/) | 1.3.6 | CSV parser + writer |
479
- | [@rip-lang/time](packages/time/) | 1.0.0 | Immutable date/time with IANA timezones + `Duration` (US-English, zero runtime deps) |
480
- | [VS Code Extension](packages/vscode/) | 0.5.7 | Syntax highlighting, type intelligence, source maps |
481
+ | Package | Purpose |
482
+ |---------|---------|
483
+ | [rip-lang](https://www.npmjs.com/package/rip-lang) | Core language compiler |
484
+ | [@rip-lang/server](packages/server/) | Multi-worker app server (web framework, hot reload, HTTPS, mDNS) |
485
+ | [@rip-lang/db](packages/db/) | DuckDB server with official UI + ActiveRecord-style client |
486
+ | [@rip-lang/ui](packages/ui/) | Unified UI system — browser widgets, email components, shared helpers, Tailwind integration |
487
+ | [@rip-lang/swarm](packages/swarm/) | Parallel job runner with worker pool |
488
+ | [@rip-lang/csv](packages/csv/) | CSV parser + writer |
489
+ | [@rip-lang/time](packages/time/) | Immutable date/time with IANA timezones + `Duration` (US-English, zero runtime deps) |
490
+ | [VS Code Extension](packages/vscode/) | Syntax highlighting, type intelligence, source maps |
481
491
 
482
492
  ```bash
483
493
  bun add -g @rip-lang/db # Installs everything (rip-lang + server + db)
@@ -515,7 +525,7 @@ rip file.rip # Run
515
525
  rip -c file.rip # Compile
516
526
  rip -t file.rip # Tokens
517
527
  rip -s file.rip # S-expressions
518
- bun run test # 1436 tests
528
+ bun run test:all # full test suite
519
529
  bun run parser # Rebuild parser
520
530
  bun run build # Build browser bundle
521
531
  ```
package/docs/RIP-LANG.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Rip Language Reference
4
4
 
5
- Rip is a modern reactive language that compiles to ES2022 JavaScript. It combines CoffeeScript's elegant syntax with built-in reactivity primitives. Zero dependencies, self-hosting, ~11,890 LOC.
5
+ Rip is a modern reactive language that compiles to ES2022 JavaScript. It combines CoffeeScript's elegant syntax with built-in reactivity primitives. Zero dependencies, self-hosting.
6
6
 
7
7
  ---
8
8
 
@@ -556,6 +556,93 @@ Common use cases: config objects, options bags, state initialization, DOM
556
556
  styling, merging defaults with overrides — anywhere you're setting multiple
557
557
  properties on an existing object.
558
558
 
559
+ ## Pick Operator (`.{ }`)
560
+
561
+ A Rip original. Project a subset of an object's properties into a new object,
562
+ with optional renaming and defaults — sugar for the common
563
+ destructure-then-construct pattern:
564
+
565
+ ```coffee
566
+ # Without .{ } — repetitive
567
+ response = { firstName: user.firstName, lastName: user.lastName, dob: user.dob }
568
+
569
+ # With .{ } — clean
570
+ response = user.{firstName, lastName, dob}
571
+ ```
572
+
573
+ `obj.{a, b, c}` compiles to `{ a: obj.a, b: obj.b, c: obj.c }`. The source is
574
+ evaluated once — if it's a complex expression (call, member access, indexed),
575
+ an arrow IIFE binds a single temp so getters and reactive reads fire exactly
576
+ once.
577
+
578
+ ### Forms
579
+
580
+ ```coffee
581
+ # Bare keys — project as-is
582
+ user.{firstName, lastName}
583
+ # → {firstName: user.firstName, lastName: user.lastName}
584
+
585
+ # Rename — left side is source key, right side is destination key
586
+ user.{firstName: given, lastName: family}
587
+ # → {given: user.firstName, family: user.lastName}
588
+
589
+ # Nullish default — fires on undefined OR null (deliberately broader than
590
+ # JS destructure's undefined-only default, to match DB NULL reality)
591
+ user.{role = 'guest', active = true}
592
+ # → {role: (user.role ?? 'guest'), active: (user.active ?? true)}
593
+
594
+ # Rename + default combined
595
+ user.{role: r = 'guest'}
596
+ # → {r: (user.role ?? 'guest')}
597
+
598
+ # Multi-line
599
+ response = user.{
600
+ firstName
601
+ lastName
602
+ dob
603
+ role = 'patient'
604
+ }
605
+ ```
606
+
607
+ ### Optional chain (`?.{ }`)
608
+
609
+ Returns `undefined` when the source is null/undefined, preserving the
610
+ distinction from a present-but-empty result:
611
+
612
+ ```coffee
613
+ maybeUser?.{firstName, lastName}
614
+ # → maybeUser == null ? undefined : {firstName: maybeUser.firstName, ...}
615
+ ```
616
+
617
+ ### Reserved words work
618
+
619
+ Reserved-word keys like `default`, `class`, `delete`, `new`, `typeof`, `if`
620
+ are automatically treated as property names inside a pick body — the same
621
+ way they work after a `.` for plain member access:
622
+
623
+ ```coffee
624
+ # Common real-world case (HTML/DOM/frameworks use `class`, `default`, …)
625
+ props.{class, default, onClick}
626
+ # → {class: props.class, default: props.default, onClick: props.onClick}
627
+ ```
628
+
629
+ ### Semantics
630
+
631
+ - **Missing keys** read as `undefined` (normal property read on the source).
632
+ - **Source evaluated once** for complex expressions (`getUser().{a, b}` only
633
+ calls `getUser()` one time).
634
+ - **Defaults fire on nullish** (`??`) — both `undefined` and `null` trigger,
635
+ unlike JS destructure which only fires on `undefined`.
636
+ - **Chainable** — `user.{firstName, role}.role` works.
637
+
638
+ ### Not supported (use explicit objects for these)
639
+
640
+ - Spread inside body: `user.{...rest}` is rejected.
641
+ - Computed or string/number keys: `user.{[k]}`, `user.{'a'}`, `user.{0}` are
642
+ rejected.
643
+ - Nested picks: `user.{address.{city, zip}}` is not supported in v1 — use an
644
+ explicit object or a helper for now.
645
+
559
646
  ## Prototype Operator (`::`)
560
647
 
561
648
  Access `.prototype` with `::` (CoffeeScript-style). Disambiguated from type annotations by spacing:
@@ -2201,8 +2288,6 @@ count = 10 # Logs: "Count: 10, Doubled: 20"
2201
2288
 
2202
2289
  Ideas and candidates that have been discussed but not yet implemented.
2203
2290
 
2204
- > **Note:** The standard library was implemented in v3.13.0. See [Section 14](#14-standard-library).
2205
-
2206
2291
  ## Future Syntax Ideas
2207
2292
 
2208
2293
  Each would need design discussion before building.
@@ -2238,4 +2323,4 @@ Each would need design discussion before building.
2238
2323
 
2239
2324
  ---
2240
2325
 
2241
- *Rip 3.13 1,436 tests — Zero dependencies — Self-hosting — ~11,890 LOC*
2326
+ *Rip — Zero dependencies — Self-hosting*
@@ -269,6 +269,36 @@ behavior is dropped** — methods, computed getters (`~>`), eager
269
269
  derived fields (`!>`), hooks, and ORM methods don't carry through.
270
270
  Algebra is a structural operation on fields, not a behavioral one.
271
271
 
272
+ ### Idiomatic shorthands
273
+
274
+ Most production code uses a few syntactic sugars. All are optional;
275
+ the Quick Tour above works as written.
276
+
277
+ ```coffee
278
+ # Open-ended ranges. With `!` (required), `..N` implies `min=1`.
279
+ User = schema
280
+ firstName! ..50 # required, 1..50 chars
281
+ bio? text # `text` is unbounded by design
282
+ phone? 1..20
283
+
284
+ # File-level default cap for uncapped VARCHAR-like fields.
285
+ schema.defaultMaxString = 500
286
+
287
+ Profile = schema :model
288
+ name! # → {min: 1, max: 500}
289
+ email!# email # → {max: 500}
290
+ bio? text # → uncapped (text opts out)
291
+
292
+ # One-line small shapes, plus a registered schema used as a field type.
293
+ Address = schema :shape; street? ..200; city? ..100; zip? ..10
294
+
295
+ Order = schema :shape
296
+ address! Address # validation recurses; errors like "address.street"
297
+ ```
298
+
299
+ See §5 for body-syntax details, §17 for nested type references, and
300
+ §20 for constraint and pragma rules.
301
+
272
302
  ---
273
303
 
274
304
  ## 3. Schemas vs types
@@ -517,6 +547,7 @@ kind (see [§18](#18-directives)). Examples:
517
547
  @timestamps # adds createdAt/updatedAt columns (:model only)
518
548
  @softDelete # adds deletedAt, soft-deletes on .destroy() (:model only)
519
549
  @index [role, active] # composite index (:model only)
550
+ @idStart 10001 # seed for the auto-id sequence (:model only, .toSQL())
520
551
  @belongs_to Organization? # nullable FK (:model only)
521
552
  @has_many Order # has-many relation (:model only)
522
553
  @mixin Timestamps # pull in a mixin's fields (any fielded kind)
@@ -864,7 +895,10 @@ user.save! # validate, run hooks, INSERT or UPDATE
864
895
  user.destroy! # run hooks, DELETE (or UPDATE deleted_at for @softDelete)
865
896
  user.ok() # boolean — current fields validate
866
897
  user.errors() # SchemaIssue[] — current fields' errors
867
- user.toJSON() # plain object of declared fields (no methods/getters)
898
+ user.toJSON() # plain object of own enumerable properties
899
+ # (id, declared fields, @timestamps columns, @softDelete
900
+ # deletedAt, @belongs_to FKs, !> eager-derived — but NOT
901
+ # methods, ~> computed getters, or internal state)
868
902
  ```
869
903
 
870
904
  Plus any methods, computed getters, and relation accessors you declared
@@ -976,6 +1010,25 @@ User.toSQL()
976
1010
  `.toSQL()` works independently of the ORM. A migration script that never
977
1011
  calls `.find()` or `.create()` can still emit full DDL.
978
1012
 
1013
+ #### Sequence start value
1014
+
1015
+ The auto-id sequence seeds at `1` by default. Override per-model with the
1016
+ `@idStart N` directive, or per-call with the `idStart` option (the option
1017
+ wins):
1018
+
1019
+ ```coffee
1020
+ User = schema :model
1021
+ name! string
1022
+ @idStart 10001 # customer-facing IDs start at 10001
1023
+
1024
+ User.toSQL() # → CREATE SEQUENCE users_seq START 10001;
1025
+ User.toSQL(idStart: 50000) # → CREATE SEQUENCE users_seq START 50000;
1026
+ ```
1027
+
1028
+ Required because DuckDB (as of 1.5.2) does not implement
1029
+ `ALTER SEQUENCE … RESTART WITH N` — so the seed has to be baked into the
1030
+ initial `CREATE SEQUENCE` rather than bumped in a follow-up migration.
1031
+
979
1032
  To emit a whole application's schema, call `.toSQL()` per model and join.
980
1033
  Order by FK dependency (models referenced via `@belongs_to` come first):
981
1034
 
@@ -1615,6 +1668,41 @@ p "[migrate] schema created"
1615
1668
  Because `.toSQL()` doesn't call the adapter, migration scripts work
1616
1669
  before the database exists or before the ORM is wired.
1617
1670
 
1671
+ ### Composing nested shapes
1672
+
1673
+ Small sub-shapes referenced by name compose into a larger contract.
1674
+ Validation recurses into each referenced schema; errors carry
1675
+ path-prefixed `field` entries so callers can pinpoint the failing
1676
+ sub-field:
1677
+
1678
+ ```coffee
1679
+ Address = schema :shape
1680
+ street! ..200
1681
+ city! ..100
1682
+ state? ..2
1683
+ zip? ..10
1684
+
1685
+ Customer = schema :shape
1686
+ id? integer
1687
+ name! ..100
1688
+ address! Address
1689
+
1690
+ OrderRequest = schema :shape
1691
+ customer! Customer
1692
+ notes? ..500
1693
+
1694
+ r = OrderRequest.safe body
1695
+ if r.ok
1696
+ process r.value
1697
+ else
1698
+ for e in r.errors
1699
+ # e.g. field: "customer.address.street" error: "required"
1700
+ console.log e.field, e.error, e.message
1701
+ ```
1702
+
1703
+ Registered `:shape` / `:input` / `:model` names can all be referenced
1704
+ as field types — see §17 for resolution rules.
1705
+
1618
1706
  ---
1619
1707
 
1620
1708
  ## 15. What's not here yet
@@ -1735,9 +1823,10 @@ Built-in type names and their runtime / SQL / TypeScript mappings:
1735
1823
 
1736
1824
  Arrays: `type[]`. SQL stores as `JSON` (DuckDB native), TS is `T[]`.
1737
1825
 
1738
- **Nested-schema identifiers.** When a field's type name resolves to
1739
- another schema in the process-global `__SchemaRegistry`, the
1740
- validator recurses:
1826
+ **Nested-schema identifiers.** A field's type name may be another
1827
+ schema declared with `:shape`, `:input`, or `:model`. When the name
1828
+ resolves to one of those in the process-global `__SchemaRegistry`,
1829
+ the validator recurses into the referenced schema:
1741
1830
 
1742
1831
  ```coffee
1743
1832
  Address = schema :shape
@@ -1789,6 +1878,7 @@ user-defined enums or shapes compose incrementally.
1789
1878
  | `@index [a, b, c]` | Composite index on the listed columns |
1790
1879
  | `@index column` | Single-column index (same as `@index [column]`) |
1791
1880
  | `@index [...] #` | Unique index |
1881
+ | `@idStart N` | Seed value for the auto-id sequence in `.toSQL()` output (default `1`). Overridden per-call by `toSQL(idStart: N)`. |
1792
1882
  | `@belongs_to Target` | FK column `target_id` referencing `targets.id`, NOT NULL |
1793
1883
  | `@belongs_to Target?` | Same, nullable |
1794
1884
  | `@has_one Target` | Accessor `target()` returning one |
@@ -2320,10 +2410,10 @@ preamble (under `SCHEMA_RUNTIME` in `src/schema.js`) that defines
2320
2410
  helpers. No import statement, no package dependency, no bootstrap call.
2321
2411
 
2322
2412
  **How big is the runtime?**
2323
- About 2,250 lines total across runtime + compile-time emission, including
2324
- the ORM, the DDL emitter, the registry, the validator plan, and the
2325
- hydration logic. The preamble injected into your compiled output is a
2326
- fraction of that (the ORM and DDL paths are tree-shaken if unused).
2413
+ It includes the validator plan, registry, hydration logic, ORM
2414
+ support, and DDL emission. In multi-bundle processes, Rip binds
2415
+ `schema` to a shared `globalThis.__ripSchema` singleton, so bundles
2416
+ share one registry and one adapter per process.
2327
2417
 
2328
2418
  **Is `.parse()` strict or permissive with extra keys?**
2329
2419
  Permissive with stripping. Unknown keys are silently dropped — they