nestjs-openapi 0.2.2 → 0.3.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.
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
- import { S as SpecFileNotFoundError, a as SpecFileReadError, b as SpecFileParseError } from './shared/nestjs-openapi.DeikubMm.mjs';
2
- export { c as ConfigLoadError, C as ConfigNotFoundError, ae as ConfigService, e as ConfigValidationError, D as DtoGlobResolutionError, E as EntryNotFoundError, I as InvalidMethodError, B as MethodExtractionService, M as MissingGenericSchemaTempFileCleanupError, f as MissingGenericSchemaTempFileWriteError, n as ModuleTraversalService, ad as OutputService, P as ProjectInitError, i as ProjectService, j as ProjectServiceLive, h as PublicApiError, a0 as SchemaGenerationError, a1 as SchemaService, ac as ValidationService, a9 as applyConstraintsToSchema, av as categorizeBrokenRefs, d as defineConfig, a7 as extractClassConstraints, a5 as extractClassValidationInfo, a6 as extractClassValidationInfoEffect, a2 as extractPropertyConstraints, a4 as extractPropertyValidationInfo, V as filterInternalSchemas, W as filterInternalSchemasEffect, Q as filterSchemas, R as filterSchemasEffect, af as findConfigFile, aw as formatValidationResult, g as generate, _ as generateSchemas, $ as generateSchemasFromFiles, ak as generatorServicesLayer, l as getAllControllers, am as getArrayInitializer, z as getControllerMethodInfos, A as getControllerMethodInfosEffect, p as getControllerName, o as getControllerPrefix, t as getControllerTags, s as getDecoratorName, u as getHttpDecorator, r as getHttpMethods, x as getMethodInfo, y as getMethodInfoEffect, aq as getModuleDecoratorArg, at as getModuleMetadata, k as getModules, a8 as getRequiredProperties, an as getStringLiteralValue, ao as getSymbolFromIdentifier, v as isHttpDecorator, q as isHttpMethod, ap as isModuleClass, a3 as isPropertyOptional, aj as loadAndResolveConfig, ah as loadConfig, ag as loadConfigFromFile, m as makeProjectContext, N as mergeGeneratedSchemas, O as mergeGeneratedSchemasEffect, K as mergeSchemas, L as mergeSchemasEffect, aa as mergeValidationConstraints, ab as mergeValidationConstraintsEffect, w as normalizePath, T as normalizeSchemas, U as normalizeSchemasEffect, X as normalizeStructureRefs, Y as normalizeStructureRefsEffect, as as resolveArrayOfClasses, ar as resolveClassFromExpression, al as resolveClassFromSymbol, ai as resolveConfig, Z as toPascalCase, F as transformMethod, G as transformMethodEffect, H as transformMethods, J as transformMethodsEffect, au as validateSpec } from './shared/nestjs-openapi.DeikubMm.mjs';
1
+ import { S as SpecFileNotFoundError, a as SpecFileParseError, b as SpecFileReadError } from './shared/nestjs-openapi.Nd-wGr8A.mjs';
2
+ export { c as ConfigLoadError, C as ConfigNotFoundError, ae as ConfigService, e as ConfigValidationError, D as DtoGlobResolutionError, E as EntryNotFoundError, I as InvalidMethodError, B as MethodExtractionService, M as MissingGenericSchemaTempFileCleanupError, f as MissingGenericSchemaTempFileWriteError, n as ModuleTraversalService, ad as OutputService, P as ProjectInitError, i as ProjectService, j as ProjectServiceLive, h as PublicApiError, a0 as SchemaGenerationError, a1 as SchemaService, ac as ValidationService, a9 as applyConstraintsToSchema, av as categorizeBrokenRefs, d as defineConfig, a7 as extractClassConstraints, a5 as extractClassValidationInfo, a6 as extractClassValidationInfoEffect, a2 as extractPropertyConstraints, a4 as extractPropertyValidationInfo, V as filterInternalSchemas, W as filterInternalSchemasEffect, Q as filterSchemas, R as filterSchemasEffect, af as findConfigFile, aw as formatValidationResult, g as generate, _ as generateSchemas, $ as generateSchemasFromFiles, ak as generatorServicesLayer, l as getAllControllers, am as getArrayInitializer, z as getControllerMethodInfos, A as getControllerMethodInfosEffect, p as getControllerName, o as getControllerPrefix, t as getControllerTags, s as getDecoratorName, u as getHttpDecorator, r as getHttpMethods, x as getMethodInfo, y as getMethodInfoEffect, aq as getModuleDecoratorArg, at as getModuleMetadata, k as getModules, a8 as getRequiredProperties, an as getStringLiteralValue, ao as getSymbolFromIdentifier, v as isHttpDecorator, q as isHttpMethod, ap as isModuleClass, a3 as isPropertyOptional, aj as loadAndResolveConfig, ah as loadConfig, ag as loadConfigFromFile, m as makeProjectContext, N as mergeGeneratedSchemas, O as mergeGeneratedSchemasEffect, K as mergeSchemas, L as mergeSchemasEffect, aa as mergeValidationConstraints, ab as mergeValidationConstraintsEffect, w as normalizePath, T as normalizeSchemas, U as normalizeSchemasEffect, X as normalizeStructureRefs, Y as normalizeStructureRefsEffect, as as resolveArrayOfClasses, ar as resolveClassFromExpression, al as resolveClassFromSymbol, ai as resolveConfig, Z as toPascalCase, F as transformMethod, G as transformMethodEffect, H as transformMethods, J as transformMethodsEffect, au as validateSpec } from './shared/nestjs-openapi.Nd-wGr8A.mjs';
3
3
  import { readFileSync } from 'node:fs';
