@dudousxd/nestjs-codegen 0.14.1 → 0.15.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.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-DT8SgPxp.cjs';
2
- export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-DT8SgPxp.cjs';
1
+ import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-CjIDPMsV.cjs';
2
+ export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-CjIDPMsV.cjs';
3
3
  import { ClassDeclaration, SourceFile, Project } from 'ts-morph';
4
4
 
5
5
  declare function defineConfig(c: UserConfig): UserConfig;
@@ -73,6 +73,12 @@ interface CodegenManifest {
73
73
  * unrelated source-file changes. Absent on pre-drift-guard manifests.
74
74
  */
75
75
  configHash?: string;
76
+ /**
77
+ * Per-top-level-key hashes of the serialized resolved config, so a drift-guard
78
+ * failure can NAME the keys that differ instead of asking the consumer to
79
+ * eyeball two whole configs. Absent on manifests written before this field.
80
+ */
81
+ configKeyHashes?: Record<string, string>;
76
82
  /** Generated output files, relative to `outDir`, recorded after the last run. */
77
83
  files: string[];
78
84
  }
@@ -367,6 +373,6 @@ interface FastDiscoveryOptions {
367
373
  }
368
374
  declare function discoverContractsFast(opts: FastDiscoveryOptions): Promise<RouteDescriptor[]>;
369
375
 
370
- declare const VERSION = "0.14.1";
376
+ declare const VERSION = "0.15.0";
371
377
 
372
378
  export { type ChainModuleRendererOptions, CodegenError, type CodegenManifest, ConfigError, DriftGuardError, type EntryPoint, type FastDiscoveryOptions, type JsonSchema, type MocksEmitOptions, type OpenApiDocument, type OpenApiEmitOptions, type OpenApiInfo, RenderContext, RenderedModule, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type WatchOptions, type Watcher, acquireLock, buildMocksFile, buildOpenApiSpec, createChainModuleRenderer, defineConfig, discoverContractsFast, emitApi, emitForms, emitMocks, emitOpenApi, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, schemaModuleToJsonSchema, schemaNodeToJsonSchema, toObjectKey, typeNameFor, watch };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-DT8SgPxp.js';
2
- export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-DT8SgPxp.js';
1
+ import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-CjIDPMsV.js';
2
+ export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-CjIDPMsV.js';
3
3
  import { ClassDeclaration, SourceFile, Project } from 'ts-morph';
4
4
 
5
5
  declare function defineConfig(c: UserConfig): UserConfig;
@@ -73,6 +73,12 @@ interface CodegenManifest {
73
73
  * unrelated source-file changes. Absent on pre-drift-guard manifests.
74
74
  */
75
75
  configHash?: string;
76
+ /**
77
+ * Per-top-level-key hashes of the serialized resolved config, so a drift-guard
78
+ * failure can NAME the keys that differ instead of asking the consumer to
79
+ * eyeball two whole configs. Absent on manifests written before this field.
80
+ */
81
+ configKeyHashes?: Record<string, string>;
76
82
  /** Generated output files, relative to `outDir`, recorded after the last run. */
77
83
  files: string[];
78
84
  }
@@ -367,6 +373,6 @@ interface FastDiscoveryOptions {
367
373
  }
368
374
  declare function discoverContractsFast(opts: FastDiscoveryOptions): Promise<RouteDescriptor[]>;
369
375
 
370
- declare const VERSION = "0.14.1";
376
+ declare const VERSION = "0.15.0";
371
377
 
372
378
  export { type ChainModuleRendererOptions, CodegenError, type CodegenManifest, ConfigError, DriftGuardError, type EntryPoint, type FastDiscoveryOptions, type JsonSchema, type MocksEmitOptions, type OpenApiDocument, type OpenApiEmitOptions, type OpenApiInfo, RenderContext, RenderedModule, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type WatchOptions, type Watcher, acquireLock, buildMocksFile, buildOpenApiSpec, createChainModuleRenderer, defineConfig, discoverContractsFast, emitApi, emitForms, emitMocks, emitOpenApi, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, schemaModuleToJsonSchema, schemaNodeToJsonSchema, toObjectKey, typeNameFor, watch };
package/dist/index.js CHANGED
@@ -746,6 +746,9 @@ function buildErrorType(c) {
746
746
  }
747
747
  return c.contractSource.error ?? "unknown";
748
748
  }
