funicular 0.2.1 → 0.3.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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +54 -0
  3. data/README.md +2 -2
  4. data/Rakefile +23 -15
  5. data/demo/test_chartjs.html +17 -17
  6. data/demo/test_component.html +15 -15
  7. data/demo/test_error_boundary.html +41 -41
  8. data/demo/test_router.html +57 -57
  9. data/demo/tic-tac-toe.html +29 -29
  10. data/docs/architecture.md +43 -30
  11. data/lib/funicular/compiler.rb +13 -13
  12. data/lib/funicular/helpers/picoruby_helper.rb +24 -1
  13. data/lib/funicular/ssr/runtime.rb +2 -0
  14. data/lib/funicular/ssr.rb +2 -1
  15. data/lib/funicular/testing/node_runner.rb +1 -1
  16. data/lib/funicular/vendor/mrbc/VERSION +1 -0
  17. data/lib/funicular/vendor/{picorbc/picorbc.js → mrbc/mrbc.js} +14 -1
  18. data/lib/funicular/vendor/mrbc/mrbc.wasm +0 -0
  19. data/lib/funicular/vendor/picoruby/VERSION +1 -1
  20. data/lib/funicular/vendor/picoruby/debug/init.iife.js +19 -4
  21. data/lib/funicular/vendor/picoruby/debug/picoruby.js +16 -10
  22. data/lib/funicular/vendor/picoruby/debug/picoruby.wasm +0 -0
  23. data/lib/funicular/vendor/picoruby/dist/init.iife.js +19 -4
  24. data/lib/funicular/vendor/picoruby/dist/picoruby.js +1 -1
  25. data/lib/funicular/vendor/picoruby/dist/picoruby.wasm +0 -0
  26. data/lib/funicular/vendor/picoruby-test-node/VERSION +1 -1
  27. data/lib/funicular/vendor/picoruby-test-node/picoruby.js +85 -84
  28. data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm +0 -0
  29. data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm.map +1 -1
  30. data/lib/funicular/version.rb +1 -1
  31. data/lib/generators/funicular/chat/templates/funicular_chat_component.rb.tt +47 -46
  32. data/lib/tasks/funicular.rake +1 -1
  33. data/minitest/commands_routes_test.rb +97 -0
  34. data/minitest/compiler_test.rb +195 -0
  35. data/minitest/configuration_test.rb +64 -0
  36. data/minitest/fixtures/funicular_app/components/greeting_component.rb +6 -6
  37. data/minitest/form_for_test.rb +2 -2
  38. data/minitest/funicular_test.rb +28 -2
  39. data/minitest/hydration_test.rb +2 -2
  40. data/minitest/middleware_test.rb +154 -0
  41. data/minitest/picoruby_helper_test.rb +139 -0
  42. data/minitest/route_parser_test.rb +139 -0
  43. data/minitest/schema_test.rb +23 -0
  44. data/minitest/ssr_test.rb +57 -0
  45. data/minitest/support/rails_stub.rb +59 -0
  46. data/minitest/test_helper.rb +20 -0
  47. data/minitest/testing_test.rb +267 -0
  48. data/minitest/view_context_test.rb +101 -0
  49. data/mrblib/component.rb +158 -237
  50. data/mrblib/debug.rb +7 -6
  51. data/mrblib/differ.rb +3 -1
  52. data/mrblib/error_boundary.rb +19 -27
  53. data/mrblib/form_builder.rb +28 -24
  54. data/mrblib/funicular.rb +2 -1
  55. data/mrblib/html_serializer.rb +14 -13
  56. data/mrblib/http.rb +12 -1
  57. data/mrblib/patcher.rb +12 -9
  58. data/mrblib/router.rb +9 -5
  59. data/mrblib/runtime.rb +28 -0
  60. data/mrblib/styles.rb +13 -17
  61. data/mrblib/vdom.rb +90 -9
  62. data/mrblib/view_context.rb +135 -0
  63. data/sig/component.rbs +34 -85
  64. data/sig/error_boundary.rbs +4 -4
  65. data/sig/form_builder.rbs +2 -1
  66. data/sig/html_serializer.rbs +2 -1
  67. data/sig/http.rbs +1 -0
  68. data/sig/patcher.rbs +1 -1
  69. data/sig/router.rbs +2 -13
  70. data/sig/runtime.rbs +13 -0
  71. data/sig/styles.rbs +3 -4
  72. data/sig/vdom.rbs +11 -2
  73. data/sig/view_context.rbs +47 -0
  74. metadata +18 -5
  75. data/lib/funicular/vendor/picorbc/VERSION +0 -1
  76. data/lib/funicular/vendor/picorbc/picorbc.wasm +0 -0
