jac-client 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl

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 (141) hide show
  1. jac_client/docs/README.md +232 -172
  2. jac_client/docs/advanced-state.md +1012 -452
  3. jac_client/docs/asset-serving/intro.md +209 -0
  4. jac_client/docs/assets/pipe_line-v2.svg +32 -0
  5. jac_client/docs/assets/pipe_line.png +0 -0
  6. jac_client/docs/file-system/intro.md +90 -0
  7. jac_client/docs/guide-example/intro.md +117 -0
  8. jac_client/docs/guide-example/step-01-setup.md +260 -0
  9. jac_client/docs/guide-example/step-02-components.md +416 -0
  10. jac_client/docs/guide-example/step-03-styling.md +478 -0
  11. jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
  12. jac_client/docs/guide-example/step-05-local-state.md +530 -0
  13. jac_client/docs/guide-example/step-06-events.md +750 -0
  14. jac_client/docs/guide-example/step-07-effects.md +469 -0
  15. jac_client/docs/guide-example/step-08-walkers.md +534 -0
  16. jac_client/docs/guide-example/step-09-authentication.md +586 -0
  17. jac_client/docs/guide-example/step-10-routing.md +540 -0
  18. jac_client/docs/guide-example/step-11-final.md +964 -0
  19. jac_client/docs/imports.md +538 -46
  20. jac_client/docs/lifecycle-hooks.md +517 -297
  21. jac_client/docs/routing.md +487 -357
  22. jac_client/docs/styling/intro.md +250 -0
  23. jac_client/docs/styling/js-styling.md +373 -0
  24. jac_client/docs/styling/material-ui.md +346 -0
  25. jac_client/docs/styling/pure-css.md +305 -0
  26. jac_client/docs/styling/sass.md +409 -0
  27. jac_client/docs/styling/styled-components.md +401 -0
  28. jac_client/docs/styling/tailwind.md +303 -0
  29. jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
  30. jac_client/examples/asset-serving/css-with-image/README.md +91 -0
  31. jac_client/examples/asset-serving/css-with-image/app.jac +67 -0
  32. jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
  33. jac_client/examples/asset-serving/css-with-image/package.json +28 -0
  34. jac_client/examples/asset-serving/css-with-image/styles.css +27 -0
  35. jac_client/examples/asset-serving/css-with-image/vite.config.js +29 -0
  36. jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
  37. jac_client/examples/asset-serving/image-asset/README.md +119 -0
  38. jac_client/examples/asset-serving/image-asset/app.jac +43 -0
  39. jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
  40. jac_client/examples/asset-serving/image-asset/package.json +28 -0
  41. jac_client/examples/asset-serving/image-asset/styles.css +27 -0
  42. jac_client/examples/asset-serving/image-asset/vite.config.js +29 -0
  43. jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
  44. jac_client/examples/asset-serving/import-alias/README.md +83 -0
  45. jac_client/examples/asset-serving/import-alias/app.jac +57 -0
  46. jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
  47. jac_client/examples/asset-serving/import-alias/package.json +28 -0
  48. jac_client/examples/asset-serving/import-alias/vite.config.js +29 -0
  49. jac_client/examples/basic/.babelrc +9 -0
  50. jac_client/examples/basic/README.md +16 -0
  51. jac_client/examples/basic/app.jac +16 -0
  52. jac_client/examples/basic/package.json +27 -0
  53. jac_client/examples/basic/vite.config.js +28 -0
  54. jac_client/examples/basic-auth/.babelrc +9 -0
  55. jac_client/examples/basic-auth/README.md +16 -0
  56. jac_client/examples/basic-auth/app.jac +308 -0
  57. jac_client/examples/basic-auth/package.json +27 -0
  58. jac_client/examples/basic-auth/vite.config.js +28 -0
  59. jac_client/examples/basic-auth-with-router/.babelrc +9 -0
  60. jac_client/examples/basic-auth-with-router/README.md +60 -0
  61. jac_client/examples/basic-auth-with-router/app.jac +464 -0
  62. jac_client/examples/basic-auth-with-router/package.json +28 -0
  63. jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
  64. jac_client/examples/basic-full-stack/.babelrc +9 -0
  65. jac_client/examples/basic-full-stack/README.md +18 -0
  66. jac_client/examples/basic-full-stack/app.jac +320 -0
  67. jac_client/examples/basic-full-stack/package.json +28 -0
  68. jac_client/examples/basic-full-stack/vite.config.js +28 -0
  69. jac_client/examples/css-styling/js-styling/.babelrc +9 -0
  70. jac_client/examples/css-styling/js-styling/README.md +183 -0
  71. jac_client/examples/css-styling/js-styling/app.jac +63 -0
  72. jac_client/examples/css-styling/js-styling/package.json +28 -0
  73. jac_client/examples/css-styling/js-styling/styles.js +100 -0
  74. jac_client/examples/css-styling/js-styling/vite.config.js +28 -0
  75. jac_client/examples/css-styling/material-ui/.babelrc +9 -0
  76. jac_client/examples/css-styling/material-ui/README.md +16 -0
  77. jac_client/examples/css-styling/material-ui/app.jac +82 -0
  78. jac_client/examples/css-styling/material-ui/package.json +32 -0
  79. jac_client/examples/css-styling/material-ui/vite.config.js +28 -0
  80. jac_client/examples/css-styling/pure-css/.babelrc +9 -0
  81. jac_client/examples/css-styling/pure-css/README.md +16 -0
  82. jac_client/examples/css-styling/pure-css/app.jac +63 -0
  83. jac_client/examples/css-styling/pure-css/package.json +28 -0
  84. jac_client/examples/css-styling/pure-css/styles.css +112 -0
  85. jac_client/examples/css-styling/pure-css/vite.config.js +28 -0
  86. jac_client/examples/css-styling/sass-example/.babelrc +9 -0
  87. jac_client/examples/css-styling/sass-example/README.md +16 -0
  88. jac_client/examples/css-styling/sass-example/app.jac +63 -0
  89. jac_client/examples/css-styling/sass-example/package.json +29 -0
  90. jac_client/examples/css-styling/sass-example/styles.scss +158 -0
  91. jac_client/examples/css-styling/sass-example/vite.config.js +28 -0
  92. jac_client/examples/css-styling/styled-components/.babelrc +9 -0
  93. jac_client/examples/css-styling/styled-components/README.md +16 -0
  94. jac_client/examples/css-styling/styled-components/app.jac +66 -0
  95. jac_client/examples/css-styling/styled-components/package.json +29 -0
  96. jac_client/examples/css-styling/styled-components/styled.js +91 -0
  97. jac_client/examples/css-styling/styled-components/vite.config.js +28 -0
  98. jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
  99. jac_client/examples/css-styling/tailwind-example/README.md +16 -0
  100. jac_client/examples/css-styling/tailwind-example/app.jac +64 -0
  101. jac_client/examples/css-styling/tailwind-example/global.css +1 -0
  102. jac_client/examples/css-styling/tailwind-example/package.json +30 -0
  103. jac_client/examples/css-styling/tailwind-example/vite.config.js +30 -0
  104. jac_client/examples/full-stack-with-auth/.babelrc +9 -0
  105. jac_client/examples/full-stack-with-auth/README.md +16 -0
  106. jac_client/examples/full-stack-with-auth/app.jac +735 -0
  107. jac_client/examples/full-stack-with-auth/package.json +28 -0
  108. jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
  109. jac_client/examples/with-router/.babelrc +9 -0
  110. jac_client/examples/with-router/README.md +17 -0
  111. jac_client/examples/with-router/app.jac +323 -0
  112. jac_client/examples/with-router/package.json +28 -0
  113. jac_client/examples/with-router/vite.config.js +28 -0
  114. jac_client/plugin/cli.py +95 -179
  115. jac_client/plugin/client.py +111 -2
  116. jac_client/plugin/client_runtime.jac +183 -890
  117. jac_client/plugin/vite_client_bundle.py +185 -205
  118. jac_client/tests/__init__.py +0 -1
  119. jac_client/tests/fixtures/{client_app.jac → basic-app/app.jac} +1 -1
  120. jac_client/tests/fixtures/cl_file/app.cl.jac +38 -0
  121. jac_client/tests/fixtures/cl_file/app.jac +15 -0
  122. jac_client/tests/fixtures/{client_app_with_antd.jac → client_app_with_antd/app.jac} +7 -0
  123. jac_client/tests/fixtures/{js_import.jac → js_import/app.jac} +2 -2
  124. jac_client/tests/fixtures/{relative_import.jac → relative_import/app.jac} +1 -1
  125. jac_client/tests/fixtures/{button.jac → relative_import/button.jac} +2 -2
  126. jac_client/tests/fixtures/spawn_test/app.jac +133 -0
  127. jac_client/tests/fixtures/{test_fragments_spread.jac → test_fragments_spread/app.jac} +11 -2
  128. jac_client/tests/test_asset_examples.py +339 -0
  129. jac_client/tests/test_cl.py +345 -151
  130. jac_client/tests/test_create_jac_app.py +41 -45
  131. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +72 -16
  132. jac_client-0.2.1.dist-info/RECORD +140 -0
  133. jac_client/examples/little-x/package-lock.json +0 -2840
  134. jac_client/examples/todo-app/README.md +0 -82
  135. jac_client/examples/todo-app/app.jac +0 -683
  136. jac_client/examples/todo-app/package-lock.json +0 -999
  137. jac_client/examples/todo-app/package.json +0 -22
  138. jac_client-0.1.0.dist-info/RECORD +0 -33
  139. /jac_client/tests/fixtures/{utils.js → js_import/utils.js} +0 -0
  140. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
  141. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/entry_points.txt +0 -0
