@ttsc/playground 0.14.0-dev.20260528.1

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 (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +230 -0
  3. package/lib/src/compiler/DEFAULT_ENTRY_FILE.d.ts +2 -0
  4. package/lib/src/compiler/DEFAULT_ENTRY_FILE.js +6 -0
  5. package/lib/src/compiler/DEFAULT_ENTRY_FILE.js.map +1 -0
  6. package/lib/src/compiler/DEFAULT_LINT_PLUGIN_NAME.d.ts +5 -0
  7. package/lib/src/compiler/DEFAULT_LINT_PLUGIN_NAME.js +9 -0
  8. package/lib/src/compiler/DEFAULT_LINT_PLUGIN_NAME.js.map +1 -0
  9. package/lib/src/compiler/DEFAULT_PLAYGROUND_COMPILER_OPTIONS.d.ts +19 -0
  10. package/lib/src/compiler/DEFAULT_PLAYGROUND_COMPILER_OPTIONS.js +23 -0
  11. package/lib/src/compiler/DEFAULT_PLAYGROUND_COMPILER_OPTIONS.js.map +1 -0
  12. package/lib/src/compiler/DEFAULT_TSCONFIG_PATH.d.ts +2 -0
  13. package/lib/src/compiler/DEFAULT_TSCONFIG_PATH.js +6 -0
  14. package/lib/src/compiler/DEFAULT_TSCONFIG_PATH.js.map +1 -0
  15. package/lib/src/compiler/DEFAULT_TYPIA_PLUGIN_NAME.d.ts +6 -0
  16. package/lib/src/compiler/DEFAULT_TYPIA_PLUGIN_NAME.js +10 -0
  17. package/lib/src/compiler/DEFAULT_TYPIA_PLUGIN_NAME.js.map +1 -0
  18. package/lib/src/compiler/DEFAULT_WORK_DIR.d.ts +2 -0
  19. package/lib/src/compiler/DEFAULT_WORK_DIR.js +6 -0
  20. package/lib/src/compiler/DEFAULT_WORK_DIR.js.map +1 -0
  21. package/lib/src/compiler/buildTsconfigJSON.d.ts +9 -0
  22. package/lib/src/compiler/buildTsconfigJSON.js +24 -0
  23. package/lib/src/compiler/buildTsconfigJSON.js.map +1 -0
  24. package/lib/src/compiler/createTypiaSourcePackMount.d.ts +11 -0
  25. package/lib/src/compiler/createTypiaSourcePackMount.js +29 -0
  26. package/lib/src/compiler/createTypiaSourcePackMount.js.map +1 -0
  27. package/lib/src/compiler/createWorkerCompiler.d.ts +25 -0
  28. package/lib/src/compiler/createWorkerCompiler.js +227 -0
  29. package/lib/src/compiler/createWorkerCompiler.js.map +1 -0
  30. package/lib/src/compiler/installDependenciesIntoMemFS.d.ts +12 -0
  31. package/lib/src/compiler/installDependenciesIntoMemFS.js +25 -0
  32. package/lib/src/compiler/installDependenciesIntoMemFS.js.map +1 -0
  33. package/lib/src/compiler/installTypiaSourcePack.d.ts +11 -0
  34. package/lib/src/compiler/installTypiaSourcePack.js +21 -0
  35. package/lib/src/compiler/installTypiaSourcePack.js.map +1 -0
  36. package/lib/src/compiler/internal/joinUnder.d.ts +2 -0
  37. package/lib/src/compiler/internal/joinUnder.js +10 -0
  38. package/lib/src/compiler/internal/joinUnder.js.map +1 -0
  39. package/lib/src/compiler/internal/parseLintDiagnostics.d.ts +6 -0
  40. package/lib/src/compiler/internal/parseLintDiagnostics.js +57 -0
  41. package/lib/src/compiler/internal/parseLintDiagnostics.js.map +1 -0
  42. package/lib/src/compiler/internal/safeParseTypiaTransform.d.ts +11 -0
  43. package/lib/src/compiler/internal/safeParseTypiaTransform.js +21 -0
  44. package/lib/src/compiler/internal/safeParseTypiaTransform.js.map +1 -0
  45. package/lib/src/compiler/lineColumnOf.d.ts +5 -0
  46. package/lib/src/compiler/lineColumnOf.js +15 -0
  47. package/lib/src/compiler/lineColumnOf.js.map +1 -0
  48. package/lib/src/compiler/loadTypiaSourcePack.d.ts +8 -0
  49. package/lib/src/compiler/loadTypiaSourcePack.js +33 -0
  50. package/lib/src/compiler/loadTypiaSourcePack.js.map +1 -0
  51. package/lib/src/compiler/mapDiagnostic.d.ts +9 -0
  52. package/lib/src/compiler/mapDiagnostic.js +22 -0
  53. package/lib/src/compiler/mapDiagnostic.js.map +1 -0
  54. package/lib/src/compiler/normalizeError.d.ts +2 -0
  55. package/lib/src/compiler/normalizeError.js +14 -0
  56. package/lib/src/compiler/normalizeError.js.map +1 -0
  57. package/lib/src/compiler/normalizeNodeModulePath.d.ts +8 -0
  58. package/lib/src/compiler/normalizeNodeModulePath.js +19 -0
  59. package/lib/src/compiler/normalizeNodeModulePath.js.map +1 -0
  60. package/lib/src/compiler/pickEmittedJS.d.ts +6 -0
  61. package/lib/src/compiler/pickEmittedJS.js +26 -0
  62. package/lib/src/compiler/pickEmittedJS.js.map +1 -0
  63. package/lib/src/index.d.ts +47 -0
  64. package/lib/src/index.js +104 -0
  65. package/lib/src/index.js.map +1 -0
  66. package/lib/src/npm/BUILT_IN_PLAYGROUND_PACKAGES.d.ts +6 -0
  67. package/lib/src/npm/BUILT_IN_PLAYGROUND_PACKAGES.js +15 -0
  68. package/lib/src/npm/BUILT_IN_PLAYGROUND_PACKAGES.js.map +1 -0
  69. package/lib/src/npm/collectExternalPackageNames.d.ts +5 -0
  70. package/lib/src/npm/collectExternalPackageNames.js +34 -0
  71. package/lib/src/npm/collectExternalPackageNames.js.map +1 -0
  72. package/lib/src/npm/installPlaygroundDependencies.d.ts +13 -0
  73. package/lib/src/npm/installPlaygroundDependencies.js +121 -0
  74. package/lib/src/npm/installPlaygroundDependencies.js.map +1 -0
  75. package/lib/src/npm/internal/npmRegistry.d.ts +57 -0
  76. package/lib/src/npm/internal/npmRegistry.js +216 -0
  77. package/lib/src/npm/internal/npmRegistry.js.map +1 -0
  78. package/lib/src/npm/packageNameFromSpecifier.d.ts +7 -0
  79. package/lib/src/npm/packageNameFromSpecifier.js +66 -0
  80. package/lib/src/npm/packageNameFromSpecifier.js.map +1 -0
  81. package/lib/src/react/ConsoleViewer.d.ts +7 -0
  82. package/lib/src/react/ConsoleViewer.js +57 -0
  83. package/lib/src/react/ConsoleViewer.js.map +1 -0
  84. package/lib/src/react/DEFAULT_OPTION_TOGGLES.d.ts +3 -0
  85. package/lib/src/react/DEFAULT_OPTION_TOGGLES.js +17 -0
  86. package/lib/src/react/DEFAULT_OPTION_TOGGLES.js.map +1 -0
  87. package/lib/src/react/DependencyProgressModal.d.ts +7 -0
  88. package/lib/src/react/DependencyProgressModal.js +16 -0
  89. package/lib/src/react/DependencyProgressModal.js.map +1 -0
  90. package/lib/src/react/DiagnosticsPanel.d.ts +4 -0
  91. package/lib/src/react/DiagnosticsPanel.js +15 -0
  92. package/lib/src/react/DiagnosticsPanel.js.map +1 -0
  93. package/lib/src/react/ExamplePicker.d.ts +9 -0
  94. package/lib/src/react/ExamplePicker.js +43 -0
  95. package/lib/src/react/ExamplePicker.js.map +1 -0
  96. package/lib/src/react/LintPane.d.ts +9 -0
  97. package/lib/src/react/LintPane.js +17 -0
  98. package/lib/src/react/LintPane.js.map +1 -0
  99. package/lib/src/react/OptionsPanel.d.ts +13 -0
  100. package/lib/src/react/OptionsPanel.js +43 -0
  101. package/lib/src/react/OptionsPanel.js.map +1 -0
  102. package/lib/src/react/PlaygroundShell.d.ts +2 -0
  103. package/lib/src/react/PlaygroundShell.js +609 -0
  104. package/lib/src/react/PlaygroundShell.js.map +1 -0
  105. package/lib/src/react/ResultViewer.d.ts +10 -0
  106. package/lib/src/react/ResultViewer.js +49 -0
  107. package/lib/src/react/ResultViewer.js.map +1 -0
  108. package/lib/src/react/SourceEditor.d.ts +2 -0
  109. package/lib/src/react/SourceEditor.js +74 -0
  110. package/lib/src/react/SourceEditor.js.map +1 -0
  111. package/lib/src/react/createCompilerClient.d.ts +14 -0
  112. package/lib/src/react/createCompilerClient.js +77 -0
  113. package/lib/src/react/createCompilerClient.js.map +1 -0
  114. package/lib/src/sandbox/createSandboxRequire.d.ts +11 -0
  115. package/lib/src/sandbox/createSandboxRequire.js +211 -0
  116. package/lib/src/sandbox/createSandboxRequire.js.map +1 -0
  117. package/lib/src/sandbox/loadTypiaRuntimePack.d.ts +7 -0
  118. package/lib/src/sandbox/loadTypiaRuntimePack.js +35 -0
  119. package/lib/src/sandbox/loadTypiaRuntimePack.js.map +1 -0
  120. package/lib/src/structures/IBuildTsconfigOptions.d.ts +19 -0
  121. package/lib/src/structures/IBuildTsconfigOptions.js +3 -0
  122. package/lib/src/structures/IBuildTsconfigOptions.js.map +1 -0
  123. package/lib/src/structures/ICompilerService.d.ts +83 -0
  124. package/lib/src/structures/ICompilerService.js +3 -0
  125. package/lib/src/structures/ICompilerService.js.map +1 -0
  126. package/lib/src/structures/IConsoleMessage.d.ts +10 -0
  127. package/lib/src/structures/IConsoleMessage.js +3 -0
  128. package/lib/src/structures/IConsoleMessage.js.map +1 -0
  129. package/lib/src/structures/ICreateCompilerClientOptions.d.ts +13 -0
  130. package/lib/src/structures/ICreateCompilerClientOptions.js +3 -0
  131. package/lib/src/structures/ICreateCompilerClientOptions.js.map +1 -0
  132. package/lib/src/structures/ICreateWorkerCompilerOptions.d.ts +35 -0
  133. package/lib/src/structures/ICreateWorkerCompilerOptions.js +3 -0
  134. package/lib/src/structures/ICreateWorkerCompilerOptions.js.map +1 -0
  135. package/lib/src/structures/IInstallTypiaSourcePackOptions.d.ts +15 -0
  136. package/lib/src/structures/IInstallTypiaSourcePackOptions.js +3 -0
  137. package/lib/src/structures/IInstallTypiaSourcePackOptions.js.map +1 -0
  138. package/lib/src/structures/ILintPluginConfig.d.ts +5 -0
  139. package/lib/src/structures/ILintPluginConfig.js +3 -0
  140. package/lib/src/structures/ILintPluginConfig.js.map +1 -0
  141. package/lib/src/structures/IOptionToggle.d.ts +12 -0
  142. package/lib/src/structures/IOptionToggle.js +3 -0
  143. package/lib/src/structures/IOptionToggle.js.map +1 -0
  144. package/lib/src/structures/IPlaygroundDependencyInstallOptions.d.ts +16 -0
  145. package/lib/src/structures/IPlaygroundDependencyInstallOptions.js +3 -0
  146. package/lib/src/structures/IPlaygroundDependencyInstallOptions.js.map +1 -0
  147. package/lib/src/structures/IPlaygroundDependencyInstallResult.d.ts +20 -0
  148. package/lib/src/structures/IPlaygroundDependencyInstallResult.js +3 -0
  149. package/lib/src/structures/IPlaygroundDependencyInstallResult.js.map +1 -0
  150. package/lib/src/structures/IPlaygroundDependencyPackage.d.ts +8 -0
  151. package/lib/src/structures/IPlaygroundDependencyPackage.js +3 -0
  152. package/lib/src/structures/IPlaygroundDependencyPackage.js.map +1 -0
  153. package/lib/src/structures/IPlaygroundDependencyProgress.d.ts +10 -0
  154. package/lib/src/structures/IPlaygroundDependencyProgress.js +3 -0
  155. package/lib/src/structures/IPlaygroundDependencyProgress.js.map +1 -0
  156. package/lib/src/structures/IPlaygroundDependencyProgressPhase.d.ts +2 -0
  157. package/lib/src/structures/IPlaygroundDependencyProgressPhase.js +3 -0
  158. package/lib/src/structures/IPlaygroundDependencyProgressPhase.js.map +1 -0
  159. package/lib/src/structures/IPlaygroundExample.d.ts +16 -0
  160. package/lib/src/structures/IPlaygroundExample.js +3 -0
  161. package/lib/src/structures/IPlaygroundExample.js.map +1 -0
  162. package/lib/src/structures/IPlaygroundShellProps.d.ts +64 -0
  163. package/lib/src/structures/IPlaygroundShellProps.js +3 -0
  164. package/lib/src/structures/IPlaygroundShellProps.js.map +1 -0
  165. package/lib/src/structures/ISourceEditorProps.d.ts +18 -0
  166. package/lib/src/structures/ISourceEditorProps.js +3 -0
  167. package/lib/src/structures/ISourceEditorProps.js.map +1 -0
  168. package/lib/src/structures/ITransformOptions.d.ts +15 -0
  169. package/lib/src/structures/ITransformOptions.js +3 -0
  170. package/lib/src/structures/ITransformOptions.js.map +1 -0
  171. package/lib/src/structures/ITypiaPluginConfig.d.ts +19 -0
  172. package/lib/src/structures/ITypiaPluginConfig.js +3 -0
  173. package/lib/src/structures/ITypiaPluginConfig.js.map +1 -0
  174. package/lib/src/structures/index.d.ts +18 -0
  175. package/lib/src/structures/index.js +35 -0
  176. package/lib/src/structures/index.js.map +1 -0
  177. package/package.json +66 -0
  178. package/src/compiler/DEFAULT_ENTRY_FILE.ts +2 -0
  179. package/src/compiler/DEFAULT_LINT_PLUGIN_NAME.ts +5 -0
  180. package/src/compiler/DEFAULT_PLAYGROUND_COMPILER_OPTIONS.ts +19 -0
  181. package/src/compiler/DEFAULT_TSCONFIG_PATH.ts +2 -0
  182. package/src/compiler/DEFAULT_TYPIA_PLUGIN_NAME.ts +6 -0
  183. package/src/compiler/DEFAULT_WORK_DIR.ts +2 -0
  184. package/src/compiler/buildTsconfigJSON.ts +22 -0
  185. package/src/compiler/createTypiaSourcePackMount.ts +32 -0
  186. package/src/compiler/createWorkerCompiler.ts +274 -0
  187. package/src/compiler/installDependenciesIntoMemFS.ts +28 -0
  188. package/src/compiler/installTypiaSourcePack.ts +24 -0
  189. package/src/compiler/internal/joinUnder.ts +5 -0
  190. package/src/compiler/internal/parseLintDiagnostics.ts +61 -0
  191. package/src/compiler/internal/safeParseTypiaTransform.ts +24 -0
  192. package/src/compiler/lineColumnOf.ts +14 -0
  193. package/src/compiler/loadTypiaSourcePack.ts +37 -0
  194. package/src/compiler/mapDiagnostic.ts +26 -0
  195. package/src/compiler/normalizeError.ts +12 -0
  196. package/src/compiler/normalizeNodeModulePath.ts +13 -0
  197. package/src/compiler/pickEmittedJS.ts +23 -0
  198. package/src/index.ts +57 -0
  199. package/src/npm/BUILT_IN_PLAYGROUND_PACKAGES.ts +11 -0
  200. package/src/npm/collectExternalPackageNames.ts +34 -0
  201. package/src/npm/installPlaygroundDependencies.ts +163 -0
  202. package/src/npm/internal/npmRegistry.ts +288 -0
  203. package/src/npm/packageNameFromSpecifier.ts +61 -0
  204. package/src/react/ConsoleViewer.tsx +121 -0
  205. package/src/react/DEFAULT_OPTION_TOGGLES.ts +15 -0
  206. package/src/react/DependencyProgressModal.tsx +73 -0
  207. package/src/react/DiagnosticsPanel.tsx +74 -0
  208. package/src/react/ExamplePicker.tsx +93 -0
  209. package/src/react/LintPane.tsx +58 -0
  210. package/src/react/OptionsPanel.tsx +132 -0
  211. package/src/react/PlaygroundShell.tsx +864 -0
  212. package/src/react/ResultViewer.tsx +75 -0
  213. package/src/react/SourceEditor.tsx +96 -0
  214. package/src/react/createCompilerClient.ts +79 -0
  215. package/src/sandbox/createSandboxRequire.ts +275 -0
  216. package/src/sandbox/loadTypiaRuntimePack.ts +36 -0
  217. package/src/structures/IBuildTsconfigOptions.ts +19 -0
  218. package/src/structures/ICompilerService.ts +95 -0
  219. package/src/structures/IConsoleMessage.ts +10 -0
  220. package/src/structures/ICreateCompilerClientOptions.ts +13 -0
  221. package/src/structures/ICreateWorkerCompilerOptions.ts +40 -0
  222. package/src/structures/IInstallTypiaSourcePackOptions.ts +15 -0
  223. package/src/structures/ILintPluginConfig.ts +5 -0
  224. package/src/structures/IOptionToggle.ts +12 -0
  225. package/src/structures/IPlaygroundDependencyInstallOptions.ts +17 -0
  226. package/src/structures/IPlaygroundDependencyInstallResult.ts +21 -0
  227. package/src/structures/IPlaygroundDependencyPackage.ts +8 -0
  228. package/src/structures/IPlaygroundDependencyProgress.ts +11 -0
  229. package/src/structures/IPlaygroundDependencyProgressPhase.ts +9 -0
  230. package/src/structures/IPlaygroundExample.ts +16 -0
  231. package/src/structures/IPlaygroundShellProps.ts +77 -0
  232. package/src/structures/ISourceEditorProps.ts +18 -0
  233. package/src/structures/ITransformOptions.ts +15 -0
  234. package/src/structures/ITypiaPluginConfig.ts +20 -0
  235. package/src/structures/index.ts +18 -0
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@ttsc/playground",
3
+ "version": "0.14.0-dev.20260528.1",
4
+ "description": "Reusable React + Web Worker scaffolding for in-browser ttsc playgrounds built on @ttsc/wasm.",
5
+ "main": "lib/src/index.js",
6
+ "types": "lib/src/index.d.ts",
7
+ "files": [
8
+ "lib",
9
+ "src",
10
+ "README.md"
11
+ ],
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./lib/src/index.d.ts",
18
+ "default": "./lib/src/index.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/samchon/ttsc"
25
+ },
26
+ "author": "Jeongho Nam",
27
+ "license": "MIT",
28
+ "bugs": {
29
+ "url": "https://github.com/samchon/ttsc/issues"
30
+ },
31
+ "homepage": "https://ttsc.dev",
32
+ "dependencies": {
33
+ "lz-string": "^1.5.0"
34
+ },
35
+ "peerDependencies": {
36
+ "@monaco-editor/react": "^4.6.0",
37
+ "monaco-editor": "^0.52.0",
38
+ "react": "^18.0.0",
39
+ "react-dom": "^18.0.0",
40
+ "tgrid": "^1.0.3",
41
+ "@ttsc/wasm": "^0.14.0-dev.20260528.1"
42
+ },
43
+ "devDependencies": {
44
+ "@monaco-editor/react": "^4.6.0",
45
+ "@types/node": "^25.3.0",
46
+ "@types/react": "^18.3.1",
47
+ "@typescript/native-preview": "7.0.0-dev.20260527.2",
48
+ "monaco-editor": "^0.52.0",
49
+ "react": "^18.3.1",
50
+ "react-dom": "^18.3.1",
51
+ "rimraf": "^6.1.2",
52
+ "tgrid": "^1.0.3",
53
+ "@ttsc/wasm": "^0.14.0-dev.20260528.1"
54
+ },
55
+ "keywords": [
56
+ "ttsc",
57
+ "playground",
58
+ "typescript",
59
+ "browser",
60
+ "wasm",
61
+ "react"
62
+ ],
63
+ "scripts": {
64
+ "build": "rimraf lib && tsgo -p ."
65
+ }
66
+ }
@@ -0,0 +1,2 @@
1
+ /** Default entry source file inside the work dir. */
2
+ export const DEFAULT_ENTRY_FILE = "src/playground.ts";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Default plugin id for the `@ttsc/lint` check verb. Override via the
3
+ * `lintPlugin.name` option of {@link createWorkerCompiler}.
4
+ */
5
+ export const DEFAULT_LINT_PLUGIN_NAME = "@ttsc/lint";
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The compiler options every playground tsconfig opts into by default. Sites
3
+ * pass these straight to `buildTsconfigJSON`; consumer code that needs a tweak
4
+ * spreads the constant and overrides individual fields.
5
+ *
6
+ * `target` and `module` are TypeScript's numeric enum values (99 = ESNext,
7
+ * 199 = NodeNext, …). They are spelled numerically here because the wasm
8
+ * doesn't run the TS configuration parser through a string→enum step before
9
+ * Go reads the JSON; passing the canonical numbers avoids version-drift.
10
+ */
11
+ export const DEFAULT_PLAYGROUND_COMPILER_OPTIONS = {
12
+ target: 99,
13
+ esModuleInterop: true,
14
+ forceConsistentCasingInFileNames: true,
15
+ moduleResolution: 100,
16
+ strict: true,
17
+ skipLibCheck: true,
18
+ experimentalDecorators: true,
19
+ } as const;
@@ -0,0 +1,2 @@
1
+ /** Default tsconfig path inside the work dir. */
2
+ export const DEFAULT_TSCONFIG_PATH = "tsconfig.json";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Default plugin id for the typia transformer. Sites that wire a renamed
3
+ * typia plugin override via the `typiaPlugin.name` option of
4
+ * {@link createWorkerCompiler}.
5
+ */
6
+ export const DEFAULT_TYPIA_PLUGIN_NAME = "typia";
@@ -0,0 +1,2 @@
1
+ /** Default in-MemFS project root mounted by {@link createWorkerCompiler}. */
2
+ export const DEFAULT_WORK_DIR = "/work";
@@ -0,0 +1,22 @@
1
+ import type { IBuildTsconfigOptions } from "../structures/IBuildTsconfigOptions";
2
+ import { DEFAULT_PLAYGROUND_COMPILER_OPTIONS } from "./DEFAULT_PLAYGROUND_COMPILER_OPTIONS";
3
+
4
+ /**
5
+ * Serialize a tsconfig JSON document the wasm-side compiler can consume.
6
+ *
7
+ * `buildTsconfigJSON` is deliberately conservative — it returns a string the
8
+ * caller writes to MemFS as-is, so there's no schema validation here. Bad
9
+ * compiler options surface as wasm-side errors on the next build call.
10
+ */
11
+ export function buildTsconfigJSON(options: IBuildTsconfigOptions): string {
12
+ return JSON.stringify({
13
+ compilerOptions: {
14
+ ...DEFAULT_PLAYGROUND_COMPILER_OPTIONS,
15
+ module: options.module,
16
+ outDir: options.outDir ?? "dist",
17
+ rootDir: options.rootDir ?? "src",
18
+ ...(options.compilerOptions ?? {}),
19
+ },
20
+ include: options.include ?? ["src"],
21
+ });
22
+ }
@@ -0,0 +1,32 @@
1
+ import type { IMemFSHost } from "@ttsc/wasm";
2
+
3
+ import type { IInstallTypiaSourcePackOptions } from "../structures/IInstallTypiaSourcePackOptions";
4
+ import { installTypiaSourcePack } from "./installTypiaSourcePack";
5
+
6
+ /**
7
+ * Build a `mount` callback for the `typiaPlugin` config of
8
+ * {@link createWorkerCompiler}.
9
+ *
10
+ * The returned function fetches the pack once (cached per URL by
11
+ * {@link loadTypiaSourcePack}) and writes every entry to the MemFS the first
12
+ * time it is invoked on a given host.
13
+ */
14
+ export function createTypiaSourcePackMount(
15
+ options: IInstallTypiaSourcePackOptions,
16
+ ): (host: IMemFSHost, workDir?: string) => Promise<void> {
17
+ return async (host: IMemFSHost, workDir?: string) => {
18
+ // Honor the caller's workDir when the site did not pin mountRoot
19
+ // explicitly. Otherwise a `createWorkerCompiler({workDir: '/foo'})`
20
+ // would still mount typia under `/work/node_modules/` and tsgo would
21
+ // never resolve `typia` from the project root.
22
+ //
23
+ // `workDir` is intentionally optional so callers built against the
24
+ // previous single-arg signature (`mount(host)`) keep working — when
25
+ // both mountRoot and workDir are absent, installTypiaSourcePack falls
26
+ // back to its own default (`/work/node_modules`).
27
+ const mountRoot =
28
+ options.mountRoot ??
29
+ (workDir ? `${workDir.replace(/\/+$/, "")}/node_modules` : undefined);
30
+ await installTypiaSourcePack(host, { ...options, mountRoot });
31
+ };
32
+ }
@@ -0,0 +1,274 @@
1
+ // Worker-side playground compiler factory.
2
+ //
3
+ // This module ships a ready-to-bind `ICompilerService` implementation. The
4
+ // consumer's worker entry boots the wasm once, builds tsconfig variants for
5
+ // the site's chosen module shape, registers the typia/lint plugin verbs (or
6
+ // the site-provided overrides), and serializes every MemFS-mutating call onto
7
+ // a single chain so concurrent compiles never corrupt each other.
8
+
9
+ import {
10
+ type IBootResult,
11
+ type ITtscCompileResult,
12
+ bootTtsc,
13
+ parseResult,
14
+ } from "@ttsc/wasm";
15
+
16
+ import type { ICompilerService } from "../structures/ICompilerService";
17
+ import type { ICreateWorkerCompilerOptions } from "../structures/ICreateWorkerCompilerOptions";
18
+ import type { ITransformOptions } from "../structures/ITransformOptions";
19
+ import { buildTsconfigJSON } from "./buildTsconfigJSON";
20
+ import { DEFAULT_ENTRY_FILE } from "./DEFAULT_ENTRY_FILE";
21
+ import { DEFAULT_LINT_PLUGIN_NAME } from "./DEFAULT_LINT_PLUGIN_NAME";
22
+ import { DEFAULT_TSCONFIG_PATH } from "./DEFAULT_TSCONFIG_PATH";
23
+ import { DEFAULT_TYPIA_PLUGIN_NAME } from "./DEFAULT_TYPIA_PLUGIN_NAME";
24
+ import { DEFAULT_WORK_DIR } from "./DEFAULT_WORK_DIR";
25
+ import { installDependenciesIntoMemFS } from "./installDependenciesIntoMemFS";
26
+ import { joinUnder } from "./internal/joinUnder";
27
+ import { parseLintDiagnostics } from "./internal/parseLintDiagnostics";
28
+ import { safeParseTypiaTransform } from "./internal/safeParseTypiaTransform";
29
+ import { mapDiagnostic } from "./mapDiagnostic";
30
+ import { normalizeError } from "./normalizeError";
31
+ import { pickEmittedJS } from "./pickEmittedJS";
32
+
33
+ /**
34
+ * Build an `ICompilerService` ready to register with tgrid's `WorkerServer`.
35
+ *
36
+ * Usage in the worker entry:
37
+ *
38
+ * ```ts
39
+ * import { createWorkerCompiler } from "@ttsc/playground";
40
+ * import { WorkerServer } from "tgrid";
41
+ *
42
+ * const service = createWorkerCompiler({
43
+ * wasmUrl: "/compiler/playground.wasm",
44
+ * apiName: "ttscPlayground",
45
+ * typiaPlugin: { mount: installTypiaPack },
46
+ * });
47
+ *
48
+ * const main = async () => {
49
+ * const worker = new WorkerServer();
50
+ * await worker.open(service);
51
+ * };
52
+ * void main();
53
+ * ```
54
+ */
55
+ export function createWorkerCompiler(
56
+ options: ICreateWorkerCompilerOptions,
57
+ ): ICompilerService {
58
+ const workDir = options.workDir ?? DEFAULT_WORK_DIR;
59
+ const tsconfigPath = options.tsconfigPath ?? DEFAULT_TSCONFIG_PATH;
60
+ const entryFile = options.entryFile ?? DEFAULT_ENTRY_FILE;
61
+
62
+ const typiaPlugin =
63
+ options.typiaPlugin === false ? null : (options.typiaPlugin ?? {});
64
+ const typiaPluginName = typiaPlugin?.name ?? DEFAULT_TYPIA_PLUGIN_NAME;
65
+ const typiaTransformModule =
66
+ typiaPlugin?.transformModule ?? "typia/lib/transform";
67
+
68
+ const lintPlugin =
69
+ options.lintPlugin === false ? null : (options.lintPlugin ?? {});
70
+ const lintPluginName = lintPlugin?.name ?? DEFAULT_LINT_PLUGIN_NAME;
71
+
72
+ // tsconfig variants. ESM for the "Compiled JS" preview lane, CommonJS for
73
+ // the bundle/Execute lane (whose `new Function` driver expects CJS).
74
+ const tsconfigPlugins = typiaPlugin
75
+ ? [{ transform: typiaTransformModule }]
76
+ : [];
77
+ const extraCompilerOptions = {
78
+ ...(options.extraCompilerOptions ?? {}),
79
+ ...(tsconfigPlugins.length > 0 ? { plugins: tsconfigPlugins } : {}),
80
+ };
81
+ const tsconfigESM = buildTsconfigJSON({
82
+ module: "ESNext",
83
+ compilerOptions: extraCompilerOptions,
84
+ });
85
+ const tsconfigCJS = buildTsconfigJSON({
86
+ module: "CommonJS",
87
+ compilerOptions: extraCompilerOptions,
88
+ });
89
+
90
+ // Cache the boot promise across calls. If the boot rejects we clear the
91
+ // cache so the next call retries — otherwise every later compile/bundle/
92
+ // lint would replay the same rejection forever (page reload required).
93
+ // Mirrors the createCompilerClient (UI-side) pattern.
94
+ let boot: Promise<IBootResult> | null = null;
95
+ function getBoot(): Promise<IBootResult> {
96
+ if (boot) return boot;
97
+ boot = (async () => {
98
+ const result = await bootTtsc({
99
+ wasmUrl: options.wasmUrl,
100
+ wasmExecUrl: options.wasmExecUrl,
101
+ apiName: options.apiName,
102
+ });
103
+ if (typiaPlugin?.mount) await typiaPlugin.mount(result.host, workDir);
104
+ return result;
105
+ })().catch((err) => {
106
+ boot = null;
107
+ throw err;
108
+ });
109
+ return boot;
110
+ }
111
+
112
+ // Serialize every MemFS-mutating call. tgrid queues incoming messages, but
113
+ // the wasm-side host runs them concurrently — a fast keystroke could
114
+ // interleave a bundle's tsconfig rewrite with a lint pass and corrupt
115
+ // either result. The chain keeps compile/bundle/lint linear.
116
+ let busy: Promise<unknown> = Promise.resolve();
117
+ const enqueue = <T>(fn: () => Promise<T>): Promise<T> => {
118
+ const next = busy.then(fn, fn);
119
+ busy = next.catch(() => {});
120
+ return next;
121
+ };
122
+
123
+ const projectFiles = (
124
+ source: string,
125
+ tsconfigText: string,
126
+ ): Record<string, string> => ({
127
+ [`${workDir}/${tsconfigPath}`]: tsconfigText,
128
+ [`${workDir}/${entryFile}`]: source,
129
+ });
130
+
131
+ const writeProject = (
132
+ host: IBootResult["host"],
133
+ files: Record<string, string>,
134
+ ): void => {
135
+ for (const [path, text] of Object.entries(files)) {
136
+ host.writeFile(path, text);
137
+ }
138
+ };
139
+
140
+ // Typia transform — run the registered typia plugin's `transform` verb,
141
+ // parse its JSON stdout, and write the rewritten TS back into MemFS so the
142
+ // subsequent `api.build` sees the post-transform text.
143
+ const applyTypiaTransform = async (
144
+ api: IBootResult["api"],
145
+ host: IBootResult["host"],
146
+ ): Promise<void> => {
147
+ if (!typiaPlugin) return;
148
+ try {
149
+ const raw = await api.plugin({
150
+ name: typiaPluginName,
151
+ command: "transform",
152
+ cwd: workDir,
153
+ tsconfig: tsconfigPath,
154
+ output: "ts",
155
+ });
156
+ if (!raw.stdout) return;
157
+ const transformed = safeParseTypiaTransform(raw.stdout);
158
+ if (!transformed) return;
159
+ for (const [rel, text] of Object.entries(transformed.typescript)) {
160
+ host.writeFile(joinUnder(workDir, rel), text);
161
+ }
162
+ } catch {
163
+ // Swallow plugin spawn / JSON parse failures so the user still sees the
164
+ // tsgo compile diagnostics from the subsequent build call. Real
165
+ // user-facing errors (e.g. typia rule violations) flow through the
166
+ // wasm-side diagnostics path.
167
+ }
168
+ };
169
+
170
+ const runBuildPipeline = async (
171
+ source: string,
172
+ runTypia: boolean,
173
+ tsconfigText: string,
174
+ ): Promise<ICompilerService.IResult> => {
175
+ try {
176
+ const { api, host } = await getBoot();
177
+ writeProject(host, projectFiles(source, tsconfigText));
178
+ if (runTypia) await applyTypiaTransform(api, host);
179
+ const raw = await api.build({
180
+ cwd: workDir,
181
+ tsconfig: tsconfigPath,
182
+ });
183
+ if (raw.code !== 0 && !raw.result) {
184
+ return {
185
+ type: "error",
186
+ target: "javascript",
187
+ value: {
188
+ message:
189
+ raw.stderr || "ttsc: build failed without a result payload",
190
+ },
191
+ };
192
+ }
193
+ const parsed = parseResult<ITtscCompileResult>(raw);
194
+ if (!parsed) {
195
+ return {
196
+ type: "error",
197
+ target: "javascript",
198
+ value: { message: "ttsc: result JSON could not be parsed" },
199
+ };
200
+ }
201
+ const diagnostics = (parsed.diagnostics ?? []).map((d) =>
202
+ mapDiagnostic(d, source),
203
+ );
204
+ const js = pickEmittedJS(parsed.output ?? {}, entryFile);
205
+ const errors = diagnostics.filter((d) => d.severity === "error");
206
+ if (errors.length > 0) {
207
+ return {
208
+ type: "failure",
209
+ target: "javascript",
210
+ value: js ?? "",
211
+ diagnostics,
212
+ };
213
+ }
214
+ return { type: "success", target: "javascript", value: js ?? "" };
215
+ } catch (error) {
216
+ return {
217
+ type: "error",
218
+ target: "javascript",
219
+ value: normalizeError(error),
220
+ };
221
+ }
222
+ };
223
+
224
+ // Lint pipeline. The lint plugin emits tsgo-style pretty diagnostics on
225
+ // stderr; we parse those lines back into IDiagnostic so the UI can render
226
+ // them in the Lint tab.
227
+ const runLintPipeline = async (
228
+ source: string,
229
+ ): Promise<ICompilerService.ILintResult> => {
230
+ if (!lintPlugin) return { diagnostics: [] };
231
+ try {
232
+ const { api, host } = await getBoot();
233
+ writeProject(host, projectFiles(source, tsconfigESM));
234
+ const raw = await api.plugin({
235
+ name: lintPluginName,
236
+ command: "check",
237
+ cwd: workDir,
238
+ tsconfig: tsconfigPath,
239
+ });
240
+ return { diagnostics: parseLintDiagnostics(raw.stderr, source) };
241
+ } catch {
242
+ return { diagnostics: [] };
243
+ }
244
+ };
245
+
246
+ return {
247
+ installDependencies: (props) =>
248
+ enqueue(async () => {
249
+ const { host } = await getBoot();
250
+ return installDependenciesIntoMemFS(host, workDir, props);
251
+ }),
252
+ compile: (props) =>
253
+ enqueue(() =>
254
+ runBuildPipeline(
255
+ props.source,
256
+ shouldRunTypia(props.options),
257
+ tsconfigESM,
258
+ ),
259
+ ),
260
+ bundle: (props) =>
261
+ enqueue(() =>
262
+ runBuildPipeline(
263
+ props.source,
264
+ shouldRunTypia(props.options),
265
+ tsconfigCJS,
266
+ ),
267
+ ),
268
+ lint: (props) => enqueue(() => runLintPipeline(props.source)),
269
+ };
270
+ }
271
+
272
+ function shouldRunTypia(options?: ITransformOptions): boolean {
273
+ return options?.typia !== false;
274
+ }
@@ -0,0 +1,28 @@
1
+ import type { IMemFSHost } from "@ttsc/wasm";
2
+
3
+ import type { ICompilerService } from "../structures/ICompilerService";
4
+ import { normalizeNodeModulePath } from "./normalizeNodeModulePath";
5
+
6
+ /**
7
+ * Mount external npm package files into the worker's MemFS under
8
+ * `<workDir>/node_modules/...`.
9
+ *
10
+ * Used by `createWorkerCompiler` to implement the `installDependencies` verb
11
+ * of `ICompilerService`. The dependency installer (UI side) feeds keys like
12
+ * `node_modules/uuid/dist/index.js`; we normalize, sanity-check, and copy
13
+ * each entry under the project root.
14
+ */
15
+ export function installDependenciesIntoMemFS(
16
+ host: IMemFSHost,
17
+ workDir: string,
18
+ props: ICompilerService.IInstallDependenciesProps,
19
+ ): ICompilerService.IInstallDependenciesResult {
20
+ let fileCount = 0;
21
+ for (const [rel, text] of Object.entries(props.files)) {
22
+ const normalized = normalizeNodeModulePath(rel);
23
+ if (!normalized) continue;
24
+ host.writeFile(`${workDir}/${normalized}`, text);
25
+ fileCount++;
26
+ }
27
+ return { installed: props.packages, fileCount };
28
+ }
@@ -0,0 +1,24 @@
1
+ import type { IMemFSHost } from "@ttsc/wasm";
2
+
3
+ import type { IInstallTypiaSourcePackOptions } from "../structures/IInstallTypiaSourcePackOptions";
4
+ import { loadTypiaSourcePack } from "./loadTypiaSourcePack";
5
+
6
+ /**
7
+ * Mounts typia + @typia/* source trees into the in-browser MemFS so the
8
+ * wasm-side compiler can resolve `import typia, { tags } from "typia"`
9
+ * against the same code the published package uses.
10
+ *
11
+ * Idempotent on the same host (re-writing the same paths is a no-op for the
12
+ * MemFS implementation).
13
+ */
14
+ export async function installTypiaSourcePack(
15
+ host: IMemFSHost,
16
+ options: IInstallTypiaSourcePackOptions,
17
+ ): Promise<void> {
18
+ const mountRoot = options.mountRoot ?? "/work/node_modules";
19
+ const pack = await loadTypiaSourcePack(options);
20
+ host.mkdirp(mountRoot);
21
+ for (const [rel, content] of Object.entries(pack)) {
22
+ host.writeFile(`${mountRoot}/${rel}`, content);
23
+ }
24
+ }
@@ -0,0 +1,5 @@
1
+ /** Join a relative path under a base directory, ignoring base when `rel` is absolute. */
2
+ export function joinUnder(base: string, rel: string): string {
3
+ if (rel.startsWith("/")) return rel;
4
+ return `${base}/${rel}`;
5
+ }
@@ -0,0 +1,61 @@
1
+ import type { ICompilerService } from "../../structures/ICompilerService";
2
+
3
+ // Match `file:line:col - severity TS<code>: [rule-name] message` lines from
4
+ // tsgo's pretty diagnostic renderer. ANSI color escapes are stripped first
5
+ // so the matcher works on the exact text rendered in the Lint tab.
6
+ const LINT_LINE_REGEXP =
7
+ /([^\s:]+):(\d+):(\d+)\s+-\s+(error|warning)\s+TS(\d+):\s+(?:\[([^\]]+)\]\s+)?(.*)$/;
8
+ // Strip real ANSI SGR sequences, which always start with ESC (0x1b). The
9
+ // leading ESC byte is required — `/\[[0-9;]*m/g` would also chew innocuous
10
+ // substrings like "[m" or "[0;1m" inside diagnostic messages, and would
11
+ // fail to actually strip the ESC bytes tsgo writes, leaving the
12
+ // LINT_LINE_REGEXP unable to anchor on the filename column.
13
+ const ANSI_REGEXP = /\x1b\[[0-9;]*m/g;
14
+
15
+ /**
16
+ * Parse the lint plugin's stderr (tsgo-style pretty diagnostics) into the
17
+ * playground's normalized diagnostic shape.
18
+ */
19
+ export function parseLintDiagnostics(
20
+ stderr: string,
21
+ source: string,
22
+ ): ICompilerService.IDiagnostic[] {
23
+ const stripped = stderr.replace(ANSI_REGEXP, "");
24
+ const lines = stripped.split(/\r?\n/);
25
+ const out: ICompilerService.IDiagnostic[] = [];
26
+ for (const line of lines) {
27
+ const m = line.match(LINT_LINE_REGEXP);
28
+ if (!m) continue;
29
+ const [, , lineStr, colStr, sev, codeStr, rule, message] = m;
30
+ const lineNum = Number(lineStr);
31
+ const colNum = Number(colStr);
32
+ if (!Number.isFinite(lineNum) || !Number.isFinite(colNum)) continue;
33
+ out.push({
34
+ line: lineNum,
35
+ column: colNum,
36
+ length: lengthOfTokenAt(source, lineNum, colNum) ?? 1,
37
+ severity: sev === "warning" ? "warning" : "error",
38
+ message: rule ? `[${rule}] ${message ?? ""}` : (message ?? ""),
39
+ code: `TS${codeStr ?? ""}`,
40
+ });
41
+ }
42
+ return out;
43
+ }
44
+
45
+ /**
46
+ * Best-effort length of the identifier-like token starting at (`line`, `column`)
47
+ * in `source`. Falls back to 1 when no token is found.
48
+ */
49
+ function lengthOfTokenAt(
50
+ source: string,
51
+ line: number,
52
+ column: number,
53
+ ): number | null {
54
+ const lines = source.split(/\r?\n/);
55
+ if (line < 1 || line > lines.length) return null;
56
+ const text = lines[line - 1] ?? "";
57
+ const start = Math.max(0, column - 1);
58
+ const match = text.slice(start).match(/^[\w$]+/);
59
+ if (!match) return null;
60
+ return match[0].length;
61
+ }
@@ -0,0 +1,24 @@
1
+ /** Shape of the typia transform plugin's stdout JSON payload. */
2
+ export interface ITypiaTransformOutput {
3
+ diagnostics?: unknown;
4
+ typescript: Record<string, string>;
5
+ }
6
+
7
+ /**
8
+ * Parse the typia transform plugin's stdout into the expected shape.
9
+ * Returns null on any parse / shape mismatch — the caller treats null as
10
+ * "transform produced no usable output".
11
+ */
12
+ export function safeParseTypiaTransform(
13
+ text: string,
14
+ ): ITypiaTransformOutput | null {
15
+ try {
16
+ const parsed = JSON.parse(text) as ITypiaTransformOutput;
17
+ if (parsed && typeof parsed === "object" && parsed.typescript) {
18
+ return parsed;
19
+ }
20
+ return null;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
@@ -0,0 +1,14 @@
1
+ /** Compute 1-based line/column for a 0-based char offset into `source`. */
2
+ export function lineColumnOf(
3
+ source: string,
4
+ start: number | undefined,
5
+ ): { line: number; column: number } {
6
+ if (typeof start !== "number" || start < 0) return { line: 1, column: 1 };
7
+ const slice = source.slice(0, Math.min(start, source.length));
8
+ const newlines = slice.match(/\n/g);
9
+ const line = newlines ? newlines.length + 1 : 1;
10
+ const lastNewline = slice.lastIndexOf("\n");
11
+ const column =
12
+ lastNewline === -1 ? slice.length + 1 : slice.length - lastNewline;
13
+ return { line, column };
14
+ }
@@ -0,0 +1,37 @@
1
+ import type { IInstallTypiaSourcePackOptions } from "../structures/IInstallTypiaSourcePackOptions";
2
+
3
+ const packCache = new Map<string, Promise<Record<string, string>>>();
4
+
5
+ /**
6
+ * Fetch the typia source pack JSON. Cached per URL across calls. On
7
+ * rejection the cache entry is cleared so the next call retries —
8
+ * otherwise a transient fetch failure during the first boot would wedge
9
+ * every later boot through `getBoot`'s typiaPlugin.mount path.
10
+ */
11
+ export function loadTypiaSourcePack(
12
+ options: IInstallTypiaSourcePackOptions,
13
+ ): Promise<Record<string, string>> {
14
+ const cached = packCache.get(options.url);
15
+ if (cached) return cached;
16
+ const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
17
+ if (!fetchImpl) {
18
+ throw new Error(
19
+ "loadTypiaSourcePack: no fetch implementation available in this environment.",
20
+ );
21
+ }
22
+ const url = options.url;
23
+ const promise = (async () => {
24
+ const response = await fetchImpl(url);
25
+ if (!response.ok) {
26
+ throw new Error(
27
+ `loadTypiaSourcePack: failed to fetch ${url}: ${response.status}`,
28
+ );
29
+ }
30
+ return (await response.json()) as Record<string, string>;
31
+ })().catch((err) => {
32
+ packCache.delete(url);
33
+ throw err;
34
+ });
35
+ packCache.set(url, promise);
36
+ return promise;
37
+ }
@@ -0,0 +1,26 @@
1
+ import type { ITtscCompileResult } from "@ttsc/wasm";
2
+
3
+ import type { ICompilerService } from "../structures/ICompilerService";
4
+ import { lineColumnOf } from "./lineColumnOf";
5
+
6
+ /**
7
+ * Convert a `@ttsc/wasm` diagnostic into the playground's normalized shape.
8
+ *
9
+ * Falls back to computing line/column from the source text when the wasm
10
+ * diagnostic did not carry them (some plugin emitters drop them).
11
+ */
12
+ export function mapDiagnostic(
13
+ diag: NonNullable<ITtscCompileResult["diagnostics"]>[number],
14
+ source: string,
15
+ ): ICompilerService.IDiagnostic {
16
+ const fallback = lineColumnOf(source, diag.start);
17
+ return {
18
+ line: diag.line && diag.line > 0 ? diag.line : fallback.line,
19
+ column:
20
+ diag.character && diag.character > 0 ? diag.character : fallback.column,
21
+ length: typeof diag.length === "number" ? diag.length : 1,
22
+ severity: diag.category === "warning" ? "warning" : "error",
23
+ message: diag.messageText,
24
+ code: typeof diag.code === "number" ? `TS${diag.code}` : String(diag.code),
25
+ };
26
+ }