@dudousxd/nestjs-codegen 0.26.0 → 0.27.1
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 +38 -0
- package/README.md +2 -2
- package/dist/cli/main.cjs +212 -73
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +213 -74
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/{index-Bbw73Kkb.d.cts → index-Dc4s883u.d.cts} +41 -4
- package/dist/{index-Bbw73Kkb.d.ts → index-Dc4s883u.d.ts} +41 -4
- package/dist/index.cjs +212 -73
- 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 +213 -74
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +216 -77
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +1 -1
- package/dist/nest/index.d.ts +1 -1
- package/dist/nest/index.js +217 -78
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @dudousxd/nestjs-codegen
|
|
2
2
|
|
|
3
|
+
## 0.27.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#103](https://github.com/DavideCarvalho/nestjs-codegen/pull/103) [`9489714`](https://github.com/DavideCarvalho/nestjs-codegen/commit/94897149bb7133c61de43d66ab6c1bdc5de98cd9) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Type discovery no longer overflows the stack on bundled declaration files. A `.d.ts` chunk that imports a type under an alias and re-publishes it bare (`import { p as ToolKind } from './chunk'; export { ToolKind }` — what tsup/rollup emit) used to be matched by the import's source name, miss, and re-enter the same file with a fresh cycle guard until `Maximum call stack size exceeded` aborted the whole `codegen` run (seen with `@dudousxd/nestjs-agent-core` 0.29 through `@dudousxd/nestjs-agent-codegen`). Imports are now matched by their local name and followed under their source name, and the cycle guard is carried through the walk (keyed by file and name).
|
|
8
|
+
|
|
9
|
+
## 0.27.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#101](https://github.com/DavideCarvalho/nestjs-codegen/pull/101) [`eb98ec8`](https://github.com/DavideCarvalho/nestjs-codegen/commit/eb98ec87be042b56a068bff9d4d71c35b6e67283) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Several page globs and several Inertia apps (scopes).
|
|
14
|
+
|
|
15
|
+
- `pages.glob` takes one glob or an array. Each page is named relative to the static base of
|
|
16
|
+
the glob that matched it (brace globs included: the base stops at the first segment with
|
|
17
|
+
glob syntax), and two files resolving to the same page name are an error.
|
|
18
|
+
- `pages.scopes` declares more Inertia apps, one per `InertiaModule.forFeature({ scope })`:
|
|
19
|
+
`{ glob, prefix?, componentNameStrategy? }`. A scope's page names get the prefix
|
|
20
|
+
`'<scope>/'` by default (`minimal/pages/Home.tsx` → `minimal/Home`). Every page lands in
|
|
21
|
+
`InertiaPages`/`InertiaPageName`; `pages.d.ts` also exports `InertiaScopePages` and
|
|
22
|
+
`InertiaScopePageName<S>`, `components.json` records each page's `scope`, and the watcher and
|
|
23
|
+
the skip-if-unchanged hash cover every scope's pages.
|
|
24
|
+
- The top-level `scopes` option never had an effect; it is deprecated and now warns.
|
|
25
|
+
|
|
26
|
+
- [#100](https://github.com/DavideCarvalho/nestjs-codegen/pull/100) [`1cc8944`](https://github.com/DavideCarvalho/nestjs-codegen/commit/1cc8944126b794cdd5748e2417f448cd1e157395) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Request handles are real Promises, and `api.ts` compiles under strict compiler flags.
|
|
27
|
+
|
|
28
|
+
- A generated leaf call (`api.users.create({ body })`) now returns a `RequestHandle<R>`: a
|
|
29
|
+
lazy, memoized `Promise<R>` that also carries `fetch()` and the extension members
|
|
30
|
+
(`queryKey`, `queryOptions`, `mutationOptions`, …). It can be returned from a TanStack
|
|
31
|
+
`mutationFn`/`queryFn`, passed to `Promise.all` or assigned to `Promise<T>` without calling
|
|
32
|
+
`.fetch()` first. Nothing is sent until it is awaited, so building `queryOptions()` or
|
|
33
|
+
reading `queryKey()` never fires a request. `.fetch()` keeps working.
|
|
34
|
+
- `api.ts` imports from `routes.ts` only the names it uses (no unused `ROUTES`/`RouteName`/…),
|
|
35
|
+
a leaf with no input to read takes no parameter, and `infiniteQueryOptions()` no longer
|
|
36
|
+
passes `getPreviousPageParam: undefined`. The output is clean under `noUnusedLocals`,
|
|
37
|
+
`noUnusedParameters`, `exactOptionalPropertyTypes`, `noImplicitOverride`,
|
|
38
|
+
`noUncheckedIndexedAccess` and `noPropertyAccessFromIndexSignature`; a fixture test compiles
|
|
39
|
+
it with all of them.
|
|
40
|
+
|
|
3
41
|
## 0.26.0
|
|
4
42
|
|
|
5
43
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -53,8 +53,8 @@ export class AppModule {}
|
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
The generated `api.ts` exports a `createApi(fetcher)` factory — create the client once,
|
|
56
|
-
injecting your fetcher. Each endpoint is a **unified awaitable handle**:
|
|
57
|
-
the request.
|
|
56
|
+
injecting your fetcher. Each endpoint is a **unified awaitable handle**: a lazy, memoized
|
|
57
|
+
`Promise` — `await` it (or return it from a TanStack `mutationFn`) to run the request.
|
|
58
58
|
|
|
59
59
|
```ts title="src/lib/api.ts"
|
|
60
60
|
import { createApi } from '../generated/api';
|
package/dist/cli/main.cjs
CHANGED
|
@@ -117,12 +117,33 @@ function validateUserConfig(userConfig) {
|
|
|
117
117
|
"validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
|
-
if (userConfig.pages &&
|
|
120
|
+
if (userConfig.pages && !isGlobInput(userConfig.pages.glob)) {
|
|
121
121
|
throw new ConfigError(
|
|
122
|
-
"Config validation failed: `pages.glob` must be a string when `pages` is set"
|
|
122
|
+
"Config validation failed: `pages.glob` must be a string or a non-empty array of strings when `pages` is set"
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
for (const [scope, scopeConfig] of Object.entries(userConfig.pages?.scopes ?? {})) {
|
|
126
|
+
if (scope === "default") {
|
|
127
|
+
throw new ConfigError(
|
|
128
|
+
'Config validation failed: `pages.scopes` cannot declare "default" \u2014 the default app is `pages.glob` (InertiaModule.forRoot).'
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (!isGlobInput(scopeConfig?.glob)) {
|
|
132
|
+
throw new ConfigError(
|
|
133
|
+
`Config validation failed: \`pages.scopes.${scope}.glob\` must be a string or a non-empty array of strings`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (userConfig.scopes && Object.keys(userConfig.scopes).length > 0) {
|
|
138
|
+
console.warn(
|
|
139
|
+
"[nestjs-codegen] The top-level `scopes` option has no effect and is deprecated. For several Inertia apps use `pages.scopes` (one entry per InertiaModule.forFeature scope); for several controller sets use one `contracts.glob` (e.g. a brace glob)."
|
|
123
140
|
);
|
|
124
141
|
}
|
|
125
142
|
}
|
|
143
|
+
function isGlobInput(value) {
|
|
144
|
+
if (typeof value === "string") return true;
|
|
145
|
+
return Array.isArray(value) && value.length > 0 && value.every((glob) => typeof glob === "string");
|
|
146
|
+
}
|
|
126
147
|
function applyDefaults(userConfig, cwd) {
|
|
127
148
|
validateUserConfig(userConfig);
|
|
128
149
|
const outDir = userConfig.codegen?.outDir ? resolveAbsolute(cwd, userConfig.codegen.outDir) : (0, import_node_path.join)(cwd, ".nestjs-codegen");
|
|
@@ -149,7 +170,17 @@ function applyDefaults(userConfig, cwd) {
|
|
|
149
170
|
pages: userConfig.pages ? {
|
|
150
171
|
glob: userConfig.pages.glob,
|
|
151
172
|
propsExport: userConfig.pages.propsExport ?? "ComponentProps",
|
|
152
|
-
componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext"
|
|
173
|
+
componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext",
|
|
174
|
+
scopes: Object.fromEntries(
|
|
175
|
+
Object.entries(userConfig.pages.scopes ?? {}).map(([scope, scopeConfig]) => [
|
|
176
|
+
scope,
|
|
177
|
+
{
|
|
178
|
+
glob: scopeConfig.glob,
|
|
179
|
+
prefix: scopeConfig.prefix ?? `${scope}/`,
|
|
180
|
+
componentNameStrategy: scopeConfig.componentNameStrategy ?? userConfig.pages?.componentNameStrategy ?? "relative-no-ext"
|
|
181
|
+
}
|
|
182
|
+
])
|
|
183
|
+
)
|
|
153
184
|
} : null,
|
|
154
185
|
contracts: {
|
|
155
186
|
glob: userConfig.contracts?.glob ?? "src/**/*.controller.ts",
|
|
@@ -226,23 +257,81 @@ var import_promises2 = require("fs/promises");
|
|
|
226
257
|
var import_node_path2 = require("path");
|
|
227
258
|
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
228
259
|
var NON_PAGE_FILE_RE = /\.(?:test|spec|stories|story)\.(?:tsx?|jsx?|vue|svelte)$/i;
|
|
260
|
+
var MAGIC_SEGMENT_RE = /[*?{}[\]()!]/;
|
|
261
|
+
function globStaticBase(glob) {
|
|
262
|
+
const segments = glob.split("/");
|
|
263
|
+
const staticSegments = [];
|
|
264
|
+
for (const segment of segments.slice(0, -1)) {
|
|
265
|
+
if (MAGIC_SEGMENT_RE.test(segment)) break;
|
|
266
|
+
staticSegments.push(segment);
|
|
267
|
+
}
|
|
268
|
+
return staticSegments.join("/");
|
|
269
|
+
}
|
|
229
270
|
async function discoverPages(opts) {
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
const pagesBase = (0, import_node_path2.join)(opts.cwd, globStatic);
|
|
271
|
+
const globs = typeof opts.glob === "string" ? [opts.glob] : opts.glob;
|
|
272
|
+
const prefix = opts.prefix ?? "";
|
|
273
|
+
const scope = opts.scope ?? "default";
|
|
274
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
235
275
|
const out = [];
|
|
236
|
-
for (const
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
276
|
+
for (const glob of globs) {
|
|
277
|
+
const files = (await (0, import_fast_glob.default)(glob, { cwd: opts.cwd, absolute: true })).filter((f) => !NON_PAGE_FILE_RE.test(f) && !seenFiles.has(f)).sort();
|
|
278
|
+
const pagesBase = (0, import_node_path2.join)(opts.cwd, globStaticBase(glob));
|
|
279
|
+
for (const file of files) {
|
|
280
|
+
seenFiles.add(file);
|
|
281
|
+
const rel = (0, import_node_path2.relative)(opts.cwd, file);
|
|
282
|
+
const nameRel = (0, import_node_path2.relative)(pagesBase, file).split(import_node_path2.sep).join("/");
|
|
283
|
+
const name = `${prefix}${computeName(nameRel, opts.componentNameStrategy)}`;
|
|
284
|
+
const source = await (0, import_promises2.readFile)(file, "utf8");
|
|
285
|
+
const propsSource = extractPropsSource(source, opts.propsExport);
|
|
286
|
+
out.push({ name, absolutePath: file, relativePath: rel, propsSource, scope });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
assertUniqueNames(out);
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
async function discoverPageScopes(config) {
|
|
293
|
+
const pages = config.pages;
|
|
294
|
+
if (!pages) return [];
|
|
295
|
+
const common = { cwd: config.codegen.cwd, propsExport: pages.propsExport };
|
|
296
|
+
const out = await discoverPages({
|
|
297
|
+
...common,
|
|
298
|
+
glob: pages.glob,
|
|
299
|
+
componentNameStrategy: pages.componentNameStrategy
|
|
300
|
+
});
|
|
301
|
+
for (const [scope, scopeConfig] of Object.entries(pages.scopes ?? {})) {
|
|
302
|
+
out.push(
|
|
303
|
+
...await discoverPages({
|
|
304
|
+
...common,
|
|
305
|
+
glob: scopeConfig.glob,
|
|
306
|
+
componentNameStrategy: scopeConfig.componentNameStrategy,
|
|
307
|
+
prefix: scopeConfig.prefix,
|
|
308
|
+
scope
|
|
309
|
+
})
|
|
310
|
+
);
|
|
243
311
|
}
|
|
312
|
+
assertUniqueNames(out);
|
|
244
313
|
return out;
|
|
245
314
|
}
|
|
315
|
+
function allPageGlobs(config) {
|
|
316
|
+
if (!config.pages) return [];
|
|
317
|
+
const asList = (glob) => typeof glob === "string" ? [glob] : glob;
|
|
318
|
+
return [
|
|
319
|
+
...asList(config.pages.glob),
|
|
320
|
+
...Object.values(config.pages.scopes ?? {}).flatMap((scope) => asList(scope.glob))
|
|
321
|
+
];
|
|
322
|
+
}
|
|
323
|
+
function assertUniqueNames(pages) {
|
|
324
|
+
const byName = /* @__PURE__ */ new Map();
|
|
325
|
+
for (const page of pages) {
|
|
326
|
+
const previous = byName.get(page.name);
|
|
327
|
+
if (previous) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
`Two Inertia pages resolve to the component name "${page.name}": ${previous.relativePath} (scope "${previous.scope}") and ${page.relativePath} (scope "${page.scope}"). Give one of them another name, directory or scope prefix.`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
byName.set(page.name, page);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
246
335
|
function computeName(rel, strat) {
|
|
247
336
|
if (typeof strat === "function") return strat(rel);
|
|
248
337
|
const noExt = rel.replace(/\.(tsx?|vue|svelte)$/, "");
|
|
@@ -654,10 +743,11 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
|
654
743
|
}
|
|
655
744
|
return [];
|
|
656
745
|
}
|
|
657
|
-
function resolveImportedType(name, sourceFile, project) {
|
|
746
|
+
function resolveImportedType(name, sourceFile, project, seen = /* @__PURE__ */ new Set()) {
|
|
658
747
|
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
659
|
-
const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
|
|
748
|
+
const namedImport = importDecl.getNamedImports().find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name);
|
|
660
749
|
if (!namedImport) continue;
|
|
750
|
+
const sourceName = namedImport.getName();
|
|
661
751
|
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
662
752
|
const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
|
|
663
753
|
for (const candidate of candidates) {
|
|
@@ -669,17 +759,17 @@ function resolveImportedType(name, sourceFile, project) {
|
|
|
669
759
|
continue;
|
|
670
760
|
}
|
|
671
761
|
}
|
|
672
|
-
const result = findTypeInFile(
|
|
762
|
+
const result = findTypeInFile(sourceName, importedFile);
|
|
673
763
|
if (result) return result;
|
|
674
|
-
const viaReExport = resolveReExportedType(
|
|
764
|
+
const viaReExport = resolveReExportedType(sourceName, importedFile, project, /* @__PURE__ */ new Set());
|
|
675
765
|
if (viaReExport) return viaReExport;
|
|
676
766
|
}
|
|
677
767
|
if (candidates.length === 0) {
|
|
678
|
-
const viaCompiler = resolveBareSpecifierType(
|
|
768
|
+
const viaCompiler = resolveBareSpecifierType(sourceName, importDecl, project);
|
|
679
769
|
if (viaCompiler) return viaCompiler;
|
|
680
770
|
}
|
|
681
771
|
}
|
|
682
|
-
return resolveReExportedType(name, sourceFile, project,
|
|
772
|
+
return resolveReExportedType(name, sourceFile, project, seen);
|
|
683
773
|
}
|
|
684
774
|
function resolveBareSpecifierType(name, importDecl, project) {
|
|
685
775
|
let target;
|
|
@@ -694,9 +784,9 @@ function resolveBareSpecifierType(name, importDecl, project) {
|
|
|
694
784
|
return resolveReExportedType(name, target, project, /* @__PURE__ */ new Set());
|
|
695
785
|
}
|
|
696
786
|
function resolveReExportedType(name, file, project, seen) {
|
|
697
|
-
const
|
|
698
|
-
if (seen.has(
|
|
699
|
-
seen.add(
|
|
787
|
+
const visit = `${file.getFilePath()}#${name}`;
|
|
788
|
+
if (seen.has(visit)) return null;
|
|
789
|
+
seen.add(visit);
|
|
700
790
|
for (const exportDecl of file.getExportDeclarations()) {
|
|
701
791
|
const moduleSpecifier = exportDecl.getModuleSpecifierValue();
|
|
702
792
|
const namedExports = exportDecl.getNamedExports();
|
|
@@ -718,7 +808,7 @@ function resolveReExportedType(name, file, project, seen) {
|
|
|
718
808
|
const sourceName = namedExports.find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name)?.getName() ?? name;
|
|
719
809
|
const local = findTypeInFile(sourceName, file);
|
|
720
810
|
if (local) return local;
|
|
721
|
-
const imported = resolveImportedType(sourceName, file, project);
|
|
811
|
+
const imported = resolveImportedType(sourceName, file, project, seen);
|
|
722
812
|
if (imported) return imported;
|
|
723
813
|
}
|
|
724
814
|
return null;
|
|
@@ -4113,28 +4203,28 @@ function renderFetcherRequest(req, binaryResponse) {
|
|
|
4113
4203
|
}
|
|
4114
4204
|
function emitReqHelper() {
|
|
4115
4205
|
return [
|
|
4116
|
-
"/**
|
|
4117
|
-
"type
|
|
4118
|
-
"
|
|
4206
|
+
"/** An awaitable request handle: a lazy, memoized `Promise` that also carries `fetch()` and the members extensions add. */",
|
|
4207
|
+
"export type RequestHandle<R, M = {}> = Promise<R> & { fetch(): Promise<R> } & M;",
|
|
4208
|
+
"class __Request<R> extends Promise<R> {",
|
|
4209
|
+
" static override get [Symbol.species](): PromiseConstructor {",
|
|
4210
|
+
" return Promise;",
|
|
4211
|
+
" }",
|
|
4212
|
+
" private __run: () => Promise<R>;",
|
|
4213
|
+
" private __p: Promise<R> | undefined;",
|
|
4214
|
+
" constructor(run: () => Promise<R>) {",
|
|
4215
|
+
" super(() => {});",
|
|
4216
|
+
" this.__run = run;",
|
|
4217
|
+
" }",
|
|
4218
|
+
" override then<T1 = R, T2 = never>(",
|
|
4119
4219
|
" onfulfilled?: ((value: R) => T1 | PromiseLike<T1>) | null,",
|
|
4120
4220
|
" onrejected?: ((reason: unknown) => T2 | PromiseLike<T2>) | null,",
|
|
4121
|
-
" ): Promise<T1 | T2
|
|
4122
|
-
"
|
|
4123
|
-
"
|
|
4124
|
-
"
|
|
4125
|
-
"}
|
|
4126
|
-
"function __req<R>(run: () => Promise<R
|
|
4127
|
-
"
|
|
4128
|
-
" const __promise = () => {",
|
|
4129
|
-
" __p ??= run();",
|
|
4130
|
-
" return __p;",
|
|
4131
|
-
" };",
|
|
4132
|
-
" return {",
|
|
4133
|
-
" then: (onfulfilled, onrejected) => __promise().then(onfulfilled, onrejected),",
|
|
4134
|
-
" catch: (onrejected) => __promise().catch(onrejected),",
|
|
4135
|
-
" finally: (onfinally) => __promise().finally(onfinally),",
|
|
4136
|
-
" fetch: run,",
|
|
4137
|
-
" };",
|
|
4221
|
+
" ): Promise<T1 | T2> {",
|
|
4222
|
+
" this.__p ??= this.__run();",
|
|
4223
|
+
" return this.__p.then(onfulfilled, onrejected);",
|
|
4224
|
+
" }",
|
|
4225
|
+
"}",
|
|
4226
|
+
"function __req<R, M extends object = {}>(run: () => Promise<R>, members?: M): RequestHandle<R, M> {",
|
|
4227
|
+
" return Object.assign(new __Request<R>(run), { fetch: run }, members) as RequestHandle<R, M>;",
|
|
4138
4228
|
"}",
|
|
4139
4229
|
""
|
|
4140
4230
|
];
|
|
@@ -4152,18 +4242,26 @@ function emitFilterFieldGuard() {
|
|
|
4152
4242
|
];
|
|
4153
4243
|
}
|
|
4154
4244
|
function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
|
|
4155
|
-
const
|
|
4156
|
-
lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
|
|
4245
|
+
const memberLines = [];
|
|
4157
4246
|
if (req.filterFieldsExpr) {
|
|
4158
|
-
|
|
4247
|
+
memberLines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
|
|
4159
4248
|
}
|
|
4160
4249
|
if (streamExpr) {
|
|
4161
|
-
|
|
4250
|
+
memberLines.push(`${pad} stream: () => ${streamExpr},`);
|
|
4162
4251
|
}
|
|
4163
4252
|
for (const [name, value] of Object.entries(members)) {
|
|
4164
|
-
|
|
4165
|
-
}
|
|
4166
|
-
|
|
4253
|
+
memberLines.push(`${pad} ${name}: ${value},`);
|
|
4254
|
+
}
|
|
4255
|
+
const body = [requestExpr, ...memberLines].join("\n");
|
|
4256
|
+
const param = /\binput\b/.test(body) ? `input?: ${req.inputType}` : "";
|
|
4257
|
+
const lines = [`${pad}${objKey}: (${param}) =>`];
|
|
4258
|
+
if (memberLines.length === 0) {
|
|
4259
|
+
lines.push(`${pad} __req<${req.responseType}>(() => ${requestExpr}),`);
|
|
4260
|
+
return lines;
|
|
4261
|
+
}
|
|
4262
|
+
lines.push(`${pad} __req(() => ${requestExpr}, {`);
|
|
4263
|
+
lines.push(...memberLines);
|
|
4264
|
+
lines.push(`${pad} }),`);
|
|
4167
4265
|
return lines;
|
|
4168
4266
|
}
|
|
4169
4267
|
function renderStreamExpr(req) {
|
|
@@ -4297,6 +4395,23 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
4297
4395
|
function isClientCallable(route) {
|
|
4298
4396
|
return route.method.toUpperCase() !== "ALL";
|
|
4299
4397
|
}
|
|
4398
|
+
var ROUTES_IMPORT_PLACEHOLDER = "/* @@routes-import@@ */";
|
|
4399
|
+
var ROUTES_EXPORTS = [
|
|
4400
|
+
// Called, never mentioned in prose: match the call so a comment saying "route" doesn't count.
|
|
4401
|
+
{ name: "route", typeOnly: false },
|
|
4402
|
+
{ name: "ROUTES", typeOnly: false },
|
|
4403
|
+
{ name: "RouteName", typeOnly: true },
|
|
4404
|
+
{ name: "ExtractParams", typeOnly: true },
|
|
4405
|
+
{ name: "RouteParams", typeOnly: true }
|
|
4406
|
+
];
|
|
4407
|
+
function finishRoutesImport(lines) {
|
|
4408
|
+
const body = lines.filter((l) => l !== ROUTES_IMPORT_PLACEHOLDER).join("\n");
|
|
4409
|
+
const used = ROUTES_EXPORTS.filter(
|
|
4410
|
+
({ name }) => new RegExp(name === "route" ? "\\broute\\(" : `\\b${name}\\b`).test(body)
|
|
4411
|
+
).map(({ name, typeOnly }) => typeOnly ? `type ${name}` : name);
|
|
4412
|
+
const importLine = used.length ? `import { ${used.join(", ")} } from './routes.js';` : null;
|
|
4413
|
+
return lines.flatMap((l) => l === ROUTES_IMPORT_PLACEHOLDER ? importLine ? [importLine] : [] : [l]).join("\n");
|
|
4414
|
+
}
|
|
4300
4415
|
function buildApiFile(routes, outDir, opts = {}) {
|
|
4301
4416
|
const fetcherImportPath = opts.fetcherImportPath;
|
|
4302
4417
|
const serialization = opts.serialization ?? "json";
|
|
@@ -4356,9 +4471,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
4356
4471
|
for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
|
|
4357
4472
|
}
|
|
4358
4473
|
lines.push(...extImports);
|
|
4359
|
-
lines.push(
|
|
4360
|
-
"import { route, ROUTES, type RouteName, type ExtractParams, type RouteParams } from './routes.js';"
|
|
4361
|
-
);
|
|
4474
|
+
lines.push(ROUTES_IMPORT_PLACEHOLDER);
|
|
4362
4475
|
const runtimeImport = fetcherImportPath ?? "@dudousxd/nestjs-client";
|
|
4363
4476
|
lines.push(`import type { Fetcher } from '${runtimeImport}';`);
|
|
4364
4477
|
if (serialization === "json" && contracted.length > 0) {
|
|
@@ -4409,7 +4522,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
4409
4522
|
lines.push(...statements, "");
|
|
4410
4523
|
}
|
|
4411
4524
|
}
|
|
4412
|
-
return lines
|
|
4525
|
+
return finishRoutesImport(lines);
|
|
4413
4526
|
}
|
|
4414
4527
|
const tree = /* @__PURE__ */ new Map();
|
|
4415
4528
|
for (const r of contracted) {
|
|
@@ -4462,7 +4575,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
4462
4575
|
lines.push(...statements, "");
|
|
4463
4576
|
}
|
|
4464
4577
|
}
|
|
4465
|
-
return lines
|
|
4578
|
+
return finishRoutesImport(lines);
|
|
4466
4579
|
}
|
|
4467
4580
|
|
|
4468
4581
|
// src/emit/emit-cache.ts
|
|
@@ -4475,6 +4588,7 @@ async function emitCache(pages, outDir) {
|
|
|
4475
4588
|
const s = await (0, import_promises4.stat)(p.absolutePath);
|
|
4476
4589
|
return {
|
|
4477
4590
|
name: p.name,
|
|
4591
|
+
scope: p.scope ?? "default",
|
|
4478
4592
|
relativePath: p.relativePath,
|
|
4479
4593
|
mtime: s.mtime.toISOString()
|
|
4480
4594
|
};
|
|
@@ -5266,10 +5380,11 @@ async function emitPages(pages, outDir, _options = {}) {
|
|
|
5266
5380
|
}).join("\n");
|
|
5267
5381
|
const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
|
|
5268
5382
|
const sharedPropsBlock = buildSharedPropsBlock(_options.sharedProps ?? null);
|
|
5383
|
+
const scopesBlock = buildScopesBlock(pages);
|
|
5269
5384
|
const content = `// Generated by @dudousxd/nestjs-codegen. Do not edit.
|
|
5270
5385
|
|
|
5271
5386
|
export type InertiaPageName = ${pageNameUnion};
|
|
5272
|
-
${propsHelper}
|
|
5387
|
+
${propsHelper}${scopesBlock}
|
|
5273
5388
|
declare module '@dudousxd/nestjs-inertia' {
|
|
5274
5389
|
interface InertiaPages {
|
|
5275
5390
|
${augBody}
|
|
@@ -5278,6 +5393,31 @@ ${sharedPropsBlock}}
|
|
|
5278
5393
|
`;
|
|
5279
5394
|
await (0, import_promises9.writeFile)((0, import_node_path13.join)(outDir, "pages.d.ts"), content, "utf8");
|
|
5280
5395
|
}
|
|
5396
|
+
function buildScopesBlock(pages) {
|
|
5397
|
+
const byScope = /* @__PURE__ */ new Map();
|
|
5398
|
+
for (const page of pages) {
|
|
5399
|
+
const scope = page.scope ?? "default";
|
|
5400
|
+
const names = byScope.get(scope) ?? [];
|
|
5401
|
+
names.push(JSON.stringify(page.name));
|
|
5402
|
+
byScope.set(scope, names);
|
|
5403
|
+
}
|
|
5404
|
+
if (![...byScope.keys()].some((scope) => scope !== "default")) return "";
|
|
5405
|
+
if (!byScope.has("default")) byScope.set("default", []);
|
|
5406
|
+
const scopes = ["default", ...[...byScope.keys()].filter((s) => s !== "default")];
|
|
5407
|
+
const body = scopes.map((scope) => {
|
|
5408
|
+
const key = needsQuotes(scope) ? JSON.stringify(scope) : scope;
|
|
5409
|
+
const names = byScope.get(scope) ?? [];
|
|
5410
|
+
return ` ${key}: ${names.length ? names.join(" | ") : "never"};`;
|
|
5411
|
+
}).join("\n");
|
|
5412
|
+
return `
|
|
5413
|
+
/** The page names of each Inertia app: \`default\` (forRoot) and every \`pages.scopes\` entry. */
|
|
5414
|
+
export interface InertiaScopePages {
|
|
5415
|
+
${body}
|
|
5416
|
+
}
|
|
5417
|
+
|
|
5418
|
+
export type InertiaScopePageName<S extends keyof InertiaScopePages> = InertiaScopePages[S];
|
|
5419
|
+
`;
|
|
5420
|
+
}
|
|
5281
5421
|
function buildSharedPropsBlock(sharedProps) {
|
|
5282
5422
|
if (!sharedProps) return "";
|
|
5283
5423
|
if (sharedProps.isImportRef) {
|
|
@@ -5485,8 +5625,7 @@ function diffConfigKeyHashes(previous, current) {
|
|
|
5485
5625
|
return [...keys].filter((key) => previous[key] !== current[key]).sort();
|
|
5486
5626
|
}
|
|
5487
5627
|
async function discoverInputFiles(config) {
|
|
5488
|
-
const globs = [config.contracts.glob, config.forms.watch];
|
|
5489
|
-
if (config.pages) globs.push(config.pages.glob);
|
|
5628
|
+
const globs = [config.contracts.glob, config.forms.watch, ...allPageGlobs(config)];
|
|
5490
5629
|
const cwd = config.codegen.cwd;
|
|
5491
5630
|
const matched = await (0, import_fast_glob3.default)(globs, { cwd, absolute: true, onlyFiles: true });
|
|
5492
5631
|
return [...new Set(matched)].sort();
|
|
@@ -5617,12 +5756,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
|
5617
5756
|
}
|
|
5618
5757
|
if (config.pages) {
|
|
5619
5758
|
const pagesConfig = config.pages;
|
|
5620
|
-
const pages = await
|
|
5621
|
-
glob: pagesConfig.glob,
|
|
5622
|
-
cwd: config.codegen.cwd,
|
|
5623
|
-
propsExport: pagesConfig.propsExport,
|
|
5624
|
-
componentNameStrategy: pagesConfig.componentNameStrategy
|
|
5625
|
-
});
|
|
5759
|
+
const pages = await discoverPageScopes(config);
|
|
5626
5760
|
const sharedProps = discoverSharedPropsFromConfig(config);
|
|
5627
5761
|
await emitPages(pages, config.codegen.outDir, {
|
|
5628
5762
|
propsExport: pagesConfig.propsExport,
|
|
@@ -5797,12 +5931,17 @@ async function watch(config, onChange, options = {}) {
|
|
|
5797
5931
|
await runInitialPass();
|
|
5798
5932
|
}
|
|
5799
5933
|
let pagesDebounceTimer;
|
|
5800
|
-
const
|
|
5801
|
-
const pagesWatcher = import_chokidar.default.watch(
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5934
|
+
const pageGlobs = allPageGlobs(config);
|
|
5935
|
+
const pagesWatcher = import_chokidar.default.watch(
|
|
5936
|
+
(pageGlobs.length ? pageGlobs : [".nestjs-codegen-no-pages"]).map(
|
|
5937
|
+
(glob) => (0, import_node_path18.join)(config.codegen.cwd, glob)
|
|
5938
|
+
),
|
|
5939
|
+
{
|
|
5940
|
+
ignoreInitial: true,
|
|
5941
|
+
persistent: true,
|
|
5942
|
+
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
5943
|
+
}
|
|
5944
|
+
);
|
|
5806
5945
|
function schedulePagesRegenerate() {
|
|
5807
5946
|
if (pagesDebounceTimer !== void 0) {
|
|
5808
5947
|
clearTimeout(pagesDebounceTimer);
|
|
@@ -5882,7 +6021,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
5882
6021
|
}
|
|
5883
6022
|
|
|
5884
6023
|
// src/index.ts
|
|
5885
|
-
var VERSION = "0.
|
|
6024
|
+
var VERSION = "0.27.1";
|
|
5886
6025
|
|
|
5887
6026
|
// src/cli/codegen.ts
|
|
5888
6027
|
async function runCodegen(opts = {}) {
|