mrmd-js 2.0.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 (52) hide show
  1. package/README.md +842 -0
  2. package/dist/index.cjs +7613 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.js +7530 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/mrmd-js.iife.js +7618 -0
  7. package/dist/mrmd-js.iife.js.map +1 -0
  8. package/package.json +47 -0
  9. package/src/analysis/format.js +371 -0
  10. package/src/analysis/index.js +18 -0
  11. package/src/analysis/is-complete.js +394 -0
  12. package/src/constants.js +44 -0
  13. package/src/execute/css.js +205 -0
  14. package/src/execute/html.js +162 -0
  15. package/src/execute/index.js +41 -0
  16. package/src/execute/interface.js +144 -0
  17. package/src/execute/javascript.js +197 -0
  18. package/src/execute/registry.js +245 -0
  19. package/src/index.js +136 -0
  20. package/src/lsp/complete.js +353 -0
  21. package/src/lsp/format.js +310 -0
  22. package/src/lsp/hover.js +126 -0
  23. package/src/lsp/index.js +55 -0
  24. package/src/lsp/inspect.js +466 -0
  25. package/src/lsp/parse.js +455 -0
  26. package/src/lsp/variables.js +283 -0
  27. package/src/runtime.js +518 -0
  28. package/src/session/console-capture.js +181 -0
  29. package/src/session/context/iframe.js +407 -0
  30. package/src/session/context/index.js +12 -0
  31. package/src/session/context/interface.js +38 -0
  32. package/src/session/context/main.js +357 -0
  33. package/src/session/index.js +16 -0
  34. package/src/session/manager.js +327 -0
  35. package/src/session/session.js +678 -0
  36. package/src/transform/async.js +133 -0
  37. package/src/transform/extract.js +251 -0
  38. package/src/transform/index.js +10 -0
  39. package/src/transform/persistence.js +176 -0
  40. package/src/types/analysis.js +24 -0
  41. package/src/types/capabilities.js +44 -0
  42. package/src/types/completion.js +47 -0
  43. package/src/types/execution.js +62 -0
  44. package/src/types/index.js +16 -0
  45. package/src/types/inspection.js +39 -0
  46. package/src/types/session.js +32 -0
  47. package/src/types/streaming.js +74 -0
  48. package/src/types/variables.js +54 -0
  49. package/src/utils/ansi-renderer.js +301 -0
  50. package/src/utils/css-applicator.js +149 -0
  51. package/src/utils/html-renderer.js +355 -0
  52. package/src/utils/index.js +25 -0
