rip-lang 3.16.0 → 3.16.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.
Files changed (57) hide show
  1. package/README.md +3 -4
  2. package/bin/rip +201 -18
  3. package/bin/rip-schema +175 -0
  4. package/docs/AGENTS.md +1 -1
  5. package/docs/RIP-APP.md +200 -19
  6. package/docs/RIP-DUCKDB.md +64 -1
  7. package/docs/RIP-INTRO.md +4 -4
  8. package/docs/RIP-LANG.md +36 -38
  9. package/docs/RIP-SCHEMA.md +1204 -364
  10. package/docs/RIP-TYPES.md +74 -103
  11. package/docs/demo/README.md +4 -3
  12. package/docs/dist/rip.js +4195 -966
  13. package/docs/dist/rip.min.js +1161 -284
  14. package/docs/dist/rip.min.js.br +0 -0
  15. package/docs/example/index.json +7 -7
  16. package/docs/example/index.json.br +0 -0
  17. package/docs/extensions/vscode/print/print-1.0.14.vsix +0 -0
  18. package/docs/extensions/vscode/print/print-latest.vsix +0 -0
  19. package/docs/extensions/vscode/rip/index.html +2 -1
  20. package/docs/extensions/vscode/rip/rip-0.5.15.vsix +0 -0
  21. package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
  22. package/docs/extensions/vscode/rip/vscode-rip-0.6.0.vsix +0 -0
  23. package/docs/index.html +1 -1
  24. package/docs/ui/bundle.json +55 -55
  25. package/docs/ui/bundle.json.br +0 -0
  26. package/docs/ui/hljs-rip.js +1 -1
  27. package/docs/ui/index.html +1 -1
  28. package/package.json +15 -7
  29. package/rip-loader.js +59 -2
  30. package/src/AGENTS.md +43 -12
  31. package/src/browser.js +52 -11
  32. package/src/compiler.js +538 -80
  33. package/src/components.js +488 -48
  34. package/src/dts.js +80 -50
  35. package/src/grammar/README.md +29 -170
  36. package/src/grammar/grammar.rip +17 -12
  37. package/src/grammar/solar.rip +4 -17
  38. package/src/lexer.js +82 -32
  39. package/src/parser.js +229 -229
  40. package/src/schema/dts.js +328 -54
  41. package/src/schema/loader-server.js +2 -1
  42. package/src/schema/runtime-browser-stubs.js +20 -9
  43. package/src/schema/runtime-ddl.js +161 -44
  44. package/src/schema/runtime-migrate.js +681 -0
  45. package/src/schema/runtime-orm.js +698 -54
  46. package/src/schema/runtime-validate.js +808 -24
  47. package/src/schema/runtime.generated.js +2395 -135
  48. package/src/schema/schema.js +1054 -94
  49. package/src/typecheck.js +1610 -127
  50. package/src/types.js +90 -6
  51. package/src/grammar/lunar.rip +0 -2412
  52. /package/docs/demo/{components → routes}/_layout.rip +0 -0
  53. /package/docs/demo/{components → routes}/about.rip +0 -0
  54. /package/docs/demo/{components → routes}/card.rip +0 -0
  55. /package/docs/demo/{components → routes}/counter.rip +0 -0
  56. /package/docs/demo/{components → routes}/index.rip +0 -0
  57. /package/docs/demo/{components → routes}/todos.rip +0 -0