749
+ function filterFieldLiterals(fields) {
750
+ return fields?.length ? fields.map((f) => JSON.stringify(f)) : [];
751
+ }
749
752
  function emitRouterTypeBlock(tree, indent, outDir, serialization) {
750
753
  const pad = " ".repeat(indent);
751
754
  const lines = [];
@@ -772,7 +775,8 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
772
775
  const params = buildParamsType(c.params);
773
776
  const safeMethod = JSON.stringify(method);
774
777
  const safeUrl = JSON.stringify(c.path);
775
- const filterFields = c.contractSource.filterFields?.length ? c.contractSource.filterFields.map((f) => JSON.stringify(f)).join(" | ") : "never";
778
+ const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
779
+ const filterFields = filterLiterals.length ? filterLiterals.join(" | ") : "never";
776
780
  const stream = c.contractSource.stream ? "true" : "false";
777
781
  const binary = c.contractSource.binaryResponse ? "true" : "false";
778
782
  lines.push(
@@ -812,6 +816,7 @@ function buildRequestModel(c) {
812
816
  const TA = buildRouterTypeAccess(c.name);
813
817
  const withParams = hasPathParams(c.params);
814
818
  const { isGet, isQuery, hasBody, hasQuery } = requestShape(c.route);
819
+ const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
815
820
  const fields = [];
816
821
  if (withParams) fields.push(`params: ${TA}['params']`);
817
822
  if (hasQuery) fields.push(`query?: ${TA}['query']`);
@@ -841,7 +846,12 @@ function buildRequestModel(c) {
841
846
  // (`[name]` rather than `[name, undefined]`) so the bare `.queryKey()` is a
842
847
  // clean prefix that partial-matches every parametrized variant — making it
843
848
  // directly usable for `invalidateQueries`.
844
- queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)`
849
+ queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)`,
850
+ // Runtime counterpart to the type-level `filterFields` union: the same
851
+ // discovered field list, emitted as a literal `[...] as const` so apps can
852
+ // validate a dynamic/user-supplied field string with `isFilterField(...)`
853
+ // instead of casting. Omitted for routes with no filter.
854
+ ...filterLiterals.length ? { filterFieldsExpr: `[${filterLiterals.join(", ")}] as const` } : {}
845
855
  };
846
856
  }
847
857
  function renderFetcherRequest(req, binaryResponse) {
@@ -876,9 +886,24 @@ function emitReqHelper() {
876
886
  ""
877
887
  ];
878
888
  }
889
+ function emitFilterFieldGuard() {
890
+ return [
891
+ "/** Runtime guard: narrows `value` to one of the leaf's `filterFields` (a `readonly K[] as const`), so a dynamic field string can be passed to `.where()` without a cast. */",
892
+ "export function isFilterField<const K extends string>(",
893
+ " fields: readonly K[],",
894
+ " value: string,",
895
+ "): value is K {",
896
+ " return (fields as readonly string[]).includes(value);",
897
+ "}",
898
+ ""
899
+ ];
900
+ }
879
901
  function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
880
902
  const lines = [`${pad}${objKey}: (input?: ${req.inputType}) => ({`];
881
903
  lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
904
+ if (req.filterFieldsExpr) {
905
+ lines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
906
+ }
882
907
  if (streamExpr) {
883
908
  lines.push(`${pad} stream: () => ${streamExpr},`);
884
909
  }
@@ -1150,6 +1175,9 @@ function buildApiFile(routes, outDir, opts = {}) {
1150
1175
  lines.push("};");
1151
1176
  lines.push("");
1152
1177
  lines.push(...emitReqHelper());
1178
+ if (contracted.some((r) => r.contract?.contractSource.filterFields?.length)) {
1179
+ lines.push(...emitFilterFieldGuard());
1180
+ }
1153
1181
  lines.push("export function createApi(fetcher: Fetcher) {");
1154
1182
  lines.push(" return {");
1155
1183
  lines.push(
@@ -2159,19 +2187,41 @@ function isManifestShape(value) {
2159
2187
  if (typeof candidate.hash !== "string") return false;
2160
2188
  if (candidate.entryPoint !== void 0 && !isEntryPoint(candidate.entryPoint)) return false;
2161
2189
  if (candidate.configHash !== void 0 && typeof candidate.configHash !== "string") return false;
2190
+ if (candidate.configKeyHashes !== void 0 && !isStringRecord(candidate.configKeyHashes)) {
2191
+ return false;
2192
+ }
2162
2193
  if (!Array.isArray(candidate.files)) return false;
2163
2194
  return candidate.files.every((entry) => typeof entry === "string");
2164
2195
  }
2196
+ function isStringRecord(value) {
2197
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
2198
+ return Object.values(value).every((entry) => typeof entry === "string");
2199
+ }
2165
2200
  function serializeConfig(config) {
2201
+ return serializeConfigValue(config, `unserializable:${config.codegen.outDir}`);
2202
+ }
2203
+ function serializeConfigValue(value, unserializableMarker) {
2166
2204
  try {
2167
- return JSON.stringify(config, (_key, value) => {
2168
- if (typeof value === "function") return `[fn:${value.name}]${value.toString()}`;
2169
- return value;
2205
+ return JSON.stringify(value, (_key, entry) => {
2206
+ if (typeof entry === "function") return `[fn:${entry.name}]`;
2207
+ return entry;
2170
2208
  });
2171
2209
  } catch {
2172
- return `unserializable:${config.codegen.outDir}:${config.contracts.glob}`;
2210
+ return unserializableMarker;
2173
2211
  }
2174
2212
  }
2213
+ function computeConfigKeyHashes(config) {
2214
+ const hashes = {};
2215
+ for (const [key, value] of Object.entries(config)) {
2216
+ if (value === void 0) continue;
2217
+ hashes[key] = createHash("sha256").update(serializeConfigValue(value, `unserializable:${key}`)).digest("hex");
2218
+ }
2219
+ return hashes;
2220
+ }
2221
+ function diffConfigKeyHashes(previous, current) {
2222
+ const keys = /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(current)]);
2223
+ return [...keys].filter((key) => previous[key] !== current[key]).sort();
2224
+ }
2175
2225
  async function discoverInputFiles(config) {
2176
2226
  const globs = [config.contracts.glob, config.forms.watch];
2177
2227
  if (config.pages) globs.push(config.pages.glob);
@@ -2206,6 +2256,7 @@ async function readManifest(outDir) {
2206
2256
  hash: parsed.hash,
2207
2257
  ...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
2208
2258
  ...parsed.configHash ? { configHash: parsed.configHash } : {},
2259
+ ...parsed.configKeyHashes ? { configKeyHashes: parsed.configKeyHashes } : {},
2209
2260
  files: parsed.files
2210
2261
  };
2211
2262
  } catch {
@@ -2259,8 +2310,9 @@ function debugWarn(message) {
2259
2310
  }
2260
2311
 
2261
2312
  // src/generate.ts
2262
- function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint) {
2263
- return `[nestjs-codegen] Config drift detected in "${outDir}": the last generate ran from the "${previousEntryPoint}" entry point, this run is from the "${currentEntryPoint}" entry point, and their resolved configs differ (e.g. \`serialization: "json"\` vs \`"superjson"\`). Both entry points must read the SAME config \u2014 export a shared config object (e.g. codegen.config.ts) and import it from BOTH nestjs-codegen.config.ts (CLI) and NestjsCodegenModule.forRoot() (Nest module), or set \`driftGuard: false\` on either config to opt out of this check.`;
2313
+ function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differingKeys) {
2314
+ const differ = differingKeys.length > 0 ? `their resolved configs differ at: ${differingKeys.map((key) => `\`${key}\``).join(", ")}` : "their resolved configs differ (re-run after this generate records per-key hashes to see which keys)";
2315
+ return `[nestjs-codegen] Config drift detected in "${outDir}": the last generate ran from the "${previousEntryPoint}" entry point, this run is from the "${currentEntryPoint}" entry point, and ${differ}. Both entry points must read the SAME config \u2014 export a shared config object (e.g. codegen.config.ts) and import it from BOTH nestjs-codegen.config.ts (CLI) and NestjsCodegenModule.forRoot() (Nest module), or set \`driftGuard: false\` on either config to opt out of this check.`;
2264
2316
  }
2265
2317
  async function generate(config, inputRoutes = [], entryPoint = "cli") {
2266
2318
  setCodegenDebug(config.debug);
@@ -2271,9 +2323,17 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
2271
2323
  return;
2272
2324
  }
2273
2325
  const configHash = computeConfigHash(config);
2326
+ const configKeyHashes = computeConfigKeyHashes(config);
2274
2327
  if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
2275
2328
  throw new DriftGuardError(
2276
- driftGuardMessage(config.codegen.outDir, manifest.entryPoint, entryPoint)
2329
+ driftGuardMessage(
2330
+ config.codegen.outDir,
2331
+ manifest.entryPoint,
2332
+ entryPoint,
2333
+ // A pre-key-hash manifest can't tell us WHICH keys differ — pass none
2334
+ // rather than diffing against {} (which would name every key).
2335
+ manifest.configKeyHashes ? diffConfigKeyHashes(manifest.configKeyHashes, configKeyHashes) : []
2336
+ )
2277
2337
  );
2278
2338
  }
2279
2339
  const extensions = config.extensions ?? [];
@@ -2343,6 +2403,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
2343
2403
  hash: inputsHash,
2344
2404
  entryPoint,
2345
2405
  configHash,
2406
+ configKeyHashes,
2346
2407
  files: outputFiles
2347
2408
  });
2348
2409
  }
@@ -4873,7 +4934,7 @@ function createChainModuleRenderer(opts) {
4873
4934
  }
4874
4935
 
4875
4936
  // src/index.ts
4876
- var VERSION = "0.14.1";
4937
+ var VERSION = "0.15.0";
4877
4938
  export {
4878
4939
  CodegenError,
4879
4940
  ConfigError,