jac_client/plugin/cli.py CHANGED
@@ -77,12 +77,16 @@ class JacCmd:
77
77
  package_data = json.load(f)
78
78
 
79
79
  # create temp folder
80
- temp_folder = os.path.join(project_path, "temp")
81
- os.makedirs(temp_folder, exist_ok=True)
80
+ src_folder = os.path.join(project_path, "src")
81
+ os.makedirs(src_folder, exist_ok=True)
82
82
 
83
- # create static/client/js folder
84
- client_js_folder = os.path.join(project_path, "static", "client", "js")
85
- os.makedirs(client_js_folder, exist_ok=True)
83
+ # create build folder
84
+ build_folder = os.path.join(project_path, "build")
85
+ os.makedirs(build_folder, exist_ok=True)
86
+
87
+ # create assets folder for static assets (images, fonts, etc.)
88
+ assets_folder = os.path.join(project_path, "assets")
89
+ os.makedirs(assets_folder, exist_ok=True)
86
90
 
87
91
  # Update package.json with Jac-specific configuration
88
92
  package_data.update(
@@ -91,12 +95,23 @@ class JacCmd:
91
95
  "description": f"Jac application: {name}",
92
96
  "type": "module",
93
97
  "scripts": {
94
- "build": "vite build",
98
+ "build": "npm run compile && vite build",
95
99
  "dev": "vite dev",
96
100
  "preview": "vite preview",
101
+ "compile": 'babel src --out-dir build --extensions ".jsx,.js" --out-file-extension .js',
102
+ },
103
+ "devDependencies": {
104
+ "vite": "^6.4.1",
105
+ "@babel/cli": "^7.28.3",
106
+ "@babel/core": "^7.28.5",
107
+ "@babel/preset-env": "^7.28.5",
108
+ "@babel/preset-react": "^7.28.5",
109
+ },
110
+ "dependencies": {
111
+ "react": "^19.2.0",
112
+ "react-dom": "^19.2.0",
113
+ "react-router-dom": "^6.30.1",
97
114
  },
98
- "devDependencies": {"vite": "^5.0.0"},
99
- "dependencies": {"react": "^18.2.0", "react-dom": "^18.2.0"},
100
115
  }
