rip-lang 3.15.4 → 3.16.1

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 (112) hide show
  1. package/README.md +6 -4
  2. package/bin/rip +167 -12
  3. package/docs/AGENTS.md +1 -1
  4. package/docs/RIP-APP.md +808 -0
  5. package/docs/RIP-DUCKDB.md +477 -0
  6. package/docs/RIP-INTRO.md +396 -0
  7. package/docs/RIP-LANG.md +59 -5
  8. package/docs/RIP-SCHEMA.md +191 -8
  9. package/docs/RIP-TYPES.md +74 -103
  10. package/docs/demo/README.md +4 -3
  11. package/docs/dist/rip.js +3627 -1470
  12. package/docs/dist/rip.min.js +671 -244
  13. package/docs/dist/rip.min.js.br +0 -0
  14. package/docs/example/index.json +7 -7
  15. package/docs/example/index.json.br +0 -0
  16. package/docs/extensions/duckdb/manifest.json +1 -1
  17. package/docs/extensions/duckdb/v1.5.2/linux_amd64/ripdb.duckdb_extension.gz +0 -0
  18. package/docs/extensions/duckdb/v1.5.2/osx_arm64/ripdb.duckdb_extension.gz +0 -0
  19. package/docs/extensions/vscode/print/index.html +2 -1
  20. package/docs/extensions/vscode/print/print-1.0.13.vsix +0 -0
  21. package/docs/extensions/vscode/print/print-1.0.14.vsix +0 -0
  22. package/docs/extensions/vscode/print/print-latest.vsix +0 -0
  23. package/docs/extensions/vscode/rip/rip-0.5.15.vsix +0 -0
  24. package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
  25. package/docs/ui/bundle.json +61 -0
  26. package/docs/ui/bundle.json.br +0 -0
  27. package/docs/ui/hljs-rip.js +0 -7
  28. package/docs/ui/index.css +66 -23
  29. package/docs/ui/index.html +6 -6
  30. package/package.json +9 -3
  31. package/rip-loader.js +64 -2
  32. package/src/AGENTS.md +63 -36
  33. package/src/browser.js +96 -14
  34. package/src/compiler.js +960 -143
  35. package/src/components.js +794 -88
  36. package/src/{types-emit.js → dts.js} +181 -71
  37. package/src/grammar/README.md +1 -1
  38. package/src/grammar/grammar.rip +111 -97
  39. package/src/lexer.js +132 -18
  40. package/src/parser.js +203 -205
  41. package/src/repl.js +74 -6
  42. package/src/schema/runtime-orm.js +168 -4
  43. package/src/schema/runtime-validate.js +146 -2
  44. package/src/schema/runtime.generated.js +314 -6
  45. package/src/schema/schema.js +5 -5
  46. package/src/sourcemaps.js +277 -1
  47. package/src/stdlib.js +253 -0
  48. package/src/typecheck.js +2023 -106
  49. package/src/types.js +127 -7
  50. package/docs/ui/accordion.rip +0 -103
  51. package/docs/ui/alert-dialog.rip +0 -53
  52. package/docs/ui/autocomplete.rip +0 -115
  53. package/docs/ui/avatar.rip +0 -37
  54. package/docs/ui/badge.rip +0 -15
  55. package/docs/ui/breadcrumb.rip +0 -47
  56. package/docs/ui/button-group.rip +0 -26
  57. package/docs/ui/button.rip +0 -23
  58. package/docs/ui/card.rip +0 -25
  59. package/docs/ui/carousel.rip +0 -110
  60. package/docs/ui/checkbox-group.rip +0 -61
  61. package/docs/ui/checkbox.rip +0 -33
  62. package/docs/ui/collapsible.rip +0 -50
  63. package/docs/ui/combobox.rip +0 -130
  64. package/docs/ui/context-menu.rip +0 -88
  65. package/docs/ui/date-picker.rip +0 -206
  66. package/docs/ui/dialog.rip +0 -60
  67. package/docs/ui/drawer.rip +0 -58
  68. package/docs/ui/editable-value.rip +0 -82
  69. package/docs/ui/field.rip +0 -53
  70. package/docs/ui/fieldset.rip +0 -22
  71. package/docs/ui/form.rip +0 -39
  72. package/docs/ui/grid.rip +0 -901
  73. package/docs/ui/input-group.rip +0 -28
  74. package/docs/ui/input.rip +0 -36
  75. package/docs/ui/label.rip +0 -16
  76. package/docs/ui/menu.rip +0 -134
  77. package/docs/ui/menubar.rip +0 -151
  78. package/docs/ui/meter.rip +0 -36
  79. package/docs/ui/multi-select.rip +0 -203
  80. package/docs/ui/native-select.rip +0 -33
  81. package/docs/ui/nav-menu.rip +0 -126
  82. package/docs/ui/number-field.rip +0 -162
  83. package/docs/ui/otp-field.rip +0 -89
  84. package/docs/ui/pagination.rip +0 -123
  85. package/docs/ui/popover.rip +0 -93
  86. package/docs/ui/preview-card.rip +0 -75
  87. package/docs/ui/progress.rip +0 -25
  88. package/docs/ui/radio-group.rip +0 -57
  89. package/docs/ui/resizable.rip +0 -123
  90. package/docs/ui/scroll-area.rip +0 -145
  91. package/docs/ui/select.rip +0 -151
  92. package/docs/ui/separator.rip +0 -17
  93. package/docs/ui/skeleton.rip +0 -22
  94. package/docs/ui/slider.rip +0 -165
  95. package/docs/ui/spinner.rip +0 -17
  96. package/docs/ui/table.rip +0 -27
  97. package/docs/ui/tabs.rip +0 -113
  98. package/docs/ui/textarea.rip +0 -48
  99. package/docs/ui/toast.rip +0 -87
  100. package/docs/ui/toggle-group.rip +0 -71
  101. package/docs/ui/toggle.rip +0 -24
  102. package/docs/ui/toolbar.rip +0 -38
  103. package/docs/ui/tooltip.rip +0 -85
  104. package/src/app.rip +0 -1571
  105. package/src/sourcemap-merge.js +0 -287
  106. /package/docs/demo/{components → routes}/_layout.rip +0 -0
  107. /package/docs/demo/{components → routes}/about.rip +0 -0
  108. /package/docs/demo/{components → routes}/card.rip +0 -0
  109. /package/docs/demo/{components → routes}/counter.rip +0 -0
  110. /package/docs/demo/{components → routes}/index.rip +0 -0
  111. /package/docs/demo/{components → routes}/todos.rip +0 -0
  112. /package/src/schema/{dts-emit.js → dts.js} +0 -0
