Add model constant export to JS, rsxapp hydration, on_stop lifecycle
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -127,21 +127,8 @@ class Database_BundleIntegration extends BundleIntegration_Abstract
|
||||
// Only regenerate if source is newer than stub
|
||||
if ($stub_mtime >= $source_mtime) {
|
||||
// Also check if the model metadata has changed
|
||||
// by comparing a hash of enums, relationships, and columns
|
||||
$model_metadata = [];
|
||||
|
||||
// Get relationships
|
||||
$model_metadata['rel'] = $fqcn::get_relationships();
|
||||
|
||||
// Get enums
|
||||
if (property_exists($fqcn, 'enums')) {
|
||||
$model_metadata['enums'] = $fqcn::$enums ?? [];
|
||||
}
|
||||
|
||||
// Get columns from models metadata if available
|
||||
if (isset($manifest_data['data']['models'][$class_name]['columns'])) {
|
||||
$model_metadata['columns'] = $manifest_data['data']['models'][$class_name]['columns'];
|
||||
}
|
||||
// by comparing a hash of enums, relationships, columns, and constants
|
||||
$model_metadata = static::_get_model_metadata_for_hash($fqcn, $class_name, $manifest_data);
|
||||
|
||||
$model_metadata_hash = md5(json_encode($model_metadata));
|
||||
$old_metadata_hash = $metadata['model_metadata_hash'] ?? '';
|
||||
@@ -164,21 +151,7 @@ class Database_BundleIntegration extends BundleIntegration_Abstract
|
||||
|
||||
// Store the metadata hash for future comparisons if not already done
|
||||
if (!isset($manifest_data['data']['files'][$file_path]['model_metadata_hash'])) {
|
||||
$model_metadata = [];
|
||||
|
||||
// Get relationships
|
||||
$model_metadata['rel'] = $fqcn::get_relationships();
|
||||
|
||||
// Get enums
|
||||
if (property_exists($fqcn, 'enums')) {
|
||||
$model_metadata['enums'] = $fqcn::$enums ?? [];
|
||||
}
|
||||
|
||||
// Get columns from models metadata if available
|
||||
if (isset($manifest_data['data']['models'][$class_name]['columns'])) {
|
||||
$model_metadata['columns'] = $manifest_data['data']['models'][$class_name]['columns'];
|
||||
}
|
||||
|
||||
$model_metadata = static::_get_model_metadata_for_hash($fqcn, $class_name, $manifest_data);
|
||||
$manifest_data['data']['files'][$file_path]['model_metadata_hash'] = md5(json_encode($model_metadata));
|
||||
}
|
||||
}
|
||||
@@ -262,6 +235,46 @@ class Database_BundleIntegration extends BundleIntegration_Abstract
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get model metadata for hash comparison (detects when stubs need regeneration)
|
||||
*
|
||||
* @param string $fqcn Fully qualified class name
|
||||
* @param string $class_name Simple class name
|
||||
* @param array $manifest_data The manifest data array
|
||||
* @return array Metadata array for hashing
|
||||
*/
|
||||
private static function _get_model_metadata_for_hash(string $fqcn, string $class_name, array $manifest_data): array
|
||||
{
|
||||
$model_metadata = [];
|
||||
|
||||
// Get relationships
|
||||
$model_metadata['rel'] = $fqcn::get_relationships();
|
||||
|
||||
// Get enums
|
||||
if (property_exists($fqcn, 'enums')) {
|
||||
$model_metadata['enums'] = $fqcn::$enums ?? [];
|
||||
}
|
||||
|
||||
// Get columns from models metadata if available
|
||||
if (isset($manifest_data['data']['models'][$class_name]['columns'])) {
|
||||
$model_metadata['columns'] = $manifest_data['data']['models'][$class_name]['columns'];
|
||||
}
|
||||
|
||||
// Get public constants defined directly on this class
|
||||
$reflection = new \ReflectionClass($fqcn);
|
||||
$constants = [];
|
||||
foreach ($reflection->getReflectionConstants(\ReflectionClassConstant::IS_PUBLIC) as $const) {
|
||||
if ($const->getDeclaringClass()->getName() === $fqcn) {
|
||||
$constants[$const->getName()] = $const->getValue();
|
||||
}
|
||||
}
|
||||
if (!empty($constants)) {
|
||||
$model_metadata['constants'] = $constants;
|
||||
}
|
||||
|
||||
return $model_metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize model name for use as filename
|
||||
*/
|
||||
@@ -314,6 +327,32 @@ class Database_BundleIntegration extends BundleIntegration_Abstract
|
||||
$js_model_base_class = config('rsx.js_model_base_class');
|
||||
$extends_class = $js_model_base_class ?: 'Rsx_Js_Model';
|
||||
|
||||
// Collect enum constant names to avoid duplicating them
|
||||
$enum_constant_names = [];
|
||||
foreach ($enums as $column => $enum_values) {
|
||||
foreach ($enum_values as $value => $props) {
|
||||
if (!empty($props['constant'])) {
|
||||
$enum_constant_names[] = $props['constant'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all public constants defined directly on this model class (not inherited)
|
||||
$reflection = new \ReflectionClass($fqcn);
|
||||
$non_enum_constants = [];
|
||||
foreach ($reflection->getReflectionConstants(\ReflectionClassConstant::IS_PUBLIC) as $const) {
|
||||
// Only include constants defined directly on this class
|
||||
if ($const->getDeclaringClass()->getName() !== $fqcn) {
|
||||
continue;
|
||||
}
|
||||
$const_name = $const->getName();
|
||||
// Skip constants already generated from enums
|
||||
if (in_array($const_name, $enum_constant_names)) {
|
||||
continue;
|
||||
}
|
||||
$non_enum_constants[$const_name] = $const->getValue();
|
||||
}
|
||||
|
||||
// Start building the stub content
|
||||
$content = "/**\n";
|
||||
$content .= " * Auto-generated JavaScript stub for {$class_name}\n";
|
||||
@@ -326,6 +365,16 @@ class Database_BundleIntegration extends BundleIntegration_Abstract
|
||||
// Add static __MODEL property for PHP model name resolution
|
||||
$content .= " static __MODEL = '{$class_name}';\n\n";
|
||||
|
||||
// Generate non-enum constants first (static properties)
|
||||
if (!empty($non_enum_constants)) {
|
||||
$content .= " // Non-enum constants\n";
|
||||
foreach ($non_enum_constants as $const_name => $const_value) {
|
||||
$value_json = json_encode($const_value);
|
||||
$content .= " static {$const_name} = {$value_json};\n";
|
||||
}
|
||||
$content .= "\n";
|
||||
}
|
||||
|
||||
// Generate enum constants and methods
|
||||
foreach ($enums as $column => $enum_values) {
|
||||
// Sort enum values by order property first, then by key
|
||||
|
||||
@@ -222,6 +222,24 @@ class Rsx {
|
||||
return !window.rsxapp.debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current logged-in user model instance
|
||||
* Returns the hydrated ORM model if available, or the raw data object
|
||||
* @returns {Rsx_Js_Model|Object|null} User model instance or null if not logged in
|
||||
*/
|
||||
static user() {
|
||||
return window.rsxapp?.user || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current site model instance
|
||||
* Returns the hydrated ORM model if available, or the raw data object
|
||||
* @returns {Rsx_Js_Model|Object|null} Site model instance or null if not set
|
||||
*/
|
||||
static site() {
|
||||
return window.rsxapp?.site || null;
|
||||
}
|
||||
|
||||
// Generates a unique number for the application instance
|
||||
static uid() {
|
||||
if (typeof Rsx._uid == undef) {
|
||||
@@ -601,6 +619,43 @@ class Rsx {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate rsxapp.user and rsxapp.site into ORM model instances
|
||||
*
|
||||
* Checks if window.rsxapp.user and window.rsxapp.site contain raw data objects
|
||||
* with __MODEL markers, and if the corresponding model classes are available,
|
||||
* replaces them with proper ORM instances.
|
||||
*
|
||||
* This enables code like:
|
||||
* const user = Rsx.user();
|
||||
* await user.some_relationship(); // Works because user is a proper model instance
|
||||
*/
|
||||
static _hydrate_rsxapp_models() {
|
||||
if (!window.rsxapp) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hydrate user if present and has __MODEL marker
|
||||
if (window.rsxapp.user && window.rsxapp.user.__MODEL) {
|
||||
const UserClass = Manifest.get_class_by_name(window.rsxapp.user.__MODEL);
|
||||
// Check class exists and extends Rsx_Js_Model - @JS-DEFENSIVE-01-EXCEPTION - dynamic model resolution
|
||||
if (UserClass && Manifest.js_is_subclass_of(UserClass, Rsx_Js_Model)) {
|
||||
window.rsxapp.user = new UserClass(window.rsxapp.user);
|
||||
console_debug('RSX_INIT', `Hydrated rsxapp.user as ${window.rsxapp.user.__MODEL}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Hydrate site if present and has __MODEL marker
|
||||
if (window.rsxapp.site && window.rsxapp.site.__MODEL) {
|
||||
const SiteClass = Manifest.get_class_by_name(window.rsxapp.site.__MODEL);
|
||||
// Check class exists and extends Rsx_Js_Model - @JS-DEFENSIVE-01-EXCEPTION - dynamic model resolution
|
||||
if (SiteClass && Manifest.js_is_subclass_of(SiteClass, Rsx_Js_Model)) {
|
||||
window.rsxapp.site = new SiteClass(window.rsxapp.site);
|
||||
console_debug('RSX_INIT', `Hydrated rsxapp.site as ${window.rsxapp.site.__MODEL}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal: Execute multi-phase initialization for all registered classes
|
||||
* This runs various initialization phases in order to properly set up the application
|
||||
@@ -617,6 +672,10 @@ class Rsx {
|
||||
// Setup exception handlers first, before any initialization phases
|
||||
Rsx._setup_exception_handlers();
|
||||
|
||||
// Hydrate rsxapp.user and rsxapp.site into ORM model instances
|
||||
// This must happen early, before any code tries to use these objects
|
||||
Rsx._hydrate_rsxapp_models();
|
||||
|
||||
// Get all registered classes from the manifest
|
||||
const all_classes = Manifest.get_all_classes();
|
||||
|
||||
@@ -697,7 +756,6 @@ class Rsx {
|
||||
y: window.scrollY
|
||||
};
|
||||
sessionStorage.setItem(Rsx._SCROLL_STORAGE_KEY, JSON.stringify(scroll_data));
|
||||
console.log('[Rsx Scroll] Saved:', scroll_data.x, scroll_data.y, 'for', scroll_data.url);
|
||||
}, 100); // 100ms debounce
|
||||
}
|
||||
|
||||
@@ -707,61 +765,46 @@ class Rsx {
|
||||
* @private
|
||||
*/
|
||||
static _restore_scroll_on_refresh() {
|
||||
console.log('[Rsx Scroll] _restore_scroll_on_refresh called');
|
||||
|
||||
// Set up scroll listener to continuously save position
|
||||
window.addEventListener('scroll', Rsx._save_scroll_position, { passive: true });
|
||||
console.log('[Rsx Scroll] Scroll listener attached');
|
||||
|
||||
// Check if this is a page refresh using Performance API
|
||||
const nav_entries = performance.getEntriesByType('navigation');
|
||||
console.log('[Rsx Scroll] Navigation entries:', nav_entries.length);
|
||||
if (nav_entries.length === 0) {
|
||||
console.log('[Rsx Scroll] No navigation entries found, skipping restore');
|
||||
return;
|
||||
}
|
||||
|
||||
const nav_type = nav_entries[0].type;
|
||||
console.log('[Rsx Scroll] Navigation type:', nav_type);
|
||||
if (nav_type !== 'reload') {
|
||||
console.log('[Rsx Scroll] Not a reload (type=' + nav_type + '), skipping restore');
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a refresh - try to restore scroll position
|
||||
const stored = sessionStorage.getItem(Rsx._SCROLL_STORAGE_KEY);
|
||||
console.log('[Rsx Scroll] Stored scroll data:', stored);
|
||||
if (!stored) {
|
||||
console.log('[Rsx Scroll] No stored scroll position found');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const scroll_data = JSON.parse(stored);
|
||||
const current_url = window.location.pathname + window.location.search;
|
||||
console.log('[Rsx Scroll] Stored URL:', scroll_data.url, 'Current URL:', current_url);
|
||||
|
||||
// Only restore if URL matches
|
||||
if (scroll_data.url !== current_url) {
|
||||
console.log('[Rsx Scroll] URL mismatch, skipping restore');
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore scroll position instantly
|
||||
console.log('[Rsx Scroll] Restoring scroll to:', scroll_data.x, scroll_data.y);
|
||||
window.scrollTo({
|
||||
left: scroll_data.x,
|
||||
top: scroll_data.y,
|
||||
behavior: 'instant'
|
||||
});
|
||||
|
||||
console.log('[Rsx Scroll] Restored scroll position on refresh:', scroll_data.x, scroll_data.y);
|
||||
|
||||
// Clear stored position after successful restore
|
||||
sessionStorage.removeItem(Rsx._SCROLL_STORAGE_KEY);
|
||||
} catch (e) {
|
||||
// Invalid JSON or other error - ignore
|
||||
console.log('[Rsx Scroll] Error restoring scroll:', e.message);
|
||||
sessionStorage.removeItem(Rsx._SCROLL_STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,6 +658,63 @@ ENUM PROPERTIES
|
||||
const statusConfig = Project_Model.status_id_enum_val();
|
||||
// {1: {label: "Planning", badge: "bg-info"}, 2: {...}, ...}
|
||||
|
||||
MODEL CONSTANTS
|
||||
All public constants defined on a PHP model are automatically exported to
|
||||
the JavaScript stub as static properties. This includes both enum constants
|
||||
(generated from $enums) and manually defined constants.
|
||||
|
||||
PHP Model Definition:
|
||||
class User_Model extends Rsx_Model_Abstract
|
||||
{
|
||||
// Enum constants (auto-generated by rsx:migrate:document_models)
|
||||
const ROLE_ADMIN = 1;
|
||||
const ROLE_USER = 2;
|
||||
|
||||
// Permission constants (manually defined)
|
||||
const PERM_MANAGE_USERS = 1;
|
||||
const PERM_EDIT_DATA = 2;
|
||||
const PERM_VIEW_DATA = 3;
|
||||
|
||||
// Invitation status constants
|
||||
const INVITATION_PENDING = 'pending';
|
||||
const INVITATION_ACCEPTED = 'accepted';
|
||||
const INVITATION_EXPIRED = 'expired';
|
||||
}
|
||||
|
||||
JavaScript Usage:
|
||||
// All constants available as static properties
|
||||
if (permission === User_Model.PERM_MANAGE_USERS) {
|
||||
showAdminPanel();
|
||||
}
|
||||
|
||||
if (invite.status === User_Model.INVITATION_PENDING) {
|
||||
showPendingBadge();
|
||||
}
|
||||
|
||||
SECURITY WARNING:
|
||||
All public constants are exported to JavaScript and visible in the
|
||||
browser. NEVER put sensitive values in model constants:
|
||||
|
||||
// NEVER DO THIS - secrets visible in browser
|
||||
const API_SECRET = 'sk-abc123...';
|
||||
const ENCRYPTION_KEY = 'my-secret-key';
|
||||
|
||||
Use private constants for sensitive values:
|
||||
|
||||
// Safe - not exported to JavaScript
|
||||
private const API_SECRET = 'sk-abc123...';
|
||||
|
||||
Constant Types Exported:
|
||||
- Enum constants (from $enums 'constant' field)
|
||||
- Non-enum public constants (string, int, float, bool, array)
|
||||
- Constants with no visibility modifier (treated as public)
|
||||
|
||||
Constants NOT Exported:
|
||||
- Private constants (private const FOO = 1)
|
||||
- Protected constants (protected const BAR = 2)
|
||||
- Constants inherited from parent classes
|
||||
- Framework constants from Rsx_Model_Abstract
|
||||
|
||||
===============================================================================
|
||||
FUTURE DEVELOPMENT
|
||||
===============================================================================
|
||||
|
||||
0
app/RSpade/resource/vscode_extension/out/auto_rename_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/auto_rename_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/auto_rename_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/auto_rename_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/class_refactor_code_actions.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/class_refactor_code_actions.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/class_refactor_code_actions.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/class_refactor_code_actions.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/class_refactor_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/class_refactor_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/class_refactor_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/class_refactor_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/combined_semantic_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/combined_semantic_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/combined_semantic_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/combined_semantic_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/comment_file_reference_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/comment_file_reference_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/comment_file_reference_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/comment_file_reference_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/config.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/config.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/config.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/config.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/convention_method_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/convention_method_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/convention_method_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/convention_method_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/debug_client.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/debug_client.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/debug_client.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/debug_client.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/decoration_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/decoration_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/decoration_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/decoration_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/definition_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/definition_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/definition_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/definition_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/extension.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/extension.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/extension.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/extension.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/file_watcher.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/file_watcher.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/file_watcher.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/file_watcher.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/folder_color_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/folder_color_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/folder_color_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/folder_color_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/folding_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/folding_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/folding_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/folding_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/formatting_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/formatting_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/formatting_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/formatting_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/git_diff_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/git_diff_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/git_diff_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/git_diff_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/git_status_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/git_status_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/git_status_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/git_status_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/ide_bridge_client.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/ide_bridge_client.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/ide_bridge_client.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/ide_bridge_client.js.map
Executable file → Normal file
10
app/RSpade/resource/vscode_extension/out/jqhtml_lifecycle_provider.js
Executable file → Normal file
10
app/RSpade/resource/vscode_extension/out/jqhtml_lifecycle_provider.js
Executable file → Normal file
@@ -29,7 +29,7 @@ const ide_bridge_client_1 = require("./ide_bridge_client");
|
||||
/**
|
||||
* JQHTML lifecycle methods that are called automatically by the framework
|
||||
*/
|
||||
const JQHTML_LIFECYCLE_METHODS = ['on_render', 'on_create', 'on_load', 'on_ready', 'on_destroy', 'cache_id'];
|
||||
const JQHTML_LIFECYCLE_METHODS = ['on_render', 'on_create', 'on_load', 'on_ready', 'on_stop', 'cache_id'];
|
||||
/**
|
||||
* Convention methods that are called automatically by the RSX framework
|
||||
*/
|
||||
@@ -52,7 +52,7 @@ const LIFECYCLE_DOCS = {
|
||||
on_create: 'Quick setup after DOM exists. Children complete before parent. DOM manipulation allowed. MUST be synchronous.',
|
||||
on_load: 'Data loading phase - fetch async data. NO DOM manipulation allowed, only update `this.data`. Template re-renders after load. MUST be async.',
|
||||
on_ready: 'Final setup phase - all data loaded. Children complete before parent. DOM manipulation allowed. Can be sync or async.',
|
||||
on_destroy: 'Component destruction phase - cleanup resources. Called before component is removed. MUST be synchronous.',
|
||||
on_stop: 'Component destruction phase - cleanup resources. Called before component is removed. MUST be synchronous.',
|
||||
cache_id: 'Returns a unique cache key for this component instance. Used by framework to cache/restore component state. Return null to disable caching.',
|
||||
};
|
||||
/**
|
||||
@@ -280,7 +280,7 @@ class JqhtmlLifecycleHoverProvider {
|
||||
markdown.isTrusted = true;
|
||||
const is_async = is_async_method(line);
|
||||
// Determine if async is required, forbidden, or optional
|
||||
const must_be_sync = ['on_create', 'on_render', 'on_destroy'].includes(word);
|
||||
const must_be_sync = ['on_create', 'on_render', 'on_stop'].includes(word);
|
||||
const must_be_async = word === 'on_load';
|
||||
const can_be_either = word === 'on_ready';
|
||||
let has_error = false;
|
||||
@@ -377,8 +377,8 @@ class JqhtmlLifecycleDiagnosticProvider {
|
||||
else if (method_name === 'on_render' && is_async) {
|
||||
diagnostics.push(new vscode.Diagnostic(method_name_range, `'on_render' must be synchronous - remove 'async' keyword`, vscode.DiagnosticSeverity.Error));
|
||||
}
|
||||
else if (method_name === 'on_destroy' && is_async) {
|
||||
diagnostics.push(new vscode.Diagnostic(method_name_range, `'on_destroy' must be synchronous - remove 'async' keyword`, vscode.DiagnosticSeverity.Error));
|
||||
else if (method_name === 'on_stop' && is_async) {
|
||||
diagnostics.push(new vscode.Diagnostic(method_name_range, `'on_stop' must be synchronous - remove 'async' keyword`, vscode.DiagnosticSeverity.Error));
|
||||
}
|
||||
else if (method_name === 'on_load' && !is_async) {
|
||||
diagnostics.push(new vscode.Diagnostic(method_name_range, `'on_load' must be async - add 'async' keyword`, vscode.DiagnosticSeverity.Error));
|
||||
|
||||
2
app/RSpade/resource/vscode_extension/out/jqhtml_lifecycle_provider.js.map
Executable file → Normal file
2
app/RSpade/resource/vscode_extension/out/jqhtml_lifecycle_provider.js.map
Executable file → Normal file
File diff suppressed because one or more lines are too long
0
app/RSpade/resource/vscode_extension/out/laravel_completion_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/laravel_completion_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/laravel_completion_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/laravel_completion_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/php_attribute_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/php_attribute_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/php_attribute_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/php_attribute_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/refactor_code_actions.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/refactor_code_actions.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/refactor_code_actions.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/refactor_code_actions.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/refactor_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/refactor_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/refactor_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/refactor_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/sort_class_methods_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/sort_class_methods_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/sort_class_methods_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/sort_class_methods_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/symlink_redirect_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/symlink_redirect_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/symlink_redirect_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/symlink_redirect_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/that_variable_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/that_variable_provider.js
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/that_variable_provider.js.map
Executable file → Normal file
0
app/RSpade/resource/vscode_extension/out/that_variable_provider.js.map
Executable file → Normal file
@@ -2,7 +2,7 @@
|
||||
"name": "rspade-framework",
|
||||
"displayName": "RSpade Framework Support",
|
||||
"description": "VS Code extension for RSpade framework with code folding, formatting, and namespace management",
|
||||
"version": "0.1.219",
|
||||
"version": "0.1.220",
|
||||
"publisher": "rspade",
|
||||
"engines": {
|
||||
"vscode": "^1.74.0"
|
||||
|
||||
Binary file not shown.
@@ -4,7 +4,7 @@ import { IdeBridgeClient } from './ide_bridge_client';
|
||||
/**
|
||||
* JQHTML lifecycle methods that are called automatically by the framework
|
||||
*/
|
||||
const JQHTML_LIFECYCLE_METHODS = ['on_render', 'on_create', 'on_load', 'on_ready', 'on_destroy', 'cache_id'];
|
||||
const JQHTML_LIFECYCLE_METHODS = ['on_render', 'on_create', 'on_load', 'on_ready', 'on_stop', 'cache_id'];
|
||||
|
||||
/**
|
||||
* Convention methods that are called automatically by the RSX framework
|
||||
@@ -29,7 +29,7 @@ const LIFECYCLE_DOCS: { [key: string]: string } = {
|
||||
on_create: 'Quick setup after DOM exists. Children complete before parent. DOM manipulation allowed. MUST be synchronous.',
|
||||
on_load: 'Data loading phase - fetch async data. NO DOM manipulation allowed, only update `this.data`. Template re-renders after load. MUST be async.',
|
||||
on_ready: 'Final setup phase - all data loaded. Children complete before parent. DOM manipulation allowed. Can be sync or async.',
|
||||
on_destroy: 'Component destruction phase - cleanup resources. Called before component is removed. MUST be synchronous.',
|
||||
on_stop: 'Component destruction phase - cleanup resources. Called before component is removed. MUST be synchronous.',
|
||||
cache_id: 'Returns a unique cache key for this component instance. Used by framework to cache/restore component state. Return null to disable caching.',
|
||||
};
|
||||
|
||||
@@ -299,7 +299,7 @@ export class JqhtmlLifecycleHoverProvider implements vscode.HoverProvider {
|
||||
const is_async = is_async_method(line);
|
||||
|
||||
// Determine if async is required, forbidden, or optional
|
||||
const must_be_sync = ['on_create', 'on_render', 'on_destroy'].includes(word);
|
||||
const must_be_sync = ['on_create', 'on_render', 'on_stop'].includes(word);
|
||||
const must_be_async = word === 'on_load';
|
||||
const can_be_either = word === 'on_ready';
|
||||
|
||||
@@ -440,11 +440,11 @@ export class JqhtmlLifecycleDiagnosticProvider {
|
||||
vscode.DiagnosticSeverity.Error
|
||||
)
|
||||
);
|
||||
} else if (method_name === 'on_destroy' && is_async) {
|
||||
} else if (method_name === 'on_stop' && is_async) {
|
||||
diagnostics.push(
|
||||
new vscode.Diagnostic(
|
||||
method_name_range,
|
||||
`'on_destroy' must be synchronous - remove 'async' keyword`,
|
||||
`'on_stop' must be synchronous - remove 'async' keyword`,
|
||||
vscode.DiagnosticSeverity.Error
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user