@ttsc/playground 0.24.0 → 0.25.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.
@@ -1,75 +1,75 @@
1
- "use client";
2
-
3
- import Editor from "@monaco-editor/react";
4
- import { useEffect, useRef, useState } from "react";
5
-
6
- interface ResultViewerProps {
7
- language: "typescript" | "javascript" | "json";
8
- value: string;
9
- }
10
-
11
- /**
12
- * Read-only Monaco pane used to render the compiled / transformed output with a
13
- * copy button. Wraps `<Editor readOnly>` and adds the toast UI.
14
- */
15
- export function ResultViewer({ language, value }: ResultViewerProps) {
16
- const [copied, setCopied] = useState(false);
17
- const copiedTimer = useRef<number | null>(null);
18
-
19
- useEffect(
20
- () => () => {
21
- if (copiedTimer.current !== null)
22
- window.clearTimeout(copiedTimer.current);
23
- },
24
- [],
25
- );
26
-
27
- const onCopy = () => {
28
- void navigator.clipboard.writeText(value);
29
- setCopied(true);
30
- if (copiedTimer.current !== null) window.clearTimeout(copiedTimer.current);
31
- copiedTimer.current = window.setTimeout(() => {
32
- setCopied(false);
33
- copiedTimer.current = null;
34
- }, 1500);
35
- };
36
-
37
- return (
38
- <div className="relative h-full w-full">
39
- {value && (
40
- <button
41
- onClick={onCopy}
42
- className="absolute right-3 top-2 z-10 rounded-md border border-[#b9d5ee] bg-white/90 px-2 py-1 font-mono text-[10px] text-[#235a97] shadow-sm transition-colors hover:bg-[#eaf4ff]"
43
- >
44
- {copied ? "Copied ✓" : "Copy"}
45
- </button>
46
- )}
47
- <Editor
48
- height="100%"
49
- language={language}
50
- theme="vs"
51
- value={value}
52
- path={`output.${
53
- language === "typescript"
54
- ? "ts"
55
- : language === "javascript"
56
- ? "js"
57
- : "json"
58
- }`}
59
- options={{
60
- readOnly: true,
61
- tabSize: 2,
62
- minimap: { enabled: false },
63
- padding: { top: 12, bottom: 12 },
64
- fontSize: 13,
65
- fontFamily:
66
- "ui-monospace, SFMono-Regular, 'JetBrains Mono', 'Fira Code', Consolas, monospace",
67
- smoothScrolling: true,
68
- scrollBeyondLastLine: false,
69
- renderLineHighlight: "none",
70
- wordWrap: "on",
71
- }}
72
- />
73
- </div>
74
- );
75
- }
1
+ "use client";
2
+
3
+ import Editor from "@monaco-editor/react";
4
+ import { useEffect, useRef, useState } from "react";
5
+
6
+ interface ResultViewerProps {
7
+ language: "typescript" | "javascript" | "json";
8
+ value: string;
9
+ }
10
+
11
+ /**
12
+ * Read-only Monaco pane used to render the compiled / transformed output with a
13
+ * copy button. Wraps `<Editor readOnly>` and adds the toast UI.
14
+ */
15
+ export function ResultViewer({ language, value }: ResultViewerProps) {
16
+ const [copied, setCopied] = useState(false);
17
+ const copiedTimer = useRef<number | null>(null);
18
+
19
+ useEffect(
20
+ () => () => {
21
+ if (copiedTimer.current !== null)
22
+ window.clearTimeout(copiedTimer.current);
23
+ },
24
+ [],
25
+ );
26
+
27
+ const onCopy = () => {
28
+ void navigator.clipboard.writeText(value);
29
+ setCopied(true);
30
+ if (copiedTimer.current !== null) window.clearTimeout(copiedTimer.current);
31
+ copiedTimer.current = window.setTimeout(() => {
32
+ setCopied(false);
33
+ copiedTimer.current = null;
34
+ }, 1500);
35
+ };
36
+
37
+ return (
38
+ <div className="relative h-full w-full">
39
+ {value && (
40
+ <button
41
+ onClick={onCopy}
42
+ className="absolute right-3 top-2 z-10 rounded-md border border-[#b9d5ee] bg-white/90 px-2 py-1 font-mono text-[10px] text-[#235a97] shadow-sm transition-colors hover:bg-[#eaf4ff]"
43
+ >
44
+ {copied ? "Copied ✓" : "Copy"}
45
+ </button>
46
+ )}
47
+ <Editor
48
+ height="100%"
49
+ language={language}
50
+ theme="vs"
51
+ value={value}
52
+ path={`output.${
53
+ language === "typescript"
54
+ ? "ts"
55
+ : language === "javascript"
56
+ ? "js"
57
+ : "json"
58
+ }`}
59
+ options={{
60
+ readOnly: true,
61
+ tabSize: 2,
62
+ minimap: { enabled: false },
63
+ padding: { top: 12, bottom: 12 },
64
+ fontSize: 13,
65
+ fontFamily:
66
+ "ui-monospace, SFMono-Regular, 'JetBrains Mono', 'Fira Code', Consolas, monospace",
67
+ smoothScrolling: true,
68
+ scrollBeyondLastLine: false,
69
+ renderLineHighlight: "none",
70
+ wordWrap: "on",
71
+ }}
72
+ />
73
+ </div>
74
+ );
75
+ }
@@ -1,95 +1,95 @@
1
- "use client";
2
-
3
- import Editor, { type Monaco } from "@monaco-editor/react";
4
- import { useCallback, useEffect, useMemo, useRef } from "react";
5
-
6
- import { DEFAULT_PLAYGROUND_COMPILER_OPTIONS } from "../compiler/DEFAULT_PLAYGROUND_COMPILER_OPTIONS";
7
- import type { ISourceEditorProps } from "../structures/ISourceEditorProps";
8
-
9
- export function SourceEditor({
10
- value,
11
- onChange,
12
- extraLibs,
13
- path = "file:///src/playground.ts",
14
- }: ISourceEditorProps) {
15
- const monacoRef = useRef<Monaco | null>(null);
16
- const libDisposables = useRef<{ dispose(): void }[]>([]);
17
- const allExtraLibs = useMemo(
18
- () => (extraLibs ? Object.entries(extraLibs) : []),
19
- [extraLibs],
20
- );
21
-
22
- const installExtraLibs = useCallback(
23
- (monaco: Monaco) => {
24
- for (const disposable of libDisposables.current) disposable.dispose();
25
- libDisposables.current = [];
26
- const tsd = monaco.languages.typescript.typescriptDefaults;
27
- for (const [file, content] of allExtraLibs) {
28
- libDisposables.current.push(tsd.addExtraLib(content, file));
29
- }
30
- },
31
- [allExtraLibs],
32
- );
33
-
34
- useEffect(() => {
35
- if (monacoRef.current) installExtraLibs(monacoRef.current);
36
- return () => {
37
- for (const disposable of libDisposables.current) disposable.dispose();
38
- libDisposables.current = [];
39
- };
40
- }, [installExtraLibs]);
41
-
42
- const handleMount = (_editor: unknown, monaco: Monaco) => {
43
- monacoRef.current = monaco;
44
- const tsd = monaco.languages.typescript.typescriptDefaults;
45
- tsd.setCompilerOptions({
46
- target: monaco.languages.typescript.ScriptTarget.ESNext,
47
- module: monaco.languages.typescript.ModuleKind.ESNext,
48
- moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
49
- esModuleInterop: DEFAULT_PLAYGROUND_COMPILER_OPTIONS.esModuleInterop,
50
- strict: DEFAULT_PLAYGROUND_COMPILER_OPTIONS.strict,
51
- experimentalDecorators:
52
- DEFAULT_PLAYGROUND_COMPILER_OPTIONS.experimentalDecorators,
53
- allowNonTsExtensions: true,
54
- // External .d.ts packs (typia, etc.) routinely reference transitive types
55
- // Monaco cannot reach (deep paths in JSDoc, optional peers). skipLibCheck
56
- // keeps the editor lean by not type-checking the lib pack.
57
- skipLibCheck: true,
58
- });
59
- tsd.setDiagnosticsOptions({
60
- noSemanticValidation: false,
61
- noSyntaxValidation: false,
62
- diagnosticCodesToIgnore: [
63
- // 2307: Cannot find module — JSDoc-only stubbed deps still occasionally
64
- // leak through Monaco's module resolution.
65
- 2307,
66
- ],
67
- });
68
- installExtraLibs(monaco);
69
- };
70
-
71
- return (
72
- <Editor
73
- height="100%"
74
- defaultLanguage="typescript"
75
- theme="vs"
76
- value={value}
77
- onChange={(v) => onChange(v ?? "")}
78
- onMount={handleMount}
79
- path={path}
80
- options={{
81
- tabSize: 2,
82
- minimap: { enabled: false },
83
- padding: { top: 12, bottom: 12 },
84
- fontSize: 13,
85
- fontFamily:
86
- "ui-monospace, SFMono-Regular, 'JetBrains Mono', 'Fira Code', Consolas, monospace",
87
- smoothScrolling: true,
88
- cursorBlinking: "smooth",
89
- scrollBeyondLastLine: false,
90
- renderLineHighlight: "line",
91
- wordWrap: "on",
92
- }}
93
- />
94
- );
95
- }
1
+ "use client";
2
+
3
+ import Editor, { type Monaco } from "@monaco-editor/react";
4
+ import { useCallback, useEffect, useMemo, useRef } from "react";
5
+
6
+ import { DEFAULT_PLAYGROUND_COMPILER_OPTIONS } from "../compiler/DEFAULT_PLAYGROUND_COMPILER_OPTIONS";
7
+ import type { ISourceEditorProps } from "../structures/ISourceEditorProps";
8
+
9
+ export function SourceEditor({
10
+ value,
11
+ onChange,
12
+ extraLibs,
13
+ path = "file:///src/playground.ts",
14
+ }: ISourceEditorProps) {
15
+ const monacoRef = useRef<Monaco | null>(null);
16
+ const libDisposables = useRef<{ dispose(): void }[]>([]);
17
+ const allExtraLibs = useMemo(
18
+ () => (extraLibs ? Object.entries(extraLibs) : []),
19
+ [extraLibs],
20
+ );
21
+
22
+ const installExtraLibs = useCallback(
23
+ (monaco: Monaco) => {
24
+ for (const disposable of libDisposables.current) disposable.dispose();
25
+ libDisposables.current = [];
26
+ const tsd = monaco.languages.typescript.typescriptDefaults;
27
+ for (const [file, content] of allExtraLibs) {
28
+ libDisposables.current.push(tsd.addExtraLib(content, file));
29
+ }
30
+ },
31
+ [allExtraLibs],
32
+ );
33
+
34
+ useEffect(() => {
35
+ if (monacoRef.current) installExtraLibs(monacoRef.current);
36
+ return () => {
37
+ for (const disposable of libDisposables.current) disposable.dispose();
38
+ libDisposables.current = [];
39
+ };
40
+ }, [installExtraLibs]);
41
+
42
+ const handleMount = (_editor: unknown, monaco: Monaco) => {
43
+ monacoRef.current = monaco;
44
+ const tsd = monaco.languages.typescript.typescriptDefaults;
45
+ tsd.setCompilerOptions({
46
+ target: monaco.languages.typescript.ScriptTarget.ESNext,
47
+ module: monaco.languages.typescript.ModuleKind.ESNext,
48
+ moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
49
+ esModuleInterop: DEFAULT_PLAYGROUND_COMPILER_OPTIONS.esModuleInterop,
50
+ strict: DEFAULT_PLAYGROUND_COMPILER_OPTIONS.strict,
51
+ experimentalDecorators:
52
+ DEFAULT_PLAYGROUND_COMPILER_OPTIONS.experimentalDecorators,
53
+ allowNonTsExtensions: true,
54
+ // External .d.ts packs (typia, etc.) routinely reference transitive types
55
+ // Monaco cannot reach (deep paths in JSDoc, optional peers). skipLibCheck
56
+ // keeps the editor lean by not type-checking the lib pack.
57
+ skipLibCheck: true,
58
+ });
59
+ tsd.setDiagnosticsOptions({
60
+ noSemanticValidation: false,
61
+ noSyntaxValidation: false,
62
+ diagnosticCodesToIgnore: [
63
+ // 2307: Cannot find module — JSDoc-only stubbed deps still occasionally
64
+ // leak through Monaco's module resolution.
65
+ 2307,
66
+ ],
67
+ });
68
+ installExtraLibs(monaco);
69
+ };
70
+
71
+ return (
72
+ <Editor
73
+ height="100%"
74
+ defaultLanguage="typescript"
75
+ theme="vs"
76
+ value={value}
77
+ onChange={(v) => onChange(v ?? "")}
78
+ onMount={handleMount}
79
+ path={path}
80
+ options={{
81
+ tabSize: 2,
82
+ minimap: { enabled: false },
83
+ padding: { top: 12, bottom: 12 },
84
+ fontSize: 13,
85
+ fontFamily:
86
+ "ui-monospace, SFMono-Regular, 'JetBrains Mono', 'Fira Code', Consolas, monospace",
87
+ smoothScrolling: true,
88
+ cursorBlinking: "smooth",
89
+ scrollBeyondLastLine: false,
90
+ renderLineHighlight: "line",
91
+ wordWrap: "on",
92
+ }}
93
+ />
94
+ );
95
+ }
@@ -13,9 +13,8 @@ interface RuntimePackEntry {
13
13
  }