package/src/browser.js CHANGED
@@ -10,7 +10,7 @@ import './schema/loader-browser.js';
10
10
  export { Lexer } from './lexer.js';
11
11
  export { parser } from './parser.js';
12
12
  export { CodeEmitter, Compiler, compile, compileToJS, formatSExpr, getStdlibCode, getReactiveRuntime, getComponentRuntime, RipError, formatError, formatErrorHTML } from './compiler.js';
13
- import { mergeChunksWithInlineMap } from './sourcemap-merge.js';
13
+ import { mergeChunksWithInlineMap } from './sourcemaps.js';
14
14
  import { getStdlibCode, formatError as _formatError } from './compiler.js';
15
15
 
16
16
  // Version info (replaced during build)
@@ -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.
@@ -124,14 +158,20 @@ if (typeof globalThis !== 'undefined') {
124
158
  // All other URLs are fetched as JSON bundles containing multiple files.
125
159
  async function processRipScripts() {
126
160
  const sources = [];
161
+ let lastBundle;
127
162
 
128
163
  // Step 1: Collect data-src URLs from the runtime script tag
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.
129
167
  const runtimeTag = document.querySelector('script[src$="rip.min.js"], script[src$="rip.js"]');
130
168
  const dataSrc = runtimeTag?.getAttribute('data-src');
131
- if (dataSrc) {
169
+ if (dataSrc !== null && dataSrc !== undefined) {
132
170
  for (const url of dataSrc.trim().split(/\s+/)) {
133
171
  if (url) sources.push({ url });
134
172
  }
173
+ } else if (runtimeTag && /^https?:$/.test(location.protocol)) {
174
+ sources.push({ url: '/app', optional: true });
135
175
  }
136
176
 
137
177
  // Step 2: Collect all <script type="text/rip"> tags (inline and external)
@@ -157,8 +197,11 @@ async function processRipScripts() {
157
197
  s.bundle = bundle;
158
198
  }
159
199
  }));