@@ -1 +1 @@
1
- 4.0.0
1
+ 4.0.1
@@ -753,6 +753,57 @@ async function createWasm() {
753
753
 
754
754
 
755
755
 
756
+ var wasmTableMirror = [];
757
+
758
+
759
+ var getWasmTableEntry = (funcPtr) => {
760
+ var func = wasmTableMirror[funcPtr];
761
+ if (!func) {
762
+ /** @suppress {checkTypes} */
763
+ wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr);
764
+ }
765
+ /** @suppress {checkTypes} */
766
+ assert(wasmTable.get(funcPtr) == func, 'JavaScript-side Wasm function table mirror is out of date!');
767
+ return func;
768
+ };
769
+ var ___call_sighandler = (fp, sig) => getWasmTableEntry(fp)(sig);
770
+
771
+ var nodePath = require('node:path');
772
+ var PATH = {
773
+ isAbs: nodePath.isAbsolute,
774
+ normalize: nodePath.normalize,
775
+ dirname: nodePath.dirname,
776
+ basename: nodePath.basename,
777
+ join: nodePath.join,
778
+ join2: nodePath.join,
779
+ };
780
+
781
+ var initRandomFill = () => {
782
+ // This block is not needed on v19+ since crypto.getRandomValues is builtin
783
+ if (ENVIRONMENT_IS_NODE) {
784
+ var nodeCrypto = require('node:crypto');
785
+ return (view) => nodeCrypto.randomFillSync(view);
786
+ }
787
+
788
+ return (view) => crypto.getRandomValues(view);
789
+ };
790
+ var randomFill = (view) => {
791
+ // Lazily init on the first invocation.
792
+ (randomFill = initRandomFill())(view);
793
+ };
794
+
795
+
796
+
797
+ /** @type{{resolve: function(...*)}} */
798
+ var PATH_FS = {
799
+ resolve:(...paths) => {
800
+ paths.unshift(FS.cwd());
801
+ return nodePath.posix.resolve(...paths);
802
+ },
803
+ relative:(from, to) => nodePath.posix.relative(from || FS.cwd(), to || FS.cwd()),
804
+ };
805
+
806
+
756
807
  var UTF8Decoder = globalThis.TextDecoder && new TextDecoder();
757
808
 
