@dudousxd/nestjs-codegen 0.14.2 → 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 +7 -0
- package/dist/cli/main.cjs +31 -3
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +31 -3
- 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 +31 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +31 -3
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +52 -5
- 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 +48 -4
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @dudousxd/nestjs-codegen
|
|
2
2
|
|
|
3
|
+
## 0.15.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 20db5c0: Emit `filterFields` as a runtime `as const` array on each filter leaf, alongside the existing type-level union, plus an `isFilterField` type guard exported from the generated `api.ts`. Previously the filterable field set existed only as a type, so a field name arriving as a plain `string` from runtime state (a saved view, a user-picked column) could not be passed to `filterQuery().where()` without a cast. Now `api.route.leaf().filterFields` is a `readonly [...] as const` value and `isFilterField(leaf.filterFields, value)` narrows an arbitrary string to the field union, so dynamic field names validate at runtime instead of being asserted with `as`. The runtime array is generated from the same discovered field list as the type-level union (single source in the emitter), so the value can never drift from the type. Purely additive — the guard is emitted only when a route carries filter fields, and leaves without a filter gain no new member.
|
|
8
|
+
- 9b5298b: Add a `@QueryList()` param decorator and a `toStringList` normalizer to the `/nest` subpath for receiving array query params safely. Express (and Nest's default query parser) returns a bare `string` for a single-value query param (`?ids=a`) and a `string[]` only for two or more (`?ids=a&ids=b`), so `ParseArrayPipe` 400s the common single-select case. `@QueryList('ids')` normalizes `string | string[] | comma-joined string | undefined` into a clean `string[]` (`['a']`, `['a','b']`, `[]`), and `toStringList` is exported for the equivalent `class-transformer` `@Transform` on a DTO field. Pairs with the client's `arrayFormat` option: once the client sends `arrayFormat: 'repeat'`, the comma-split becomes a no-op fallback that still covers hand-rolled and `curl` callers. Documented under a new "Receiving array query params" docs page.
|
|
9
|
+
|
|
3
10
|
## 0.14.2
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
package/dist/cli/main.cjs
CHANGED
|
@@ -775,6 +775,9 @@ function buildErrorType(c) {
|
|
|
775
775
|
}
|
|
776
776
|
return c.contractSource.error ?? "unknown";
|
|
777
777
|
}
|
|
778
|
+
function filterFieldLiterals(fields) {
|
|
779
|
+
return fields?.length ? fields.map((f) => JSON.stringify(f)) : [];
|
|
780
|
+
}
|
|
778
781
|
function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
779
782
|
const pad = " ".repeat(indent);
|
|
780
783
|
const lines = [];
|
|
@@ -801,7 +804,8 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
|
801
804
|
const params = buildParamsType(c.params);
|
|
802
805
|
const safeMethod = JSON.stringify(method);
|
|
803
806
|
const safeUrl = JSON.stringify(c.path);
|
|
804
|
-
const
|
|
807
|
+
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
808
|
+
const filterFields = filterLiterals.length ? filterLiterals.join(" | ") : "never";
|
|
805
809
|
const stream = c.contractSource.stream ? "true" : "false";
|
|
806
810
|
const binary = c.contractSource.binaryResponse ? "true" : "false";
|
|
807
811
|
lines.push(
|
|
@@ -841,6 +845,7 @@ function buildRequestModel(c) {
|
|
|
841
845
|
const TA = buildRouterTypeAccess(c.name);
|
|
842
846
|
const withParams = hasPathParams(c.params);
|
|
843
847
|
const { isGet, isQuery, hasBody, hasQuery } = requestShape(c.route);
|
|
848
|
+
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
844
849
|
const fields = [];
|
|
845
850
|
if (withParams) fields.push(`params: ${TA}['params']`);
|
|
846
851
|
if (hasQuery) fields.push(`query?: ${TA}['query']`);
|
|
@@ -870,7 +875,12 @@ function buildRequestModel(c) {
|
|
|
870
875
|
// (`[name]` rather than `[name, undefined]`) so the bare `.queryKey()` is a
|
|
871
876
|
// clean prefix that partial-matches every parametrized variant — making it
|
|
872
877
|
// directly usable for `invalidateQueries`.
|
|
873
|
-
queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)
|
|
878
|
+
queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)`,
|
|
879
|
+
// Runtime counterpart to the type-level `filterFields` union: the same
|
|
880
|
+
// discovered field list, emitted as a literal `[...] as const` so apps can
|
|
881
|
+
// validate a dynamic/user-supplied field string with `isFilterField(...)`
|
|
882
|
+
// instead of casting. Omitted for routes with no filter.
|
|
883
|
+
...filterLiterals.length ? { filterFieldsExpr: `[${filterLiterals.join(", ")}] as const` } : {}
|
|
874
884
|
};
|
|
875
885
|
}
|
|
876
886
|
function renderFetcherRequest(req, binaryResponse) {
|
|
@@ -905,9 +915,24 @@ function emitReqHelper() {
|
|
|
905
915
|
""
|
|
906
916
|
];
|
|
907
917
|
}
|
|
918
|
+
function emitFilterFieldGuard() {
|
|
919
|
+
return [
|
|
920
|
+
"/** 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. */",
|
|
921
|
+
"export function isFilterField<const K extends string>(",
|
|
922
|
+
" fields: readonly K[],",
|
|
923
|
+
" value: string,",
|
|
924
|
+
"): value is K {",
|
|
925
|
+
" return (fields as readonly string[]).includes(value);",
|
|
926
|
+
"}",
|
|
927
|
+
""
|
|
928
|
+
];
|
|
929
|
+
}
|
|
908
930
|
function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
|
|
909
931
|
const lines = [`${pad}${objKey}: (input?: ${req.inputType}) => ({`];
|
|
910
932
|
lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
|
|
933
|
+
if (req.filterFieldsExpr) {
|
|
934
|
+
lines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
|
|
935
|
+
}
|
|
911
936
|
if (streamExpr) {
|
|
912
937
|
lines.push(`${pad} stream: () => ${streamExpr},`);
|
|
913
938
|
}
|
|
@@ -1179,6 +1204,9 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1179
1204
|
lines.push("};");
|
|
1180
1205
|
lines.push("");
|
|
1181
1206
|
lines.push(...emitReqHelper());
|
|
1207
|
+
if (contracted.some((r) => r.contract?.contractSource.filterFields?.length)) {
|
|
1208
|
+
lines.push(...emitFilterFieldGuard());
|
|
1209
|
+
}
|
|
1182
1210
|
lines.push("export function createApi(fetcher: Fetcher) {");
|
|
1183
1211
|
lines.push(" return {");
|
|
1184
1212
|
lines.push(
|
|
@@ -4834,7 +4862,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
4834
4862
|
}
|
|
4835
4863
|
|
|
4836
4864
|
// src/index.ts
|
|
4837
|
-
var VERSION = "0.
|
|
4865
|
+
var VERSION = "0.15.0";
|
|
4838
4866
|
|
|
4839
4867
|
// src/cli/codegen.ts
|
|
4840
4868
|
async function runCodegen(opts = {}) {
|