@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/CHANGELOG.md +13 -0
- package/dist/cli/main.cjs +71 -10
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +71 -10
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.cjs.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-DT8SgPxp.d.cts → index-CjIDPMsV.d.cts} +7 -0
- package/dist/{index-DT8SgPxp.d.ts → index-CjIDPMsV.d.ts} +7 -0
- package/dist/index.cjs +71 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +71 -10
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +92 -12
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +56 -3
- package/dist/nest/index.d.ts +56 -3
- package/dist/nest/index.js +88 -11
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/main.js
CHANGED
|
@@ -741,6 +741,9 @@ function buildErrorType(c) {
|
|
|
741
741
|
}
|
|
742
742
|
return c.contractSource.error ?? "unknown";
|
|
743
743
|
}
|
|
744
|
+
function filterFieldLiterals(fields) {
|
|
745
|
+
return fields?.length ? fields.map((f) => JSON.stringify(f)) : [];
|
|
746
|
+
}
|
|
744
747
|
function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
745
748
|
const pad = " ".repeat(indent);
|
|
746
749
|
const lines = [];
|
|
@@ -767,7 +770,8 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
|
767
770
|
const params = buildParamsType(c.params);
|
|
768
771
|
const safeMethod = JSON.stringify(method);
|
|
769
772
|
const safeUrl = JSON.stringify(c.path);
|
|
770
|
-
const
|
|
773
|
+
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
774
|
+
const filterFields = filterLiterals.length ? filterLiterals.join(" | ") : "never";
|
|
771
775
|
const stream = c.contractSource.stream ? "true" : "false";
|
|
772
776
|
const binary = c.contractSource.binaryResponse ? "true" : "false";
|
|
773
777
|
lines.push(
|
|
@@ -807,6 +811,7 @@ function buildRequestModel(c) {
|
|
|
807
811
|
const TA = buildRouterTypeAccess(c.name);
|
|
808
812
|
const withParams = hasPathParams(c.params);
|
|
809
813
|
const { isGet, isQuery, hasBody, hasQuery } = requestShape(c.route);
|
|
814
|
+
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
810
815
|
const fields = [];
|
|
811
816
|
if (withParams) fields.push(`params: ${TA}['params']`);
|
|
812
817
|
if (hasQuery) fields.push(`query?: ${TA}['query']`);
|
|
@@ -836,7 +841,12 @@ function buildRequestModel(c) {
|
|
|
836
841
|
// (`[name]` rather than `[name, undefined]`) so the bare `.queryKey()` is a
|
|
837
842
|
// clean prefix that partial-matches every parametrized variant — making it
|
|
838
843
|
// directly usable for `invalidateQueries`.
|
|
839
|
-
queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)
|
|
844
|
+
queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)`,
|
|
845
|
+
// Runtime counterpart to the type-level `filterFields` union: the same
|
|
846
|
+
// discovered field list, emitted as a literal `[...] as const` so apps can
|
|
847
|
+
// validate a dynamic/user-supplied field string with `isFilterField(...)`
|
|
848
|
+
// instead of casting. Omitted for routes with no filter.
|
|
849
|
+
...filterLiterals.length ? { filterFieldsExpr: `[${filterLiterals.join(", ")}] as const` } : {}
|
|
840
850
|
};
|
|
841
851
|
}
|
|
842
852
|
function renderFetcherRequest(req, binaryResponse) {
|
|
@@ -871,9 +881,24 @@ function emitReqHelper() {
|
|
|
871
881
|
""
|
|
872
882
|
];
|
|
873
883
|
}
|
|
884
|
+
function emitFilterFieldGuard() {
|
|
885
|
+
return [
|
|
886
|
+
"/** 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. */",
|
|
887
|
+
"export function isFilterField<const K extends string>(",
|
|
888
|
+
" fields: readonly K[],",
|
|
889
|
+
" value: string,",
|
|
890
|
+
"): value is K {",
|
|
891
|
+
" return (fields as readonly string[]).includes(value);",
|
|
892
|
+
"}",
|
|
893
|
+
""
|
|
894
|
+
];
|
|
895
|
+
}
|
|
874
896
|
function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
|
|
875
897
|
const lines = [`${pad}${objKey}: (input?: ${req.inputType}) => ({`];
|
|
876
898
|
lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
|
|
899
|
+
if (req.filterFieldsExpr) {
|
|
900
|
+
lines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
|
|
901
|
+
}
|
|
877
902
|
if (streamExpr) {
|
|
878
903
|
lines.push(`${pad} stream: () => ${streamExpr},`);
|
|
879
904
|
}
|
|
@@ -1145,6 +1170,9 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1145
1170
|
lines.push("};");
|
|
1146
1171
|
lines.push("");
|
|
1147
1172
|
lines.push(...emitReqHelper());
|
|
1173
|
+
if (contracted.some((r) => r.contract?.contractSource.filterFields?.length)) {
|
|
1174
|
+
lines.push(...emitFilterFieldGuard());
|
|
1175
|
+
}
|
|
1148
1176
|
lines.push("export function createApi(fetcher: Fetcher) {");
|
|
1149
1177
|
lines.push(" return {");
|
|
1150
1178
|
lines.push(
|
|
@@ -2151,19 +2179,41 @@ function isManifestShape(value) {
|
|
|
2151
2179
|
if (typeof candidate.hash !== "string") return false;
|
|
2152
2180
|
if (candidate.entryPoint !== void 0 && !isEntryPoint(candidate.entryPoint)) return false;
|
|
2153
2181
|
if (candidate.configHash !== void 0 && typeof candidate.configHash !== "string") return false;
|
|
2182
|
+
if (candidate.configKeyHashes !== void 0 && !isStringRecord(candidate.configKeyHashes)) {
|
|
2183
|
+
return false;
|
|
2184
|
+
}
|
|
2154
2185
|
if (!Array.isArray(candidate.files)) return false;
|
|
2155
2186
|
return candidate.files.every((entry) => typeof entry === "string");
|
|
2156
2187
|
}
|
|
2188
|
+
function isStringRecord(value) {
|
|
2189
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
2190
|
+
return Object.values(value).every((entry) => typeof entry === "string");
|
|
2191
|
+
}
|
|
2157
2192
|
function serializeConfig(config) {
|
|
2193
|
+
return serializeConfigValue(config, `unserializable:${config.codegen.outDir}`);
|
|
2194
|
+
}
|
|
2195
|
+
function serializeConfigValue(value, unserializableMarker) {
|
|
2158
2196
|
try {
|
|
2159
|
-
return JSON.stringify(
|
|
2160
|
-
if (typeof
|
|
2161
|
-
return
|
|
2197
|
+
return JSON.stringify(value, (_key, entry) => {
|
|
2198
|
+
if (typeof entry === "function") return `[fn:${entry.name}]`;
|
|
2199
|
+
return entry;
|
|
2162
2200
|
});
|
|
2163
2201
|
} catch {
|
|
2164
|
-
return
|
|
2202
|
+
return unserializableMarker;
|
|
2165
2203
|
}
|
|
2166
2204
|
}
|
|
2205
|
+
function computeConfigKeyHashes(config) {
|
|
2206
|
+
const hashes = {};
|
|
2207
|
+
for (const [key, value] of Object.entries(config)) {
|
|
2208
|
+
if (value === void 0) continue;
|
|
2209
|
+
hashes[key] = createHash("sha256").update(serializeConfigValue(value, `unserializable:${key}`)).digest("hex");
|
|
2210
|
+
}
|
|
2211
|
+
return hashes;
|
|
2212
|
+
}
|
|
2213
|
+
function diffConfigKeyHashes(previous, current) {
|
|
2214
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(current)]);
|
|
2215
|
+
return [...keys].filter((key) => previous[key] !== current[key]).sort();
|
|
2216
|
+
}
|
|
2167
2217
|
async function discoverInputFiles(config) {
|
|
2168
2218
|
const globs = [config.contracts.glob, config.forms.watch];
|
|
2169
2219
|
if (config.pages) globs.push(config.pages.glob);
|
|
@@ -2198,6 +2248,7 @@ async function readManifest(outDir) {
|
|
|
2198
2248
|
hash: parsed.hash,
|
|
2199
2249
|
...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
|
|
2200
2250
|
...parsed.configHash ? { configHash: parsed.configHash } : {},
|
|
2251
|
+
...parsed.configKeyHashes ? { configKeyHashes: parsed.configKeyHashes } : {},
|
|
2201
2252
|
files: parsed.files
|
|
2202
2253
|
};
|
|
2203
2254
|
} catch {
|
|
@@ -2251,8 +2302,9 @@ function debugWarn(message) {
|
|
|
2251
2302
|
}
|
|
2252
2303
|
|
|
2253
2304
|
// src/generate.ts
|
|
2254
|
-
function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint) {
|
|
2255
|
-
|
|
2305
|
+
function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differingKeys) {
|
|
2306
|
+
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)";
|
|
2307
|
+
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.`;
|
|
2256
2308
|
}
|
|
2257
2309
|
async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
2258
2310
|
setCodegenDebug(config.debug);
|
|
@@ -2263,9 +2315,17 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
|
2263
2315
|
return;
|
|
2264
2316
|
}
|
|
2265
2317
|
const configHash = computeConfigHash(config);
|
|
2318
|
+
const configKeyHashes = computeConfigKeyHashes(config);
|
|
2266
2319
|
if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
|
|
2267
2320
|
throw new DriftGuardError(
|
|
2268
|
-
driftGuardMessage(
|
|
2321
|
+
driftGuardMessage(
|
|
2322
|
+
config.codegen.outDir,
|
|
2323
|
+
manifest.entryPoint,
|
|
2324
|
+
entryPoint,
|
|
2325
|
+
// A pre-key-hash manifest can't tell us WHICH keys differ — pass none
|
|
2326
|
+
// rather than diffing against {} (which would name every key).
|
|
2327
|
+
manifest.configKeyHashes ? diffConfigKeyHashes(manifest.configKeyHashes, configKeyHashes) : []
|
|
2328
|
+
)
|
|
2269
2329
|
);
|
|
2270
2330
|
}
|
|
2271
2331
|
const extensions = config.extensions ?? [];
|
|
@@ -2335,6 +2395,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
|
2335
2395
|
hash: inputsHash,
|
|
2336
2396
|
entryPoint,
|
|
2337
2397
|
configHash,
|
|
2398
|
+
configKeyHashes,
|
|
2338
2399
|
files: outputFiles
|
|
2339
2400
|
});
|
|
2340
2401
|
}
|
|
@@ -4782,7 +4843,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
4782
4843
|
}
|
|
4783
4844
|
|
|
4784
4845
|
// src/index.ts
|
|
4785
|
-
var VERSION = "0.
|
|
4846
|
+
var VERSION = "0.15.0";
|
|
4786
4847
|
|
|
4787
4848
|
// src/cli/codegen.ts
|
|
4788
4849
|
async function runCodegen(opts = {}) {
|