758
809
  var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => {
@@ -813,78 +864,6 @@ async function createWasm() {
813
864
  return str;
814
865
  };
815
866
 
816
- /**
817
- * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the
818
- * emscripten HEAP, returns a copy of that string as a Javascript String object.
819
- *
820
- * @param {number} ptr
821
- * @param {number=} maxBytesToRead - An optional length that specifies the
822
- * maximum number of bytes to read. You can omit this parameter to scan the
823
- * string until the first 0 byte. If maxBytesToRead is passed, and the string
824
- * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the
825
- * string will cut short at that byte index.
826
- * @param {boolean=} ignoreNul - If true, the function will not stop on a NUL character.
827
- * @return {string}
828
- */
829
- var UTF8ToString = (ptr, maxBytesToRead, ignoreNul) => {
830
- assert(typeof ptr == 'number', `UTF8ToString expects a number (got ${typeof ptr})`);
831
- return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead, ignoreNul) : '';
832
- };
833
- var ___assert_fail = (condition, filename, line, func) =>
834
- abort(`Assertion failed: ${UTF8ToString(condition)}, at: ` + [filename ? UTF8ToString(filename) : 'unknown filename', line, func ? UTF8ToString(func) : 'unknown function']);
835
-
836
- var wasmTableMirror = [];
837
-
838
-
839
- var getWasmTableEntry = (funcPtr) => {
840
- var func = wasmTableMirror[funcPtr];
841
- if (!func) {
842
- /** @suppress {checkTypes} */
843
- wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr);
844
- }
845
- /** @suppress {checkTypes} */
846
- assert(wasmTable.get(funcPtr) == func, 'JavaScript-side Wasm function table mirror is out of date!');
847
- return func;
848
- };
849
- var ___call_sighandler = (fp, sig) => getWasmTableEntry(fp)(sig);
850
-
851
- var nodePath = require('node:path');
852
- var PATH = {
853
- isAbs: nodePath.isAbsolute,
854
- normalize: nodePath.normalize,
855
- dirname: nodePath.dirname,
856
- basename: nodePath.basename,
857
- join: nodePath.join,
858
- join2: nodePath.join,
859
- };
860
-
861
- var initRandomFill = () => {
862
- // This block is not needed on v19+ since crypto.getRandomValues is builtin
863
- if (ENVIRONMENT_IS_NODE) {
864
- var nodeCrypto = require('node:crypto');
865
- return (view) => nodeCrypto.randomFillSync(view);
866
- }
867
-
868
- return (view) => crypto.getRandomValues(view);
869
- };
870
- var randomFill = (view) => {
871
- // Lazily init on the first invocation.
872
- (randomFill = initRandomFill())(view);
873
- };
874
-
875
-
876
-
877
- /** @type{{resolve: function(...*)}} */
878
- var PATH_FS = {
879
- resolve:(...paths) => {
880
- paths.unshift(FS.cwd());
881
- return nodePath.posix.resolve(...paths);
882
- },
883
- relative:(from, to) => nodePath.posix.relative(from || FS.cwd(), to || FS.cwd()),
884
- };
885
-
886
-
887
-
888
867
  var FS_stdin_getChar_buffer = [];
889
868
 