14
14
 
15
15
  interface RuntimePackCancellationReason {
16
- kind: "abort" | "timeout";
16
+ kind: "abort";
17
17
  reason?: unknown;
18
- timeoutMs?: number;
19
18
  }
20
19
 
21
20
  interface RuntimePackCancellation {
@@ -23,25 +22,23 @@ interface RuntimePackCancellation {
23
22
  dispose: () => void;
24
23
  }
25
24
 
26
- export const DEFAULT_RUNTIME_PACK_TIMEOUT_MS = 30_000;
27
-
28
25
  const packCache = new Map<string, RuntimePackEntry>();
29
26
 
30
27
  /**
31
28
  * Fetches the prebuilt runtime pack once per URL.
32
29
  *
33
- * Concurrent callers share one load. A caller abort or deadline cancels that
34
- * shared attempt; rejection removes it from the cache so the next call retries
35
- * from scratch. Successful packs remain cached.
30
+ * Concurrent callers share one load. A caller abort cancels that shared
31
+ * attempt; rejection removes it from the cache so the next call retries from
32
+ * scratch. Successful packs remain cached. Nothing else ends the load: how long
33
+ * a fetch takes belongs to the network, not to a number chosen here.
36
34
  */
37
35
  export function loadTypiaRuntimePack(
38
36
  url: string,
39
37
  options: ILoadTypiaRuntimePackOptions = {},
40
38
  ): Promise<Record<string, string>> {
41
- const timeoutMs = resolveRuntimePackTimeout(options.timeoutMs);
42
39
  const cached = packCache.get(url);
43
40
  if (cached) {
44
- attachRuntimePackCancellation(cached, options.signal, timeoutMs);
41
+ attachRuntimePackCancellation(cached, options.signal);
45
42
  return cached.promise;
46
43
  }
47
44
 
@@ -80,23 +77,13 @@ export function loadTypiaRuntimePack(
80
77
 
81
78
  entry = { controller, promise };
82
79
  packCache.set(url, entry);
83
- attachRuntimePackCancellation(entry, options.signal, timeoutMs);
80
+ attachRuntimePackCancellation(entry, options.signal);
84
81
  return promise;
85
82
  }
86
83
 
87
- function resolveRuntimePackTimeout(timeoutMs: number | undefined): number {
88
- const value = timeoutMs ?? DEFAULT_RUNTIME_PACK_TIMEOUT_MS;
89
- if (!Number.isSafeInteger(value) || value <= 0 || value > 2_147_483_647)
90
- throw new RangeError(
91
- "loadTypiaRuntimePack: timeoutMs must be a positive integer no greater than 2147483647.",
92
- );
93
- return value;
94
- }
95
-
96
84
  function attachRuntimePackCancellation(
97
85
  entry: RuntimePackEntry,
98
86
  callerSignal: AbortSignal | undefined,
99
- timeoutMs: number,
100
87
  ): void {
101
88
  const abortFromCaller = (): void => {
102
89
  if (!entry.controller.signal.aborted)
@@ -108,15 +95,7 @@ function attachRuntimePackCancellation(
108
95
  if (callerSignal?.aborted) abortFromCaller();
109
96
  else callerSignal?.addEventListener("abort", abortFromCaller, { once: true });
110
97
 
111
- const timer = setTimeout(() => {
112
- if (!entry.controller.signal.aborted)
113
- entry.controller.abort({
114
- kind: "timeout",
115
- timeoutMs,
116
- } satisfies RuntimePackCancellationReason);
117
- }, timeoutMs);
118
98
  const cleanup = (): void => {
119
- clearTimeout(timer);
120
99
  callerSignal?.removeEventListener("abort", abortFromCaller);
121
100
  };
122
101
  void entry.promise.then(cleanup, cleanup);
@@ -160,11 +139,6 @@ function runtimePackCancellationError(
160
139
  phase: string,
161
140
  ): Error {
162
141
  const reason = signal.reason as RuntimePackCancellationReason | undefined;
163
- if (reason?.kind === "timeout")
164
- return new Error(
165
- `loadTypiaRuntimePack: timed out after ${reason.timeoutMs}ms while ${phase}.`,
166
- );
167
-
168
142
  const error = new Error(`loadTypiaRuntimePack: aborted while ${phase}.`);
169
143
  const cause = reason?.kind === "abort" ? reason.reason : signal.reason;
170
144
  if (cause !== undefined) (error as Error & { cause?: unknown }).cause = cause;
@@ -12,8 +12,6 @@ export interface IInstallTypiaSourcePackOptions {
12
12
  mountRoot?: string;
13
13
  /** Cancel the shared in-flight load. */
14
14
  signal?: AbortSignal;
15
- /** Maximum fetch and JSON-read time. Defaults to 30 seconds. */
16
- timeoutMs?: number;
17
15
  /**
18
16
  * Optional fetcher. Defaults to `globalThis.fetch`. Override for tests or for
19
17
  * sites that want their own caching strategy.
@@ -1,7 +1,5 @@
1
- /** Cancellation and deadline policy for `loadTypiaRuntimePack`. */
1
+ /** Cancellation policy for `loadTypiaRuntimePack`. */
2
2
  export interface ILoadTypiaRuntimePackOptions {
3
3
  /** Cancel the shared in-flight load. */
4
4
  signal?: AbortSignal;
5
- /** Maximum fetch and JSON-read time. Defaults to 30 seconds. */
6
- timeoutMs?: number;
7
5
  }