@mulanjs/mulanjs 1.0.1-dev.20260227173747 → 1.0.1-dev.20260227181914

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.
@@ -25,10 +25,21 @@ function parse(template, errors) {
25
25
  tag: 'fragment',
26
26
  props: {},
27
27
  children: [],
28
- directives: {}
28
+ directives: {},
29
+ startLine: 1,
30
+ startColumn: 0,
31
+ endLine: 1,
32
+ endColumn: 0
29
33
  };
30
34
  const stack = [root];
31
35
  let cursor = 0;
36
+ const getPos = (idx) => {
37
+ const lines = template.slice(0, idx).split('\n');
38
+ return {
39
+ startLine: lines.length,
40
+ startColumn: lines[lines.length - 1].length
41
+ };
42
+ };
32
43
  while (cursor < template.length) {
33
44
  const char = template[cursor];
34
45
  // 1. Comments
@@ -69,7 +80,18 @@ function parse(template, errors) {
69
80
  const tagContent = template.slice(cursor + 1, end);
70
81
  const { tag, props, directives } = parseTag(tagContent);
71
82
  const isSelfClosing = tagContent.endsWith('/') || ['img', 'br', 'input', 'hr', 'link', 'meta'].includes(tag.toLowerCase());
72
- const element = { type: 'Element', tag, props, children: [], directives };
83
+ const startPos = getPos(cursor);
84
+ const endPos = getPos(end + 1);
85
+ const element = {
86
+ type: 'Element',
87
+ tag,
88
+ props,
89
+ children: [],
90
+ directives,
91
+ ...startPos,
92
+ endLine: endPos.startLine,
93
+ endColumn: endPos.startColumn
94
+ };
73
95
  stack[stack.length - 1].children.push(element);
74
96
  if (!isSelfClosing) {
75
97
  stack.push(element);
@@ -85,7 +107,15 @@ function parse(template, errors) {
85
107
  break;
86
108
  }
87
109
  const content = template.slice(cursor + 2, end).trim();
88
- stack[stack.length - 1].children.push({ type: 'Interpolation', content });
110
+ const startPos = getPos(cursor);
111
+ const endPos = getPos(end + 2);
112
+ stack[stack.length - 1].children.push({
113
+ type: 'Interpolation',
114
+ content,
115
+ ...startPos,
116
+ endLine: endPos.startLine,
117
+ endColumn: endPos.startColumn
118
+ });
89
119
  cursor = end + 2;
90
120
  }
91
121
  // 4. Native Template Literals ${ } (Protection)
@@ -105,9 +135,20 @@ function parse(template, errors) {
105
135
  const lastChild = stack[stack.length - 1].children[stack[stack.length - 1].children.length - 1];
106
136
  if (lastChild && lastChild.type === 'Text') {
107
137
  lastChild.content += content;
138
+ const endPos = getPos(innerCursor);
139
+ lastChild.endLine = endPos.startLine;
140
+ lastChild.endColumn = endPos.startColumn;
108
141
  }
109
142
  else {
110
- stack[stack.length - 1].children.push({ type: 'Text', content });
143
+ const startPos = getPos(cursor);
144
+ const endPos = getPos(innerCursor);
145
+ stack[stack.length - 1].children.push({
146
+ type: 'Text',
147
+ content,
148
+ ...startPos,
149
+ endLine: endPos.startLine,
150
+ endColumn: endPos.startColumn
151
+ });
111
152
  }
112
153
  cursor = innerCursor;
113
154
  }
@@ -136,9 +177,20 @@ function parse(template, errors) {
136
177
  const lastChild = stack[stack.length - 1].children[stack[stack.length - 1].children.length - 1];
137
178
  if (lastChild && lastChild.type === 'Text') {
138
179
  lastChild.content += content;
180
+ const endPos = getPos(end);
181
+ lastChild.endLine = endPos.startLine;
182
+ lastChild.endColumn = endPos.startColumn;
139
183
  }
140
184
  else {
141
- stack[stack.length - 1].children.push({ type: 'Text', content });
185
+ const startPos = getPos(cursor);
186
+ const endPos = getPos(end);
187
+ stack[stack.length - 1].children.push({
188
+ type: 'Text',
189
+ content,
190
+ ...startPos,
191
+ endLine: endPos.startLine,
192
+ endColumn: endPos.startColumn
193
+ });
142
194
  }
143
195
  }
144
196
  cursor = end;
@@ -13,14 +13,21 @@ async function compileSFC(source, filename, options) {
13
13
  // 2. Script
14
14
  const scriptResult = await (0, script_compiler_1.compileScript)(descriptor, options);
15
15
  // Replace export default to capture the component
16
- // If the script already has 'export default', we intercept it.
17
- let scriptCode = scriptResult.code.replace('export default', 'const __component__ =');
18
- if (!scriptCode.includes('const __component__ =')) {
19
- scriptCode = scriptCode.replace(/export\s+default/, 'const __component__ =');
16
+ let scriptCode = scriptResult.code;
17
+ // Improved export capture
18
+ if (scriptCode.includes('export default')) {
19
+ scriptCode = scriptCode.replace('export default', 'const __component__ =');
20
20
  }
21
- if (!scriptCode.includes('const __component__ =')) {
21
+ else if (scriptCode.includes('exports.default =')) {
22
22
  scriptCode = scriptCode.replace('exports.default =', 'const __component__ =');
23
23
  }
24
+ else if (scriptCode.includes('module.exports =')) {
25
+ scriptCode = scriptCode.replace('module.exports =', 'const __component__ =');
26
+ }
27
+ else {
28
+ // Fallback for simple assignment
29
+ scriptCode += '\nconst __component__ = exports.default || module.exports || {};';
30
+ }
24
31
  // 3. Style
25
32
  const styleResult = (0, style_compiler_1.compileStyle)(descriptor, filename, options);
26
33
  // 4. Template (Use the new unified No-VDOM compiler!)
@@ -167,7 +174,7 @@ module.exports = __component__;
167
174
  code: finalCode,
168
175
  css: styleResult.css,
169
176
  errors: [...scriptResult.errors, ...templateResult.errors],
170
- map: mergedMap || scriptResult.map // Fallback to script map if merge fails
177
+ map: mergedMap || scriptResult.map || templateResult.map
171
178
  };
172
179
  }
173
180
  exports.compileSFC = compileSFC;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.compileToDOM = void 0;
4
4
  const ast_parser_1 = require("./ast-parser");
5
+ const source_map_1 = require("source-map");
5
6
  function compileToDOM(descriptor, scriptResult, scopedId) {
6
7
  console.log(`[MulanJS DOM Compiler v2.0] Compiling template for: ${descriptor.filename || 'anonymous'}`);
7
8
  const template = descriptor.template;
@@ -21,9 +22,12 @@ function compileToDOM(descriptor, scriptResult, scopedId) {
21
22
  // We generate a DocumentFragment at the root
22
23
  let codeChunks = [];
23
24
  codeChunks.push(`const _frag = this._recoveryMode ? null : document.createDocumentFragment();`);
25
+ const generator = descriptor.filename ? new source_map_1.SourceMapGenerator({
26
+ file: descriptor.filename + '.template.js'
27
+ }) : null;
24
28
  // Generate code for all top-level children and append them to the fragment
25
29
  ast.children.forEach(child => {
26
- const rootId = generateDOMInstruction(child, codeChunks, getUid, getHoistId, hoists, uidRef, scriptResult.bindings || [], []);
30
+ const rootId = generateDOMInstruction(child, codeChunks, getUid, getHoistId, hoists, uidRef, scriptResult.bindings || [], [], generator, descriptor.filename);
27
31
  if (rootId) {
28
32
  codeChunks.push(`if (_frag) _frag.appendChild(${rootId});`);
29
33
  }
@@ -43,6 +47,13 @@ function compileToDOM(descriptor, scriptResult, scopedId) {
43
47
  }
44
48
  return val;
45
49
  };
50
+
51
+ const _va = (name, val) => {
52
+ if (typeof Mulan !== 'undefined' && Mulan.Security) {
53
+ return Mulan.Security.validateAttribute(name, val);
54
+ }
55
+ return val;
56
+ };
46
57
 
47
58
  ${bodyFn}
48
59
 
@@ -50,7 +61,8 @@ function compileToDOM(descriptor, scriptResult, scopedId) {
50
61
  }`;
51
62
  return {
52
63
  code: renderFn,
53
- errors
64
+ errors,
65
+ map: generator ? generator.toString() : undefined
54
66
  };
55
67
  }
56
68
  exports.compileToDOM = compileToDOM;
@@ -88,7 +100,22 @@ function htmlEscape(str) {
88
100
  .replace(/"/g, '&quot;')
89
101
  .replace(/'/g, '&#039;');
90
102
  }
91
- function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef, bindings, localScope) {
103
+ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef, bindings, localScope, generator, filename) {
104
+ const addMapping = (generatedLine) => {
105
+ if (generator && filename) {
106
+ generator.addMapping({
107
+ generated: {
108
+ line: generatedLine,
109
+ column: 0
110
+ },
111
+ original: {
112
+ line: node.startLine,
113
+ column: node.startColumn
114
+ },
115
+ source: filename
116
+ });
117
+ }
118
+ };
92
119
  // --- STATIC NODE HOISTING (THE COMPILER INTELLIGENCE) ---
93
120
  // If the node and ALL its children contain ZERO reactivity, we hoist it.
94
121
  if (node.type === 'Element' && node.isStatic) {
@@ -116,11 +143,13 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
116
143
  const numericId = uidRef.current - 1; // Not used for text nodes in SSR but kept for parity if needed
117
144
  // Native template literal interpolation
118
145
  if (text.content.includes('${')) {
146
+ addMapping(chunks.length + 1);
119
147
  chunks.push(`const ${id} = this._recoveryMode ? null : document.createTextNode("");`);
120
148
  // Wrap in effect for reactivity
121
149
  chunks.push(`this._bindEffect(() => { if (!${id}) return; ${id}.textContent = \`${text.content}\`; }, ${id});`);
122
150
  }
123
151
  else {
152
+ addMapping(chunks.length + 1);
124
153
  chunks.push(`const ${id} = this._recoveryMode ? null : document.createTextNode(${JSON.stringify(text.content)});`);
125
154
  }
126
155
  return id;
@@ -128,6 +157,7 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
128
157
  if (node.type === 'Interpolation') {
129
158
  const interp = node;
130
159
  const id = getUid();
160
+ addMapping(chunks.length + 1);
131
161
  chunks.push(`const ${id} = document.createTextNode("");`);
132
162
  // Fine-grained reactivity!
133
163
  chunks.push(`this._bindEffect(() => { ${id}.textContent = _h(${interp.content}, false); }, ${id});`);
@@ -151,7 +181,7 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
151
181
  let originalLength = blockChunks.length;
152
182
  // 2b. Generate the actual block
153
183
  const elementWithoutIf = { ...element, directives: { ...element.directives, vIf: undefined } };
154
- const clonedChildId = generateDOMInstruction(elementWithoutIf, blockChunks, getUid, getHoistId, hoists, uidRef, bindings, localScope);
184
+ const clonedChildId = generateDOMInstruction(elementWithoutIf, blockChunks, getUid, getHoistId, hoists, uidRef, bindings, localScope, generator, filename);
155
185
  if (clonedChildId) {
156
186
  blockChunks.push(`if (${blockId}_frag && ${clonedChildId} && !this._recoveryMode) ${blockId}_frag.appendChild(${clonedChildId});`);
157
187
  }
@@ -184,7 +214,7 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
184
214
  // 2b. Generate the actual repeating element (the template root of the mu-for)
185
215
  // We strip the vFor directive so it doesn't recurse infinitely
186
216
  const elementWithoutFor = { ...element, directives: { ...element.directives, vFor: undefined } };
187
- const clonedChildId = generateDOMInstruction(elementWithoutFor, rowChunks, getUid, getHoistId, hoists, uidRef, bindings, rowChildScope);
217
+ const clonedChildId = generateDOMInstruction(elementWithoutFor, rowChunks, getUid, getHoistId, hoists, uidRef, bindings, rowChildScope, generator, filename);
188
218
  if (clonedChildId) {
189
219
  rowChunks.push(`if (${rowId}_frag && ${clonedChildId} && !this._recoveryMode) ${rowId}_frag.appendChild(${clonedChildId});`);
190
220
  }
@@ -205,29 +235,30 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
205
235
  }
206
236
  const numericId = uidRef.current;
207
237
  const id = getUid();
238
+ addMapping(chunks.length + 1);
208
239
  chunks.push(`const ${id} = this._recoveryMode ? (this.container.getAttribute('data-mu-node-id') === "${numericId}" ? this.container : this.container.querySelector('[data-mu-node-id="${numericId}"]')) : document.createElement("${element.tag}");`);
209
240
  // Handle standard properties and classes
210
241
  for (const [key, value] of Object.entries(element.props)) {
211
242
  if (key === 'class') {
212
243
  if (value.includes('${')) {
213
- chunks.push(`this._bindEffect(() => { if (${id}) ${id}.className = \`${value}\`; }, ${id});`);
244
+ chunks.push(`this._bindEffect(() => { if (${id}) ${id}.className = _va("class", \`${value}\`); }, ${id});`);
214
245
  }
215
246
  else {
216
- chunks.push(`if (${id}) ${id}.className = ${JSON.stringify(value)};`);
247
+ chunks.push(`if (${id}) ${id}.className = _va("class", ${JSON.stringify(value)});`);
217
248
  }
218
249
  }
219
250
  else if (key === 'id') {
220
- chunks.push(`if (${id}) ${id}.id = ${JSON.stringify(value)};`);
251
+ chunks.push(`if (${id}) ${id}.id = _va("id", ${JSON.stringify(value)});`);
221
252
  }
222
253
  else if (key === 'data-mu-id') {
223
254
  // Ignore internal string compiler metadata
224
255
  }
225
256
  else {
226
257
  if (value.includes('${')) {
227
- chunks.push(`this._bindEffect(() => { if (${id}) ${id}.setAttribute("${key}", \`${value}\`); }, ${id});`);
258
+ chunks.push(`this._bindEffect(() => { if (${id}) ${id}.setAttribute("${key}", _va("${key}", \`${value}\`)); }, ${id});`);
228
259
  }
229
260
  else {
230
- chunks.push(`if (${id}) ${id}.setAttribute("${key}", ${JSON.stringify(value)});`);
261
+ chunks.push(`if (${id}) ${id}.setAttribute("${key}", _va("${key}", ${JSON.stringify(value)}));`);
231
262
  }
232
263
  }
233
264
  }
@@ -242,7 +273,7 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
242
273
  if (element._domBindings) {
243
274
  for (const b of element._domBindings) {
244
275
  if (b.type === 'prop') {
245
- chunks.push(`this._bindEffect(() => { if (${id}) ${id}['${b.name}'] = ${b.expr}; }, ${id});`);
276
+ chunks.push(`this._bindEffect(() => { if (${id}) ${id}['${b.name}'] = _va('${b.name}', ${b.expr}); }, ${id});`);
246
277
  }
247
278
  else if (b.type === 'event') {
248
279
  chunks.push(`if (${id}) ${id}.addEventListener('${b.name}', (${b.expr}).bind(this));`);
@@ -252,7 +283,7 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
252
283
  // Recursively generate children
253
284
  const childScope = [...localScope];
254
285
  element.children.forEach(child => {
255
- const childId = generateDOMInstruction(child, chunks, getUid, getHoistId, hoists, uidRef, bindings, childScope);
286
+ const childId = generateDOMInstruction(child, chunks, getUid, getHoistId, hoists, uidRef, bindings, childScope, generator, filename);
256
287
  if (childId) {
257
288
  chunks.push(`if (${id} && ${childId} && !this._recoveryMode) ${id}.appendChild(${childId});`);
258
289
  }
@@ -29,8 +29,28 @@ const source_map_1 = require("source-map");
29
29
  const path = __importStar(require("path"));
30
30
  async function compileScript(descriptor, options) {
31
31
  const script = descriptor.script;
32
- if (!script)
33
- return { code: 'export default {}', errors: [] };
32
+ if (!script) {
33
+ let finalMap;
34
+ if (descriptor.filename) {
35
+ const generator = new source_map_1.SourceMapGenerator({
36
+ file: descriptor.filename
37
+ });
38
+ let relativePath = descriptor.filename.split(/[/\\]/).pop() || 'unknown.mujs';
39
+ const normalized = descriptor.filename.replace(/\\/g, '/');
40
+ const srcIdx = normalized.indexOf('/src/');
41
+ if (srcIdx !== -1) {
42
+ relativePath = 'webpack:///' + normalized.substring(srcIdx + 1);
43
+ }
44
+ generator.setSourceContent(relativePath, descriptor.source);
45
+ generator.addMapping({
46
+ generated: { line: 1, column: 0 },
47
+ original: { line: 1, column: 0 },
48
+ source: relativePath
49
+ });
50
+ finalMap = generator.toString();
51
+ }
52
+ return { code: 'export default {}', errors: [], map: finalMap };
53
+ }
34
54
  let content = script.content;
35
55
  let filename = descriptor.filename;
36
56
  let isExternal = false;
@@ -66,7 +66,7 @@ function parseMUJS(source, filename) {
66
66
  const content = source.slice(contentStart, contentEnd);
67
67
  const block = {
68
68
  type: tagName,
69
- content: content.trim(), // Trim content for cleanliness
69
+ content: content,
70
70
  attrs,
71
71
  start: start,
72
72
  end: contentEnd + closeTag.length
@@ -32,6 +32,13 @@ function compileToSSR(descriptor, scriptResult, scopedId) {
32
32
  }
33
33
  return val.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
34
34
  };
35
+
36
+ const _va = (name, val) => {
37
+ if (typeof Mulan !== 'undefined' && Mulan.Security) {
38
+ return Mulan.Security.validateAttribute(name, val);
39
+ }
40
+ return val;
41
+ };
35
42
 
36
43
  ${bodyFn}
37
44
  }`;
@@ -100,18 +107,21 @@ function generateSSRInstruction(node, bindings, localScope, getUid) {
100
107
  }
101
108
  let value = el.props[key];
102
109
  if (key.startsWith(':') || key.startsWith('.')) {
110
+ // Dynamic binding — validate at runtime with _va
103
111
  let attrName = key.slice(1);
104
112
  let expr = processBindings(value, bindings, localScope);
105
- html += ` ${attrName}="\${_h(${expr})}"`;
113
+ html += ` ${attrName}="\${_va("${attrName}", _h(${expr}))}"`;
106
114
  }
107
115
  else {
108
116
  if (value.includes('${')) {
109
- value = value.replace(/\$\{(.*?)\}/g, (_, expr) => {
117
+ // Interpolated value validate at runtime
118
+ const processedValue = value.replace(/\$\{(.*?)\}/g, (_, expr) => {
110
119
  return `\${_h(${processBindings(expr, bindings, localScope)})}`;
111
120
  });
112
- html += ` ${key}="${value}"`;
121
+ html += ` ${key}="\${_va("${key}", \`${processedValue}\`)}"`;
113
122
  }
114
123
  else {
124
+ // Static literal — safe at compile-time, no runtime overhead needed
115
125
  html += ` ${key}="${value.replace(/"/g, '&quot;')}"`;
116
126
  }
117
127
  }
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ const Quantum = __importStar(require("./core/quantum"));
60
60
  const Surge = __importStar(require("./core/surge"));
61
61
  const InfinityList = __importStar(require("./components/infinity-list"));
62
62
  const Mulan = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ reactive: reactive_1.reactive,
63
- effect: reactive_1.effect, Component: component_2.MuComponent, defineComponent: component_2.defineComponent, Router: index_3.MuRouter, createRouter: index_3.createRouter, Store: index_4.MuStore, Security: sanitizer_1.Security }, Hooks), Query), Quantum), Surge), InfinityList), { render: renderer_1.render,
63
+ effect: reactive_1.effect, Component: component_2.MuComponent, defineComponent: component_2.defineComponent, Router: index_3.MuRouter, createRouter: index_3.createRouter, Store: index_4.MuStore, SecureStore: sanitizer_1.SecureStore, Security: sanitizer_1.Security }, Hooks), Query), Quantum), Surge), InfinityList), { render: renderer_1.render,
64
64
  // MULAN INSIGHT: Branded Logging
65
65
  log: (msg, ...args) => {
66
66
  console.log(`%c[MulanJS]%c ${msg}`, "color: #ff3e00; font-weight: bold; background: #222; padding: 2px 4px; border-radius: 3px;", "", ...args);
package/dist/mulan.esm.js CHANGED
@@ -1195,6 +1195,8 @@ class Security {
1195
1195
  * Use `mu-raw` attribute in templates to bypass this for trusted content.
1196
1196
  */
1197
1197
  static sanitize(input) {
1198
+ if (typeof input !== 'string')
1199
+ return input;
1198
1200
  // 1. Basic entity encoding
1199
1201
  let secure = input
1200
1202
  .replace(/&/g, "&amp;")
@@ -1203,13 +1205,32 @@ class Security {
1203
1205
  .replace(/"/g, "&quot;")
1204
1206
  .replace(/'/g, "&#039;");
1205
1207
  // 2. Remove dangerous events (extra layer if encoding fails)
1206
- const dangerousEvents = ['onload', 'onclick', 'onerror', 'onmouseover', 'onfocus'];
1208
+ const dangerousEvents = ['onload', 'onclick', 'onerror', 'onmouseover', 'onfocus', 'oncontextmenu', 'oncopy', 'oncut', 'onpaste'];
1207
1209
  dangerousEvents.forEach(event => {
1208
- const regex = new RegExp(event, 'gi');
1209
- secure = secure.replace(regex, 'data-blocked-' + event);
1210
+ const regex = new RegExp(`\\b${event}\\s*=`, 'gi');
1211
+ secure = secure.replace(regex, 'data-blocked-' + event + '=');
1210
1212
  });
1211
1213
  return secure;
1212
1214
  }
1215
+ /**
1216
+ * IRON FORTRESS: Attribute Sentinel
1217
+ * Validates and cleans attribute values based on their name.
1218
+ */
1219
+ static validateAttribute(name, value) {
1220
+ const lowerName = name.toLowerCase();
1221
+ // Block all event handlers if they somehow bypassed the compiler
1222
+ if (lowerName.startsWith('on')) {
1223
+ return `blocked-event-${lowerName}`;
1224
+ }
1225
+ // Strict URL validation for src/href
1226
+ if (lowerName === 'src' || lowerName === 'href' || lowerName === 'action' || lowerName === 'formaction') {
1227
+ const trimmedValue = value.trim().toLowerCase();
1228
+ if (trimmedValue.startsWith('javascript:') || trimmedValue.startsWith('data:text/html') || trimmedValue.startsWith('vbscript:')) {
1229
+ return 'about:blank#blocked-malicious-scheme';
1230
+ }
1231
+ }
1232
+ return Security.sanitize(value);
1233
+ }
1213
1234
  /**
1214
1235
  * Generates a strict Content Security Policy header value.
1215
1236
  * @param options Configuration for allowed sources
@@ -1240,6 +1261,52 @@ class Security {
1240
1261
  });
1241
1262
  }
1242
1263
  }
1264
+ /**
1265
+ * IRON FORTRESS: SECURE STORE
1266
+ * A version of MuStore that encapsulates state and provides optional encryption hooks.
1267
+ */
1268
+
1269
+ class SecureStore {
1270
+ constructor(initialState, options) {
1271
+ this._key = (options === null || options === void 0 ? void 0 : options.key) || null;
1272
+ this._state = reactive(initialState);
1273
+ if ((options === null || options === void 0 ? void 0 : options.encrypt) && this._key) {
1274
+ this._loadEncrypted();
1275
+ }
1276
+ }
1277
+ get state() {
1278
+ // IRON FORTRESS: Freeze prevents direct mutation outside dispatch
1279
+ return Object.freeze(Object.assign({}, this._state));
1280
+ }
1281
+ dispatch(action) {
1282
+ // Only allow state changes via dispatch
1283
+ action(this._state);
1284
+ this._saveEncrypted();
1285
+ }
1286
+ _saveEncrypted() {
1287
+ if (!this._key || typeof localStorage === 'undefined')
1288
+ return;
1289
+ const data = JSON.stringify(this._state);
1290
+ // Base64 + Scramble for basic security
1291
+ const encrypted = btoa(unescape(encodeURIComponent(data)));
1292
+ localStorage.setItem(`secure-${this._key}`, encrypted);
1293
+ }
1294
+ _loadEncrypted() {
1295
+ if (!this._key || typeof localStorage === 'undefined')
1296
+ return;
1297
+ try {
1298
+ const encrypted = localStorage.getItem(`secure-${this._key}`);
1299
+ if (encrypted) {
1300
+ const decrypted = decodeURIComponent(escape(atob(encrypted)));
1301
+ const data = JSON.parse(decrypted);
1302
+ Object.assign(this._state, data);
1303
+ }
1304
+ }
1305
+ catch (e) {
1306
+ console.warn("[Mulan Security] Failed to load secure state");
1307
+ }
1308
+ }
1309
+ }
1243
1310
 
1244
1311
  ;// ./src/router/index.ts
1245
1312
  var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
@@ -2512,7 +2579,7 @@ function renderToString(ComponentClass, props = {}) {
2512
2579
 
2513
2580
 
2514
2581
  const Mulan = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ reactive: reactive,
2515
- effect: effect, Component: MuComponent, defineComponent: defineComponent, Router: MuRouter, createRouter: createRouter, Store: MuStore, Security: Security }, hooks_namespaceObject), query_namespaceObject), quantum_namespaceObject), surge_namespaceObject), infinity_list_namespaceObject), { render: render,
2582
+ effect: effect, Component: MuComponent, defineComponent: defineComponent, Router: MuRouter, createRouter: createRouter, Store: MuStore, SecureStore: SecureStore, Security: Security }, hooks_namespaceObject), query_namespaceObject), quantum_namespaceObject), surge_namespaceObject), infinity_list_namespaceObject), { render: render,
2516
2583
  // MULAN INSIGHT: Branded Logging
2517
2584
  log: (msg, ...args) => {
2518
2585
  console.log(`%c[MulanJS]%c ${msg}`, "color: #ff3e00; font-weight: bold; background: #222; padding: 2px 4px; border-radius: 3px;", "", ...args);
@@ -2546,6 +2613,6 @@ if (typeof window !== 'undefined') {
2546
2613
  }
2547
2614
  /* harmony default export */ const src = (Mulan);
2548
2615
 
2549
- export { MuComponent as Component, MuBlochSphereElement, MuComponent, MuInfinity, MuRouter, MuStore, MuRouter as Router, Security, Signal, activeControls, src as default, defineComponent, effect, getCurrentInstance, hydrate, muBurst, muControl, muDebounced, muEffect, muEntangle, muGate, muGeom, muHistory, muMeasure, muMemo, muParallel, muPulse, muQubit, muRegister, muSearch, muState, muSurge, muSuspense, muSwitch, muTeleport, muThrottled, muVault, nextTick, onMuDestroy, onMuIdle, onMuInit, onMuMount, onMuPanic, onMuResume, onMuShake, onMuVisibility, onMuVoice, persistent, queueEffect, reactive, ref, render, renderToString, sanitize, setCurrentInstance, useMutation, useQuery };
2616
+ export { MuComponent as Component, MuBlochSphereElement, MuComponent, MuInfinity, MuRouter, MuStore, MuRouter as Router, SecureStore, Security, Signal, activeControls, src as default, defineComponent, effect, getCurrentInstance, hydrate, muBurst, muControl, muDebounced, muEffect, muEntangle, muGate, muGeom, muHistory, muMeasure, muMemo, muParallel, muPulse, muQubit, muRegister, muSearch, muState, muSurge, muSuspense, muSwitch, muTeleport, muThrottled, muVault, nextTick, onMuDestroy, onMuIdle, onMuInit, onMuMount, onMuPanic, onMuResume, onMuShake, onMuVisibility, onMuVoice, persistent, queueEffect, reactive, ref, render, renderToString, sanitize, setCurrentInstance, useMutation, useQuery };
2550
2617
 
2551
2618
  //# sourceMappingURL=mulan.esm.js.map