@@ -0,0 +1,407 @@
1
+ /**
2
+ * Iframe Execution Context
3
+ *
4
+ * Executes JavaScript in an isolated iframe environment.
5
+ * Variables persist in the iframe's global scope between executions.
6
+ *
7
+ * @module session/context/iframe
8
+ */
9
+
10
+ import { ConsoleCapture } from '../console-capture.js';
11
+
12
+ /**
13
+ * @typedef {import('./interface.js').ExecutionContext} ExecutionContext
14
+ * @typedef {import('./interface.js').RawExecutionResult} RawExecutionResult
15
+ * @typedef {import('./interface.js').LogEntry} LogEntry
16
+ */
17
+
18
+ /**
19
+ * @typedef {Object} StdinRequest
20
+ * @property {string} prompt - Prompt text to display
21
+ * @property {boolean} password - Whether to hide input
22
+ * @property {string} execId - Execution ID for this request
23
+ */
24
+
25
+ /**
26
+ * @callback OnStdinRequestCallback
27
+ * @param {StdinRequest} request - The stdin request
28
+ * @returns {Promise<string>} - Resolves with user input
29
+ */
30
+
31
+ /**
32
+ * @typedef {Object} IframeContextOptions
33
+ * @property {boolean} [visible=false] - Whether iframe is visible
34
+ * @property {HTMLElement} [target] - Target element for visible iframe
35
+ * @property {boolean} [allowMainAccess=true] - Allow access to main document
36
+ * @property {Record<string, *>} [utilities] - Custom utilities to inject
37
+ * @property {Partial<CSSStyleDeclaration>} [styles] - Styles for visible iframe
38
+ */
39
+
40
+ /**
41
+ * Iframe-based execution context
42
+ * @implements {ExecutionContext}
43
+ */
44
+ export class IframeContext {
45
+ /** @type {HTMLIFrameElement | null} */
46
+ #iframe = null;
47
+
48
+ /** @type {Window | null} */
49
+ #ctx = null;
50
+
51
+ /** @type {Set<string>} */
52
+ #trackedVars = new Set();
53
+
54
+ /** @type {ConsoleCapture | null} */
55
+ #consoleCapture = null;
56
+
57
+ /** @type {IframeContextOptions} */
58
+ #options;
59
+
60
+ /** @type {boolean} */
61
+ #initialized = false;
62
+
63
+ /** @type {OnStdinRequestCallback | null} */
64
+ #onStdinRequest = null;
65
+
66
+ /** @type {string} */
67
+ #currentExecId = '';
68
+
69
+ /**
70
+ * @param {IframeContextOptions} [options]
71
+ */
72
+ constructor(options = {}) {
73
+ this.#options = {
74
+ visible: false,
75
+ allowMainAccess: true,
76
+ ...options,
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Initialize the iframe
82
+ */
83
+ #initialize() {
84
+ if (this.#initialized) return;
85
+
86
+ // Create iframe
87
+ this.#iframe = document.createElement('iframe');
88
+ this.#iframe.sandbox.add('allow-scripts');
89
+ this.#iframe.sandbox.add('allow-same-origin');
90
+
91
+ if (this.#options.visible && this.#options.target) {
92
+ // Visible mode
93
+ const styles = this.#options.styles || {};
94
+ this.#iframe.style.width = styles.width || '100%';
95
+ this.#iframe.style.height = styles.height || '100%';
96
+ this.#iframe.style.border = styles.border || 'none';
97
+ this.#iframe.style.display = 'block';
98
+
99
+ // Apply additional styles
100
+ for (const [key, value] of Object.entries(styles)) {
101
+ if (value && typeof value === 'string') {
102
+ this.#iframe.style.setProperty(key, value);
103
+ }
104
+ }
105
+
106
+ this.#options.target.appendChild(this.#iframe);
107
+ } else {
108
+ // Hidden mode
109
+ this.#iframe.style.display = 'none';
110
+ document.body.appendChild(this.#iframe);
111
+ }
112
+
113
+ // Get context
114
+ this.#ctx = /** @type {Window} */ (this.#iframe.contentWindow);
115
+
116
+ // Set up utilities
117
+ this.#setupUtilities();
118
+
119
+ // Set up console capture
120
+ this.#consoleCapture = new ConsoleCapture(this.#ctx);
121
+ this.#consoleCapture.start();
122
+
123
+ // Initialize tracking set in iframe
124
+ this.#ctx.__userVars__ = this.#trackedVars;
125
+
126
+ this.#initialized = true;
127
+ }
128
+
129
+ /**
130
+ * Set up utility functions in the context
131
+ */
132
+ #setupUtilities() {
133
+ if (!this.#ctx) return;
134
+
135
+ // Access to main document
136
+ if (this.#options.allowMainAccess) {
137
+ this.#ctx.mainDocument = document;
138
+ this.#ctx.mainWindow = window;
139
+ }
140
+
141
+ // Sleep helper
142
+ this.#ctx.sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
143
+
144
+ // Print helper
145
+ this.#ctx.print = (...args) => {
146
+ this.#ctx?.console.log(...args);
147
+ };
148
+
149
+ // Input helper - prompts for user input (like Python's input())
150
+ // Returns a Promise that resolves when user provides input
151
+ const self = this;
152
+ this.#ctx.input = async (prompt = '', options = {}) => {
153
+ // Print prompt to console (like Python does)
154
+ if (prompt) {
155
+ self.#ctx?.console.log(prompt);
156
+ }
157
+
158
+ // If no stdin handler is set, fall back to browser prompt()
159
+ if (!self.#onStdinRequest) {
160
+ const result = self.#ctx?.prompt(prompt) ?? '';
161
+ return result;
162
+ }
163
+
164
+ // Request input from the external handler
165
+ const request = {
166
+ prompt: prompt,
167
+ password: options.password ?? false,
168
+ execId: self.#currentExecId,
169
+ };
170
+
171
+ try {
172
+ const response = await self.#onStdinRequest(request);
173
+ // Remove trailing newline if present (input() in Python strips it)
174
+ return response.replace(/\n$/, '');
175
+ } catch (error) {
176
+ // If cancelled, throw an error like Python's KeyboardInterrupt
177
+ throw new Error('Input cancelled');
178
+ }
179
+ };
180
+
181
+ // Display helper for rich output
182
+ this.#ctx.display = (data, mimeType = 'text/plain') => {
183
+ // Store for retrieval
184
+ if (!this.#ctx.__displayQueue__) {
185
+ this.#ctx.__displayQueue__ = [];
186
+ }
187
+
188
+ let content;
189
+ if (typeof data === 'string') {
190
+ content = data;
191
+ } else if (data instanceof HTMLElement) {
192
+ content = data.outerHTML;
193
+ mimeType = 'text/html';
194
+ } else {
195
+ try {
196
+ content = JSON.stringify(data, null, 2);
197
+ mimeType = 'application/json';
198
+ } catch {
199
+ content = String(data);
200
+ }
201
+ }
202
+
203
+ this.#ctx.__displayQueue__.push({ data: { [mimeType]: content }, metadata: {} });
204
+ };
205
+
206
+ // Inject custom utilities
207
+ if (this.#options.utilities) {
208
+ for (const [key, value] of Object.entries(this.#options.utilities)) {
209
+ this.#ctx[key] = value;
210
+ }
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Set the stdin request handler
216
+ * @param {OnStdinRequestCallback | null} handler
217
+ */
218
+ setStdinHandler(handler) {
219
+ this.#onStdinRequest = handler;
220
+ }
221
+
222
+ /**
223
+ * Get the current stdin request handler
224
+ * @returns {OnStdinRequestCallback | null}
225
+ */
226
+ getStdinHandler() {
227
+ return this.#onStdinRequest;
228
+ }
229
+
230
+ /**
231
+ * Execute code in the iframe
232
+ * @param {string} code - Already transformed/wrapped code from executor
233
+ * @param {{ execId?: string }} [options] - Execution options
234
+ * @returns {Promise<RawExecutionResult>}
235
+ */
236
+ async execute(code, options = {}) {
237
+ this.#initialize();
238
+
239
+ if (!this.#ctx) {
240
+ throw new Error('Context not initialized');
241
+ }
242
+
243
+ // Set current execution ID for input() calls
244
+ this.#currentExecId = options.execId || '';
245
+
246
+ // Clear display queue
247
+ this.#ctx.__displayQueue__ = [];
248
+
249
+ // Clear console capture
250
+ this.#consoleCapture?.clear();
251
+
252
+ const startTime = performance.now();
253
+
254
+ try {
255
+ // Execute - code is already transformed/wrapped by the executor
256
+ const result = await this.#ctx.eval(code);
257
+ const duration = performance.now() - startTime;
258
+
259
+ // Get logs
260
+ const logs = this.#consoleCapture?.flush() || [];
261
+
262
+ return {
263
+ result,
264
+ logs,
265
+ duration,
266
+ };
267
+ } catch (error) {
268
+ const duration = performance.now() - startTime;
269
+ const logs = this.#consoleCapture?.flush() || [];
270
+
271
+ return {
272
+ result: undefined,
273
+ logs,
274
+ error: error instanceof Error ? error : new Error(String(error)),
275
+ duration,
276
+ };
277
+ } finally {
278
+ // Clear current exec ID
279
+ this.#currentExecId = '';
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Get all user-defined variables
285
+ * @returns {Record<string, *>}
286
+ */
287
+ getVariables() {
288
+ if (!this.#ctx) return {};
289
+
290
+ const vars = {};
291
+ for (const name of this.#trackedVars) {
292
+ try {
293
+ vars[name] = this.#ctx[name];
294
+ } catch {
295
+ // Skip inaccessible
296
+ }
297
+ }
298
+ return vars;
299
+ }
300
+
301
+ /**
302
+ * Get a specific variable
303
+ * @param {string} name
304
+ * @returns {*}
305
+ */
306
+ getVariable(name) {
307
+ if (!this.#ctx) return undefined;
308
+ return this.#ctx[name];
309
+ }
310
+
311
+ /**
312
+ * Check if variable exists
313
+ * @param {string} name
314
+ * @returns {boolean}
315
+ */
316
+ hasVariable(name) {
317
+ if (!this.#ctx) return false;
318
+ return name in this.#ctx;
319
+ }
320
+
321
+ /**
322
+ * Get the global object
323
+ * @returns {Window}
324
+ */
325
+ getGlobal() {
326
+ this.#initialize();
327
+ return /** @type {Window} */ (this.#ctx);
328
+ }
329
+
330
+ /**
331
+ * Track a declared variable
332
+ * @param {string} name
333
+ */
334
+ trackVariable(name) {
335
+ this.#trackedVars.add(name);
336
+ }
337
+
338
+ /**
339
+ * Get tracked variable names
340
+ * @returns {Set<string>}
341
+ */
342
+ getTrackedVariables() {
343
+ return this.#trackedVars;
344
+ }
345
+
346
+ /**
347
+ * Reset the context
348
+ */
349
+ reset() {
350
+ if (!this.#initialized) return;
351
+
352
+ // Destroy and reinitialize
353
+ this.destroy();
354
+ this.#initialized = false;
355
+ this.#trackedVars = new Set();
356
+ this.#initialize();
357
+ }
358
+
359
+ /**
360
+ * Destroy the context
361
+ */
362
+ destroy() {
363
+ this.#consoleCapture?.stop();
364
+ this.#consoleCapture = null;
365
+
366
+ if (this.#iframe) {
367
+ this.#iframe.parentElement?.removeChild(this.#iframe);
368
+ this.#iframe = null;
369
+ }
370
+
371
+ this.#ctx = null;
372
+ this.#initialized = false;
373
+ }
374
+
375
+ /**
376
+ * Check if this is main context
377
+ * @returns {boolean}
378
+ */
379
+ isMainContext() {
380
+ return false;
381
+ }
382
+
383
+ /**
384
+ * Get the iframe element
385
+ * @returns {HTMLIFrameElement | null}
386
+ */
387
+ getIframe() {
388
+ return this.#iframe;
389
+ }
390
+
391
+ /**
392
+ * Get display data queue
393
+ * @returns {Array<{data: Record<string, string>, metadata: Record<string, *>}>}
394
+ */
395
+ getDisplayQueue() {
396
+ return this.#ctx?.__displayQueue__ || [];
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Create an iframe context
402
+ * @param {IframeContextOptions} [options]
403
+ * @returns {IframeContext}
404
+ */
405
+ export function createIframeContext(options) {
406
+ return new IframeContext(options);
407
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Execution Contexts
3
+ *
4
+ * Different execution environments for JavaScript code.
5
+ * @module session/context
6
+ */
7
+
8
+ export { IframeContext, createIframeContext } from './iframe.js';
9
+ export { MainContext, createMainContext } from './main.js';
10
+
11
+ // Re-export interface types
12
+ export * from './interface.js';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Execution Context Interface
3
+ *
4
+ * Defines the interface for execution contexts (iframe, worker, main).
5
+ * @module session/context/interface
6
+ */
7
+
8
+ /**
9
+ * @typedef {Object} LogEntry
10
+ * @property {'log' | 'info' | 'warn' | 'error'} type
11
+ * @property {Array<*>} args
12
+ * @property {number} timestamp
13
+ */
14
+
15
+ /**
16
+ * @typedef {Object} RawExecutionResult
17
+ * @property {*} result - Return value
18
+ * @property {LogEntry[]} logs - Captured log entries
19
+ * @property {Error} [error] - Error if execution failed
20
+ * @property {number} duration - Duration in milliseconds
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} ExecutionContext
25
+ * @property {(code: string) => Promise<RawExecutionResult>} execute - Execute code
26
+ * @property {() => Record<string, *>} getVariables - Get all user-defined variables
27
+ * @property {(name: string) => *} getVariable - Get a specific variable
28
+ * @property {(name: string) => boolean} hasVariable - Check if variable exists
29
+ * @property {() => Window} getGlobal - Get the global object
30
+ * @property {(name: string) => void} trackVariable - Track a declared variable
31
+ * @property {() => Set<string>} getTrackedVariables - Get tracked variable names
32
+ * @property {() => void} reset - Clear all variables and state
33
+ * @property {() => void} destroy - Cleanup and release resources
34
+ * @property {() => boolean} isMainContext - Whether this is main window
35
+ * @property {() => HTMLIFrameElement | null} getIframe - Get iframe if applicable
36
+ */
37
+
38
+ export {};