rip-lang 3.14.5 → 3.15.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 +9 -5
- package/bin/rip +5 -0
- package/docs/AGENTS.md +1 -1
- package/docs/RIP-LANG.md +17 -5
- package/docs/RIP-SCHEMA.md +4 -4
- package/docs/demo/README.md +43 -0
- package/docs/demo/components/_layout.rip +28 -0
- package/docs/demo/components/about.rip +36 -0
- package/docs/demo/components/card.rip +10 -0
- package/docs/demo/components/counter.rip +33 -0
- package/docs/demo/components/index.rip +30 -0
- package/docs/demo/components/todos.rip +48 -0
- package/docs/demo/css/styles.css +472 -0
- package/docs/dist/rip.js +3211 -4619
- package/docs/dist/rip.min.js +270 -683
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/example/index.json +6 -6
- package/docs/extensions/duckdb/index.html +7 -5
- package/docs/extensions/duckdb/manifest.json +1 -1
- package/docs/extensions/duckdb/v1.5.2/linux_amd64/ripdb.duckdb_extension.gz +0 -0
- package/docs/extensions/duckdb/v1.5.2/osx_arm64/ripdb.duckdb_extension.gz +0 -0
- package/package.json +11 -3
- package/src/AGENTS.md +105 -9
- package/src/{ui.rip → app.rip} +24 -2
- package/src/browser.js +154 -37
- package/src/compiler.js +87 -9
- package/src/grammar/grammar.rip +1 -1
- package/src/grammar/solar.rip +0 -1
- package/src/lexer.js +25 -3
- package/src/parser.js +4 -4
- package/src/typecheck.js +3 -2
- package/src/types-emit.js +1021 -0
- package/src/types.js +11 -1035
- package/src/schema.js +0 -3389
package/docs/dist/rip.min.js.br
CHANGED
|
Binary file
|
package/docs/example/index.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"css": "@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');\n\n/**\n * Rip UI Demo — Self-contained styles\n * No external dependencies (no Tailwind)\n */\n\n/* ============================================\n Design Tokens\n ============================================ */\n\n:root {\n --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n\n --gray-50: #f9fafb;\n --gray-100: #f3f4f6;\n --gray-200: #e5e7eb;\n --gray-300: #d1d5db;\n --gray-400: #9ca3af;\n --gray-500: #6b7280;\n --gray-600: #4b5563;\n --gray-700: #374151;\n --gray-800: #1f2937;\n --gray-900: #111827;\n\n --blue-50: #eff6ff;\n --blue-100: #dbeafe;\n --blue-500: #3b82f6;\n --blue-600: #2563eb;\n --blue-700: #1d4ed8;\n\n --green-50: #f0fdf4;\n --green-500: #22c55e;\n --green-600: #16a34a;\n\n --red-50: #fef2f2;\n --red-500: #ef4444;\n\n --surface: var(--gray-50);\n --card: #ffffff;\n --text: var(--gray-900);\n --text-secondary: var(--gray-500);\n --text-muted: var(--gray-400);\n --border: var(--gray-200);\n --accent: var(--blue-600);\n --accent-hover: var(--blue-700);\n\n --radius: 0.75rem;\n --radius-lg: 1rem;\n --shadow: 0 1px 3px rgb(0 0 0 / 0.1), 0 1px 2px rgb(0 0 0 / 0.06);\n --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);\n --transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n/* ============================================\n Reset\n ============================================ */\n\n*, *::before, *::after { box-sizing: border-box; }\n* { margin: 0; }\n\nhtml { -webkit-text-size-adjust: 100%; }\n\nbody {\n font-family: var(--font);\n line-height: 1.6;\n color: var(--text);\n background: var(--surface);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\na { color: inherit; text-decoration: none; }\nimg, svg { display: block; max-width: 100%; }\ninput, button, textarea, select { font: inherit; }\n\n#app {\n max-width: 52rem;\n margin: 0 auto;\n padding: 2rem;\n}\n\n/* ============================================\n Layout Shell\n ============================================ */\n\n.app-layout {\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n}\n\n.app-nav {\n background: var(--card);\n border-bottom: 1px solid var(--border);\n border-radius: var(--radius);\n padding: 0 1.5rem;\n position: sticky;\n top: 0;\n z-index: 100;\n}\n\n.nav-inner {\n max-width: 48rem;\n margin: 0 auto;\n display: flex;\n align-items: center;\n justify-content: space-between;\n height: 3.5rem;\n}\n\n.nav-brand {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-weight: 600;\n font-size: 0.9375rem;\n letter-spacing: -0.02em;\n}\n\n.nav-links {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n\n.nav-link {\n padding: 0.375rem 0.75rem;\n border-radius: 0.5rem;\n color: var(--text-secondary);\n font-size: 0.8125rem;\n font-weight: 500;\n transition: color var(--transition), background-color var(--transition);\n}\n\n.nav-link:hover {\n color: var(--text);\n background-color: var(--gray-100);\n}\n\n.nav-link.active {\n color: var(--accent);\n background: var(--blue-50);\n}\n\n.app-main {\n flex: 1;\n max-width: 48rem;\n width: 100%;\n margin: 0 auto;\n padding: 2rem 1.5rem;\n}\n\n/* ============================================\n Page Sections\n ============================================ */\n\n.page-header {\n margin-bottom: 2rem;\n}\n\n.page-title {\n font-size: 1.5rem;\n font-weight: 700;\n letter-spacing: -0.03em;\n line-height: 1.2;\n color: var(--text);\n}\n\n.page-subtitle {\n font-size: 0.875rem;\n color: var(--text-secondary);\n margin-top: 0.25rem;\n}\n\n/* ============================================\n Card\n ============================================ */\n\n.card {\n background: var(--card);\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n box-shadow: var(--shadow);\n padding: 1.5rem;\n}\n\n.card + .card {\n margin-top: 1rem;\n}\n\n.card-title {\n font-size: 0.8125rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--text-secondary);\n margin-bottom: 1rem;\n}\n\n/* ============================================\n Button\n ============================================ */\n\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.5rem 1rem;\n border: 1px solid var(--border);\n border-radius: 0.5rem;\n background-color: var(--card);\n color: var(--text);\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: pointer;\n transition: color var(--transition), background-color var(--transition), border-color var(--transition), transform var(--transition);\n user-select: none;\n}\n\n.btn:hover { background-color: var(--gray-50); border-color: var(--gray-300); }\n.btn:active { transform: scale(0.98); }\n\n.btn-primary {\n background: var(--accent);\n border-color: var(--accent);\n color: white;\n}\n\n.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }\n\n.btn-danger {\n color: var(--red-500);\n border-color: var(--red-500);\n}\n\n.btn-danger:hover { background: var(--red-50); }\n\n.btn-sm {\n padding: 0.25rem 0.5rem;\n font-size: 0.75rem;\n}\n\n.btn-group {\n display: flex;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n\n/* ============================================\n Counter Display\n ============================================ */\n\n.counter-display {\n font-size: 4rem;\n font-weight: 700;\n letter-spacing: -0.04em;\n text-align: center;\n padding: 2rem 0;\n font-variant-numeric: tabular-nums;\n color: var(--text);\n transition: color var(--transition);\n}\n\n.counter-display.positive { color: var(--green-600); }\n.counter-display.negative { color: var(--red-500); }\n\n.counter-controls {\n display: flex;\n justify-content: center;\n gap: 0.5rem;\n}\n\n/* ============================================\n Input\n ============================================ */\n\n.input {\n width: 100%;\n padding: 0.5rem 0.75rem;\n border: 1px solid var(--border);\n border-radius: 0.5rem;\n background-color: var(--card);\n color: var(--text);\n font-size: 0.875rem;\n transition: border-color var(--transition), box-shadow var(--transition);\n}\n\n.input::placeholder { color: var(--text-muted); }\n.input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--blue-100); }\n\n/* ============================================\n Todo List\n ============================================ */\n\n.todo-input-row {\n display: flex;\n gap: 0.5rem;\n margin-bottom: 1rem;\n}\n\n.todo-input-row .input { flex: 1; }\n\n.todo-list {\n list-style: none;\n padding: 0;\n}\n\n.todo-item {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 0;\n border-bottom: 1px solid var(--gray-100);\n transition: opacity var(--transition);\n}\n\n.todo-item:last-child { border-bottom: none; }\n\n.todo-checkbox {\n width: 1.25rem;\n height: 1.25rem;\n border: 2px solid var(--gray-300);\n border-radius: 0.375rem;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background-color var(--transition), border-color var(--transition);\n flex-shrink: 0;\n}\n\n.todo-checkbox:hover { border-color: var(--accent); }\n\n.todo-checkbox.checked {\n background: var(--accent);\n border-color: var(--accent);\n}\n\n.todo-checkbox.checked::after {\n content: '';\n width: 0.4rem;\n height: 0.25rem;\n border-left: 2px solid white;\n border-bottom: 2px solid white;\n transform: rotate(-45deg);\n margin-bottom: 1px;\n}\n\n.todo-text {\n flex: 1;\n font-size: 0.875rem;\n color: var(--text);\n}\n\n.todo-text.done {\n text-decoration: line-through;\n color: var(--text-muted);\n}\n\n.todo-delete {\n opacity: 0;\n padding: 0.125rem 0.375rem;\n border: none;\n background: none;\n color: var(--text-muted);\n cursor: pointer;\n border-radius: 0.25rem;\n font-size: 0.75rem;\n transition: opacity var(--transition), color var(--transition), background-color var(--transition);\n}\n\n.todo-item:hover .todo-delete { opacity: 1; }\n.todo-delete:hover { color: var(--red-500); background: var(--red-50); }\n\n.todo-stats {\n display: flex;\n justify-content: space-between;\n padding-top: 0.75rem;\n border-top: 1px solid var(--gray-100);\n font-size: 0.75rem;\n color: var(--text-secondary);\n}\n\n/* ============================================\n Feature Grid (Home page)\n ============================================ */\n\n.feature-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 1rem;\n}\n\n@media (max-width: 640px) {\n .feature-grid { grid-template-columns: 1fr; }\n}\n\n.feature-card {\n background-color: var(--card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n padding: 1.25rem;\n transition: border-color var(--transition), box-shadow var(--transition);\n}\n\n.feature-card:hover {\n border-color: var(--gray-300);\n box-shadow: var(--shadow);\n}\n\n.feature-icon {\n font-size: 1.5rem;\n margin-bottom: 0.75rem;\n}\n\n.feature-name {\n font-size: 0.875rem;\n font-weight: 600;\n color: var(--text);\n margin-bottom: 0.25rem;\n}\n\n.feature-desc {\n font-size: 0.8125rem;\n color: var(--text-secondary);\n line-height: 1.5;\n}\n\n/* ============================================\n Code Block\n ============================================ */\n\n.code-block {\n background: var(--gray-800);\n color: var(--gray-100);\n padding: 1rem 1.25rem;\n border-radius: var(--radius);\n font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;\n font-size: 0.8125rem;\n line-height: 1.6;\n overflow-x: auto;\n white-space: pre;\n}\n\n.code-keyword { color: #c084fc; }\n.code-string { color: #86efac; }\n.code-comment { color: var(--gray-500); }\n.code-number { color: #fbbf24; }\n\n/* ============================================\n Stat badge\n ============================================ */\n\n.stat {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.75rem;\n background: var(--gray-100);\n border-radius: var(--radius);\n font-size: 0.8125rem;\n font-weight: 500;\n color: var(--text-secondary);\n}\n\n.stat-value {\n font-weight: 700;\n color: var(--text);\n font-variant-numeric: tabular-nums;\n}",
|
|
2
|
+
"css": "@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');\n\n/**\n * Rip App Demo — Self-contained styles\n * No external dependencies (no Tailwind)\n */\n\n/* ============================================\n Design Tokens\n ============================================ */\n\n:root {\n --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n\n --gray-50: #f9fafb;\n --gray-100: #f3f4f6;\n --gray-200: #e5e7eb;\n --gray-300: #d1d5db;\n --gray-400: #9ca3af;\n --gray-500: #6b7280;\n --gray-600: #4b5563;\n --gray-700: #374151;\n --gray-800: #1f2937;\n --gray-900: #111827;\n\n --blue-50: #eff6ff;\n --blue-100: #dbeafe;\n --blue-500: #3b82f6;\n --blue-600: #2563eb;\n --blue-700: #1d4ed8;\n\n --green-50: #f0fdf4;\n --green-500: #22c55e;\n --green-600: #16a34a;\n\n --red-50: #fef2f2;\n --red-500: #ef4444;\n\n --surface: var(--gray-50);\n --card: #ffffff;\n --text: var(--gray-900);\n --text-secondary: var(--gray-500);\n --text-muted: var(--gray-400);\n --border: var(--gray-200);\n --accent: var(--blue-600);\n --accent-hover: var(--blue-700);\n\n --radius: 0.75rem;\n --radius-lg: 1rem;\n --shadow: 0 1px 3px rgb(0 0 0 / 0.1), 0 1px 2px rgb(0 0 0 / 0.06);\n --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);\n --transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n/* ============================================\n Reset\n ============================================ */\n\n*, *::before, *::after { box-sizing: border-box; }\n* { margin: 0; }\n\nhtml { -webkit-text-size-adjust: 100%; }\n\nbody {\n font-family: var(--font);\n line-height: 1.6;\n color: var(--text);\n background: var(--surface);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\na { color: inherit; text-decoration: none; }\nimg, svg { display: block; max-width: 100%; }\ninput, button, textarea, select { font: inherit; }\n\n#app {\n max-width: 52rem;\n margin: 0 auto;\n padding: 2rem;\n}\n\n/* ============================================\n Layout Shell\n ============================================ */\n\n.app-layout {\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n}\n\n.app-nav {\n background: var(--card);\n border-bottom: 1px solid var(--border);\n border-radius: var(--radius);\n padding: 0 1.5rem;\n position: sticky;\n top: 0;\n z-index: 100;\n}\n\n.nav-inner {\n max-width: 48rem;\n margin: 0 auto;\n display: flex;\n align-items: center;\n justify-content: space-between;\n height: 3.5rem;\n}\n\n.nav-brand {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-weight: 600;\n font-size: 0.9375rem;\n letter-spacing: -0.02em;\n}\n\n.nav-links {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n\n.nav-link {\n padding: 0.375rem 0.75rem;\n border-radius: 0.5rem;\n color: var(--text-secondary);\n font-size: 0.8125rem;\n font-weight: 500;\n transition: color var(--transition), background-color var(--transition);\n}\n\n.nav-link:hover {\n color: var(--text);\n background-color: var(--gray-100);\n}\n\n.nav-link.active {\n color: var(--accent);\n background: var(--blue-50);\n}\n\n.app-main {\n flex: 1;\n max-width: 48rem;\n width: 100%;\n margin: 0 auto;\n padding: 2rem 1.5rem;\n}\n\n/* ============================================\n Page Sections\n ============================================ */\n\n.page-header {\n margin-bottom: 2rem;\n}\n\n.page-title {\n font-size: 1.5rem;\n font-weight: 700;\n letter-spacing: -0.03em;\n line-height: 1.2;\n color: var(--text);\n}\n\n.page-subtitle {\n font-size: 0.875rem;\n color: var(--text-secondary);\n margin-top: 0.25rem;\n}\n\n/* ============================================\n Card\n ============================================ */\n\n.card {\n background: var(--card);\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n box-shadow: var(--shadow);\n padding: 1.5rem;\n}\n\n.card + .card {\n margin-top: 1rem;\n}\n\n.card-title {\n font-size: 0.8125rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--text-secondary);\n margin-bottom: 1rem;\n}\n\n/* ============================================\n Button\n ============================================ */\n\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.5rem 1rem;\n border: 1px solid var(--border);\n border-radius: 0.5rem;\n background-color: var(--card);\n color: var(--text);\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: pointer;\n transition: color var(--transition), background-color var(--transition), border-color var(--transition), transform var(--transition);\n user-select: none;\n}\n\n.btn:hover { background-color: var(--gray-50); border-color: var(--gray-300); }\n.btn:active { transform: scale(0.98); }\n\n.btn-primary {\n background: var(--accent);\n border-color: var(--accent);\n color: white;\n}\n\n.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }\n\n.btn-danger {\n color: var(--red-500);\n border-color: var(--red-500);\n}\n\n.btn-danger:hover { background: var(--red-50); }\n\n.btn-sm {\n padding: 0.25rem 0.5rem;\n font-size: 0.75rem;\n}\n\n.btn-group {\n display: flex;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n\n/* ============================================\n Counter Display\n ============================================ */\n\n.counter-display {\n font-size: 4rem;\n font-weight: 700;\n letter-spacing: -0.04em;\n text-align: center;\n padding: 2rem 0;\n font-variant-numeric: tabular-nums;\n color: var(--text);\n transition: color var(--transition);\n}\n\n.counter-display.positive { color: var(--green-600); }\n.counter-display.negative { color: var(--red-500); }\n\n.counter-controls {\n display: flex;\n justify-content: center;\n gap: 0.5rem;\n}\n\n/* ============================================\n Input\n ============================================ */\n\n.input {\n width: 100%;\n padding: 0.5rem 0.75rem;\n border: 1px solid var(--border);\n border-radius: 0.5rem;\n background-color: var(--card);\n color: var(--text);\n font-size: 0.875rem;\n transition: border-color var(--transition), box-shadow var(--transition);\n}\n\n.input::placeholder { color: var(--text-muted); }\n.input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--blue-100); }\n\n/* ============================================\n Todo List\n ============================================ */\n\n.todo-input-row {\n display: flex;\n gap: 0.5rem;\n margin-bottom: 1rem;\n}\n\n.todo-input-row .input { flex: 1; }\n\n.todo-list {\n list-style: none;\n padding: 0;\n}\n\n.todo-item {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 0;\n border-bottom: 1px solid var(--gray-100);\n transition: opacity var(--transition);\n}\n\n.todo-item:last-child { border-bottom: none; }\n\n.todo-checkbox {\n width: 1.25rem;\n height: 1.25rem;\n border: 2px solid var(--gray-300);\n border-radius: 0.375rem;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background-color var(--transition), border-color var(--transition);\n flex-shrink: 0;\n}\n\n.todo-checkbox:hover { border-color: var(--accent); }\n\n.todo-checkbox.checked {\n background: var(--accent);\n border-color: var(--accent);\n}\n\n.todo-checkbox.checked::after {\n content: '';\n width: 0.4rem;\n height: 0.25rem;\n border-left: 2px solid white;\n border-bottom: 2px solid white;\n transform: rotate(-45deg);\n margin-bottom: 1px;\n}\n\n.todo-text {\n flex: 1;\n font-size: 0.875rem;\n color: var(--text);\n}\n\n.todo-text.done {\n text-decoration: line-through;\n color: var(--text-muted);\n}\n\n.todo-delete {\n opacity: 0;\n padding: 0.125rem 0.375rem;\n border: none;\n background: none;\n color: var(--text-muted);\n cursor: pointer;\n border-radius: 0.25rem;\n font-size: 0.75rem;\n transition: opacity var(--transition), color var(--transition), background-color var(--transition);\n}\n\n.todo-item:hover .todo-delete { opacity: 1; }\n.todo-delete:hover { color: var(--red-500); background: var(--red-50); }\n\n.todo-stats {\n display: flex;\n justify-content: space-between;\n padding-top: 0.75rem;\n border-top: 1px solid var(--gray-100);\n font-size: 0.75rem;\n color: var(--text-secondary);\n}\n\n/* ============================================\n Feature Grid (Home page)\n ============================================ */\n\n.feature-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 1rem;\n}\n\n@media (max-width: 640px) {\n .feature-grid { grid-template-columns: 1fr; }\n}\n\n.feature-card {\n background-color: var(--card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n padding: 1.25rem;\n transition: border-color var(--transition), box-shadow var(--transition);\n}\n\n.feature-card:hover {\n border-color: var(--gray-300);\n box-shadow: var(--shadow);\n}\n\n.feature-icon {\n font-size: 1.5rem;\n margin-bottom: 0.75rem;\n}\n\n.feature-name {\n font-size: 0.875rem;\n font-weight: 600;\n color: var(--text);\n margin-bottom: 0.25rem;\n}\n\n.feature-desc {\n font-size: 0.8125rem;\n color: var(--text-secondary);\n line-height: 1.5;\n}\n\n/* ============================================\n Code Block\n ============================================ */\n\n.code-block {\n background: var(--gray-800);\n color: var(--gray-100);\n padding: 1rem 1.25rem;\n border-radius: var(--radius);\n font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;\n font-size: 0.8125rem;\n line-height: 1.6;\n overflow-x: auto;\n white-space: pre;\n}\n\n.code-keyword { color: #c084fc; }\n.code-string { color: #86efac; }\n.code-comment { color: var(--gray-500); }\n.code-number { color: #fbbf24; }\n\n/* ============================================\n Stat badge\n ============================================ */\n\n.stat {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.75rem;\n background: var(--gray-100);\n border-radius: var(--radius);\n font-size: 0.8125rem;\n font-weight: 500;\n color: var(--text-secondary);\n}\n\n.stat-value {\n font-weight: 700;\n color: var(--text);\n font-variant-numeric: tabular-nums;\n}",
|
|
3
3
|
"components": {
|
|
4
4
|
"components/counter.rip": "# Counter Page\n\nexport Counter = component\n count := @app.data.count or 0\n step := 1\n\n ~> @app.data.count = count\n\n increment: -> count += step\n decrement: -> count -= step\n reset: -> count = 0\n\n render\n div\n .page-header\n h1.page-title \"Counter\"\n p.page-subtitle \"Reactive state — persists across reload\"\n\n .card\n .counter-display \"#{count}\"\n\n .counter-controls\n button.btn @click: @decrement, \"- #{step}\"\n button.btn @click: @reset, \"Reset\"\n button.btn.btn-primary @click: @increment, \"+ #{step}\"\n\n .card\n .card-title \"Step Size\"\n .btn-group\n button.btn @click: (-> step = 1), \"1\"\n button.btn @click: (-> step = 5), \"5\"\n button.btn @click: (-> step = 10), \"10\"\n button.btn @click: (-> step = 100), \"100\"\n",
|
|
5
|
-
"components/index.rip": "# Home Page\n\nexport Home = component\n\n render\n div\n .page-header\n h1.page-title \"Rip
|
|
6
|
-
"components/todos.rip": "# Todos Page\n\nexport Todos = component\n newTodo := ''\n todos := @app.data.todos or []\n nextId := (@app.data.nextId or 1)\n\n ~> @app.data.todos = todos\n ~> @app.data.nextId = nextId\n\n remaining ~= todos.filter((t) -> not t.done).length\n\n addTodo: ->\n if newTodo.trim()\n todos = [...todos, { id: nextId, text: newTodo.trim(), done: false }]\n nextId++\n newTodo = ''\n\n deleteTodo: (id) ->\n todos = todos.filter (t) -> t.id isnt id\n\n clearAll: ->\n todos = []\n\n handleKey: (e) ->\n if e.key is 'Enter'\n @addTodo()\n\n render\n div\n .page-header\n h1.page-title \"Todos\"\n p.page-subtitle \"List rendering and state management\"\n\n .card\n .todo-input-row\n input.input type: \"text\", value
|
|
5
|
+
"components/index.rip": "# Home Page\n\nexport Home = component\n\n render\n div\n .page-header\n h1.page-title \"Rip App Framework\"\n p.page-subtitle \"Zero-build reactive web apps\"\n\n .feature-grid\n .feature-card\n .feature-icon \"🗂\"\n .feature-name \"Component Store\"\n .feature-desc \"Component source files loaded on demand. Compiled in the browser.\"\n\n .feature-card\n .feature-icon \"🔀\"\n .feature-name \"File-Based Router\"\n .feature-desc \"URLs map to components. Dynamic segments and nested layouts.\"\n\n .feature-card\n .feature-icon \"⚡\"\n .feature-name \"Reactive Stash\"\n .feature-desc \"Deep state tree with path navigation. Every property tracked.\"\n\n .feature-card\n .feature-icon \"🎯\"\n .feature-name \"Fine-Grained Rendering\"\n .feature-desc \"No virtual DOM. Direct DOM updates via reactive effects.\"\n",
|
|
6
|
+
"components/todos.rip": "# Todos Page\n\nexport Todos = component\n newTodo := ''\n todos := @app.data.todos or []\n nextId := (@app.data.nextId or 1)\n\n ~> @app.data.todos = todos\n ~> @app.data.nextId = nextId\n\n remaining ~= todos.filter((t) -> not t.done).length\n\n addTodo: ->\n if newTodo.trim()\n todos = [...todos, { id: nextId, text: newTodo.trim(), done: false }]\n nextId++\n newTodo = ''\n\n deleteTodo: (id) ->\n todos = todos.filter (t) -> t.id isnt id\n\n clearAll: ->\n todos = []\n\n handleKey: (e) ->\n if e.key is 'Enter'\n @addTodo()\n\n render\n div\n .page-header\n h1.page-title \"Todos\"\n p.page-subtitle \"List rendering and state management\"\n\n .card\n .todo-input-row\n input.input type: \"text\", value <=> newTodo, placeholder: \"What needs to be done?\", @keydown: @handleKey\n button.btn.btn-primary @click: @addTodo, \"Add\"\n\n ul.todo-list\n for todo in todos\n li.todo-item\n span.todo-text todo.text\n button.todo-delete @click: (=> @deleteTodo(todo.id)), \"x\"\n\n .todo-stats\n span \"#{remaining} remaining\"\n button.btn.btn-sm @click: @clearAll, \"Clear all\"\n",
|
|
7
7
|
"components/card.rip": "# Card — reusable content card component\n\nexport Card = component\n title =! \"\"\n\n render\n div.card\n if title\n h3 \"#{title}\"\n @children\n",
|
|
8
|
-
"components/about.rip": "# About Page — uses the Card component for content sections\n\nexport About = component\n\n render\n div\n .page-header\n h1.page-title \"About Rip
|
|
9
|
-
"components/_layout.rip": "# Root Layout (with error boundary and navigation indicator)\n\nexport Layout = component\n errorMsg := null\n\n ~> localStorage.setItem '__rip_route', @router.path\n\n onError: (err) ->\n errorMsg = err.message\n\n render\n .app-layout\n nav.app-nav\n .nav-inner\n a.nav-brand \"Rip
|
|
8
|
+
"components/about.rip": "# About Page — uses the Card component for content sections\n\nexport About = component\n\n render\n div\n .page-header\n h1.page-title \"About Rip App\"\n p.page-subtitle \"Zero-build reactive web apps!\"\n\n Card title: \"The Idea\"\n p \"Traditional frameworks build and bundle on the server, then ship static artifacts. Rip App ships the 40KB compiler to the browser instead. Components arrive as .rip source files, are compiled on demand, and render with fine-grained reactivity. No build step, no bundler, no configuration files.\"\n\n Card title: \"Two Keywords\"\n p \"The component model adds exactly two keywords to the Rip language: component and render. Everything else — reactive state (:=), computed values (~=), effects (~>), methods, lifecycle hooks — is standard Rip syntax that already exists.\"\n\n Card title: \"Architecture\"\n p \"Components live as source files in the unified stash. The Router maps URLs to components using file-based conventions (like Next.js). When you navigate, the Renderer compiles the matching .rip file and mounts the resulting component. Each reactive binding creates a direct DOM effect — no virtual DOM diffing.\"\n\n Card title: \"Stack\"\n .btn-group\n .stat\n span \"Compiler \"\n span.stat-value \"40KB\"\n .stat\n span \"Framework \"\n span.stat-value \"~8KB\"\n .stat\n span \"Build step \"\n span.stat-value \"None\"\n .stat\n span \"Dependencies \"\n span.stat-value \"Zero\"\n\n Card title: \"Modules\"\n p \"The framework lives in one file: app.rip. It uses Rip's built-in reactive primitives directly — the same signals that power := and ~= in components. Deep reactive stash with path navigation. Component store for source management. File-based router mapping URLs to components. Renderer that compiles and mounts components with layout support.\"\n",
|
|
9
|
+
"components/_layout.rip": "# Root Layout (with error boundary and navigation indicator)\n\nexport Layout = component\n errorMsg := null\n\n ~> localStorage.setItem '__rip_route', @router.path\n\n onError: (err) ->\n errorMsg = err.message\n\n render\n .app-layout\n nav.app-nav\n .nav-inner\n a.nav-brand \"Rip App\"\n .nav-links\n a.('nav-link', @router.path is '/' and 'active') href: \"#/\", \"Home\"\n a.('nav-link', @router.path is '/counter' and 'active') href: \"#counter\", \"Counter\"\n a.('nav-link', @router.path is '/todos' and 'active') href: \"#todos\", \"Todos\"\n a.('nav-link', @router.path is '/about' and 'active') href: \"#about\", \"About\"\n if @router.navigating\n span.nav-loading \"Loading...\"\n main.app-main\n if errorMsg\n .error-banner\n p \"Something went wrong: #{errorMsg}\"\n button.btn @click: (-> errorMsg = null), \"Dismiss\"\n #content\n"
|
|
10
10
|
},
|
|
11
11
|
"data": {
|
|
12
|
-
"title": "Rip
|
|
12
|
+
"title": "Rip App Demo"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -36,15 +36,17 @@
|
|
|
36
36
|
<p class="lead">A custom DuckDB extension repository. Install <code>ripdb</code> directly from this URL.</p>
|
|
37
37
|
|
|
38
38
|
<h2>Install</h2>
|
|
39
|
-
<pre><code
|
|
39
|
+
<pre><code>$ duckdb -unsigned
|
|
40
40
|
INSTALL ripdb FROM 'PAGE_BASE_URL';
|
|
41
41
|
LOAD ripdb;</code></pre>
|
|
42
42
|
|
|
43
43
|
<p>
|
|
44
|
-
Custom-repository extensions are unsigned
|
|
45
|
-
<code
|
|
46
|
-
|
|
47
|
-
session
|
|
44
|
+
Custom-repository extensions are unsigned. Start DuckDB with the
|
|
45
|
+
<code>-unsigned</code> CLI flag — that's the only supported way to
|
|
46
|
+
enable unsigned-extension support. <code>SET allow_unsigned_extensions
|
|
47
|
+
= true;</code> from a running session is rejected with
|
|
48
|
+
<em>"Cannot change allow_unsigned_extensions setting while database is
|
|
49
|
+
running."</em>
|
|
48
50
|
</p>
|
|
49
51
|
|
|
50
52
|
<h2>Currently published</h2>
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rip-lang",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.0",
|
|
4
4
|
"description": "A modern language that compiles to JavaScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/compiler.js",
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
},
|
|
18
18
|
"./loader": "./rip-loader.js"
|
|
19
19
|
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@rip-lang/schema": "0.1.0"
|
|
22
|
+
},
|
|
20
23
|
"bin": {
|
|
21
24
|
"rip": "bin/rip"
|
|
22
25
|
},
|
|
@@ -34,6 +37,7 @@
|
|
|
34
37
|
"bump": "bun scripts/bump.js",
|
|
35
38
|
"gen:dom": "bun scripts/gen-dom.js",
|
|
36
39
|
"gallery": "bun scripts/gallery.js",
|
|
40
|
+
"bundle:demo": "bun scripts/bundle-app.js docs/demo -o docs/example/index.json -t 'Rip App Demo'",
|
|
37
41
|
"parser": "bun src/grammar/solar.rip -o src/parser.js src/grammar/grammar.rip",
|
|
38
42
|
"postinstall": "bun scripts/link-local.js --quiet && bun scripts/link-check.js --quiet",
|
|
39
43
|
"link-local": "bun scripts/link-local.js",
|
|
@@ -42,10 +46,14 @@
|
|
|
42
46
|
"serve": "bun scripts/serve.js",
|
|
43
47
|
"test": "bun test/runner.js",
|
|
44
48
|
"test:types": "bun test/types/runner.js",
|
|
45
|
-
"test:
|
|
49
|
+
"test:schema": "bun run --cwd packages/schema test",
|
|
50
|
+
"test:schema-fresh": "bun run --cwd packages/schema test:runtime-fresh",
|
|
51
|
+
"build:schema-runtime": "bun run --cwd packages/schema build:runtime",
|
|
52
|
+
"test:bundle": "bun test/bundle.test.js",
|
|
53
|
+
"test:graph": "bun scripts/check-bundle-graph.js",
|
|
46
54
|
"test:server": "./bin/rip packages/server/tests/runner.rip",
|
|
47
55
|
"test:time": "bun run --cwd packages/time test",
|
|
48
|
-
"test:all": "bun run test && bun run test:types && bun run test:
|
|
56
|
+
"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",
|
|
49
57
|
"test:ui": "bun run --cwd packages/ui test:e2e",
|
|
50
58
|
"test:ui:chromium": "bun run --cwd packages/ui test:e2e:chromium",
|
|
51
59
|
"test:ui:axe": "bun run --cwd packages/ui test:e2e:axe",
|
package/src/AGENTS.md
CHANGED
|
@@ -1,6 +1,77 @@
|
|
|
1
1
|
# Compiler Subsystem — Agent Guide
|
|
2
2
|
|
|
3
|
-
This covers `compiler.js`, `lexer.js`, `components.js`, `browser.js`, `types.js`, `typecheck.js`, and the `grammar/` directory.
|
|
3
|
+
This covers `compiler.js`, `lexer.js`, `components.js`, `browser.js`, `types.js`, `types-emit.js`, `app.rip`, `typecheck.js`, and the `grammar/` directory. The schema feature lives in `packages/schema/` (entry point `packages/schema/src/schema.js`, imported here as `@rip-lang/schema`); see `packages/schema/README.md` for its layout.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Module Map — browser-side vs CLI-only
|
|
8
|
+
|
|
9
|
+
The browser bundle (`docs/dist/rip.min.js`) is built from `src/browser.js` plus the compiled `src/app.rip`. Every module statically reachable from either entry ends up in the bundle. `scripts/check-bundle-graph.js` walks both entries on every `bun run build` and fails if any reachable file matches a forbidden list.
|
|
10
|
+
|
|
11
|
+
| Module | Browser? | Purpose |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `src/browser.js` | yes (entry) | `<script type="text/rip">` discovery, `processRipScripts`, `importRip`, REPL |
|
|
14
|
+
| `src/app.rip` | yes (entry) | Rip App framework runtime: stash, resource, timing, components store, router, renderer, launch, ARIA helpers |
|
|
15
|
+
| `src/parser.js` | yes | generated LR table |
|
|
16
|
+
| `src/lexer.js` | yes | tokenizer + rewriter pipeline |
|
|
17
|
+
| `src/compiler.js` | yes | codegen + reactive runtime + component runtime + `compileToJS` + `setTypesEmitter` hook + `emitEnum` |
|
|
18
|
+
| `src/components.js` | yes | render rewriter + component runtime |
|
|
19
|
+
| `packages/schema/src/schema.js` | yes (via `@rip-lang/schema`) | lexer rewrite + body parser + emitSchema codegen + `setSchemaRuntimeProvider` hook (no fragment imports) |
|
|
20
|
+
| `packages/schema/src/loader-browser.js` | yes (browser only, via `@rip-lang/schema/loader-browser`) | imports validate + browser-stubs fragments; eager-installs browser runtime; registers provider |
|
|
21
|
+
| `packages/schema/src/loader-server.js` | **no** (CLI / server / tests, via `@rip-lang/schema/loader-server`) | imports all five fragments; eager-installs migration runtime; registers provider |
|
|
22
|
+
| `packages/schema/src/runtime.generated.js` | yes (browser uses 2 of 5 exports) | autogen from `runtime-*.js` fragments; CI staleness check via `bun run test:schema-fresh` |
|
|
23
|
+
| `packages/schema/src/runtime-validate.js` | source for the `validate` fragment (universal) |
|
|
24
|
+
| `packages/schema/src/runtime-db-naming.js` | source for `db-naming` fragment (server + migration) |
|
|
25
|
+
| `packages/schema/src/runtime-orm.js` | source for `orm` fragment (server + migration) |
|
|
26
|
+
| `packages/schema/src/runtime-ddl.js` | source for `ddl` fragment (migration only) |
|
|
27
|
+
| `packages/schema/src/runtime-browser-stubs.js` | source for `browser-stubs` fragment (browser only) |
|
|
28
|
+
| `src/types.js` | yes | only `installTypeSupport(Lexer)` — token-stream type stripper |
|
|
29
|
+
| `src/error.js` | yes | runtime error formatting |
|
|
30
|
+
| `src/sourcemaps.js` | yes | inline source-map generation |
|
|
31
|
+
| `src/generated/dom-tags.js` | yes | HTML/SVG tag set for render-block tag detection |
|
|
32
|
+
| `src/generated/dom-events.js` | yes | event-name set for `onClick`/`onKeydown` auto-wire |
|
|
33
|
+
| `src/types-emit.js` | **no** | `.d.ts` emitter + intrinsic decl tables — CLI / typecheck only |
|
|
34
|
+
| `packages/schema/src/dts-emit.js` | **no** | schema `.d.ts` emitter — CLI / typecheck only |
|
|
35
|
+
| `src/typecheck.js` | **no** | TypeScript LSP integration — CLI only |
|
|
36
|
+
| `src/repl.js` | **no** | interactive CLI REPL |
|
|
37
|
+
|
|
38
|
+
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.
|
|
39
|
+
|
|
40
|
+
### Registration-hook pattern (`setTypesEmitter`, `setSchemaRuntimeProvider`)
|
|
41
|
+
|
|
42
|
+
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.
|
|
43
|
+
|
|
44
|
+
`compiler.js` exports `setTypesEmitter(fn)`. The default emitter is `null`. The two `compile()` callsites that produce `.d.ts` output guard with `(typeTokens && _typesEmitter)` and silently skip if no emitter is registered.
|
|
45
|
+
|
|
46
|
+
`src/types-emit.js` calls `setTypesEmitter(emitTypes)` at module load. Any caller that wants `.d.ts` output side-effect-imports `types-emit.js`:
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
// CLI entry — bin/rip
|
|
50
|
+
import '../src/types-emit.js'; // installs emitter
|
|
51
|
+
|
|
52
|
+
// LSP integration
|
|
53
|
+
import { ... } from './types-emit.js';
|
|
54
|
+
|
|
55
|
+
// Test runner that exercises type emission
|
|
56
|
+
import '../src/types-emit.js';
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The browser bundle never imports `types-emit.js`, so the emitter stays null and the `.d.ts` path is dead code that the bundler prunes.
|
|
60
|
+
|
|
61
|
+
**Failure mode to remember:** If you write code that calls `compile(source, { types: 'emit' })` and inspects `result.dts`, you **must** import `src/types-emit.js` (directly or indirectly) somewhere in that code path. Without it, `result.dts` is `null` regardless of source content. Symptom: types emission "silently does nothing" — no error, no warning, just empty output. The fix is one line: `import '../src/types-emit.js';`.
|
|
62
|
+
|
|
63
|
+
The schema runtime uses an analogous hook: `packages/schema/src/schema.js` exports `setSchemaRuntimeProvider(fn)`, default null. `packages/schema/src/loader-server.js` and `packages/schema/src/loader-browser.js` are the two providers. CLI / tests / server side-effect-import `@rip-lang/schema/loader-server` (full migration runtime, all four modes). The browser bundle (`src/browser.js`) side-effect-imports `@rip-lang/schema/loader-browser` (validate + browser-stubs only). Same failure mode applies — call `getSchemaRuntime()` without registering a provider and you get a clear error pointing at which loader to import.
|
|
64
|
+
|
|
65
|
+
The mode matrix exposed by `getSchemaRuntime({ mode })`:
|
|
66
|
+
|
|
67
|
+
| mode | composition | typical caller |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `validate` | VALIDATE | isomorphic validate-only contexts |
|
|
70
|
+
| `browser` | VALIDATE + BROWSER_STUBS | the `<script type="text/rip">` runtime |
|
|
71
|
+
| `server` | VALIDATE + DB_NAMING + ORM | `@rip-lang/server` and friends |
|
|
72
|
+
| `migration` | VALIDATE + DB_NAMING + ORM + DDL | CLI / migration tool / tests (default) |
|
|
73
|
+
|
|
74
|
+
Edits to `packages/schema/src/runtime-*.js` require running `bun run build:schema-runtime` (delegates to the schema package) to regenerate `runtime.generated.js`. CI fails (`bun run test:schema-fresh`) if the generated file is stale.
|
|
4
75
|
|
|
5
76
|
---
|
|
6
77
|
|
|
@@ -552,12 +623,14 @@ enum Status
|
|
|
552
623
|
|
|
553
624
|
### Architecture
|
|
554
625
|
|
|
555
|
-
Type emission
|
|
626
|
+
Type emission is split across two files by execution context:
|
|
627
|
+
|
|
628
|
+
- `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.
|
|
629
|
+
- `types-emit.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()`.
|
|
630
|
+
|
|
631
|
+
`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.
|
|
556
632
|
|
|
557
|
-
|
|
558
|
-
- `emitTypes(tokens)` emits `.d.ts`
|
|
559
|
-
- `emitEnum()` emits runtime JS for enums
|
|
560
|
-
- `typecheck.js` drives `rip check` and mediates TypeScript diagnostics
|
|
633
|
+
`typecheck.js` (CLI only) drives `rip check`, mediates TypeScript diagnostics, and side-effect-imports `types-emit.js` for the intrinsic decl tables.
|
|
561
634
|
|
|
562
635
|
Types are processed at the token layer before parsing.
|
|
563
636
|
|
|
@@ -569,8 +642,31 @@ Types are processed at the token layer before parsing.
|
|
|
569
642
|
|
|
570
643
|
## Schema System
|
|
571
644
|
|
|
572
|
-
Inline schemas are a
|
|
573
|
-
`
|
|
645
|
+
Inline schemas are a compiler sidecar that parallels `types.js` and
|
|
646
|
+
`components.js`, but unlike those two it lives in its own workspace
|
|
647
|
+
package — `packages/schema/`, imported here as `@rip-lang/schema`. The
|
|
648
|
+
implementation is split across several files by execution context:
|
|
649
|
+
|
|
650
|
+
- `packages/schema/src/schema.js` (browser + server) — lexer rewrite,
|
|
651
|
+
body parsers, `emitSchema` codegen, and `setSchemaRuntimeProvider`
|
|
652
|
+
hook. The runtime body itself is **not** here; this file imports zero
|
|
653
|
+
fragments so the bundler can decide per-entry which fragments to include.
|
|
654
|
+
- `packages/schema/src/runtime-{validate,db-naming,orm,ddl,browser-stubs}.js`
|
|
655
|
+
(sources) and `packages/schema/src/runtime.generated.js` (autogen) —
|
|
656
|
+
five runtime fragments composed at call time by `getSchemaRuntime({ mode })`.
|
|
657
|
+
Edit a source fragment, run `bun run build:schema-runtime` to refresh
|
|
658
|
+
the generated file, commit. CI's `test:schema-fresh` fails on staleness.
|
|
659
|
+
- `@rip-lang/schema/loader-server` and `@rip-lang/schema/loader-browser`
|
|
660
|
+
— the import boundary that decides which fragments end up in which
|
|
661
|
+
bundle. Server loader pulls all five; browser loader pulls only
|
|
662
|
+
validate + browser-stubs. Bun's tree-shaker uses these import sets to
|
|
663
|
+
omit server-only fragments from `docs/dist/rip.min.js`.
|
|
664
|
+
- `@rip-lang/schema/dts-emit` (CLI/LSP only) — `emitSchemaTypes` walks
|
|
665
|
+
parsed schema s-expressions and emits `declare const Foo: Schema<...>`
|
|
666
|
+
lines for the TypeScript language service. Imported only by
|
|
667
|
+
`types-emit.js` and `typecheck.js`. Lives inside `packages/schema/src/`
|
|
668
|
+
so all schema-related code is colocated; the `dts-emit` name signals
|
|
669
|
+
that this is a compile-time `.d.ts` emitter, not a `runtime-*` fragment.
|
|
574
670
|
|
|
575
671
|
### Lexer path
|
|
576
672
|
|
|
@@ -669,7 +765,7 @@ signatures both enforce this.
|
|
|
669
765
|
`@ensure` entirely (trusted data). Runtime method name
|
|
670
766
|
`_applyEnsures` mirrors the directive (parallel to
|
|
671
767
|
`_applyTransforms` for `-> transform` and `_applyEagerDerived` for
|
|
672
|
-
`!> derived`). See `src/schema.js`.
|
|
768
|
+
`!> derived`). See `packages/schema/src/schema.js`.
|
|
673
769
|
|
|
674
770
|
### Shadow TS
|
|
675
771
|
|
package/src/{ui.rip → app.rip}
RENAMED
|
@@ -761,7 +761,16 @@ compileAndImport = (source, compile, components = null, path = null, resolver =
|
|
|
761
761
|
resolver.compiling ?= {}
|
|
762
762
|
resolver.compiling[path] = true
|
|
763
763
|
|
|
764
|
-
|
|
764
|
+
# Browser-debugger support — when enabled, compile with an inline source
|
|
765
|
+
# map keyed to the component's logical path. We still need to compensate
|
|
766
|
+
# for line shifts introduced by the header + preamble we prepend below;
|
|
767
|
+
# `prefixLines` tracks that and we apply `offsetSourceMap` once at the end.
|
|
768
|
+
debug = globalThis?.__ripDebug?.enabled and path
|
|
769
|
+
prefixLines = 0
|
|
770
|
+
js = if debug
|
|
771
|
+
compile(source, sourceMap: 'inline', filename: path)
|
|
772
|
+
else
|
|
773
|
+
compile(source)
|
|
765
774
|
|
|
766
775
|
if resolver
|
|
767
776
|
importedNames = new Set()
|
|
@@ -812,9 +821,21 @@ compileAndImport = (source, compile, components = null, path = null, resolver =
|
|
|
812
821
|
if names.length > 0
|
|
813
822
|
preamble = "const {#{names.join(', ')}} = globalThis['#{resolver.key}'];\n"
|
|
814
823
|
js = preamble + js
|
|
824
|
+
prefixLines += 1
|
|
815
825
|
|
|
816
826
|
header = if path then "// #{path}\n" else ''
|
|
817
|
-
|
|
827
|
+
prefixLines += 1 if header
|
|
828
|
+
finalJs = header + js
|
|
829
|
+
|
|
830
|
+
# Source-map post-processing: prepend N semicolons to map.mappings so
|
|
831
|
+
# generated lines line up with `header + preamble + js`. The Blob URL
|
|
832
|
+
# itself becomes the source identity in DevTools, so an explicit
|
|
833
|
+
# //# sourceURL pragma isn't needed here (only for eval'd code).
|
|
834
|
+
if debug and prefixLines > 0
|
|
835
|
+
offset = globalThis?.__ripDebug?.offsetSourceMap
|
|
836
|
+
finalJs = offset(finalJs, prefixLines) if offset
|
|
837
|
+
|
|
838
|
+
blob = new Blob([finalJs], { type: 'application/javascript' })
|
|
818
839
|
url = URL.createObjectURL(blob)
|
|
819
840
|
|
|
820
841
|
# Cache blob URL so other files can rewrite imports to point here
|
|
@@ -1171,6 +1192,7 @@ export launch = (appBase = '', opts = {}) ->
|
|
|
1171
1192
|
components: appComponents
|
|
1172
1193
|
router: router
|
|
1173
1194
|
renderer: renderer
|
|
1195
|
+
resolver: resolver
|
|
1174
1196
|
cache: renderer.cache
|
|
1175
1197
|
version: '0.3.0'
|
|
1176
1198
|
|
package/src/browser.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// Browser-compatible entry point for Rip compiler
|
|
2
2
|
// Includes runtime for <script type="text/rip"> support
|
|
3
3
|
|
|
4
|
+
// Side-effect import — registers the BROWSER schema runtime provider.
|
|
5
|
+
// Pulls in only the validate + browser-stubs fragments; tree-shakes
|
|
6
|
+
// db-naming, orm, and ddl fragments out of the bundle. Must be the
|
|
7
|
+
// first import so any later module-load eager-installs see it.
|
|
8
|
+
import '@rip-lang/schema/loader-browser';
|
|
9
|
+
|
|
4
10
|
export { Lexer } from './lexer.js';
|
|
5
11
|
export { parser } from './parser.js';
|
|
6
12
|
export { CodeEmitter, Compiler, compile, compileToJS, formatSExpr, getStdlibCode, getReactiveRuntime, getComponentRuntime, RipError, formatError, formatErrorHTML } from './compiler.js';
|
|
@@ -14,7 +20,7 @@ export const BUILD_DATE = "0000-00-00@00:00:00GMT";
|
|
|
14
20
|
import { compile, compileToJS, formatSExpr, getReactiveRuntime, getComponentRuntime } from './compiler.js';
|
|
15
21
|
|
|
16
22
|
// Eagerly register Rip's reactive and component runtimes on globalThis so that
|
|
17
|
-
// framework code (
|
|
23
|
+
// framework code (app.rip) and browser-compiled scripts can use them directly
|
|
18
24
|
if (typeof globalThis !== 'undefined') {
|
|
19
25
|
if (!globalThis.__rip) new Function(getReactiveRuntime())();
|
|
20
26
|
if (!globalThis.__ripComponent) new Function(getComponentRuntime())();
|
|
@@ -26,6 +32,90 @@ const dedent = s => {
|
|
|
26
32
|
return s.replace(RegExp(`^[ \t]{${i}}`, 'gm'), '').trim();
|
|
27
33
|
}
|
|
28
34
|
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Source-map helpers — for browser-side debugger + DevTools navigation.
|
|
37
|
+
//
|
|
38
|
+
// `compileToJS(src, { sourceMap: 'inline', filename: <name>.rip })` returns
|
|
39
|
+
// JS with a trailing `//# sourceMappingURL=data:application/json;base64,...`
|
|
40
|
+
// comment. We add a leading `//# sourceURL=<name>.rip.js` so DevTools shows
|
|
41
|
+
// the eval'd code as a navigable virtual file (named differently than the
|
|
42
|
+
// source-map's `sources[]` entry to avoid DevTools merging entries).
|
|
43
|
+
//
|
|
44
|
+
// CR/LF in the `sourceURL` would let an attacker inject another pragma; sanitize.
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
const sanitizeSourceURL = (url) =>
|
|
48
|
+
String(url).replace(/[\r\n]/g, '').replace(/\s+$/g, '');
|
|
49
|
+
|
|
50
|
+
// Insert `//# sourceURL=<name>` BEFORE the existing `//# sourceMappingURL=...`
|
|
51
|
+
// comment (or append at end if none). NEVER prepend — that would shift every
|
|
52
|
+
// generated-line mapping by 1 line, breaking line-only source maps.
|
|
53
|
+
function addSourceURL(js, generatedName) {
|
|
54
|
+
const safe = sanitizeSourceURL(generatedName);
|
|
55
|
+
const pragma = `//# sourceURL=${safe}`;
|
|
56
|
+
const mapRe = /\n?\/\/# sourceMappingURL=[^\n]*\s*$/;
|
|
57
|
+
const m = js.match(mapRe);
|
|
58
|
+
if (m) return js.slice(0, m.index) + '\n' + pragma + js.slice(m.index);
|
|
59
|
+
return js + '\n' + pragma;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Shift all generated-line mappings by N lines by prepending N semicolons to
|
|
63
|
+
// the `mappings` field. Each `;` in source-map V3 mappings represents an empty
|
|
64
|
+
// generated line. Used to compensate for a runtime async IIFE wrapper that
|
|
65
|
+
// adds N lines BEFORE the compiled code.
|
|
66
|
+
function offsetSourceMap(js, offsetLines) {
|
|
67
|
+
if (!offsetLines) return js;
|
|
68
|
+
return js.replace(
|
|
69
|
+
/\/\/# sourceMappingURL=data:application\/json;base64,([A-Za-z0-9+/=]+)/,
|
|
70
|
+
(_, b64) => {
|
|
71
|
+
let json;
|
|
72
|
+
try {
|
|
73
|
+
// UTF-8-safe decode: counterpart of the encode in compiler.js. The
|
|
74
|
+
// map JSON may contain non-ASCII chars (sourcesContent), so we go
|
|
75
|
+
// bytes -> string via TextDecoder.
|
|
76
|
+
const bin = atob(b64);
|
|
77
|
+
const bytes = new Uint8Array(bin.length);
|
|
78
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
79
|
+
json = new TextDecoder().decode(bytes);
|
|
80
|
+
} catch { return _; /* leave unchanged on decode failure */ }
|
|
81
|
+
let map;
|
|
82
|
+
try { map = JSON.parse(json); } catch { return _; }
|
|
83
|
+
map.mappings = ';'.repeat(offsetLines) + map.mappings;
|
|
84
|
+
// UTF-8-safe re-encode.
|
|
85
|
+
const bytes = new TextEncoder().encode(JSON.stringify(map));
|
|
86
|
+
let out = '';
|
|
87
|
+
for (let i = 0; i < bytes.length; i++) out += String.fromCharCode(bytes[i]);
|
|
88
|
+
return `//# sourceMappingURL=data:application/json;base64,${btoa(out)}`;
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Wrap compiled JS in an async IIFE for top-level await support, applying the
|
|
94
|
+
// 1-line source-map offset that the wrapper introduces, AND adding a
|
|
95
|
+
// `sourceURL` pragma so DevTools shows the eval'd code with a sensible name.
|
|
96
|
+
function wrapForEval(js, ripName) {
|
|
97
|
+
const generatedName = `${sanitizeSourceURL(ripName)}.js`;
|
|
98
|
+
const shifted = offsetSourceMap(js, 1);
|
|
99
|
+
const tagged = addSourceURL(shifted, generatedName);
|
|
100
|
+
return `(async()=>{\n${tagged}\n})()`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Expose the helpers on globalThis so `app.rip` (compiled separately into
|
|
104
|
+
// the bundle) can apply the same source-map post-processing to its
|
|
105
|
+
// component-load path. The `enabled` flag is owned by processRipScripts —
|
|
106
|
+
// when it reads `data-debug` from the runtime <script> tag, it sets this
|
|
107
|
+
// flag accordingly. Code paths outside processRipScripts (notably
|
|
108
|
+
// `app.launch()`'s component compile path) gate their source-map work on
|
|
109
|
+
// __ripDebug.enabled.
|
|
110
|
+
if (typeof globalThis !== 'undefined') {
|
|
111
|
+
globalThis.__ripDebug = {
|
|
112
|
+
enabled: true, // default ON — processRipScripts may flip to false
|
|
113
|
+
offsetSourceMap,
|
|
114
|
+
addSourceURL,
|
|
115
|
+
sanitizeSourceURL,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
29
119
|
// Browser runtime: collect all sources (inline scripts, data-src files, bundles),
|
|
30
120
|
// compile them in a shared scope, and execute as one async IIFE.
|
|
31
121
|
//
|
|
@@ -85,27 +175,35 @@ async function processRipScripts() {
|
|
|
85
175
|
// for full routing support. Otherwise compile everything upfront.
|
|
86
176
|
if (hasRouter && bundles.length > 0) {
|
|
87
177
|
// Compile non-bundle sources (inline scripts, individual .rip files)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
178
|
+
// with per-component source maps for browser-debugger support. The
|
|
179
|
+
// bundle itself is launched separately via `app.launch(bundle)` —
|
|
180
|
+
// its components get source maps too via `globalThis.__ripDebug`,
|
|
181
|
+
// which `app.rip`'s component-compile path reads to apply the same
|
|
182
|
+
// offset+sourceURL treatment we apply here.
|
|
183
|
+
const debug = runtimeTag?.getAttribute('data-debug') !== 'false';
|
|
184
|
+
if (globalThis.__ripDebug) globalThis.__ripDebug.enabled = debug;
|
|
185
|
+
const baseOpts = { skipRuntimes: true, skipExports: true, skipImports: true };
|
|
186
|
+
let inlineCounter = 0;
|
|
187
|
+
for (const s of individual) {
|
|
188
|
+
const ripName = s.url || `inline-${++inlineCounter}.rip`;
|
|
189
|
+
const opts = debug
|
|
190
|
+
? { ...baseOpts, sourceMap: 'inline', filename: ripName }
|
|
191
|
+
: baseOpts;
|
|
192
|
+
let js;
|
|
193
|
+
try { js = compileToJS(s.code, opts); }
|
|
194
|
+
catch (e) { console.error(_formatError(e, { source: s.code, file: ripName, color: false })); continue; }
|
|
195
|
+
try { await (0, eval)(debug ? wrapForEval(js, ripName) : `(async()=>{\n${js}\n})()`); }
|
|
196
|
+
catch (e) { console.error(`Rip runtime error in ${ripName}:`, e); }
|
|
99
197
|
}
|
|
100
198
|
|
|
101
199
|
// Launch with the last bundle (app bundle) — handles router, renderer, stash
|
|
102
|
-
const
|
|
103
|
-
if (
|
|
200
|
+
const app = importRip.modules?.['app.rip'];
|
|
201
|
+
if (app?.launch) {
|
|
104
202
|
const appBundle = bundles[bundles.length - 1];
|
|
105
203
|
const persistAttr = runtimeTag.getAttribute('data-persist');
|
|
106
204
|
const launchOpts = { bundle: appBundle, hash: routerAttr === 'hash' };
|
|
107
205
|
if (persistAttr != null) launchOpts.persist = persistAttr === 'local' ? 'local' : true;
|
|
108
|
-
await
|
|
206
|
+
await app.launch('', launchOpts);
|
|
109
207
|
}
|
|
110
208
|
} else {
|
|
111
209
|
// No routing — expand bundles into individual sources, compile everything
|
|
@@ -124,15 +222,29 @@ async function processRipScripts() {
|
|
|
124
222
|
}
|
|
125
223
|
expanded.push(...individual);
|
|
126
224
|
|
|
127
|
-
|
|
225
|
+
// Source maps are ON by default — `data-debug="false"` opts out.
|
|
226
|
+
// Individually compile each source with its own source map; we'll
|
|
227
|
+
// sequentially eval them per-component so each has a self-consistent
|
|
228
|
+
// map (DevTools only honours the last sourceMappingURL inside an
|
|
229
|
+
// eval, so concatenating maps doesn't work).
|
|
230
|
+
const debug = runtimeTag?.getAttribute('data-debug') !== 'false';
|
|
231
|
+
// Update the global flag so app.launch()'s compile path (in app.rip)
|
|
232
|
+
// sees the same setting as our local `debug` variable.
|
|
233
|
+
if (globalThis.__ripDebug) globalThis.__ripDebug.enabled = debug;
|
|
234
|
+
const baseOpts = { skipRuntimes: true, skipExports: true, skipImports: true };
|
|
128
235
|
const compiled = [];
|
|
236
|
+
let inlineCounter = 0;
|
|
129
237
|
for (const s of expanded) {
|
|
130
238
|
if (!s.code) continue;
|
|
239
|
+
const ripName = s.url || `inline-${++inlineCounter}.rip`;
|
|
240
|
+
const opts = debug
|
|
241
|
+
? { ...baseOpts, sourceMap: 'inline', filename: ripName }
|
|
242
|
+
: baseOpts;
|
|
131
243
|
try {
|
|
132
244
|
const js = compileToJS(s.code, opts);
|
|
133
|
-
compiled.push({ js, url:
|
|
245
|
+
compiled.push({ js, url: ripName });
|
|
134
246
|
} catch (e) {
|
|
135
|
-
console.error(_formatError(e, { source: s.code, file:
|
|
247
|
+
console.error(_formatError(e, { source: s.code, file: ripName, color: false }));
|
|
136
248
|
}
|
|
137
249
|
}
|
|
138
250
|
|
|
@@ -157,30 +269,35 @@ async function processRipScripts() {
|
|
|
157
269
|
}
|
|
158
270
|
}
|
|
159
271
|
|
|
160
|
-
// Execute
|
|
272
|
+
// Execute compiled code per-component so each has its own valid
|
|
273
|
+
// source map (DevTools only honours the last `sourceMappingURL`
|
|
274
|
+
// pragma inside one evaluated chunk, so concatenating multiple
|
|
275
|
+
// source-mapped chunks would lose all but the last). Components
|
|
276
|
+
// share scope via globalThis attachment — typical Rip definitions
|
|
277
|
+
// (`Foo = component`, `Bar = ...`) compile to globalThis-attached
|
|
278
|
+
// bindings under `skipExports/skipImports`, which survive between
|
|
279
|
+
// the per-component evals.
|
|
161
280
|
if (compiled.length > 0) {
|
|
162
|
-
let
|
|
281
|
+
let anyError = false;
|
|
282
|
+
for (const c of compiled) {
|
|
283
|
+
try {
|
|
284
|
+
await (0, eval)(debug ? wrapForEval(c.js, c.url) : `(async()=>{\n${c.js}\n})()`);
|
|
285
|
+
} catch (e) {
|
|
286
|
+
anyError = true;
|
|
287
|
+
if (e instanceof SyntaxError) console.error(`Rip syntax error in ${c.url}: ${e.message}`);
|
|
288
|
+
else console.error(`Rip runtime error in ${c.url}:`, e);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
163
291
|
|
|
292
|
+
// Final mount step — runs after all components are defined.
|
|
164
293
|
const mount = runtimeTag?.getAttribute('data-mount');
|
|
165
294
|
if (mount) {
|
|
166
295
|
const target = runtimeTag.getAttribute('data-target') || 'body';
|
|
167
|
-
|
|
296
|
+
try { await (0, eval)(`(async()=>{ ${mount}.mount(${JSON.stringify(target)}); })()`); }
|
|
297
|
+
catch (e) { console.error(`Rip mount error (${mount}):`, e); }
|
|
168
298
|
}
|
|
169
299
|
|
|
170
|
-
|
|
171
|
-
await (0, eval)(`(async()=>{\n${js}\n})()`);
|
|
172
|
-
document.body.classList.add('ready');
|
|
173
|
-
} catch (e) {
|
|
174
|
-
if (e instanceof SyntaxError) {
|
|
175
|
-
console.error(`Rip syntax error in combined output: ${e.message}`);
|
|
176
|
-
for (const c of compiled) {
|
|
177
|
-
try { new Function(`(async()=>{\n${c.js}\n})()`); }
|
|
178
|
-
catch (e2) { console.error(` → source: ${c.url}`, e2.message); }
|
|
179
|
-
}
|
|
180
|
-
} else {
|
|
181
|
-
console.error('Rip runtime error:', e);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
300
|
+
if (!anyError) document.body.classList.add('ready');
|
|
184
301
|
}
|
|
185
302
|
}
|
|
186
303
|
}
|
|
@@ -232,10 +349,10 @@ export { processRipScripts };
|
|
|
232
349
|
/**
|
|
233
350
|
* Import a .rip file as an ES module
|
|
234
351
|
* Fetches the URL, compiles Rip→JS, dynamically imports via Blob URL
|
|
235
|
-
* Usage: const { launch } = await importRip('/
|
|
352
|
+
* Usage: const { launch } = await importRip('/app.rip')
|
|
236
353
|
*
|
|
237
354
|
* Pre-compiled modules can be registered on importRip.modules to skip fetching.
|
|
238
|
-
* The browser bundle uses this to embed
|
|
355
|
+
* The browser bundle uses this to embed app.rip without a server round-trip.
|
|
239
356
|
*/
|
|
240
357
|
export async function importRip(url) {
|
|
241
358
|
for (const [key, mod] of Object.entries(importRip.modules)) {
|