890
869
  var lengthBytesUTF8 = (str) => {
@@ -2139,6 +2118,24 @@ async function createWasm() {
2139
2118
 
2140
2119
 
2141
2120
 
2121
+ /**
2122
+ * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the
2123
+ * emscripten HEAP, returns a copy of that string as a Javascript String object.
2124
+ *
2125
+ * @param {number} ptr
2126
+ * @param {number=} maxBytesToRead - An optional length that specifies the
2127
+ * maximum number of bytes to read. You can omit this parameter to scan the
2128
+ * string until the first 0 byte. If maxBytesToRead is passed, and the string
2129
+ * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the
2130
+ * string will cut short at that byte index.
2131
+ * @param {boolean=} ignoreNul - If true, the function will not stop on a NUL character.
2132
+ * @return {string}
2133
+ */
2134
+ var UTF8ToString = (ptr, maxBytesToRead, ignoreNul) => {
2135
+ assert(typeof ptr == 'number', `UTF8ToString expects a number (got ${typeof ptr})`);
2136
+ return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead, ignoreNul) : '';
2137
+ };
2138
+
2142
2139
  var strError = (errno) => UTF8ToString(_strerror(errno));
2143
2140
 
2144
2141
 
@@ -5939,14 +5936,14 @@ function checkIncomingModuleAPI() {
5939
5936
  ignoredModuleProp('loadSplitModule');
5940
5937
  }
5941
5938
  var ASM_CONSTS = {
5942
- 2633744: ($0) => { globalThis.picorubyRefs[$0] = null; },
5943
- 2633784: ($0) => { globalThis.picorubyRefs[$0] = true; },
5944
- 2633824: ($0) => { globalThis.picorubyRefs[$0] = false; },
5945
- 2633865: ($0, $1) => { globalThis.picorubyRefs[$0] = $1; },
5946
- 2633903: ($0, $1) => { globalThis.picorubyRefs[$0] = $1; },
5947
- 2633941: ($0, $1, $2) => { const str = UTF8ToString($1, $2); globalThis.picorubyRefs[$0] = str; },
5948
- 2634014: ($0, $1) => { const arr = globalThis.picorubyRefs[$0]; const elem = globalThis.picorubyRefs[$1]; arr.push(elem); delete globalThis.picorubyRefs[$1]; },
5949
- 2634153: ($0, $1, $2) => { const obj = globalThis.picorubyRefs[$0]; const key = UTF8ToString($1); const val = globalThis.picorubyRefs[$2]; obj[key] = val; delete globalThis.picorubyRefs[$2]; }
5939
+ 2640990: ($0) => { globalThis.picorubyRefs[$0] = null; },
5940
+ 2641030: ($0) => { globalThis.picorubyRefs[$0] = true; },
5941
+ 2641070: ($0) => { globalThis.picorubyRefs[$0] = false; },
5942
+ 2641111: ($0, $1) => { globalThis.picorubyRefs[$0] = $1; },
5943
+ 2641149: ($0, $1) => { globalThis.picorubyRefs[$0] = $1; },
5944
+ 2641187: ($0, $1, $2) => { const str = UTF8ToString($1, $2); globalThis.picorubyRefs[$0] = str; },
5945
+ 2641260: ($0, $1) => { const arr = globalThis.picorubyRefs[$0]; const elem = globalThis.picorubyRefs[$1]; arr.push(elem); delete globalThis.picorubyRefs[$1]; },
5946
+ 2641399: ($0, $1, $2) => { const obj = globalThis.picorubyRefs[$0]; const key = UTF8ToString($1); const val = globalThis.picorubyRefs[$2]; obj[key] = val; delete globalThis.picorubyRefs[$2]; }
5950
5947
  };
5951
5948
  function ble_dataview_length(ref_id) { try { const dv = globalThis.picorubyRefs[ref_id]; if (dv && dv.byteLength !== undefined) { return dv.byteLength; } return 0; } catch(e) { console.error('ble_dataview_length failed:', e); return 0; } }
5952
5949
  function ble_dataview_read(ref_id,out_buf,max_len) { try { const dv = globalThis.picorubyRefs[ref_id]; if (!dv) return 0; const len = Math.min(dv.byteLength, max_len); for (let i = 0; i < len; i++) { HEAPU8[out_buf + i] = dv.getUint8(i); } return len; } catch(e) { console.error('ble_dataview_read failed:', e); return 0; } }
@@ -6035,7 +6032,7 @@ function serial_capture_start(ref_id) { try { const port = globalThis.picorubyRe
6035
6032
  function serial_binary_capture_start(ref_id) { try { const port = globalThis.picorubyRefs[ref_id]; if (!port) return; if (!globalThis.picorubySerialBinCap) { const buffers = new WeakMap(); const active = new WeakSet(); const MAX_BYTES = 256 * 1024; globalThis.picorubySerialBinCap = { start(p) { buffers.set(p, { chunks: [], totalBytes: 0 }); active.add(p); }, isActive(p) { return active.has(p); }, append(p, value) { const buf = buffers.get(p); if (!buf) return; const copy = new Uint8Array(value); buf.chunks.push(copy); buf.totalBytes += copy.length; while (buf.totalBytes > MAX_BYTES && buf.chunks.length > 1) { const removed = buf.chunks.shift(); buf.totalBytes -= removed.length; } }, read(p, outPtr, maxBytes) { const buf = buffers.get(p); if (!buf || buf.totalBytes === 0) return 0; let written = 0; while (written < maxBytes && buf.chunks.length > 0) { const chunk = buf.chunks[0]; const needed = maxBytes - written; if (chunk.length <= needed) { HEAPU8.set(chunk, outPtr + written); written += chunk.length; buf.chunks.shift(); } else { HEAPU8.set(chunk.subarray(0, needed), outPtr + written); buf.chunks[0] = chunk.subarray(needed); written += needed; } } buf.totalBytes -= written; return written; }, stop(p) { active.delete(p); buffers.delete(p); }, }; } globalThis.picorubySerialBinCap.start(port); } catch (e) { console.error('serial_binary_capture_start failed:', e); } }
6036
6033
  function serial_binary_capture_read(ref_id,out_buf,max_bytes) { try { const port = globalThis.picorubyRefs[ref_id]; if (!port || !globalThis.picorubySerialBinCap) return 0; return globalThis.picorubySerialBinCap.read(port, out_buf, max_bytes); } catch (e) { console.error('serial_binary_capture_read failed:', e); return 0; } }
6037
6034
  function serial_binary_capture_stop(ref_id) { try { const port = globalThis.picorubyRefs[ref_id]; if (!port || !globalThis.picorubySerialBinCap) return; globalThis.picorubySerialBinCap.stop(port); } catch (e) { console.error('serial_binary_capture_stop failed:', e); } }
6038
- function serial_capture_get(ref_id,stop) { try { const port = globalThis.picorubyRefs[ref_id]; if (!port || !globalThis.picorubySerialCapture) { return globalThis.picorubyRefs.push("") - 1; } const cap = globalThis.picorubySerialCapture; const out = stop ? cap.stop(port) : cap.peek(port); return globalThis.picorubyRefs.push(out) - 1; } catch (e) { console.error('serial_capture_get failed:', e); return globalThis.picorubyRefs.push("") - 1; } }
6035
+ function serial_capture_copy(ref_id,stop,out_buf,max_bytes) { try { const port = globalThis.picorubyRefs[ref_id]; if (!port || !globalThis.picorubySerialCapture) { if (max_bytes > 0) HEAPU8[out_buf] = 0; return 0; } const cap = globalThis.picorubySerialCapture; const out = stop ? cap.stop(port) : cap.peek(port); const bytes = new TextEncoder().encode(out); const n = Math.min(bytes.length, Math.max(0, max_bytes - 1)); if (n > 0) { HEAPU8.set(bytes.subarray(0, n), out_buf); } if (max_bytes > 0) { HEAPU8[out_buf + n] = 0; } return n; } catch (e) { console.error('serial_capture_copy failed:', e); if (max_bytes > 0) HEAPU8[out_buf] = 0; return 0; } }
6039
6036
  function ws_new(url) { try { const ws = new WebSocket(UTF8ToString(url)); const refId = globalThis.picorubyRefs.push(ws) - 1; return refId; } catch(e) { console.error('WebSocket creation failed:', e); return -1; } }
6040
6037
  function ws_send(ref_id,data) { try { const ws = globalThis.picorubyRefs[ref_id]; if (ws && ws.readyState === WebSocket.OPEN) { ws.send(UTF8ToString(data)); } } catch(e) { console.error('WebSocket send failed:', e); } }
6041
6038
  function ws_send_binary(ref_id,data,length) { try { const ws = globalThis.picorubyRefs[ref_id]; if (ws && ws.readyState === WebSocket.OPEN) { const buffer = new Uint8Array(HEAPU8.buffer, data, length); const copy = new Uint8Array(buffer); ws.send(copy.buffer); } } catch(e) { console.error('WebSocket send_binary failed:', e); } }
@@ -6073,6 +6070,8 @@ var _resume_binary_task = Module['_resume_binary_task'] = makeInvalidEarlyAccess
6073
6070
  var _call_ruby_callback_sync_generic = Module['_call_ruby_callback_sync_generic'] = makeInvalidEarlyAccess('_call_ruby_callback_sync_generic');
6074
6071
  var _mrb_tick_wasm = Module['_mrb_tick_wasm'] = makeInvalidEarlyAccess('_mrb_tick_wasm');
6075
6072
  var _mrb_run_step = Module['_mrb_run_step'] = makeInvalidEarlyAccess('_mrb_run_step');
6073
+ var _mrb_run_step_status = Module['_mrb_run_step_status'] = makeInvalidEarlyAccess('_mrb_run_step_status');
6074
+ var _mrb_gc_scheduler_pending_wasm = Module['_mrb_gc_scheduler_pending_wasm'] = makeInvalidEarlyAccess('_mrb_gc_scheduler_pending_wasm');
6076
6075
  var _picorb_init = Module['_picorb_init'] = makeInvalidEarlyAccess('_picorb_init');
6077
6076
  var _picorb_create_task = Module['_picorb_create_task'] = makeInvalidEarlyAccess('_picorb_create_task');
6078
6077
  var _picorb_create_task_with_filename = Module['_picorb_create_task_with_filename'] = makeInvalidEarlyAccess('_picorb_create_task_with_filename');
@@ -6119,6 +6118,8 @@ function assignWasmExports(wasmExports) {
6119
6118
  assert(typeof wasmExports['call_ruby_callback_sync_generic'] != 'undefined', 'missing Wasm export: call_ruby_callback_sync_generic');
6120
6119
  assert(typeof wasmExports['mrb_tick_wasm'] != 'undefined', 'missing Wasm export: mrb_tick_wasm');
6121
6120
  assert(typeof wasmExports['mrb_run_step'] != 'undefined', 'missing Wasm export: mrb_run_step');
6121
+ assert(typeof wasmExports['mrb_run_step_status'] != 'undefined', 'missing Wasm export: mrb_run_step_status');
6122
+ assert(typeof wasmExports['mrb_gc_scheduler_pending_wasm'] != 'undefined', 'missing Wasm export: mrb_gc_scheduler_pending_wasm');
6122
6123
  assert(typeof wasmExports['picorb_init'] != 'undefined', 'missing Wasm export: picorb_init');
6123
6124
  assert(typeof wasmExports['picorb_create_task'] != 'undefined', 'missing Wasm export: picorb_create_task');
6124
6125
  assert(typeof wasmExports['picorb_create_task_with_filename'] != 'undefined', 'missing Wasm export: picorb_create_task_with_filename');
@@ -6161,6 +6162,8 @@ function assignWasmExports(wasmExports) {
6161
6162
  _call_ruby_callback_sync_generic = Module['_call_ruby_callback_sync_generic'] = createExportWrapper('call_ruby_callback_sync_generic', 3);
6162
6163
  _mrb_tick_wasm = Module['_mrb_tick_wasm'] = createExportWrapper('mrb_tick_wasm', 0);
6163
6164
  _mrb_run_step = Module['_mrb_run_step'] = createExportWrapper('mrb_run_step', 0);
6165
+ _mrb_run_step_status = Module['_mrb_run_step_status'] = createExportWrapper('mrb_run_step_status', 0);
6166
+ _mrb_gc_scheduler_pending_wasm = Module['_mrb_gc_scheduler_pending_wasm'] = createExportWrapper('mrb_gc_scheduler_pending_wasm', 0);
6164
6167
  _picorb_init = Module['_picorb_init'] = createExportWrapper('picorb_init', 0);
6165
6168
  _picorb_create_task = Module['_picorb_create_task'] = createExportWrapper('picorb_create_task', 1);
6166
6169
  _picorb_create_task_with_filename = Module['_picorb_create_task_with_filename'] = createExportWrapper('picorb_create_task_with_filename', 2);
@@ -6186,8 +6189,6 @@ function assignWasmExports(wasmExports) {
6186
6189
  }
6187
6190
 
6188
6191
  var wasmImports = {
6189
- /** @export */
6190
- __assert_fail: ___assert_fail,
6191
6192
  /** @export */
6192
6193
  __call_sighandler: ___call_sighandler,
6193
6194
  /** @export */
@@ -6463,7 +6464,7 @@ var wasmImports = {
6463
6464
  /** @export */
6464
6465
  serial_binary_capture_stop,
6465
6466
  /** @export */
6466
- serial_capture_get,
6467
+ serial_capture_copy,
6467
6468
  /** @export */
6468
6469
  serial_capture_start,
6469
6470
  /** @export */