101
116
  )
102
117
 
@@ -117,190 +132,91 @@ class JacCmd:
117
132
  # Create a basic Jac file
118
133
  main_jac_content = """
119
134
  # Pages
120
- cl def HomeView() -> any {
121
- return <div
122
- style={{
123
- "minHeight": "100vh",
124
- "fontFamily": "-apple-system, BlinkMacSystemFont, sans-serif"
125
- }}>
126
- <main
127
- style={{
128
- "maxWidth": "1200px",
129
- "margin": "0 auto",
130
- "padding": "60px 40px"
131
- }}>
132
- <div
133
- style={{
134
- "textAlign": "center",
135
- "marginBottom": "80px"
136
- }}>
137
- <h1
138
- style={{
139
- "fontSize": "56px",
140
- "marginBottom": "20px"
141
- }}>
142
- Welcome to
143
- <span style={{"color": "#007bff"}}>OneLang</span>
144
- </h1>
145
- <p
146
- style={{
147
- "fontSize": "20px",
148
- "color": "#666"
149
- }}>
150
- One Language. One Stack. Zero Friction.
151
- </p>
152
- </div>
153
-
154
- <div
155
- style={{
156
- "display": "grid",
157
- "gridTemplateColumns": "repeat(2, 1fr)",
158
- "gap": "24px",
159
- "marginBottom": "60px"
160
- }}>
161
- <a
162
- href="https://docs.jaseci.org"
163
- target="_blank"
164
- style={{
165
- "padding": "32px",
166
- "backgroundColor": "white",
167
- "border": "1px solid #eaeaea",
168
- "borderRadius": "8px",
169
- "textDecoration": "none",
170
- "color": "#000"
171
- }}>
172
- <h3
173
- style={{
174
- "marginTop": "0",
175
- "marginBottom": "12px"
176
- }}>📖 Documentation</h3>
177
- <p style={{"color": "#666", "margin": "0"}}>
178
- Learn how to build with OneLang
179
- </p>
180
- </a>
181
- <a
182
- href="https://docs.jaseci.org/learn"
183
- target="_blank"
184
- style={{
185
- "padding": "32px",
186
- "backgroundColor": "white",
187
- "border": "1px solid #eaeaea",
188
- "borderRadius": "8px",
189
- "textDecoration": "none",
190
- "color": "#000"
191
- }}>
192
- <h3
193
- style={{
194
- "marginTop": "0",
195
- "marginBottom": "12px"
196
- }}>🎓 Learn</h3>
197
- <p style={{"color": "#666", "margin": "0"}}>
198
- Tutorials and guides
199
- </p>
200
- </a>
201
- <a
202
- href="/examples"
203
- style={{
204
- "padding": "32px",
205
- "backgroundColor": "white",
206
- "border": "1px solid #eaeaea",
207
- "borderRadius": "8px",
208
- "textDecoration": "none",
209
- "color": "#000"
210
- }}>
211
- <h3
212
- style={{
213
- "marginTop": "0",
214
- "marginBottom": "12px"
215
- }}>💡 Examples</h3>
216
- <p style={{"color": "#666", "margin": "0"}}>
217
- Sample applications
218
- </p>
219
- </a>
220
- <a
221
- href="https://github.com/Jaseci-Labs/jaseci"
222
- target="_blank"
223
- style={{
224
- "padding": "32px",
225
- "backgroundColor": "white",
226
- "border": "1px solid #eaeaea",
227
- "borderRadius": "8px",
228
- "textDecoration": "none",
229
- "color": "#000"
230
- }}>
231
- <h3
232
- style={{
233
- "marginTop": "0",
234
- "marginBottom": "12px"
235
- }}>🔧 Community</h3>
236
- <p style={{"color": "#666", "margin": "0"}}>
237
- GitHub repository
238
- </p>
239
- </a>
240
- </div>
241
-
242
- <footer
243
- style={{
244
- "borderTop": "1px solid #eaeaea",
245
- "paddingTop": "40px",
246
- "textAlign": "center",
247
- "color": "#999"
248
- }}>
249
- <p>
250
- Get started by editing
251
- <code
252
- style={{
253
- "backgroundColor": "#f5f5f5",
254
- "padding": "2px 6px",
255
- "borderRadius": "3px"
256
- }}>app.jac</code>
257
- </p>
258
- </footer>
259
- </main>
260
- </div>;
135
+ cl import from react {useState, useEffect}
136
+ cl {
137
+ def app() -> any {
138
+ let [count, setCount] = useState(0);
139
+ useEffect(lambda -> None {
140
+ console.log("Count: ", count);
141
+ }, [count]);
142
+ return <div>
143
+ <h1>Hello, World!</h1>
144
+ <p>Count: {count}</p>
145
+ <button onClick={lambda e: any -> None {setCount(count + 1);}}>Increment</button>
146
+ </div>;
147
+ }
261
148
  }
149
+ """
262
150
 