4
4
  import { fail } from 'node:assert';
5
5
  import { resolve } from 'node:path';
6
6
  import { Module } from '@nestjs/common';
7
- import { Effect, Exit, Cause, Option } from 'effect';
7
+ import { Effect, Either, Exit, Cause, Option } from 'effect';
8
8
  export { generateAsync, generate as generateEffect, generateFromConfigAsync, generateFromConfigEffect, generatePathsAsync, generatePathsEffect } from './internal.mjs';
9
9
  import 'node:crypto';
10
10
  import 'ts-morph';
@@ -94,21 +94,40 @@ function generateSwaggerUiHtml(title, jsonPath) {
94
94
  </body>
95
95
  </html>`;
96
96
  }
97
+ function isErrorWithCode(cause, code) {
98
+ return cause !== null && typeof cause === "object" && "code" in cause && cause.code === code;
99
+ }
100
+ function getSpecFileCandidates(filePath, options = {}) {
101
+ const candidates = [filePath];
102
+ candidates.push(...options.fallbackSpecFiles ?? []);
103
+ return [...new Set(candidates)];
104
+ }
105
+ const readSpecFileContentEffect = (filePath) => Effect.try({
106
+ try: () => readFileSync(resolve(process.cwd(), filePath), "utf-8"),
107
+ catch: (cause) => isErrorWithCode(cause, "ENOENT") ? SpecFileNotFoundError.create(filePath) : SpecFileReadError.create(filePath, cause)
108
+ });
97
109
  const loadSpecFileEffect = Effect.fn("OpenApiModule.loadSpecFile")(
98
- function* (filePath) {
99
- const resolvedPath = resolve(process.cwd(), filePath);
100
- const content = yield* Effect.try({
101
- try: () => readFileSync(resolvedPath, "utf-8"),
102
- catch: (cause) => cause && typeof cause === "object" && "code" in cause && cause.code === "ENOENT" ? SpecFileNotFoundError.create(filePath) : SpecFileReadError.create(filePath, cause)
103
- });
104
- return yield* Effect.try({
105
- try: () => JSON.parse(content),
106
- catch: (cause) => SpecFileParseError.create(filePath, cause)
107
- });
110
+ function* (filePath, options = {}) {
111
+ for (const candidate of getSpecFileCandidates(filePath, options)) {
112
+ const contentResult = yield* readSpecFileContentEffect(candidate).pipe(
113
+ Effect.either
114
+ );
115
+ if (Either.isLeft(contentResult)) {
116
+ if (contentResult.left instanceof SpecFileNotFoundError) {
117
+ continue;
118
+ }
119
+ return yield* Effect.fail(contentResult.left);
120
+ }
121
+ return yield* Effect.try({
122
+ try: () => JSON.parse(contentResult.right),
123
+ catch: (cause) => SpecFileParseError.create(candidate, cause)
124
+ });
125
+ }
126
+ return yield* Effect.fail(SpecFileNotFoundError.create(filePath));
108
127
  }
109
128
  );
110
- function loadSpecFile(filePath) {
111
- const exit = Effect.runSyncExit(loadSpecFileEffect(filePath));
129
+ function loadSpecFile(filePath, options = {}) {
130
+ const exit = Effect.runSyncExit(loadSpecFileEffect(filePath, options));
112
131
  if (Exit.isSuccess(exit)) {
113
132
  return exit.value;
114
133
  }
@@ -131,6 +150,7 @@ function resolveOptions(options) {
131
150
  }
132
151
  return {
133
152
  specFile: options.specFile,
153
+ fallbackSpecFiles: options.fallbackSpecFiles ?? [],
134
154
  enabled: options.enabled ?? true,
135
155
  jsonPath: options.jsonPath ?? "/openapi.json",
136
156
  swagger: {
@@ -140,6 +160,26 @@ function resolveOptions(options) {
140
160
  }
141
161
  };
142
162
  }
163
+ function resolveOptionsWithSpecTitle(options, spec) {
164
+ return {
165
+ ...options,
166
+ swagger: {
167
+ ...options.swagger,
168
+ title: options.swagger.title || spec.info.title
169
+ }
170
+ };
171
+ }
172
+ function createOpenApiModuleState(options) {
173
+ const resolvedOptions = resolveOptions(options);
174
+ if (!resolvedOptions.enabled) {
175
+ return { options: resolvedOptions };
176
+ }
177
+ const spec = loadSpecFile(resolvedOptions.specFile, resolvedOptions);
178
+ return {
179
+ spec,
180
+ options: resolveOptionsWithSpecTitle(resolvedOptions, spec)
181
+ };
182
+ }
143
183
  _OpenApiModule_decorators = [Module({})];
144
184
  let _OpenApiModule = class _OpenApiModule {
145
185
  /**
@@ -149,8 +189,8 @@ let _OpenApiModule = class _OpenApiModule {
149
189
  * @returns Dynamic module configuration
150
190
  */
151
191
  static forRoot(options) {
152
- const resolvedOptions = resolveOptions(options);
153
- if (!resolvedOptions.enabled) {
192
+ const state = createOpenApiModuleState(options);
193
+ if (!state.options.enabled || !state.spec) {
154
194
  return {
155
195
  module: _OpenApiModule,
156
196
  providers: [],
@@ -158,25 +198,17 @@ let _OpenApiModule = class _OpenApiModule {
158
198
  exports: []
159
199
  };
160
200
  }
161
- const spec = loadSpecFile(resolvedOptions.specFile);
162
- const finalOptions = {
163
- ...resolvedOptions,
164
- swagger: {
165
- ...resolvedOptions.swagger,
166
- title: resolvedOptions.swagger.title || spec.info.title
167
- }
168
- };
169
201
  const providers = [
170
202
  {
171
203
  provide: OPENAPI_MODULE_OPTIONS,
172
- useValue: finalOptions
204
+ useValue: state.options
173
205
  },
174
206
  {
175
207
  provide: OPENAPI_SPEC,
176
- useValue: spec
208
+ useValue: state.spec
177
209
  }
178
210
  ];
179
- const controllers = createOpenApiControllers(finalOptions, spec);
211
+ const controllers = createOpenApiControllers(state.options, state.spec);
180
212
  return {
181
213
  module: _OpenApiModule,
182
214
  providers,
@@ -184,11 +216,97 @@ let _OpenApiModule = class _OpenApiModule {
184
216
  exports: [OPENAPI_MODULE_OPTIONS, OPENAPI_SPEC]
185
217
  };
186
218
  }
219
+ /**
220
+ * Register OpenAPI JSON and Swagger UI routes on an already-created Nest app.
221
+ *
222
+ * Use this when the route config depends on services that are only available
223
+ * during bootstrap. For module-level static config, prefer `forRoot()`.
224
+ */
225
+ static setup(path, app, documentSource, options = {}) {
226
+ if (options.enabled === false) {
227
+ return;
228
+ }
229
+ const spec = loadDocumentSource(documentSource);
230
+ const httpAdapter = app.getHttpAdapter();
231
+ const swaggerPath = resolveSetupPath(path, app, options);
232
+ const jsonPath = resolveJsonDocumentUrl(swaggerPath, app, options);
233
+ if (shouldServeJson(options.raw)) {
234
+ httpAdapter.get(jsonPath, (_request, response) => {
235
+ sendResponse(response, spec, "application/json");
236
+ });
237
+ }
238
+ if (options.ui !== false) {
239
+ httpAdapter.get(swaggerPath, (_request, response) => {
240
+ sendResponse(
241
+ response,
242
+ generateSwaggerUiHtml(
243
+ options.customSiteTitle ?? spec.info.title,
244
+ jsonPath
245
+ ),
246
+ "text/html"
247
+ );
248
+ });
249
+ }
250
+ }
187
251
  };
188
252
  _init = __decoratorStart();
189
253
  _OpenApiModule = __decorateElement(_init, 0, "OpenApiModule", _OpenApiModule_decorators, _OpenApiModule);
190
254
  __runInitializers(_init, 1, _OpenApiModule);
191
255
  let OpenApiModule = _OpenApiModule;
256
+ function loadDocumentSource(source) {
257
+ if (typeof source === "function") {
258
+ return source();
259
+ }
260
+ if (isDocumentFileSource(source)) {
261
+ return loadSpecFile(source.specFile, source);
262
+ }
263
+ return source;
264
+ }
265
+ function isDocumentFileSource(source) {
266
+ return source !== null && typeof source === "object" && "specFile" in source && typeof source.specFile === "string";
267
+ }
268
+ function resolveSetupPath(path, app, options) {
269
+ return joinRoutePath(
270
+ options.useGlobalPrefix === true ? getGlobalPrefix(app) : void 0,
271
+ path
272
+ );
273
+ }
274
+ function resolveJsonDocumentUrl(finalSwaggerPath, app, options) {
275
+ if (!options.jsonDocumentUrl) {
276
+ return `${finalSwaggerPath}-json`;
277
+ }
278
+ return joinRoutePath(
279
+ options.useGlobalPrefix === true ? getGlobalPrefix(app) : void 0,
280
+ options.jsonDocumentUrl
281
+ );
282
+ }
283
+ function getGlobalPrefix(app) {
284
+ const appWithConfig = app;
285
+ return appWithConfig.config?.getGlobalPrefix?.() ?? "";
286
+ }
287
+ function shouldServeJson(raw) {
288
+ return raw === void 0 || raw === true || Array.isArray(raw) && raw.length > 0;
289
+ }
290
+ function joinRoutePath(...parts) {
291
+ return `/${parts.filter((part) => part !== void 0 && part !== "").map((part) => part.replace(/^\/+|\/+$/g, "")).filter((part) => part.length > 0).join("/")}`;
292
+ }
293
+ function sendResponse(response, body, contentType) {
294
+ const responseLike = response;
295
+ responseLike.setHeader?.("Content-Type", contentType);
296
+ if (contentType === "application/json" && responseLike.json) {
297
+ responseLike.json(body);
298
+ return;
299
+ }
300
+ if (responseLike.type && responseLike.send) {
301
+ responseLike.type(contentType).send(body);
302
+ return;
303
+ }
304
+ if (responseLike.send) {
305
+ responseLike.send(body);
306
+ return;
307
+ }
308
+ responseLike.end?.(typeof body === "string" ? body : JSON.stringify(body));
309
+ }
192
310
  function createJsonController(controllerPath, spec) {
193
311
  class JsonSpecController {
194
312
  getSpec() {
@@ -1,3 +1,3 @@
1
- export { E as GenerateOptions, x as generate, y as generateAsync, D as generateFromConfigAsync, B as generateFromConfigEffect, A as generatePathsAsync, z as generatePathsEffect } from './shared/nestjs-openapi.NtbZNAvU.mjs';
1
+ export { E as GenerateOptions, x as generate, y as generateAsync, D as generateFromConfigAsync, B as generateFromConfigEffect, A as generatePathsAsync, z as generatePathsEffect } from './shared/nestjs-openapi.CAanamW0.mjs';
2
2
  import 'effect';
3
3
  import 'ts-morph';
@@ -1,3 +1,3 @@
1
- export { E as GenerateOptions, x as generate, y as generateAsync, D as generateFromConfigAsync, B as generateFromConfigEffect, A as generatePathsAsync, z as generatePathsEffect } from './shared/nestjs-openapi.NtbZNAvU.js';
1
+ export { E as GenerateOptions, x as generate, y as generateAsync, D as generateFromConfigAsync, B as generateFromConfigEffect, A as generatePathsAsync, z as generatePathsEffect } from './shared/nestjs-openapi.CAanamW0.js';
2
2
  import 'effect';
3
3
  import 'ts-morph';
package/dist/internal.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Effect } from 'effect';
2
- import { i as ProjectService, n as ModuleTraversalService, B as MethodExtractionService, J as transformMethodsEffect, ax as generateEffect, ay as runProjectApiPromise, ak as generatorServicesLayer, az as runtimeLayerFor, aA as runGeneratorApiPromise } from './shared/nestjs-openapi.DeikubMm.mjs';
2
+ import { i as ProjectService, n as ModuleTraversalService, B as MethodExtractionService, J as transformMethodsEffect, ax as generateEffect, ay as runProjectApiPromise, ak as generatorServicesLayer, az as runtimeLayerFor, aA as runGeneratorApiPromise } from './shared/nestjs-openapi.Nd-wGr8A.mjs';
3
3
  import 'node:fs';
4
4
  import 'node:path';
5
5
  import 'node:crypto';