160
- for (const r of results) {
161
- 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
+ }
162
205
  }
163
206
 
164
207
  // Separate bundles from individual sources
@@ -170,7 +213,21 @@ async function processRipScripts() {
170
213
  }
171
214
 
172
215
  const routerAttr = runtimeTag?.getAttribute('data-router');
173
- const hasRouter = routerAttr != null;
216
+ lastBundle = bundles[bundles.length - 1];
217
+ // data-router auto-infers from bundle.data.router (set by the server when
218
+ // the app has a routes/ directory). Explicit attribute always wins:
219
+ // data-router → enabled
220
+ // data-router="hash" → hash routing
221
+ // data-router="false" → explicitly disabled
222
+ // (omitted) → fall back to bundle.data.router
223
+ let hasRouter, hashRouter;
224
+ if (routerAttr != null) {
225
+ hasRouter = routerAttr !== 'false';
226
+ hashRouter = routerAttr === 'hash';
227
+ } else {
228
+ hasRouter = !!(lastBundle?.data?.router);
229
+ hashRouter = false;
230
+ }
174
231
 
175
232
  // Step 3b: If data-router is present and we have a bundle, use launch()
176
233
  // for full routing support. Otherwise compile everything upfront.
@@ -191,7 +248,7 @@ async function processRipScripts() {
191
248
  ? { ...baseOpts, sourceMap: 'inline', filename: ripName }
192
249
  : baseOpts;
193
250
  let js;
194
- try { js = compileToJS(s.code, opts); }
251
+ try { js = rewriteRipPkgImports(compileToJS(s.code, opts)); }
195
252
  catch (e) { console.error(_formatError(e, { source: s.code, file: ripName, color: false })); continue; }
196
253
  try { await (0, eval)(debug ? wrapForEval(js, ripName) : `(async()=>{\n${js}\n})()`); }
197
254
  catch (e) { console.error(`Rip runtime error in ${ripName}:`, e); }
@@ -200,17 +257,17 @@ async function processRipScripts() {
200
257
  // Launch with the last bundle (app bundle) — handles router, renderer, stash
201
258
  const app = importRip.modules?.['app.rip'];
202
259
  if (app?.launch) {
203
- const appBundle = bundles[bundles.length - 1];
204
260
  const persistAttr = runtimeTag.getAttribute('data-persist');
205
- const launchOpts = { bundle: appBundle, hash: routerAttr === 'hash' };
261
+ const launchOpts = { bundle: lastBundle, hash: hashRouter };
206
262
  if (persistAttr != null) launchOpts.persist = persistAttr === 'local' ? 'local' : true;
207
- await app.launch('', launchOpts);
263
+ await app.launch(launchOpts);
208
264
  }
209
265
  } else {
210
266
  // No routing — expand bundles into individual sources, compile everything
211
267
  const expanded = [];
212
268
  for (const b of bundles) {
213
- 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)) {
214
271
  expanded.push({ code, url: name });
215
272
  }
216
273
  if (b.data) {
@@ -223,6 +280,22 @@ async function processRipScripts() {
223
280
  }
224
281
  expanded.push(...individual);
225
282
 
283
+ // Expose the components-source store on window.__RIP__ so source-text
284
+ // access (e.g. view-source UIs) works the same on the no-router path
285
+ // as it does after launch(). Mirrors what launch() sets up — minus
286
+ // app/router/renderer, since those don't exist on this path.
287
+ if (bundles.length > 0 && typeof globalThis.createComponents === 'function') {
288
+ const sourceStore = globalThis.createComponents();
289
+ for (const b of bundles) {
290
+ const mods = b.modules || b.components;
291
+ if (mods) sourceStore.load(mods);
292
+ }
293
+ if (typeof window !== 'undefined') {
294
+ if (!window.__RIP__) window.__RIP__ = {};
295
+ window.__RIP__.components = sourceStore;
296
+ }
297
+ }
298
+
226
299
  // Bundle / multi-source path. Components defined in one .rip file
227
300
  // need to be visible to siblings (e.g. `WidgetGallery` referencing
228
301
  // `Toast`, `Dialog`, `Menu`). To make that work we concatenate
@@ -248,7 +321,7 @@ async function processRipScripts() {
248
321
  ? { ...baseOpts, sourceMap: 'inline', filename: ripName }
249
322
  : baseOpts;
250
323
  try {
251
- const js = compileToJS(s.code, opts);
324
+ const js = rewriteRipPkgImports(compileToJS(s.code, opts));
252
325
  compiled.push({ js, url: ripName });
253
326
  } catch (e) {
254
327
  console.error(_formatError(e, { source: s.code, file: ripName, color: false }));
@@ -257,7 +330,7 @@ async function processRipScripts() {
257
330
 
258
331
  // Create app stash
259
332
  if (!globalThis.__ripApp && runtimeTag) {
260
- const stashFn = globalThis.stash;
333
+ const stashFn = globalThis.createStash;
261
334
  if (stashFn) {
262
335
  let initial = {};
263
336
  const stateAttr = runtimeTag.getAttribute('data-state');
@@ -315,10 +388,19 @@ async function processRipScripts() {
315
388
 
316
389
  // Step 6: data-reload enables SSE hot-reload from dev server
317
390
  // Skip if launch() was called — it connects its own SSE watch.
391
+ // Auto-infers from bundle.data.watch (set by the server when --watch is on).
392
+ // Explicit attribute always wins:
393
+ // data-reload → enabled
394
+ // data-reload="false" → explicitly disabled
395
+ // (omitted) → fall back to bundle.data.watch
318
396
  // Uses exponential backoff: 1s → 2s → 4s → … → 30s (then 30s forever).
319
397
  // The retry delay only affects reconnection to a DOWN server — once connected,
320
398
  // the server pushes reload notifications instantly regardless of this value.
321
- if (runtimeTag?.hasAttribute('data-reload') && !globalThis.__ripLaunched) {
399
+ const reloadAttr = runtimeTag?.getAttribute('data-reload');
400
+ const shouldReload = reloadAttr != null
401
+ ? reloadAttr !== 'false'
402
+ : !!(lastBundle?.data?.watch);
403
+ if (shouldReload && !globalThis.__ripLaunched) {
322
404
  let ready = false;
323
405
  let retryDelay = 1000;
324
406
  const maxDelay = 30000;
@@ -373,7 +455,7 @@ export async function importRip(url) {
373
455
  if (!r.ok) throw new Error(`importRip: ${url} (${r.status})`);
374
456
  return r.text();
375
457
  });
376
- const js = compileToJS(source);
458
+ const js = rewriteRipPkgImports(compileToJS(source));
377
459
  const header = `// ${url}\n`;
378
460
  const blob = new Blob([header + js], { type: 'application/javascript' });
379
461
  const blobUrl = URL.createObjectURL(blob);