151
+ with open(os.path.join(project_path, "app.jac"), "w") as f:
152
+ f.write(main_jac_content)
263
153
 
264
- # Main App component with declarative router
265
- cl def App() -> any {
266
-
267
- home_route = {
268
- "path": "/",
269
- "component": lambda -> any { return HomeView(); },
270
- "guard": None
271
- };
272
-
273
- routes = [home_route];
274
- router = initRouter(routes, "/");
275
-
276
- # add all the wrapper components here
277
- return <div class="app-container">
278
- {router.render()}
279
- </div>;
280
- }
281
-
282
- # Main SPA entry point - simplified with reactive routing
283
- cl def jac_app() -> any {
284
- return App();
154
+ # create .babelrc file
155
+ babel_config_content = """
156
+ {
157
+ "presets": [[
158
+ "@babel/preset-env",
159
+ {
160
+ "modules": false
161
+ }
162
+ ], "@babel/preset-react"]
285
163
  }
286
164
  """
165
+ with open(os.path.join(project_path, ".babelrc"), "w") as f:
166
+ f.write(babel_config_content)
167
+
168
+ # create vite.config.js file
169
+ vite_config_content = """
170
+ import { defineConfig } from "vite";
171
+ import path from "path";
172
+ import { fileURLToPath } from "url";
173
+
174
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
175
+
176
+ export default defineConfig({
177
+ root: ".", // base folder
178
+ build: {
179
+ rollupOptions: {
180
+ input: "build/main.js", // your compiled entry file
181
+ output: {
182
+ entryFileNames: "client.[hash].js", // name of the final js file
183
+ assetFileNames: "[name].[ext]",
184
+ },
185
+ },
186
+ outDir: "dist", // final bundled output
187
+ emptyOutDir: true,
188
+ },
189
+ publicDir: false,
190
+ resolve: {
191
+ alias: {
192
+ "@jac-client/utils": path.resolve(__dirname, "src/client_runtime.js"),
193
+ "@jac-client/assets": path.resolve(__dirname, "src/assets"),
194
+ },
195
+ },
196
+ });
287
197
 
288
- with open(os.path.join(project_path, "app.jac"), "w") as f:
289
- f.write(main_jac_content)
198
+ """
199
+ with open(os.path.join(project_path, "vite.config.js"), "w") as f:
200
+ f.write(vite_config_content)
290
201
 
291
202
  # Create README.md
292
203
  readme_content = f"""# {name}
293
204
 
294
- ## Running Jac Code
205
+ ## Running Jac Code
295
206
 
296
- To run your Jac code, use the Jac CLI:
207
+ make sure node modules are installed:
208
+ ```bash
209
+ npm install
210
+ ```
297
211
 
298
- ```bash
299
- jac serve app.jac
300
- ```
212
+ To run your Jac code, use the Jac CLI:
301
213
 
302
- Happy coding with Jac!
303
- """
214
+ ```bash
215
+ jac serve app.jac
216
+ ```
217
+
218
+ Happy coding with Jac!
219
+ """
304
220
 
305
221
  with open(os.path.join(project_path, "README.md"), "w") as f:
306
222
  f.write(readme_content)
@@ -1,14 +1,79 @@
1
+ import hashlib
2
+ import html
3
+ import mimetypes
1
4
  import types
5
+ from http.server import BaseHTTPRequestHandler
2
6
  from pathlib import Path
7
+ from typing import Any, Literal, TypeAlias
3
8
 
4
9
  from jaclang.runtimelib.client_bundle import ClientBundle
5
10
  from jaclang.runtimelib.machine import (
6
11
  JacMachine as Jac,
7
12
  hookimpl,
8
13
  )
14
+ from jaclang.runtimelib.server import ModuleIntrospector
9
15
 
10
16
  from .vite_client_bundle import ViteClientBundleBuilder
11
17
 
18
+ JsonValue: TypeAlias = (
19
+ None | str | int | float | bool | list["JsonValue"] | dict[str, "JsonValue"]
20
+ )
21
+ StatusCode: TypeAlias = Literal[200, 201, 400, 401, 404, 503]
22
+
23
+
24
+ class JacClientModuleIntrospector(ModuleIntrospector):
25
+ """Jac Client Module Introspector."""
26
+
27
+ def render_page(
28
+ self, function_name: str, args: dict[str, Any], username: str
29
+ ) -> dict[str, Any]:
30
+ """Render HTML page for client function using the Vite bundle."""
31
+ self.load()
32
+
33
+ available_exports = set(self._client_manifest.get("exports", [])) or set(
34
+ self.get_client_functions().keys()
35
+ )
36
+ if function_name not in available_exports:
37
+ raise ValueError(f"Client function '{function_name}' not found")
38
+
39
+ bundle_hash = self.ensure_bundle()
40
+
41
+ # Find CSS file in dist directory
42
+ base_path = Path(Jac.base_path_dir)
43
+ dist_dir = base_path / "dist"
44
+ css_link = ""
45
+
46
+ # Try to find CSS file (main.css is the default Vite output)
47
+ css_file = dist_dir / "main.css"
48
+ if css_file.exists():
49
+ css_hash = hashlib.sha256(css_file.read_bytes()).hexdigest()[:8]
50
+ css_link = (
51
+ f'<link rel="stylesheet" href="/static/main.css?hash={css_hash}"/>'
52
+ )
53
+
54
+ head_content = f'<meta charset="utf-8"/>\n <title>{html.escape(function_name)}</title>'
55
+ if css_link:
56
+ head_content += f"\n {css_link}"
57
+
58
+ page = (
59
+ "<!DOCTYPE html>"
60
+ '<html lang="en">'
61
+ "<head>"
62
+ f"{head_content}"
63
+ "</head>"
64
+ "<body>"
65
+ '<div id="root"></div>'
66
+ f'<script src="/static/client.js?hash={bundle_hash}" defer></script>'
67
+ "</body>"
68
+ "</html>"
69
+ )
70
+
71
+ return {
72
+ "html": page,
73
+ "bundle_hash": bundle_hash,
74
+ "bundle_code": self._bundle.code,
75
+ }
76
+
12
77
 
13
78
  class JacClient:
14
79
  """Jac Client."""
@@ -19,10 +84,9 @@ class JacClient:
19
84
  """Get the client bundle builder instance."""
20
85
  base_path = Path(Jac.base_path_dir)
21
86
  package_json_path = base_path / "package.json"
22
- output_dir = base_path / "static" / "client" / "js"
87
+ output_dir = base_path / "dist"
23
88
  # Use the plugin's client_runtime.jac file
24
89
  runtime_path = Path(__file__).with_name("client_runtime.jac")
25
- print(f"Runtime path: {runtime_path}")
26
90
  return ViteClientBundleBuilder(
27
91
  runtime_path=runtime_path,
28
92
  vite_package_json=package_json_path,
@@ -39,3 +103,48 @@ class JacClient:
39
103
  """Build a client bundle for the supplied module."""
40
104
  builder = JacClient.get_client_bundle_builder()
41
105
  return builder.build(module, force=force)
106
+
107
+ @staticmethod
108
+ @hookimpl
109
+ def get_module_introspector(
110
+ module_name: str, base_path: str | None
111
+ ) -> ModuleIntrospector:
112
+ """Get a module introspector for the supplied module."""
113
+ return JacClientModuleIntrospector(module_name, base_path)
114
+
115
+ @staticmethod
116
+ @hookimpl
117
+ def send_static_file(
118
+ handler: BaseHTTPRequestHandler,
119
+ file_path: Path,
120
+ content_type: str | None = None,
121
+ ) -> None:
122
+ """Send static file response (images, fonts, etc.).
123
+
124
+ Args:
125
+ handler: HTTP request handler
126
+ file_path: Path to the file to serve
127
+ content_type: MIME type (auto-detected if None)
128
+ """
129
+ from jaclang.runtimelib.server import ResponseBuilder
130
+
131
+ if not file_path.exists() or not file_path.is_file():
132
+ ResponseBuilder.send_json(handler, 404, {"error": "File not found"})
133
+ return
134
+
135
+ try:
136
+ file_content = file_path.read_bytes()
137
+ if content_type is None:
138
+ content_type, _ = mimetypes.guess_type(str(file_path))
139
+ if content_type is None:
140
+ content_type = "application/octet-stream"
141
+
142
+ handler.send_response(200)
143
+ handler.send_header("Content-Type", content_type)
144
+ handler.send_header("Content-Length", str(len(file_content)))
145
+ handler.send_header("Cache-Control", "public, max-age=3600")
146
+ ResponseBuilder._add_cors_headers(handler)
147
+ handler.end_headers()
148
+ handler.wfile.write(file_content)
149
+ except Exception as exc:
150
+ ResponseBuilder.send_json(handler, 500, {"error": str(exc)})