Binary file
@@ -165,7 +165,7 @@ export default function(hljs) {
165
165
 
166
166
  const OPERATORS = {
167
167
  className: 'operator',
168
- begin: /\|>|::|:=|~=|~>|<=>|\.=|=!|!\?|\?!|=~|\?\?=|\?\?|\?\.|\.\.\.|\.\.|=>|->|\*\*|\/\/|%%|===|!==|==|!=|<=|>=|&&|\|\||[+\-*\/%&|^~<>=!?]/,
168
+ begin: /::|:=|~=|~>|<=>|\.=|=!|!\?|\?!|=~|\?\?=|\?\?|\?\.|\.\.\.|\.\.|=>|->|\*\*|\/\/|%%|===|!==|==|!=|<=|>=|&&|\|\||[+\-*\/%&|^~<>=!?]/,
169
169
  relevance: 0,
170
170
  };
171
171
 
@@ -103,7 +103,7 @@ export WidgetGallery = component
103
103
  return unless entry
104
104
  sourceName = entry.name
105
105
  sourceLines = entry.lines
106
- src = window.__RIP__?.components?.read("components/#{id}.rip")
106
+ src = window.__RIP__?.components?.read("_pkg/ui/#{id}.rip")
107
107
  return unless src
108
108
  sourceCode = src
109
109
  _closeSource: -> sourceCode = null
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "rip-lang",
3
- "version": "3.16.0",
3
+ "version": "3.16.2",
4
4
  "description": "A modern language that compiles to JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
7
7
  "workspaces": [
8
+ "examples/*",
8
9
  "packages/*"
9
10
  ],
11
+ "catalog": {
12
+ "typescript": "5.9.3"
13
+ },
10
14
  "browser": "docs/dist/rip.min.js",
11
15
  "exports": {
12
16
  ".": {
@@ -35,8 +39,8 @@
35
39
  "bump": "bun scripts/bump.js",
36
40
  "gen:dom": "bun scripts/gen-dom.js",
37
41
  "gallery": "bun scripts/gallery.js",
38
- "bundle:demo": "bun scripts/bundle-app.js docs/demo -o docs/example/index.json -t 'Rip App Demo'",
39
- "bundle:ui": "bun scripts/bundle-app.js packages/ui/browser -o docs/ui/bundle.json -t 'Rip UI'",
42
+ "bundle:demo": "bun scripts/bundle-app.js docs/demo/routes --prefix _route --css docs/demo/css -o docs/example/index.json -t 'Rip App Demo'",
43
+ "bundle:ui": "bun scripts/bundle-app.js packages/ui/browser/components --prefix _pkg/ui -o docs/ui/bundle.json -t 'Rip UI'",
40
44
  "parser": "bun src/grammar/solar.rip -o src/parser.js src/grammar/grammar.rip",
41
45
  "postinstall": "node scripts/postinstall.js --quiet",
42
46
  "link-local": "bun scripts/link-local.js",
@@ -45,14 +49,17 @@
45
49
  "serve": "bun scripts/serve.js",
46
50
  "test": "bun test/runner.js",
47
51
  "test:types": "bun test/types/runner.js",
48
- "test:schema": "bun test/schema/singleton.test.js && bun test/schema/errors.test.js && bun test/schema/modes.test.js && bun test/schema/build.test.js",
52
+ "test:schema": "bun test/schema/singleton.test.js && bun test/schema/errors.test.js && bun test/schema/modes.test.js && bun test/schema/build.test.js && bun test/schema/extract.test.js && bun test/schema/infer.test.js",
49
53
  "test:schema-fresh": "bun scripts/build-schema-runtime.js --check",
54
+ "test:docs": "bun scripts/doctest.js",
50
55
  "build:schema-runtime": "bun scripts/build-schema-runtime.js",
51
56
  "test:bundle": "bun test/bundle.test.js",
57
+ "test:loader": "bun test/loader-reexport.test.js",
52
58
  "test:graph": "bun scripts/check-bundle-graph.js",
53
59
  "test:server": "./bin/rip packages/server/tests/runner.rip",
54
60
  "test:time": "bun run --cwd packages/time test",
55
- "test:all": "bun run test && bun run test:types && bun run test:schema && bun run test:schema-fresh && bun run test:graph && bun run test:bundle && bun run test:server && bun run test:time",
61
+ "test:validate": "./bin/rip packages/validate/test.rip",
62
+ "test:all": "bun run test && bun run test:types && bun run test:schema && bun run test:schema-fresh && bun run test:docs && bun run test:loader && bun run test:graph && bun run test:bundle && bun run test:server && bun run test:time && bun run test:validate",
56
63
  "test:ui": "bun run --cwd packages/ui test:e2e",
57
64
  "test:ui:chromium": "bun run --cwd packages/ui test:e2e:chromium",
58
65
  "test:ui:axe": "bun run --cwd packages/ui test:e2e:axe",
@@ -86,7 +93,8 @@
86
93
  "homepage": "https://github.com/shreeve/rip-lang#readme",
87
94
  "author": "Steve Shreeve <steve.shreeve@gmail.com>",
88
95
  "license": "MIT",
89
- "devDependencies": {
90
- "typescript": "5.9.3"
96
+ "dependencies": {
97
+ "@types/bun": "1.3.14",
98
+ "typescript": "catalog:"
91
99
  }
92
100
  }
package/rip-loader.js CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  import { plugin } from "bun";
4
4
  import { fileURLToPath } from "url";
5
+ import { dirname, resolve as resolvePath } from "path";
6
+ import { readFileSync, existsSync } from "fs";
5
7
  import { compileToJS, formatError } from "./src/compiler.js";
6
8
  // Register the full schema runtime provider so .rip files containing
7
9
  // `schema :model` blocks compile correctly inside spawned workers.
@@ -9,11 +11,65 @@ import { compileToJS, formatError } from "./src/compiler.js";
9
11
  // would call compileToJS without ever registering a provider.
10
12
  import "./src/schema/loader-server.js";
11
13
 
14
+ // ── Undeclared-import diagnostic ────────────────────────────────────────
15
+ // Walk up from an importer to its nearest package.json, then verify that any
16
+ // `@rip-lang/<pkg>` specifier is declared in dependencies/devDependencies/
17
+ // peerDependencies/optionalDependencies (or is the package's own self-import).
18
+ //
19
+ // Throws a clear error before `import.meta.resolve` is even attempted, so
20
+ // "works on my machine" failures rooted in link-global rescue surface loudly
21
+ // instead of silently shipping.
22
+ const declarationCache = new Map(); // importerDir → { pkgName, declared } | null
23
+
24
+ function getDeclarationInfo(importerPath) {
25
+ const start = dirname(importerPath);
26
+ if (declarationCache.has(start)) return declarationCache.get(start);
27
+ let cur = start;
28
+ let info = null;
29
+ while (true) {
30
+ const pkgPath = resolvePath(cur, 'package.json');
31
+ if (existsSync(pkgPath)) {
32
+ try {
33
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
34
+ info = {
35
+ pkgName: pkg.name || null,
36
+ declared: new Set([
37
+ ...Object.keys(pkg.dependencies || {}),
38
+ ...Object.keys(pkg.devDependencies || {}),
39
+ ...Object.keys(pkg.peerDependencies || {}),
40
+ ...Object.keys(pkg.optionalDependencies || {}),
41
+ ]),
42
+ };
43
+ } catch {
44
+ info = { pkgName: null, declared: new Set() };
45
+ }
46
+ break;
47
+ }
48
+ const parent = dirname(cur);
49
+ if (parent === cur) break;
50
+ cur = parent;
51
+ }
52
+ declarationCache.set(start, info);
53
+ return info;
54
+ }
55
+
56
+ export function assertDeclaredRipImport(importerPath, specifier) {
57
+ const m = specifier.match(/^(@rip-lang\/[^\/]+)/);
58
+ if (!m) return;
59
+ const pkgKey = m[1];
60
+ const info = getDeclarationInfo(importerPath);
61
+ if (!info) return; // ad-hoc script outside any package — don't block
62
+ if (info.pkgName === pkgKey) return; // self-import
63
+ if (info.declared.has(pkgKey)) return;
64
+ throw new Error(
65
+ `Import of '${pkgKey}' is not declared in package.json. ` +
66
+ `Run \`bun add ${pkgKey}\` (or use \`workspace:*\` inside this monorepo).`
67
+ );
68
+ }
69
+
12
70
  await plugin({
13
71
  name: "rip-loader",
14
72
  async setup(build) {
15
- const { readFileSync } = await import("fs");
16
-
17
73
  // Handle .rip files
18
74
  build.onLoad({ filter: /\.rip$/ }, async (args) => {
19
75
  try {
@@ -25,6 +81,7 @@ await plugin({
25
81
  // is broken in plugin handlers — so we use import.meta.resolve, which
26
82
  // resolves from this file's location (inside the global node_modules tree).
27
83
  js = js.replace(/(from\s+|import\s*\()(['"])(@rip-lang\/[^'"]+)\2/g, (match, prefix, quote, specifier) => {
84
+ assertDeclaredRipImport(args.path, specifier);
28
85
  try {
29
86
  return `${prefix}${quote}${fileURLToPath(import.meta.resolve(specifier))}${quote}`;
30
87
  } catch {
package/src/AGENTS.md CHANGED
@@ -27,6 +27,7 @@ The browser bundle (`docs/dist/rip.min.js`) is built from `src/browser.js` plus
27
27
  | `src/schema/runtime-db-naming.js` | source for `db-naming` fragment (server + migration) |
28
28
  | `src/schema/runtime-orm.js` | source for `orm` fragment (server + migration) |
29
29
  | `src/schema/runtime-ddl.js` | source for `ddl` fragment (migration only) |
30
+ | `src/schema/runtime-migrate.js` | source for `migrate` fragment (migration only) — introspection, differ, status/make/migrate |
30
31
  | `src/schema/runtime-browser-stubs.js` | source for `browser-stubs` fragment (browser only) |
31
32
  | `src/types.js` | yes | only `installTypeSupport(Lexer)` — token-stream type stripper |
32
33
  | `src/error.js` | yes | runtime error formatting |
@@ -40,6 +41,8 @@ The browser bundle (`docs/dist/rip.min.js`) is built from `src/browser.js` plus
40
41
 
41
42
  The forbidden list in `scripts/check-bundle-graph.js` enforces this. If a code change would put a forbidden module on the browser graph, `bun run build` aborts before the bundler runs.
42
43
 
44
+ **Decision record — "core Rip" vs "type-enabled Rip" (June 2026):** this module graph *is* the split. Tier 0 is the erasure core (everything reachable from `src/browser.js` — types stripped, never checked); tier 1 adds `.d.ts` emission (`dts.js`, registered via `setTypesEmitter`); tier 2 adds type checking (`typecheck.js` + the TypeScript service, loaded only by `rip check`). A package-level split (`@rip-lang/core` + a typed variant) was considered and rejected: it would add publishing and versioning friction to express a boundary the import graph and the bundle-graph guard already enforce mechanically. Don't re-propose the package split; if the boundary needs strengthening, extend the forbidden list or route new type-aware behavior through a registration hook instead.
45
+
43
46
  ### Registration-hook pattern (`setTypesEmitter`, `setSchemaRuntimeProvider`)
44
47
 
45
48
  The same pattern is used twice — once for `.d.ts` emission, once for the schema runtime body. Both make the bundler's tree-shaker keep CLI/server-only code out of the browser bundle.
@@ -72,7 +75,7 @@ The mode matrix exposed by `getSchemaRuntime({ mode })`:
72
75
  | `validate` | VALIDATE | isomorphic validate-only contexts |
73
76
  | `browser` | VALIDATE + BROWSER_STUBS | the `<script type="text/rip">` runtime |
74
77
  | `server` | VALIDATE + DB_NAMING + ORM | `@rip-lang/server` and friends |
75
- | `migration` | VALIDATE + DB_NAMING + ORM + DDL | CLI / migration tool / tests (default) |
78
+ | `migration` | VALIDATE + DB_NAMING + ORM + DDL + MIGRATE | CLI / migration tool / tests (default) |
76
79
 
77
80
  Edits to `src/schema/runtime-*.js` require running `bun run build:schema-runtime` to regenerate `runtime.generated.js`. CI fails (`bun run test:schema-fresh`) if the generated file is stale.
78
81
 
@@ -236,20 +239,44 @@ Complete node reference:
236
239
  ['render', body]
237
240
  ```
238
241
 
242
+ ## Rewriter Pipeline (canonical order)
243
+
244
+ The single source of truth is `rewrite()` in `src/lexer.js`. Current order, with
245
+ the passes contributed by sidecar files marked:
246
+
247
+ 1. `removeLeadingNewlines`
248
+ 2. `rewriteDottedPicks` — must run before `rewriteMapLiterals` so pick bodies see raw `{` `}` for depth counting
249
+ 3. `rewriteMapLiterals`
250
+ 4. `closeMergeAssignments`
251
+ 5. `closeOpenCalls`
252
+ 6. `closeOpenIndexes`
253
+ 7. `rewriteTypes` — installed by `types.js`; must run before `normalizeLines`, otherwise a type-arrow `=>` inside `(...) => T` is treated as a single-liner function and wrapped in spurious INDENT/OUTDENT, derailing the type collector
254
+ 8. `normalizeLines`
255
+ 9. `rewriteRender?.()` — installed by `components.js` (optional sidecar)
256
+ 10. `rewriteSchema?.()` — installed by `schema/schema.js` (optional sidecar)
257
+ 11. `tagPostfixConditionals`
258
+ 12. `rewriteTaggedTemplates`
259
+ 13. `addImplicitBracesAndParens`
260
+ 14. `addImplicitCallCommas`
261
+
262
+ When pass order changes in `rewrite()`, update this list — other sections of
263
+ this file describe pass positions relative to their neighbors and assume this
264
+ listing is accurate.
265
+
239
266
  ## Lexer Token Format
240
267
 
241
268
  Tokens are `[tag, val]` arrays with extra properties:
242
269
 
243
270
  - `.pre` — whitespace count before token
244
- - `.data` — metadata like `{ await, predicate, quote, invert, parsedValue }`
271
+ - `.data` — metadata like `{ await, optional, quote, invert, parsedValue }`
245
272
  - `.loc` — `{ r, c, n }`
246
273
  - `.spaced` — sugar for `.pre > 0`
247
274
  - `.newLine` — whether preceded by newline
248
275
 
249
276
  Identifier suffixes:
250
277
 
251
- - `!` sets `.data.await = true`
252
- - `?` sets `.data.predicate = true`
278
+ - `!` sets `.data.bang = true` — a neutral "trailing `!`" flag resolved by context downstream: dammit/`await` at a call site (`fetch!` → `await fetch()`), or the void marker at a function definition (`foo! = ->`, `def foo!` → no implicit return). Void-ness is stamped onto the function node as `isVoid` by `applyVoidMarker` and read locally by the arrow emitters; `def` reads `meta(name, 'bang')` directly.
279
+ - `?` sets `.data.optional = true` (existence check on values; optional marker on prop/type-field names)
253
280
  - `as!` in loops emits `FORASAWAIT` for `for await`
254
281
 
255
282
  Tagged template bridge:
@@ -356,7 +383,7 @@ The component system is a compiler sidecar. `installComponentSupport(CodeEmitter
356
383
 
357
384
  ### Render Rewriter
358
385
 
359
- `rewriteRender()` runs after `normalizeLines` and before `tagPostfixConditionals`.
386
+ `rewriteRender()` runs after `normalizeLines` and before `rewriteSchema()` (see "Rewriter Pipeline" above for the full order).
360
387
 
361
388
  Inside `render` blocks it rewrites template syntax into function-call syntax:
362
389
 
@@ -415,7 +442,7 @@ Block factories need locals and `ctx.member` references instead of `this._elN` a
415
442
  - `_factoryVars` — variables that need local `let` declarations
416
443
  - `_fragChildren` — fragment-to-children tracking for removals
417
444
  - `_pushEffect(body)` — emits `__effect(...)` or `disposers.push(__effect(...))`
418
- - `_loopVarStack` — threads loop variables through nested factories
445
+ - `_loopVarStack` — threads loop variables through nested factories. Each frame is `{ itemVar, indexVar, reactiveSource }`; `reactiveSource` is computed once when the loop is emitted (via `hasReactiveDeps(collection)`) and tells `hasReactiveDeps` to treat direct member access rooted at `itemVar`/`indexVar` (`item.foo`, `item[0]`, `item.a.b`) as reactive. Alias and destructuring forms are not tracked.
419
446
 
420
447
  Factory mode is entered in `emitConditionBranch` and `emitTemplateLoop` via save/restore of `[_createLines, _setupLines, _factoryMode, _factoryVars]`.
421
448
 
@@ -653,7 +680,7 @@ enum Status
653
680
  Type emission is split across two files by execution context:
654
681
 
655
682
  - `types.js` (browser-side, ~21 KB) — `installTypeSupport(Lexer)` adds `rewriteTypes()` to strip type annotations from the token stream so user-typed Rip parses. This is the only thing the browser needs from type machinery.
656
- - `dts.js` (CLI/LSP only, ~38 KB) — `emitTypes(tokens, sexpr, source)` generates `.d.ts`, plus `expandSuffixes`, `emitComponentTypes`, and the intrinsic declaration tables (`INTRINSIC_TYPE_DECLS`, `SIGNAL_*`, `COMPUTED_*`, `EFFECT_*`, etc.). Registers itself with the compiler at module load via `setTypesEmitter()`.
683
+ - `dts.js` (CLI/LSP only, ~38 KB) — `emitTypes(tokens, sexpr, source, schemaBehavior)` generates `.d.ts`, plus `tsType`, `emitComponentTypes`, and the intrinsic declaration tables (`INTRINSIC_TYPE_DECLS`, `SIGNAL_*`, `COMPUTED_*`, `EFFECT_*`, etc.). Registers itself with the compiler at module load via `setTypesEmitter()`. `schemaBehavior` (the generator's per-compile buffer of compiled `~>`/`!>` bodies, populated only in `inlineTypes`/shadow mode) drives computed/derived return-type inference.
657
684
 
658
685
  `emitEnum` (runtime JS for `enum` blocks) lives in `compiler.js` next to the rest of the codegen dispatch — it's not type machinery, it's real runtime emission.
659
686
 
@@ -700,8 +727,10 @@ several files by execution context:
700
727
  ### Lexer path
701
728
 
702
729
  - `installSchemaSupport(Lexer, CodeEmitter)` adds `rewriteSchema()` to the
703
- Lexer prototype. It runs between `rewriteRender()` and `rewriteTypes()` in
704
- the rewriter pipeline.
730
+ Lexer prototype. It runs immediately after `rewriteRender()` and before
731
+ `tagPostfixConditionals()` in the rewriter pipeline (see "Rewriter
732
+ Pipeline" earlier in this file for the full order; note `rewriteTypes()`
733
+ is not adjacent — it runs much earlier, before `normalizeLines()`).
705
734
  - `rewriteSchema()` detects a contextual `schema` identifier at expression-
706
735
  start positions followed by either a `:kind` SYMBOL or a direct INDENT.
707
736
  The matching INDENT...OUTDENT range is parsed by a schema-specific
@@ -798,9 +827,11 @@ signatures both enforce this.
798
827
 
799
828
  ### Shadow TS
800
829
 
801
- `emitSchemaTypes(sexpr, lines)` walks the parsed s-expression for named
802
- schema declarations, emits mixins first so intersections resolve, then
803
- emits type aliases and `declare const` per kind:
830
+ `emitSchemaTypes(sexpr, lines, schemaBehavior)` walks the parsed s-expression
831
+ for named schema declarations, emits mixins first so intersections resolve, then
832
+ emits type aliases and `declare const` per kind (`schemaBehavior`, when set in
833
+ shadow mode, anchors computed/derived members to
834
+ `ReturnType<typeof __<Name>__behavior.field>`):
804
835
 
805
836
  - `:input` → `Schema<ValueType, ValueType>`
806
837
  - `:shape` → `Schema<ShapeInstance, ShapeData>` (or `Schema<Data, Data>` when
package/src/browser.js CHANGED
@@ -48,6 +48,40 @@ const dedent = s => {
48
48
  const sanitizeSourceURL = (url) =>
49
49
  String(url).replace(/[\r\n]/g, '').replace(/\s+$/g, '');
50
50
 
51
+ // Rewrite `import { … } from '@rip-lang/<pkg>'` into a `globalThis`
52
+ // destructure. The browser bundle copies every function export from
53
+ // `@rip-lang/app` (and friends) onto `globalThis` at startup (see
54
+ // `_entry.js` in scripts/build.js), so consumers can import them by name
55
+ // in source while the runtime form is a plain destructure. Named imports
56
+ // only — default / namespace forms warn and pass through.
57
+ function rewriteRipPkgImports(js) {
58
+ const re = /^(\s*)import\s+([^'"]+?)\s+from\s+['"](@rip-lang\/[^'"]+)['"];?\s*$/gm;
59
+ return js.replace(re, (full, indent, clause, spec) => {
60
+ const trimmed = clause.trim();
61
+ if (trimmed.startsWith('type ')) return `${indent}// type-only import erased: ${spec}`;
62
+ const open = trimmed.indexOf('{');
63
+ const close = trimmed.lastIndexOf('}');
64
+ if (open < 0 || close <= open) {
65
+ console.warn(`[Rip] Skipping non-named import from ${spec}; only \`import { … } from '@rip-lang/*'\` is supported in browser bundles.`);
66
+ return full;
67
+ }
68
+ const inside = trimmed.slice(open + 1, close);
69
+ const parts = [];
70
+ for (let raw of inside.split(',')) {
71
+ let name = raw.trim().replace(/^type\s+/, '');
72
+ if (!name) continue;
73
+ if (/\s+as\s+/.test(name)) {
74
+ const [orig, alias] = name.split(/\s+as\s+/).map(s => s.trim());
75
+ parts.push(`${orig}: ${alias}`);
76
+ } else {
77
+ parts.push(name);
78
+ }
79
+ }
80
+ if (parts.length === 0) return `${indent}// import erased: ${spec}`;
81
+ return `${indent}const { ${parts.join(', ')} } = globalThis;`;
82
+ });
83
+ }
84
+
51
85
  // Insert `//# sourceURL=<name>` BEFORE the existing `//# sourceMappingURL=...`
52
86
  // comment (or append at end if none). NEVER prepend — that would shift every
53
87
  // generated-line mapping by 1 line, breaking line-only source maps.
@@ -127,15 +161,17 @@ async function processRipScripts() {
127
161
  let lastBundle;
128
162
 
129
163
  // Step 1: Collect data-src URLs from the runtime script tag
130
- // When data-src is omitted, default to '/app' (auto-scanned bundle from serve middleware).
164
+ // When data-src is omitted, default to '/app' (auto-scanned bundle from
165
+ // serve middleware). The default is silent on failure — only explicit
166
+ // data-src URLs warn — so static / file:// pages aren't noisy.
131
167
  const runtimeTag = document.querySelector('script[src$="rip.min.js"], script[src$="rip.js"]');
132
168
  const dataSrc = runtimeTag?.getAttribute('data-src');
133
169
  if (dataSrc !== null && dataSrc !== undefined) {
134
170
  for (const url of dataSrc.trim().split(/\s+/)) {
135
171
  if (url) sources.push({ url });
136
172
  }
137
- } else if (runtimeTag) {
138
- sources.push({ url: '/app' });
173
+ } else if (runtimeTag && /^https?:$/.test(location.protocol)) {
174
+ sources.push({ url: '/app', optional: true });
139
175
  }
140
176
 
141
177
  // Step 2: Collect all <script type="text/rip"> tags (inline and external)
@@ -161,8 +197,11 @@ async function processRipScripts() {
161
197
  s.bundle = bundle;
162
198
  }
163
199
  }));
164
- for (const r of results) {
165
- if (r.status === 'rejected') console.warn('Rip: fetch failed:', r.reason.message);
200
+ for (let i = 0; i < results.length; i++) {
201
+ const r = results[i];
202
+ if (r.status === 'rejected' && !sources[i].optional) {
203
+ console.warn('Rip: fetch failed:', r.reason.message);
204
+ }
166
205
  }
167
206
 
168
207
  // Separate bundles from individual sources
@@ -209,7 +248,7 @@ async function processRipScripts() {
209
248
  ? { ...baseOpts, sourceMap: 'inline', filename: ripName }
210
249
  : baseOpts;
211
250
  let js;
212
- try { js = compileToJS(s.code, opts); }
251
+ try { js = rewriteRipPkgImports(compileToJS(s.code, opts)); }
213
252
  catch (e) { console.error(_formatError(e, { source: s.code, file: ripName, color: false })); continue; }
214
253
  try { await (0, eval)(debug ? wrapForEval(js, ripName) : `(async()=>{\n${js}\n})()`); }
215
254
  catch (e) { console.error(`Rip runtime error in ${ripName}:`, e); }
@@ -227,7 +266,8 @@ async function processRipScripts() {
227
266
  // No routing — expand bundles into individual sources, compile everything
228
267
  const expanded = [];
229
268
  for (const b of bundles) {
230
- for (const [name, code] of Object.entries(b.components || {})) {
269
+ const mods = b.modules || b.components || {};
270
+ for (const [name, code] of Object.entries(mods)) {
231
271
  expanded.push({ code, url: name });
232
272
  }
233
273
  if (b.data) {
@@ -247,7 +287,8 @@ async function processRipScripts() {
247
287
  if (bundles.length > 0 && typeof globalThis.createComponents === 'function') {
248
288
  const sourceStore = globalThis.createComponents();
249
289
  for (const b of bundles) {
250
- if (b.components) sourceStore.load(b.components);
290
+ const mods = b.modules || b.components;
291
+ if (mods) sourceStore.load(mods);
251
292
  }
252
293
  if (typeof window !== 'undefined') {
253
294
  if (!window.__RIP__) window.__RIP__ = {};
@@ -280,7 +321,7 @@ async function processRipScripts() {
280
321
  ? { ...baseOpts, sourceMap: 'inline', filename: ripName }
281
322
  : baseOpts;
282
323
  try {
283
- const js = compileToJS(s.code, opts);
324
+ const js = rewriteRipPkgImports(compileToJS(s.code, opts));
284
325
  compiled.push({ js, url: ripName });
285
326
  } catch (e) {
286
327
  console.error(_formatError(e, { source: s.code, file: ripName, color: false }));
@@ -289,7 +330,7 @@ async function processRipScripts() {
289
330
 
290
331
  // Create app stash
291
332
  if (!globalThis.__ripApp && runtimeTag) {
292
- const stashFn = globalThis.stash;
333
+ const stashFn = globalThis.createStash;
293
334
  if (stashFn) {
294
335
  let initial = {};
295
336
  const stateAttr = runtimeTag.getAttribute('data-state');
@@ -414,7 +455,7 @@ export async function importRip(url) {
414
455
  if (!r.ok) throw new Error(`importRip: ${url} (${r.status})`);
415
456
  return r.text();
416
457
  });
417
- const js = compileToJS(source);
458
+ const js = rewriteRipPkgImports(compileToJS(source));
418
459
  const header = `// ${url}\n`;
419
460
  const blob = new Blob([header + js], { type: 'application/javascript' });
420
461
  const blobUrl = URL.createObjectURL(blob);