litestar-vite-plugin 0.15.0-alpha.6 → 0.15.0-alpha.7

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.
@@ -35,6 +35,13 @@ export interface TypesConfig {
35
35
  * @default 'routes.json'
36
36
  */
37
37
  routesPath?: string;
38
+ /**
39
+ * Path where Inertia page props metadata is exported by Litestar.
40
+ * The Vite plugin watches this file for page props type generation.
41
+ *
42
+ * @default 'inertia-pages.json'
43
+ */
44
+ pagePropsPath?: string;
38
45
  /**
39
46
  * Generate Zod schemas in addition to TypeScript types.
40
47
  *
package/dist/js/index.js CHANGED
@@ -197,10 +197,7 @@ function resolveLitestarPlugin(pluginConfig) {
197
197
  }
198
198
  const resourceDir = path.resolve(resolvedConfig.root, pluginConfig.resourceDirectory);
199
199
  if (!fs.existsSync(resourceDir) && typeof resolvedConfig.logger?.warn === "function") {
200
- resolvedConfig.logger.warn(
201
- `${colors.cyan("litestar-vite")} ${colors.yellow("Resource directory not found:")} ${resourceDir}
202
- Expected directory: ${colors.dim(pluginConfig.resourceDirectory)}`
203
- );
200
+ resolvedConfig.logger.warn(`${colors.cyan("litestar-vite")} ${colors.yellow("Resource directory not found:")} ${pluginConfig.resourceDirectory}`);
204
201
  }
205
202
  const hint = pluginConfig.types !== false ? pluginConfig.types.routesPath : void 0;
206
203
  litestarMeta = await loadLitestarMeta(resolvedConfig, hint);
@@ -320,12 +317,13 @@ function resolveLitestarPlugin(pluginConfig) {
320
317
  res.end(transformedHtml);
321
318
  return;
322
319
  } catch (e) {
323
- resolvedConfig.logger.error(`Error serving index.html from ${indexPath}: ${e instanceof Error ? e.message : e}`);
320
+ const relIndexPath = path.relative(server.config.root, indexPath);
321
+ resolvedConfig.logger.error(`Error serving index.html from ${relIndexPath}: ${e instanceof Error ? e.message : e}`);
324
322
  next(e);
325
323
  return;
326
324
  }
327
325
  }
328
- if (!indexPath && req.url === "/index.html") {
326
+ if (!indexPath && (req.url === "/" || req.url === "/index.html")) {
329
327
  try {
330
328
  const placeholderPath = path.join(dirname(), "dev-server-index.html");
331
329
  const placeholderContent = await fs.promises.readFile(placeholderPath, "utf-8");
@@ -474,6 +472,7 @@ function resolvePluginConfig(config) {
474
472
  output: "src/generated/types",
475
473
  openapiPath: "src/generated/openapi.json",
476
474
  routesPath: "src/generated/routes.json",
475
+ pagePropsPath: "src/generated/inertia-pages.json",
477
476
  generateZod: false,
478
477
  generateSdk: false,
479
478
  debounce: 300
@@ -485,6 +484,7 @@ function resolvePluginConfig(config) {
485
484
  output: pythonDefaults.types.output,
486
485
  openapiPath: pythonDefaults.types.openapiPath,
487
486
  routesPath: pythonDefaults.types.routesPath,
487
+ pagePropsPath: pythonDefaults.types.pagePropsPath ?? path.join(pythonDefaults.types.output, "inertia-pages.json"),
488
488
  generateZod: pythonDefaults.types.generateZod,
489
489
  generateSdk: pythonDefaults.types.generateSdk,
490
490
  debounce: 300
@@ -493,11 +493,13 @@ function resolvePluginConfig(config) {
493
493
  } else if (typeof resolvedConfig.types === "object" && resolvedConfig.types !== null) {
494
494
  const userProvidedOpenapi = Object.hasOwn(resolvedConfig.types, "openapiPath");
495
495
  const userProvidedRoutes = Object.hasOwn(resolvedConfig.types, "routesPath");
496
+ const userProvidedPageProps = Object.hasOwn(resolvedConfig.types, "pagePropsPath");
496
497
  typesConfig = {
497
498
  enabled: resolvedConfig.types.enabled ?? true,
498
499
  output: resolvedConfig.types.output ?? "src/generated/types",
499
500
  openapiPath: resolvedConfig.types.openapiPath ?? (resolvedConfig.types.output ? path.join(resolvedConfig.types.output, "openapi.json") : "src/generated/openapi.json"),
500
501
  routesPath: resolvedConfig.types.routesPath ?? (resolvedConfig.types.output ? path.join(resolvedConfig.types.output, "routes.json") : "src/generated/routes.json"),
502
+ pagePropsPath: resolvedConfig.types.pagePropsPath ?? (resolvedConfig.types.output ? path.join(resolvedConfig.types.output, "inertia-pages.json") : "src/generated/inertia-pages.json"),
501
503
  generateZod: resolvedConfig.types.generateZod ?? false,
502
504
  generateSdk: resolvedConfig.types.generateSdk ?? false,
503
505
  debounce: resolvedConfig.types.debounce ?? 300
@@ -508,6 +510,9 @@ function resolvePluginConfig(config) {
508
510
  if (!userProvidedRoutes && resolvedConfig.types.output) {
509
511
  typesConfig.routesPath = path.join(typesConfig.output, "routes.json");
510
512
  }
513
+ if (!userProvidedPageProps && resolvedConfig.types.output) {
514
+ typesConfig.pagePropsPath = path.join(typesConfig.output, "inertia-pages.json");
515
+ }
511
516
  }
512
517
  return {
513
518
  input: resolvedConfig.input,
@@ -564,6 +569,154 @@ function resolveFullReloadConfig({ refresh: config }) {
564
569
  return plugin;
565
570
  });
566
571
  }
572
+ async function emitPagePropsTypes(pagesPath, outputDir) {
573
+ const contents = await fs.promises.readFile(pagesPath, "utf-8");
574
+ const json = JSON.parse(contents);
575
+ const outDir = path.resolve(process.cwd(), outputDir);
576
+ await fs.promises.mkdir(outDir, { recursive: true });
577
+ const outFile = path.join(outDir, "page-props.ts");
578
+ const { includeDefaultAuth, includeDefaultFlash } = json.typeGenConfig;
579
+ let userTypes = "";
580
+ let authTypes = "";
581
+ let flashTypes = "";
582
+ if (includeDefaultAuth) {
583
+ userTypes = `/**
584
+ * Default User interface - minimal baseline for common auth patterns.
585
+ * Users extend this via module augmentation with their full user model.
586
+ *
587
+ * @example
588
+ * declare module 'litestar-vite/inertia' {
589
+ * interface User {
590
+ * avatarUrl?: string | null
591
+ * roles: Role[]
592
+ * teams: Team[]
593
+ * }
594
+ * }
595
+ */
596
+ export interface User {
597
+ id: string
598
+ email: string
599
+ name?: string | null
600
+ }
601
+
602
+ `;
603
+ authTypes = `/**
604
+ * Default AuthData interface - mirrors Laravel Jetstream pattern.
605
+ * isAuthenticated + optional user is the universal pattern.
606
+ */
607
+ export interface AuthData {
608
+ isAuthenticated: boolean
609
+ user?: User
610
+ }
611
+
612
+ `;
613
+ } else {
614
+ userTypes = `/**
615
+ * User interface - define via module augmentation.
616
+ * Default auth types are disabled.
617
+ *
618
+ * @example
619
+ * declare module 'litestar-vite/inertia' {
620
+ * interface User {
621
+ * uuid: string
622
+ * username: string
623
+ * }
624
+ * }
625
+ */
626
+ export interface User {}
627
+
628
+ `;
629
+ authTypes = `/**
630
+ * AuthData interface - define via module augmentation.
631
+ * Default auth types are disabled.
632
+ */
633
+ export interface AuthData {}
634
+
635
+ `;
636
+ }
637
+ if (includeDefaultFlash) {
638
+ flashTypes = `/**
639
+ * Default FlashMessages interface - category to messages mapping.
640
+ * Standard categories: success, error, info, warning.
641
+ */
642
+ export interface FlashMessages {
643
+ [category: string]: string[]
644
+ }
645
+
646
+ `;
647
+ } else {
648
+ flashTypes = `/**
649
+ * FlashMessages interface - define via module augmentation.
650
+ * Default flash types are disabled.
651
+ */
652
+ export interface FlashMessages {}
653
+
654
+ `;
655
+ }
656
+ const sharedPropsContent = includeDefaultAuth || includeDefaultFlash ? ` auth?: AuthData
657
+ flash?: FlashMessages` : "";
658
+ const pageEntries = [];
659
+ for (const [component, data] of Object.entries(json.pages)) {
660
+ const propsType = data.propsType ? data.propsType : "Record<string, unknown>";
661
+ pageEntries.push(` "${component}": ${propsType} & FullSharedProps`);
662
+ }
663
+ const body = `// AUTO-GENERATED by litestar-vite. Do not edit.
664
+ /* eslint-disable */
665
+
666
+ ${userTypes}${authTypes}${flashTypes}/**
667
+ * Generated shared props (always present).
668
+ * Includes built-in props + static config props.
669
+ */
670
+ export interface GeneratedSharedProps {
671
+ errors?: Record<string, string[]>
672
+ csrf_token?: string
673
+ }
674
+
675
+ /**
676
+ * User-defined shared props for dynamic share() calls in guards/middleware.
677
+ * Extend this interface via module augmentation.
678
+ *
679
+ * @example
680
+ * declare module 'litestar-vite/inertia' {
681
+ * interface User {
682
+ * avatarUrl?: string | null
683
+ * roles: Role[]
684
+ * teams: Team[]
685
+ * }
686
+ * interface SharedProps {
687
+ * locale?: string
688
+ * currentTeam?: CurrentTeam
689
+ * }
690
+ * }
691
+ */
692
+ export interface SharedProps {
693
+ ${sharedPropsContent}
694
+ }
695
+
696
+ /** Full shared props = generated + user-defined */
697
+ export type FullSharedProps = GeneratedSharedProps & SharedProps
698
+
699
+ /** Page props mapped by component name */
700
+ export interface PageProps {
701
+ ${pageEntries.join("\n")}
702
+ }
703
+
704
+ /** Component name union type */
705
+ export type ComponentName = keyof PageProps
706
+
707
+ /** Type-safe props for a specific component */
708
+ export type InertiaPageProps<C extends ComponentName> = PageProps[C]
709
+
710
+ /** Get props type for a specific page component */
711
+ export type PagePropsFor<C extends ComponentName> = PageProps[C]
712
+
713
+ // Re-export for module augmentation
714
+ declare module "litestar-vite/inertia" {
715
+ export { User, AuthData, FlashMessages, SharedProps, GeneratedSharedProps, FullSharedProps, PageProps, ComponentName, InertiaPageProps, PagePropsFor }
716
+ }
717
+ `;
718
+ await fs.promises.writeFile(outFile, body, "utf-8");
719
+ }
567
720
  async function emitRouteTypes(routesPath, outputDir) {
568
721
  const contents = await fs.promises.readFile(routesPath, "utf-8");
569
722
  const json = JSON.parse(contents);
@@ -689,6 +842,7 @@ export { getCsrfToken, csrfHeaders, csrfFetch } from "litestar-vite-plugin/helpe
689
842
  function resolveTypeGenerationPlugin(typesConfig, executor, hasPythonConfig) {
690
843
  let lastTypesHash = null;
691
844
  let lastRoutesHash = null;
845
+ let lastPagePropsHash = null;
692
846
  let server = null;
693
847
  let isGenerating = false;
694
848
  let resolvedConfig = null;
@@ -703,6 +857,7 @@ function resolveTypeGenerationPlugin(typesConfig, executor, hasPythonConfig) {
703
857
  const projectRoot = resolvedConfig?.root ?? process.cwd();
704
858
  const openapiPath = path.resolve(projectRoot, typesConfig.openapiPath);
705
859
  const routesPath = path.resolve(projectRoot, typesConfig.routesPath);
860
+ const pagePropsPath = path.resolve(projectRoot, typesConfig.pagePropsPath);
706
861
  let generated = false;
707
862
  const candidates = [path.resolve(projectRoot, "openapi-ts.config.ts"), path.resolve(projectRoot, "hey-api.config.ts"), path.resolve(projectRoot, ".hey-api.config.ts")];
708
863
  const configPath = candidates.find((p) => fs.existsSync(p)) || null;
@@ -711,7 +866,8 @@ function resolveTypeGenerationPlugin(typesConfig, executor, hasPythonConfig) {
711
866
  if (fs.existsSync(openapiPath) && shouldRunOpenApiTs) {
712
867
  resolvedConfig?.logger.info(`${colors.cyan("litestar-vite")} ${colors.dim("generating TypeScript types...")}`);
713
868
  if (resolvedConfig) {
714
- resolvedConfig.logger.info(`${colors.cyan("litestar-vite")} ${colors.dim("openapi-ts config: ")}${configPath ?? "<built-in defaults>"}`);
869
+ const relConfigPath = configPath ? path.relative(resolvedConfig.root, configPath) : null;
870
+ resolvedConfig.logger.info(`${colors.cyan("litestar-vite")} ${colors.dim("openapi-ts config: ")}${relConfigPath ?? "<built-in defaults>"}`);
715
871
  }
716
872
  const sdkOutput = path.join(typesConfig.output, "api");
717
873
  let args;
@@ -746,6 +902,10 @@ function resolveTypeGenerationPlugin(typesConfig, executor, hasPythonConfig) {
746
902
  await emitRouteTypes(routesPath, typesConfig.output);
747
903
  generated = true;
748
904
  }
905
+ if (fs.existsSync(pagePropsPath)) {
906
+ await emitPagePropsTypes(pagePropsPath, typesConfig.output);
907
+ generated = true;
908
+ }
749
909
  if (generated && resolvedConfig) {
750
910
  const duration = Date.now() - startTime;
751
911
  resolvedConfig.logger.info(`${colors.cyan("litestar-vite")} ${colors.green("TypeScript artifacts updated")} ${colors.dim(`in ${duration}ms`)}`);
@@ -789,11 +949,12 @@ function resolveTypeGenerationPlugin(typesConfig, executor, hasPythonConfig) {
789
949
  server = devServer;
790
950
  if (typesConfig.enabled) {
791
951
  const root = resolvedConfig?.root ?? process.cwd();
792
- const openapiAbs = path.resolve(root, typesConfig.openapiPath);
793
- const routesAbs = path.resolve(root, typesConfig.routesPath);
794
- resolvedConfig?.logger.info(`${colors.cyan("litestar-vite")} ${colors.dim("watching schema/routes:")} ${colors.yellow(openapiAbs)}, ${colors.yellow(routesAbs)}`);
952
+ const openapiRel = typesConfig.openapiPath;
953
+ const routesRel = typesConfig.routesPath;
954
+ resolvedConfig?.logger.info(`${colors.cyan("litestar-vite")} ${colors.dim("watching schema/routes:")} ${colors.yellow(openapiRel)}, ${colors.yellow(routesRel)}`);
795
955
  if (chosenConfigPath) {
796
- resolvedConfig?.logger.info(`${colors.cyan("litestar-vite")} ${colors.dim("openapi-ts config:")} ${colors.yellow(chosenConfigPath)}`);
956
+ const relConfigPath = path.relative(root, chosenConfigPath);
957
+ resolvedConfig?.logger.info(`${colors.cyan("litestar-vite")} ${colors.dim("openapi-ts config:")} ${colors.yellow(relConfigPath)}`);
797
958
  }
798
959
  }
799
960
  },
@@ -831,17 +992,24 @@ Solutions:
831
992
  const relativePath = path.relative(root, file);
832
993
  const openapiPath = typesConfig.openapiPath.replace(/^\.\//, "");
833
994
  const routesPath = typesConfig.routesPath.replace(/^\.\//, "");
834
- if (relativePath === openapiPath || relativePath === routesPath || file.endsWith(openapiPath) || file.endsWith(routesPath)) {
995
+ const pagePropsPath = typesConfig.pagePropsPath.replace(/^\.\//, "");
996
+ const isOpenapi = relativePath === openapiPath || file.endsWith(openapiPath);
997
+ const isRoutes = relativePath === routesPath || file.endsWith(routesPath);
998
+ const isPageProps = relativePath === pagePropsPath || file.endsWith(pagePropsPath);
999
+ if (isOpenapi || isRoutes || isPageProps) {
835
1000
  if (resolvedConfig) {
836
1001
  resolvedConfig.logger.info(`${colors.cyan("litestar-vite")} ${colors.dim("schema changed:")} ${colors.yellow(relativePath)}`);
837
1002
  }
838
1003
  const newHash = await hashFile(file);
839
- if (relativePath === openapiPath) {
1004
+ if (isOpenapi) {
840
1005
  if (lastTypesHash === newHash) return;
841
1006
  lastTypesHash = newHash;
842
- } else {
1007
+ } else if (isRoutes) {
843
1008
  if (lastRoutesHash === newHash) return;
844
1009
  lastRoutesHash = newHash;
1010
+ } else if (isPageProps) {
1011
+ if (lastPagePropsHash === newHash) return;
1012
+ lastPagePropsHash = newHash;
845
1013
  }
846
1014
  debouncedRunTypeGeneration();
847
1015
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litestar-vite-plugin",
3
- "version": "0.15.0-alpha.6",
3
+ "version": "0.15.0-alpha.7",
4
4
  "type": "module",
5
5
  "description": "Litestar plugin for Vite.",
6
6
  "keywords": [