@finesoft/front 0.5.0 → 0.5.2

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.
Files changed (128) hide show
  1. package/README.md +4 -4
  2. package/dist/Outlet.svelte +39 -0
  3. package/dist/Outlet.svelte.d.ts +7 -0
  4. package/dist/browser-DIU6Sxl3.mjs +1237 -0
  5. package/dist/browser-kFMjlLGT.d.mts +262 -0
  6. package/dist/browser.d.mts +7 -2
  7. package/dist/browser.mjs +9 -1
  8. package/dist/controller-types-CgmJ6-le.d.mts +16 -0
  9. package/dist/cookies-Bpf9VayB.d.mts +779 -0
  10. package/dist/fetch-policy-BHT8RtrL.mjs +82 -0
  11. package/dist/host-guard-DDWxLpFL.mjs +222 -0
  12. package/dist/http-B6CJqDyf.d.mts +46 -0
  13. package/dist/http-CaxrMD1A.d.mts +1 -0
  14. package/dist/http-D70PL72H.mjs +257 -0
  15. package/dist/http.d.mts +3 -0
  16. package/dist/http.mjs +2 -0
  17. package/dist/index-node.d.mts +15 -0
  18. package/dist/index-node.mjs +15 -0
  19. package/dist/index.d.mts +48 -698
  20. package/dist/index.mjs +44 -261
  21. package/dist/load-node.d.mts +5 -0
  22. package/dist/load-node.mjs +10 -0
  23. package/dist/load-portable.d.mts +5 -0
  24. package/dist/load-portable.mjs +8 -0
  25. package/dist/lru-map-BKoUAySU.mjs +50 -0
  26. package/dist/messages-CAt2QdGr.mjs +140 -0
  27. package/dist/native-contract-DuR25hYB.d.mts +14 -0
  28. package/dist/native-contract.d.mts +2 -0
  29. package/dist/native-contract.mjs +1 -0
  30. package/dist/node-D9hB4dsz.d.mts +35 -0
  31. package/dist/node.d.mts +2 -0
  32. package/dist/node.mjs +59 -0
  33. package/dist/path-CGFl2w7D.mjs +113 -0
  34. package/dist/path-CXT6xGPO.d.mts +261 -0
  35. package/dist/portable-CaxrMD1A.d.mts +1 -0
  36. package/dist/portable.d.mts +11 -0
  37. package/dist/portable.mjs +12 -0
  38. package/dist/proxy-1SphZ7x7.mjs +436 -0
  39. package/dist/proxy-2dSWO-Xw.d.mts +53 -0
  40. package/dist/public-types-BcJM-AYc.mjs +835 -0
  41. package/dist/react-DhwBRw01.d.mts +16 -0
  42. package/dist/react.d.mts +3 -0
  43. package/dist/react.mjs +29 -0
  44. package/dist/rolldown-runtime-B4iAMlE-.mjs +35 -0
  45. package/dist/secure-fetch-Xlht2jd7.d.mts +30 -0
  46. package/dist/server-controller-proxy-BkVuVWVD.d.mts +38 -0
  47. package/dist/session-DnB4ZC3x.d.mts +1279 -0
  48. package/dist/src-Ftl_0rhu.mjs +28 -0
  49. package/dist/src-qwx7Vw8g.mjs +3807 -0
  50. package/dist/ssr-BEUNDvbj.d.mts +210 -0
  51. package/dist/ssr-C8xnYXoY.mjs +357 -0
  52. package/dist/ssr.d.mts +3 -0
  53. package/dist/ssr.mjs +3 -0
  54. package/dist/svelte-Dr5to3SE.d.mts +16 -0
  55. package/dist/svelte.d.mts +3 -0
  56. package/dist/svelte.mjs +13 -0
  57. package/dist/typegen-C-WeJCtf.d.mts +12 -0
  58. package/dist/typegen-cli.d.mts +1 -0
  59. package/dist/typegen-cli.mjs +11 -0
  60. package/dist/typegen.d.mts +3 -0
  61. package/dist/typegen.mjs +2 -0
  62. package/dist/types-BuaZHRG7.mjs +402 -0
  63. package/dist/undici-CPfL25Hr.mjs +22262 -0
  64. package/dist/vite-Cj4SPA8D.d.mts +277 -0
  65. package/dist/vite.d.mts +4 -0
  66. package/dist/vite.mjs +2354 -0
  67. package/dist/vue-DGmzuKho.d.mts +32 -0
  68. package/dist/vue.d.mts +3 -0
  69. package/dist/vue.mjs +56 -0
  70. package/dist/web.d.mts +6 -0
  71. package/dist/web.mjs +8 -0
  72. package/dist/worker.d.mts +2 -0
  73. package/dist/worker.mjs +2 -0
  74. package/docs/01-getting-started.md +67 -199
  75. package/docs/02-routing-and-controllers.md +163 -241
  76. package/docs/03-middleware.md +10 -212
  77. package/docs/04-rendering-and-hydration.md +6 -333
  78. package/docs/05-i18n.md +7 -237
  79. package/docs/06-http-client.md +20 -263
  80. package/docs/07-di-container.md +23 -257
  81. package/docs/08-observability.md +6 -286
  82. package/docs/09-server-and-deployment.md +59 -219
  83. package/docs/10-features-platform-pwa.md +7 -231
  84. package/docs/11-navigation.md +143 -288
  85. package/docs/12-session-restoration.md +6 -214
  86. package/docs/README.md +7 -7
  87. package/docs/advanced/custom-action-handler.md +29 -229
  88. package/docs/advanced/custom-adapter.md +7 -259
  89. package/docs/advanced/custom-event-recorder.md +7 -312
  90. package/docs/advanced/inline-proxy-codegen.md +6 -185
  91. package/docs/advanced/multi-tenant-scopes.md +10 -323
  92. package/docs/engineering/ci-release-flow.md +35 -222
  93. package/docs/engineering/project-structure.md +34 -277
  94. package/docs/engineering/testing.md +8 -310
  95. package/docs/pitfalls/container-scope-leak.md +2 -214
  96. package/docs/pitfalls/i18n-bundle-size.md +6 -176
  97. package/docs/pitfalls/proxy-binary-payloads.md +8 -12
  98. package/docs/pitfalls/ssr-hydration-mismatch.md +4 -160
  99. package/docs/pitfalls/ssr-vs-csr-globals.md +9 -170
  100. package/docs/zh/01-getting-started.md +67 -199
  101. package/docs/zh/02-routing-and-controllers.md +166 -244
  102. package/docs/zh/03-middleware.md +10 -212
  103. package/docs/zh/04-rendering-and-hydration.md +6 -333
  104. package/docs/zh/05-i18n.md +7 -237
  105. package/docs/zh/06-http-client.md +20 -263
  106. package/docs/zh/07-di-container.md +23 -257
  107. package/docs/zh/08-observability.md +6 -283
  108. package/docs/zh/09-server-and-deployment.md +59 -219
  109. package/docs/zh/10-features-platform-pwa.md +7 -231
  110. package/docs/zh/11-navigation.md +130 -290
  111. package/docs/zh/12-session-restoration.md +6 -214
  112. package/docs/zh/README.md +4 -4
  113. package/docs/zh/advanced/custom-action-handler.md +29 -229
  114. package/docs/zh/advanced/custom-adapter.md +7 -259
  115. package/docs/zh/advanced/custom-event-recorder.md +7 -312
  116. package/docs/zh/advanced/inline-proxy-codegen.md +6 -185
  117. package/docs/zh/advanced/multi-tenant-scopes.md +10 -323
  118. package/docs/zh/engineering/ci-release-flow.md +35 -222
  119. package/docs/zh/engineering/project-structure.md +34 -277
  120. package/docs/zh/engineering/testing.md +8 -310
  121. package/docs/zh/pitfalls/container-scope-leak.md +2 -214
  122. package/docs/zh/pitfalls/i18n-bundle-size.md +6 -176
  123. package/docs/zh/pitfalls/proxy-binary-payloads.md +8 -12
  124. package/docs/zh/pitfalls/ssr-hydration-mismatch.md +4 -160
  125. package/docs/zh/pitfalls/ssr-vs-csr-globals.md +9 -170
  126. package/package.json +118 -20
  127. package/dist/browser-BYZq9Jp7.mjs +0 -2
  128. package/dist/browser-JTs2jqVY.d.mts +0 -2811
@@ -0,0 +1,835 @@
1
+ import { t as LruMap } from "./lru-map-BKoUAySU.mjs";
2
+ import "./src-Ftl_0rhu.mjs";
3
+ import { createRequire } from "node:module";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import { createHash } from "node:crypto";
7
+ //#region ../server/src/controller-types.ts
8
+ const OUTPUT = ".finesoft/controller-types.d.ts";
9
+ const GENERATED = "// Generated by @finesoft/front. Edit route schemas, not this file.\n";
10
+ const PROBE = "__finesoftRouteInputProbe";
11
+ const moduleNames = /* @__PURE__ */ new Set([
12
+ "@finesoft/front",
13
+ "@finesoft/core",
14
+ "@finesoft/web",
15
+ "@finesoft/ssr"
16
+ ]);
17
+ const cached = /* @__PURE__ */ new Map();
18
+ const hash = (content) => createHash("sha256").update(content === void 0 ? "missing" : `file:${content}`).digest("hex");
19
+ let retained;
20
+ const slash$1 = (value) => value.replaceAll("\\", "/");
21
+ const inside = (root, file) => {
22
+ const relative = path.relative(root, file);
23
+ return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
24
+ };
25
+ function assertEditablePath(root, file) {
26
+ let existing = file;
27
+ while (!fs.existsSync(existing)) {
28
+ if (fs.lstatSync(existing, { throwIfNoEntry: false })) throw Error(`Controller type generation cannot edit outside ${root}`);
29
+ existing = path.dirname(existing);
30
+ }
31
+ if (!inside(root, fs.realpathSync(existing))) throw Error(`Controller type generation cannot edit outside ${root}`);
32
+ }
33
+ function applyEdits(source, edits) {
34
+ let boundary = source.length;
35
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
36
+ if (edit.end > boundary) throw Error("Overlapping controller type edits");
37
+ source = source.slice(0, edit.start) + edit.text + source.slice(edit.end);
38
+ boundary = edit.start;
39
+ }
40
+ return source;
41
+ }
42
+ function stamp(files) {
43
+ return files.map((file) => {
44
+ try {
45
+ const stat = fs.statSync(file);
46
+ return `${file}:${stat.mtimeMs}:${stat.size}`;
47
+ } catch {
48
+ return `${file}:missing`;
49
+ }
50
+ }).join("\n");
51
+ }
52
+ /** Generates type-only references; never imports or executes application modules. */
53
+ function generateControllerTypes(options = {}) {
54
+ return generateTypes(options);
55
+ }
56
+ function generateTypes(options, observe, force = false) {
57
+ const requestedRoot = path.resolve(options.root ?? process.cwd());
58
+ const requestedConfig = path.resolve(requestedRoot, options.tsconfig ?? "tsconfig.json");
59
+ if (!fs.existsSync(requestedConfig) || !fs.existsSync(path.join(requestedRoot, "src"))) return {
60
+ controllers: 0,
61
+ changed: []
62
+ };
63
+ const root = fs.realpathSync(requestedRoot);
64
+ const configFile = fs.realpathSync(requestedConfig);
65
+ const compiler = createRequire(path.join(root, "package.json"))("typescript");
66
+ const tokens = (source) => {
67
+ const scanner = compiler.createScanner(compiler.ScriptTarget.Latest, true);
68
+ scanner.setText(source);
69
+ const result = [];
70
+ while (scanner.scan() !== compiler.SyntaxKind.EndOfFileToken) result.push(`${scanner.getToken()}:${scanner.getTokenValue() || scanner.getTokenText()}`);
71
+ return result.join("\n");
72
+ };
73
+ const configFiles = /* @__PURE__ */ new Set([configFile]);
74
+ const configContents = /* @__PURE__ */ new Map();
75
+ const readConfig = (file) => {
76
+ configFiles.add(file);
77
+ const content = compiler.sys.readFile(file);
78
+ configContents.set(file, content);
79
+ return content;
80
+ };
81
+ const config = compiler.readConfigFile(configFile, readConfig);
82
+ if (config.error) throw Error(compiler.flattenDiagnosticMessageText(config.error.messageText, "\n"));
83
+ const parsed = compiler.parseJsonConfigFileContent(config.config, {
84
+ ...compiler.sys,
85
+ readFile: readConfig
86
+ }, path.dirname(configFile));
87
+ if (parsed.errors.length) throw Error(parsed.errors.map((error) => compiler.flattenDiagnosticMessageText(error.messageText, "\n")).join("\n"));
88
+ if (!parsed.fileNames.length && parsed.projectReferences?.length) {
89
+ const projects = [];
90
+ const seen = /* @__PURE__ */ new Set();
91
+ const inspect = (reference) => {
92
+ const file = compiler.resolveProjectReferencePath(reference);
93
+ if (seen.has(file)) return;
94
+ seen.add(file);
95
+ const project = compiler.getParsedCommandLineOfConfigFile(file, {}, {
96
+ ...compiler.sys,
97
+ onUnRecoverableConfigFileDiagnostic: (error) => {
98
+ throw Error(compiler.flattenDiagnosticMessageText(error.messageText, "\n"));
99
+ }
100
+ });
101
+ if (project?.fileNames.some((file) => inside(path.join(root, "src"), file))) projects.push(file);
102
+ else project?.projectReferences?.forEach(inspect);
103
+ };
104
+ parsed.projectReferences.forEach(inspect);
105
+ if (projects.length === 1) return generateTypes({
106
+ ...options,
107
+ tsconfig: projects[0]
108
+ }, observe, force);
109
+ throw Error("Select the application project with controllerTypes.tsconfig; the solution config must reference exactly one project containing src.");
110
+ }
111
+ const cacheKey = `${root}#${configFile}`;
112
+ const rootFiles = parsed.fileNames.filter((file) => inside(root, file));
113
+ const previous = cached.get(cacheKey);
114
+ const initialFiles = [.../* @__PURE__ */ new Set([
115
+ ...configFiles,
116
+ ...rootFiles,
117
+ ...previous?.files ?? []
118
+ ])].sort();
119
+ const inputStamps = new Map(initialFiles.map((file) => [file, stamp([file])]));
120
+ if (!force && previous && previous.stamp === [...inputStamps.values()].join("\n")) {
121
+ observe?.(previous);
122
+ return {
123
+ ...previous.result,
124
+ changed: []
125
+ };
126
+ }
127
+ const output = path.join(root, OUTPUT);
128
+ const typeImports = (source) => source.statements.filter((statement) => compiler.isImportDeclaration(statement) && compiler.isStringLiteral(statement.moduleSpecifier) && path.resolve(path.dirname(source.fileName), statement.moduleSpecifier.text) === output.replace(/\.d\.ts$/, "") && statement.importClause?.isTypeOnly === true && !!statement.importClause.namedBindings && compiler.isNamedImports(statement.importClause.namedBindings));
129
+ const typeImport = (type) => {
130
+ if (!compiler.isTypeReferenceNode(type) || !compiler.isIdentifier(type.typeName)) return;
131
+ let owner = type.parent;
132
+ while (owner && !compiler.isClassDeclaration(owner)) owner = owner.parent;
133
+ if (!owner?.name) return;
134
+ const className = owner.name.text;
135
+ const source = type.getSourceFile();
136
+ const key = `${slash$1(path.relative(root, source.fileName))}#${className}`;
137
+ const hash = createHash("sha256").update(key).digest("hex").slice(0, 12);
138
+ const exports = ["Input", "Failure"].flatMap((suffix) => {
139
+ const name = `${className}${suffix}`;
140
+ return [name, `${name}_${hash}`];
141
+ });
142
+ return typeImports(source).flatMap((statement) => statement.importClause.namedBindings.elements).find((element) => element.name.text === type.getText() && exports.includes((element.propertyName ?? element.name).text) && checker.getSymbolAtLocation(type.typeName) === checker.getSymbolAtLocation(element.name));
143
+ };
144
+ const settings = {
145
+ ...parsed.options,
146
+ noEmit: true,
147
+ skipLibCheck: true
148
+ };
149
+ const memoize = (read) => {
150
+ const values = /* @__PURE__ */ new Map();
151
+ return (file) => {
152
+ if (!values.has(file)) values.set(file, read(file));
153
+ return values.get(file);
154
+ };
155
+ };
156
+ const readFiles = /* @__PURE__ */ new Set();
157
+ const readFile = memoize((file) => {
158
+ readFiles.add(file);
159
+ if (!inputStamps.has(file)) inputStamps.set(file, stamp([file]));
160
+ return compiler.sys.readFile(file);
161
+ });
162
+ const fileExists = memoize(compiler.sys.fileExists.bind(compiler.sys));
163
+ const directoryExists = memoize(compiler.sys.directoryExists.bind(compiler.sys));
164
+ const realpath = compiler.sys.realpath && memoize(compiler.sys.realpath.bind(compiler.sys));
165
+ const resolutions = compiler.createModuleResolutionCache(root, (file) => compiler.sys.useCaseSensitiveFileNames ? file : file.toLowerCase(), settings);
166
+ let lastProgram = retained?.key === cacheKey && retained.compiler === compiler ? retained.program : void 0;
167
+ let firstPhase = true;
168
+ const programFiles = [...rootFiles, output];
169
+ const buildProgram = (options, overrides = /* @__PURE__ */ new Map()) => {
170
+ const host = compiler.createCompilerHost(options);
171
+ const getSourceFile = host.getSourceFile.bind(host);
172
+ let previous = lastProgram;
173
+ host.readFile = (file) => overrides.get(file) ?? readFile(file);
174
+ host.fileExists = (file) => overrides.has(file) || fileExists(file);
175
+ host.directoryExists = (directory) => directory === path.dirname(output) && overrides.has(output) || directoryExists(directory);
176
+ host.realpath = realpath;
177
+ if (overrides.has(output) && !lastProgram?.getSourceFile(output)) resolutions.clear();
178
+ host.getModuleResolutionCache = () => resolutions;
179
+ host.resolveModuleNameLiterals = (imports, file, redirected, options, source) => imports.map((specifier) => compiler.resolveModuleName(specifier.text, file, options, host, resolutions, redirected, compiler.getModeForUsageLocation(source, specifier, options)));
180
+ const refreshResolutions = firstPhase;
181
+ firstPhase = false;
182
+ host.hasInvalidatedResolutions = () => refreshResolutions;
183
+ host.getSourceFile = (file, language, onError, fresh) => {
184
+ const source = previous?.getSourceFile(file);
185
+ const target = typeof language === "number" ? language : language.languageVersion;
186
+ const format = typeof language === "number" ? void 0 : language.impliedNodeFormat;
187
+ if (!fresh && source && source.languageVersion === target && source.impliedNodeFormat === format && source.text === host.readFile(file)) return source;
188
+ return getSourceFile(file, language, onError, fresh);
189
+ };
190
+ try {
191
+ return lastProgram = compiler.createProgram(programFiles, options, host, previous);
192
+ } finally {
193
+ previous = void 0;
194
+ }
195
+ };
196
+ const sourceProgram = buildProgram(settings);
197
+ const syntaxErrors = sourceProgram.getSyntacticDiagnostics();
198
+ if (syntaxErrors.length) throw Error(`Fix source syntax before generating controller types: ${syntaxErrors.map((error) => `${error.file?.fileName}: ${compiler.flattenDiagnosticMessageText(error.messageText, "\n")}`).join("\n")}`);
199
+ const checker = sourceProgram.getTypeChecker();
200
+ const resolve = (symbol) => symbol && (symbol.flags & compiler.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol);
201
+ const definitions = /* @__PURE__ */ new Set();
202
+ const bases = /* @__PURE__ */ new Set();
203
+ const serverBases = /* @__PURE__ */ new Set();
204
+ for (const source of sourceProgram.getSourceFiles()) {
205
+ if (!inside(root, source.fileName) || source.isDeclarationFile) continue;
206
+ for (const statement of source.statements) {
207
+ if (!compiler.isImportDeclaration(statement) || !compiler.isStringLiteral(statement.moduleSpecifier) || !moduleNames.has(statement.moduleSpecifier.text)) continue;
208
+ const symbol = checker.getSymbolAtLocation(statement.moduleSpecifier);
209
+ if (!symbol) continue;
210
+ for (const exported of checker.getExportsOfModule(symbol)) {
211
+ const resolved = resolve(exported);
212
+ if (resolved && exported.name === "definePage") definitions.add(resolved);
213
+ if (resolved && exported.name === "BaseController") bases.add(resolved);
214
+ if (resolved && exported.name === "BaseServerController") {
215
+ bases.add(resolved);
216
+ serverBases.add(resolved);
217
+ }
218
+ }
219
+ }
220
+ }
221
+ const registrations = [];
222
+ const serverController = (node) => {
223
+ const base = node.heritageClauses?.find((clause) => clause.token === compiler.SyntaxKind.ExtendsKeyword)?.types[0];
224
+ return !!base && serverBases.has(resolve(checker.getSymbolAtLocation(base.expression)));
225
+ };
226
+ const classes = [];
227
+ const visit = (node) => {
228
+ if (compiler.isClassDeclaration(node) && node.name) {
229
+ const base = node.heritageClauses?.find((clause) => clause.token === compiler.SyntaxKind.ExtendsKeyword)?.types[0];
230
+ if (base && bases.has(resolve(checker.getSymbolAtLocation(base.expression)))) classes.push(node);
231
+ }
232
+ if (compiler.isCallExpression(node) && definitions.has(resolve(checker.getSymbolAtLocation(node.expression))) && node.arguments[0]) {
233
+ const input = checker.getTypeAtLocation(node.arguments[0]);
234
+ const routes = input.getProperty("routes");
235
+ const create = input.getProperty("create");
236
+ if (routes && create) {
237
+ const factory = checker.getTypeOfSymbolAtLocation(create, node);
238
+ for (const signature of factory.getCallSignatures()) {
239
+ const result = signature.getReturnType();
240
+ for (const type of result.isUnion() ? result.types : [result]) {
241
+ const declaration = type.getSymbol()?.valueDeclaration;
242
+ if (!declaration || !compiler.isClassDeclaration(declaration) || !declaration.name) continue;
243
+ const base = declaration.heritageClauses?.find((clause) => clause.token === compiler.SyntaxKind.ExtendsKeyword)?.types[0];
244
+ if (!base || !bases.has(resolve(checker.getSymbolAtLocation(base.expression)))) continue;
245
+ if (!inside(root, declaration.getSourceFile().fileName)) throw Error(`Controller type generation cannot edit outside ${root}`);
246
+ registrations.push({
247
+ call: node,
248
+ controller: declaration
249
+ });
250
+ }
251
+ }
252
+ }
253
+ }
254
+ compiler.forEachChild(node, visit);
255
+ };
256
+ for (const source of sourceProgram.getSourceFiles()) if (inside(root, source.fileName) && !source.isDeclarationFile && !source.fileName.includes(`${path.sep}node_modules${path.sep}`)) visit(source);
257
+ const virtual = /* @__PURE__ */ new Map();
258
+ const calls = /* @__PURE__ */ new Map();
259
+ for (const { call } of registrations) {
260
+ const file = call.getSourceFile().fileName;
261
+ let positions = calls.get(file);
262
+ if (!positions) calls.set(file, positions = /* @__PURE__ */ new Set());
263
+ positions.add(call.expression.getStart());
264
+ }
265
+ for (const [file, positions] of calls) {
266
+ const source = sourceProgram.getSourceFile(file);
267
+ const edits = [];
268
+ const replace = (node) => {
269
+ if (compiler.isCallExpression(node) && positions.has(node.expression.getStart())) edits.push({
270
+ start: node.expression.getStart(),
271
+ end: node.expression.end,
272
+ text: PROBE
273
+ });
274
+ compiler.forEachChild(node, replace);
275
+ };
276
+ replace(source);
277
+ virtual.set(file, applyEdits(source.text, edits) + `\ndeclare function ${PROBE}<const R extends readonly (string | import("@finesoft/front").PageRoute)[]>(input: { routes: R; [key: string]: unknown }): import("@finesoft/front").RouteInputFor<R>;\n`);
278
+ }
279
+ virtual.set(output, "export {};\n");
280
+ const program = buildProgram(settings, virtual);
281
+ const types = program.getTypeChecker();
282
+ const inferred = /* @__PURE__ */ new Map();
283
+ for (const [file] of calls) {
284
+ const found = [];
285
+ const collect = (node) => {
286
+ if (compiler.isCallExpression(node) && compiler.isIdentifier(node.expression) && node.expression.text === PROBE) found.push(types.getTypeAtLocation(node));
287
+ compiler.forEachChild(node, collect);
288
+ };
289
+ collect(program.getSourceFile(file));
290
+ inferred.set(file, found);
291
+ }
292
+ const byCall = /* @__PURE__ */ new Map();
293
+ for (const [file] of calls) [...new Set(registrations.filter((entry) => entry.call.getSourceFile().fileName === file).map((entry) => entry.call))].forEach((call, index) => byCall.set(call, inferred.get(file)[index]));
294
+ const printer = compiler.createPrinter();
295
+ const outputSource = program.getSourceFile(output);
296
+ const factory = compiler.factory;
297
+ const schemaOutputs = /* @__PURE__ */ new Map();
298
+ let schemaOutputsReady = false;
299
+ const propertyType = (type, key, location) => {
300
+ const property = types.getNonNullableType(type).getProperty(key);
301
+ return property && types.getTypeOfSymbolAtLocation(property, location);
302
+ };
303
+ const schemaOutput = (type) => {
304
+ if (!(type.flags & (compiler.TypeFlags.Object | compiler.TypeFlags.Intersection))) return;
305
+ if (!schemaOutputsReady) {
306
+ schemaOutputsReady = true;
307
+ for (const source of program.getSourceFiles()) {
308
+ const symbol = types.getSymbolAtLocation(source);
309
+ if (!symbol || source.fileName === output) continue;
310
+ for (const exported of types.getExportsOfModule(symbol)) {
311
+ const type = types.getTypeOfSymbolAtLocation(exported, source);
312
+ const signatures = type.getCallSignatures();
313
+ const schema = signatures.length ? signatures[0].getReturnType() : type;
314
+ const standard = propertyType(schema, "~standard", source);
315
+ const metadata = standard && propertyType(standard, "types", source);
316
+ const result = metadata && propertyType(metadata, "output", source);
317
+ if (!result || !(result.flags & (compiler.TypeFlags.Object | compiler.TypeFlags.Intersection))) continue;
318
+ let modulePath = slash$1(path.relative(path.dirname(output), source.fileName)).replace(/(\.d)?\.([cm]?)tsx?$/, ".$2js");
319
+ if (!modulePath.startsWith(".")) modulePath = `./${modulePath}`;
320
+ const reference = factory.createImportTypeNode(factory.createLiteralTypeNode(factory.createStringLiteral(modulePath)), void 0, factory.createIdentifier(exported.name), void 0, true);
321
+ const schemaReference = signatures.length ? factory.createTypeReferenceNode("ReturnType", [reference]) : reference;
322
+ schemaOutputs.set(result, factory.createImportTypeNode(factory.createLiteralTypeNode(factory.createStringLiteral("@finesoft/front")), void 0, factory.createIdentifier("InferOutput"), [schemaReference]));
323
+ }
324
+ }
325
+ }
326
+ return schemaOutputs.get(type);
327
+ };
328
+ const buildTypeNode = (type, depth = 0) => {
329
+ const schema = depth >= 2 ? schemaOutput(type) : void 0;
330
+ if (schema) return schema;
331
+ if (type.isUnion()) return factory.createUnionTypeNode(type.types.map((value) => buildTypeNode(value, depth)));
332
+ const node = types.typeToTypeNode(type, outputSource, compiler.NodeBuilderFlags.NoTruncation | compiler.NodeBuilderFlags.UseFullyQualifiedType | compiler.NodeBuilderFlags.InTypeAlias);
333
+ if (!node) throw Error("Cannot serialize inferred controller input");
334
+ if (!compiler.isTypeLiteralNode(node)) return node;
335
+ return factory.updateTypeLiteralNode(node, factory.createNodeArray(node.members.map((member) => {
336
+ if (!compiler.isPropertySignature(member) || !member.type || !(compiler.isIdentifier(member.name) || compiler.isStringLiteral(member.name))) return member;
337
+ const value = propertyType(type, member.name.text, outputSource);
338
+ return value ? factory.updatePropertySignature(member, member.modifiers, member.name, member.questionToken, buildTypeNode(value, depth + 1)) : member;
339
+ })));
340
+ };
341
+ const printType = (type) => {
342
+ if (!type || type.flags & (compiler.TypeFlags.Any | compiler.TypeFlags.Unknown)) throw Error("Cannot infer controller input. Check route schemas and @finesoft/front types.");
343
+ const node = buildTypeNode(type);
344
+ const rejectAny = (value) => {
345
+ if (value.kind === compiler.SyntaxKind.AnyKeyword) throw Error("Cannot generate an any controller input. Check route schemas and their imports.");
346
+ compiler.forEachChild(value, rejectAny);
347
+ };
348
+ rejectAny(node);
349
+ const transformed = compiler.transform(node, [(context) => {
350
+ const visitType = (value) => {
351
+ if (compiler.isImportTypeNode(value) && compiler.isLiteralTypeNode(value.argument) && compiler.isStringLiteral(value.argument.literal) && path.isAbsolute(value.argument.literal.text)) {
352
+ let relative = slash$1(path.relative(path.dirname(output), value.argument.literal.text));
353
+ if (!relative.startsWith(".")) relative = `./${relative}`;
354
+ return context.factory.updateImportTypeNode(value, context.factory.createLiteralTypeNode(context.factory.createStringLiteral(relative)), value.attributes, value.qualifier, value.typeArguments, value.isTypeOf);
355
+ }
356
+ return compiler.visitEachChild(value, visitType, context);
357
+ };
358
+ return (node) => compiler.visitNode(node, visitType);
359
+ }]);
360
+ try {
361
+ return printer.printNode(compiler.EmitHint.Unspecified, transformed.transformed[0], outputSource);
362
+ } finally {
363
+ transformed.dispose();
364
+ }
365
+ };
366
+ const managed = /* @__PURE__ */ new Map();
367
+ for (const { call, controller } of registrations) {
368
+ const file = controller.getSourceFile();
369
+ if (!controller.members.filter((member) => compiler.isMethodDeclaration(member) && compiler.isIdentifier(member.name) && ["execute", "fallback"].includes(member.name.text)).some((method) => method.parameters.some((parameter) => !parameter.type || typeImport(parameter.type)))) continue;
370
+ const name = controller.name.text;
371
+ let entry = managed.get(controller);
372
+ if (!entry) managed.set(controller, entry = {
373
+ node: controller,
374
+ key: `${slash$1(path.relative(root, file.fileName))}#${name}`,
375
+ name,
376
+ inputs: []
377
+ });
378
+ entry.inputs.push(printType(byCall.get(call)));
379
+ }
380
+ const saved = fs.existsSync(output) ? fs.readFileSync(output, "utf8") : "";
381
+ let replayable = true;
382
+ if (saved.startsWith(GENERATED)) {
383
+ const previous = compiler.createSourceFile(output, saved, compiler.ScriptTarget.Latest, true);
384
+ const inputs = /* @__PURE__ */ new Map();
385
+ for (const statement of previous.statements) if (compiler.isInterfaceDeclaration(statement) && statement.name.text === "ControllerInputs") {
386
+ for (const member of statement.members) if (compiler.isPropertySignature(member) && compiler.isStringLiteral(member.name) && member.type) inputs.set(member.name.text, member.type.getText(previous));
387
+ }
388
+ for (const controller of classes) {
389
+ if (managed.has(controller)) continue;
390
+ const source = controller.getSourceFile();
391
+ const name = controller.name.text;
392
+ const key = `${slash$1(path.relative(root, source.fileName))}#${name}`;
393
+ if (inputs.has(key) && controller.members.some((member) => compiler.isMethodDeclaration(member) && member.parameters.some((parameter) => parameter.type && typeImport(parameter.type)))) {
394
+ replayable = false;
395
+ managed.set(controller, {
396
+ node: controller,
397
+ key,
398
+ name,
399
+ inputs: [inputs.get(key)]
400
+ });
401
+ }
402
+ }
403
+ }
404
+ const updates = /* @__PURE__ */ new Map();
405
+ const sourceEdits = /* @__PURE__ */ new Map();
406
+ const aliases = /* @__PURE__ */ new Map();
407
+ const names = /* @__PURE__ */ new Map();
408
+ for (const entry of managed.values()) names.set(entry.name, (names.get(entry.name) ?? 0) + 1);
409
+ const exportedName = (entry, suffix = "Input") => `${entry.name}${suffix}${names.get(entry.name) > 1 ? `_${createHash("sha256").update(entry.key).digest("hex").slice(0, 12)}` : ""}`;
410
+ const modulePaths = /* @__PURE__ */ new Map();
411
+ const localNames = /* @__PURE__ */ new Map();
412
+ const fileCounts = /* @__PURE__ */ new Map();
413
+ const ownedImports = /* @__PURE__ */ new Set();
414
+ for (const entry of managed.values()) {
415
+ const annotations = entry.node.members.flatMap((member) => compiler.isMethodDeclaration(member) && compiler.isIdentifier(member.name) && ["execute", "fallback"].includes(member.name.text) ? member.parameters.flatMap((parameter) => parameter.type ? [parameter.type] : []) : []);
416
+ for (const clause of entry.node.heritageClauses ?? []) if (clause.token === compiler.SyntaxKind.ExtendsKeyword) annotations.push(...clause.types[0].typeArguments?.slice(0, 1) ?? []);
417
+ for (const type of annotations) {
418
+ const imported = typeImport(type);
419
+ if (imported) ownedImports.add(imported);
420
+ }
421
+ }
422
+ for (const entry of managed.values()) {
423
+ const source = entry.node.getSourceFile();
424
+ fileCounts.set(source, (fileCounts.get(source) ?? 0) + 1);
425
+ if (localNames.has(source)) continue;
426
+ const imported = new Set(typeImports(source).flatMap((statement) => statement.importClause.namedBindings.elements.filter((element) => ownedImports.has(element)).map((element) => element.name.text)));
427
+ const used = /* @__PURE__ */ new Set();
428
+ const visit = (node) => {
429
+ if (compiler.isIdentifier(node) && !imported.has(node.text)) used.add(node.text);
430
+ compiler.forEachChild(node, visit);
431
+ };
432
+ visit(source);
433
+ localNames.set(source, used);
434
+ }
435
+ for (const entry of managed.values()) {
436
+ const source = entry.node.getSourceFile();
437
+ let edits = sourceEdits.get(source);
438
+ if (!edits) sourceEdits.set(source, edits = []);
439
+ const imports = typeImports(source).flatMap((statement) => statement.importClause.namedBindings.elements);
440
+ const alias = (suffix) => {
441
+ const used = localNames.get(source);
442
+ const exported = `${entry.name}${suffix}`;
443
+ let name = imports.find((element) => {
444
+ const name = (element.propertyName ?? element.name).text;
445
+ return ownedImports.has(element) && (name === exported || name.startsWith(exported) && /^_[a-f0-9]{12}$/.test(name.slice(exported.length)));
446
+ })?.name.text ?? (fileCounts.get(source) === 1 ? suffix : exported);
447
+ if (used.has(name)) name = `${entry.name}${suffix}`;
448
+ for (let i = 2; used.has(name); i++) name = `${entry.name}${suffix}${i}`;
449
+ used.add(name);
450
+ return name;
451
+ };
452
+ const parameterType = alias("Input");
453
+ const failureType = alias("Failure");
454
+ let modulePath = slash$1(path.relative(path.dirname(source.fileName), output)).replace(/\.d\.ts$/, "");
455
+ if (!modulePath.startsWith(".")) modulePath = `./${modulePath}`;
456
+ const list = aliases.get(source) ?? [];
457
+ const importAs = (suffix, local) => {
458
+ const name = exportedName(entry, suffix);
459
+ list.push(name === local ? name : `${name} as ${local}`);
460
+ };
461
+ importAs("Input", parameterType);
462
+ aliases.set(source, list);
463
+ modulePaths.set(source, modulePath);
464
+ for (const member of entry.node.members) {
465
+ if (!compiler.isMethodDeclaration(member) || !compiler.isIdentifier(member.name) || !["execute", "fallback"].includes(member.name.text)) continue;
466
+ if (member.parameters.length > 1) throw Error(`Use ${member.name.text}({ params, query, context${member.name.text === "fallback" ? ", error" : ""} }) in ${source.fileName}`);
467
+ const parameter = member.parameters[0];
468
+ if (!parameter || parameter.type && !typeImport(parameter.type)) continue;
469
+ const desired = member.name.text === "execute" ? parameterType : failureType;
470
+ if (member.name.text === "fallback") importAs("Failure", failureType);
471
+ edits.push(parameter.type ? {
472
+ start: parameter.type.getStart(),
473
+ end: parameter.type.end,
474
+ text: desired
475
+ } : {
476
+ start: parameter.questionToken?.end ?? parameter.name.end,
477
+ end: parameter.questionToken?.end ?? parameter.name.end,
478
+ text: `: ${desired}`
479
+ });
480
+ }
481
+ const base = entry.node.heritageClauses.find((clause) => clause.token === compiler.SyntaxKind.ExtendsKeyword).types[0];
482
+ const execute = entry.node.members.find((member) => compiler.isMethodDeclaration(member) && compiler.isIdentifier(member.name) && member.name.text === "execute");
483
+ let result = "import(\"@finesoft/front\").BasePage";
484
+ if (execute?.type) {
485
+ const type = checker.getTypeAtLocation(execute.type);
486
+ const awaited = checker.getAwaitedType(type);
487
+ result = awaited === type ? execute.type.getText() : `Awaited<${execute.type.getText()}>`;
488
+ if (compiler.isTypeReferenceNode(execute.type) && execute.type.typeName.getText() === "Promise" && execute.type.typeArguments?.length === 1) {
489
+ const value = execute.type.typeArguments[0];
490
+ if (checker.getTypeAtLocation(value) === awaited) result = value.getText();
491
+ }
492
+ }
493
+ const argumentsText = `<${parameterType}, ${result}>`;
494
+ if ((!base.typeArguments?.length || typeImport(base.typeArguments[0])) && tokens(source.text.slice(base.expression.end, base.end)) !== tokens(argumentsText)) edits.push({
495
+ start: base.expression.end,
496
+ end: base.end,
497
+ text: argumentsText
498
+ });
499
+ }
500
+ for (const [source, edits] of sourceEdits) {
501
+ const imports = typeImports(source);
502
+ const specifiers = [...aliases.get(source), ...imports.flatMap((statement) => statement.importClause.namedBindings.elements.filter((element) => !ownedImports.has(element)).map((element) => element.getText()))];
503
+ const desired = [...new Set(specifiers)].sort();
504
+ const declaration = `import type { ${desired.join(", ")} } from ${JSON.stringify(modulePaths.get(source))};`;
505
+ if (imports.length) {
506
+ const [first, ...rest] = imports;
507
+ if (first.importClause.namedBindings.elements.map((element) => tokens(element.getText())).sort().join("\n") !== desired.map(tokens).sort().join("\n")) edits.push({
508
+ start: first.getStart(),
509
+ end: first.end,
510
+ text: declaration
511
+ });
512
+ for (const statement of rest) edits.push({
513
+ start: statement.getStart(),
514
+ end: statement.end,
515
+ text: ""
516
+ });
517
+ } else {
518
+ const position = source.statements.filter(compiler.isImportDeclaration).at(-1)?.end ?? source.statements[0]?.getStart() ?? source.end;
519
+ edits.push({
520
+ start: position,
521
+ end: position,
522
+ text: `\n${declaration}\n`
523
+ });
524
+ }
525
+ updates.set(source.fileName, applyEdits(source.text, edits));
526
+ }
527
+ if (managed.size || fs.existsSync(output)) {
528
+ if (fs.existsSync(output) && !fs.readFileSync(output, "utf8").startsWith(GENERATED)) throw Error(`Refusing to overwrite unowned controller declarations: ${output}`);
529
+ updates.set(output, `${GENERATED}export interface ControllerInputs {\n${[...managed.values()].sort((a, b) => a.key.localeCompare(b.key)).map((entry) => ` ${JSON.stringify(entry.key)}: ${[...new Set(entry.inputs)].join(" | ")};`).join("\n")}\n}\n${[...managed.values()].sort((a, b) => a.key.localeCompare(b.key)).map((entry) => `export type ${exportedName(entry)} = ControllerInputs[${JSON.stringify(entry.key)}] & { readonly context: ${serverController(entry.node) ? "import(\"@finesoft/front\").ServerControllerContext" : "import(\"@finesoft/front\").ControllerContext"} };\nexport type ${exportedName(entry, "Failure")} = ${exportedName(entry)} & { readonly error: Error };`).join("\n")}\n`);
530
+ }
531
+ if (updates.has(output)) {
532
+ const validation = buildProgram({
533
+ ...settings,
534
+ skipLibCheck: false
535
+ }, updates);
536
+ const generated = validation.getSourceFile(output);
537
+ const errors = [...validation.getSyntacticDiagnostics(generated), ...validation.getSemanticDiagnostics(generated)];
538
+ if (errors.length) throw Error(`Cannot generate controller declarations:\n${compiler.formatDiagnosticsWithColorAndContext(errors, {
539
+ getCanonicalFileName: (file) => file,
540
+ getCurrentDirectory: () => root,
541
+ getNewLine: () => "\n"
542
+ })}`);
543
+ }
544
+ for (const [source] of sourceEdits) if (fs.readFileSync(source.fileName, "utf8") !== source.text) throw Error(`Controller changed during type generation: ${source.fileName}`);
545
+ for (const file of updates.keys()) assertEditablePath(root, file);
546
+ const changed = [];
547
+ for (const [file, content] of updates) {
548
+ if (fs.existsSync(file)) {
549
+ const existing = fs.readFileSync(file, "utf8");
550
+ if (file === output ? tokens(existing) === tokens(content) : existing === content) continue;
551
+ }
552
+ fs.mkdirSync(path.dirname(file), { recursive: true });
553
+ fs.writeFileSync(file, content);
554
+ inputStamps.set(file, stamp([file]));
555
+ changed.push(file);
556
+ }
557
+ const files = [.../* @__PURE__ */ new Set([
558
+ ...configFiles,
559
+ ...rootFiles,
560
+ output,
561
+ ...readFiles
562
+ ])].sort();
563
+ const result = {
564
+ controllers: managed.size,
565
+ changed
566
+ };
567
+ const hashes = /* @__PURE__ */ new Map();
568
+ for (const file of files) {
569
+ if (file === output) continue;
570
+ const unchanged = !force && inputStamps.get(file) === previous?.stamps.get(file);
571
+ hashes.set(file, unchanged && previous?.hashes.get(file) || hash(configContents.get(file) ?? updates.get(file) ?? readFile(file)));
572
+ }
573
+ const snapshot = {
574
+ compiler,
575
+ root,
576
+ files,
577
+ stamp: files.map((file) => inputStamps.get(file) ?? stamp([file])).join("\n"),
578
+ stamps: inputStamps,
579
+ hashes,
580
+ output,
581
+ content: changed.includes(output) ? updates.get(output) : saved,
582
+ replayable,
583
+ result
584
+ };
585
+ cached.set(cacheKey, snapshot);
586
+ retained = {
587
+ key: cacheKey,
588
+ compiler,
589
+ program: lastProgram
590
+ };
591
+ observe?.(snapshot);
592
+ return result;
593
+ }
594
+ /** Watch events are the invalidation source; one-shot calls still inspect the complete project. */
595
+ function createControllerTypeWatcher(options) {
596
+ let snapshot;
597
+ let onChange;
598
+ const subscriptions = /* @__PURE__ */ new Map();
599
+ const directories = /* @__PURE__ */ new Map();
600
+ const versions = new LruMap(8);
601
+ const key = (hashes) => hash([...hashes.values()].join(""));
602
+ const subscribe = () => {
603
+ if (!onChange || !snapshot) return;
604
+ const { compiler, hashes } = snapshot;
605
+ if (!compiler.sys.watchFile || !compiler.sys.watchDirectory) throw Error("TypeScript file watching is unavailable");
606
+ for (const [file, subscription] of subscriptions) {
607
+ if (hashes.has(file)) continue;
608
+ subscription.close();
609
+ subscriptions.delete(file);
610
+ }
611
+ for (const file of hashes.keys()) if (!subscriptions.has(file)) subscriptions.set(file, compiler.sys.watchFile(file, () => onChange?.(file), 250, {
612
+ watchFile: inside(snapshot.root, file) && !slash$1(file).includes("/node_modules/") ? compiler.WatchFileKind.UseFsEvents : compiler.WatchFileKind.UseFsEventsOnParentDirectory,
613
+ fallbackPolling: compiler.PollingWatchKind.PriorityInterval,
614
+ synchronousWatchDirectory: true
615
+ }));
616
+ const desired = /* @__PURE__ */ new Set();
617
+ for (const file of hashes.keys()) {
618
+ const directory = path.dirname(file);
619
+ if (inside(snapshot.root, directory) && !slash$1(file).includes("/node_modules/")) continue;
620
+ desired.add(directory);
621
+ desired.add(path.dirname(directory));
622
+ }
623
+ for (const [directory, subscription] of directories) {
624
+ if (desired.has(directory)) continue;
625
+ subscription.close();
626
+ directories.delete(directory);
627
+ }
628
+ for (const directory of desired) if (!directories.has(directory)) directories.set(directory, compiler.sys.watchDirectory(directory, (file) => {
629
+ if (!snapshot?.hashes.has(file)) onChange?.(file);
630
+ }, false, {
631
+ watchDirectory: compiler.WatchDirectoryKind.UseFsEvents,
632
+ synchronousWatchDirectory: true
633
+ }));
634
+ };
635
+ const generate = (force) => {
636
+ return {
637
+ ...generateTypes(options, (next) => {
638
+ if (snapshot?.files.join("\n") !== next.files.join("\n")) versions.clear();
639
+ snapshot = next;
640
+ subscribe();
641
+ if (!next.replayable) {
642
+ versions.clear();
643
+ return;
644
+ }
645
+ const fingerprint = key(next.hashes);
646
+ versions.set(fingerprint, {
647
+ content: next.content,
648
+ controllers: next.result.controllers
649
+ });
650
+ }, force),
651
+ cache: "generated"
652
+ };
653
+ };
654
+ generate(false);
655
+ return {
656
+ watch(listener) {
657
+ onChange = listener;
658
+ subscribe();
659
+ if (snapshot) {
660
+ for (const file of snapshot.hashes.keys()) if (stamp([file]) !== snapshot.stamps.get(file)) onChange(file);
661
+ }
662
+ },
663
+ update(files, cachedOnly = false) {
664
+ if (!snapshot) return cachedOnly ? void 0 : generate(true);
665
+ if (snapshot.content && !fs.existsSync(snapshot.output)) return cachedOnly ? void 0 : generate(true);
666
+ const hashes = new Map(snapshot.hashes);
667
+ let changed = false;
668
+ for (const file of files) {
669
+ if (!hashes.has(file) || file.endsWith(".json") || !fs.existsSync(file)) {
670
+ versions.clear();
671
+ return cachedOnly ? void 0 : generate(true);
672
+ }
673
+ const next = hash(fs.readFileSync(file, "utf8"));
674
+ changed ||= next !== hashes.get(file);
675
+ hashes.set(file, next);
676
+ }
677
+ if (!changed) return {
678
+ ...snapshot.result,
679
+ changed: [],
680
+ cache: "unchanged"
681
+ };
682
+ const fingerprint = key(hashes);
683
+ const known = versions.get(fingerprint);
684
+ if (!known) return cachedOnly ? void 0 : generate(true);
685
+ const exists = fs.existsSync(snapshot.output);
686
+ const existing = exists ? fs.readFileSync(snapshot.output, "utf8") : "";
687
+ if (exists && !existing.startsWith(GENERATED)) throw Error(`Refusing to overwrite unowned controller declarations: ${snapshot.output}`);
688
+ const written = [];
689
+ if (existing !== known.content) {
690
+ assertEditablePath(snapshot.root, snapshot.output);
691
+ fs.mkdirSync(path.dirname(snapshot.output), { recursive: true });
692
+ fs.writeFileSync(snapshot.output, known.content);
693
+ written.push(snapshot.output);
694
+ }
695
+ const result = {
696
+ controllers: known.controllers,
697
+ changed: written
698
+ };
699
+ snapshot = {
700
+ ...snapshot,
701
+ hashes,
702
+ content: known.content,
703
+ result
704
+ };
705
+ return {
706
+ ...result,
707
+ cache: "reused"
708
+ };
709
+ },
710
+ close() {
711
+ onChange = void 0;
712
+ for (const subscription of subscriptions.values()) subscription.close();
713
+ for (const subscription of directories.values()) subscription.close();
714
+ subscriptions.clear();
715
+ directories.clear();
716
+ snapshot = void 0;
717
+ versions.clear();
718
+ releaseControllerTypes(options.root ?? process.cwd());
719
+ }
720
+ };
721
+ }
722
+ /** Release development-only compiler state when its host closes. */
723
+ function releaseControllerTypes(root) {
724
+ const prefix = `${fs.existsSync(root) ? fs.realpathSync(root) : path.resolve(root)}#`;
725
+ for (const key of cached.keys()) if (key.startsWith(prefix)) cached.delete(key);
726
+ if (retained?.key.startsWith(prefix)) retained = void 0;
727
+ }
728
+ //#endregion
729
+ //#region ../server/src/public-types.ts
730
+ const slash = (value) => value.replaceAll("\\", "/");
731
+ const relative = (from, to) => {
732
+ const name = slash(path.relative(from, to));
733
+ return name.startsWith("./") || name.startsWith("../") ? name : `./${name}`;
734
+ };
735
+ /** Maintain an exact root alias with only the project's installed platform/native contracts. */
736
+ function generateFrontTypes(options = {}) {
737
+ const requestedRoot = path.resolve(options.root ?? process.cwd());
738
+ const requestedConfig = path.resolve(requestedRoot, options.tsconfig ?? "tsconfig.json");
739
+ if (!fs.existsSync(requestedConfig)) return [];
740
+ const root = fs.realpathSync(requestedRoot);
741
+ const configFile = fs.realpathSync(requestedConfig);
742
+ const require = createRequire(path.join(root, "package.json"));
743
+ let front;
744
+ try {
745
+ front = require.resolve("@finesoft/front");
746
+ } catch {
747
+ return [];
748
+ }
749
+ const c = require("typescript");
750
+ const input = fs.readFileSync(configFile, "utf8");
751
+ const parsed = c.parseConfigFileTextToJson(configFile, input);
752
+ if (parsed.error) throw Error(c.flattenDiagnosticMessageText(parsed.error.messageText, "\n"));
753
+ const output = path.join(root, ".finesoft/front.d.ts");
754
+ const resolved = c.parseJsonConfigFileContent(parsed.config, c.sys, path.dirname(configFile), void 0, configFile);
755
+ const configBase = resolved.options.baseUrl ?? path.dirname(configFile);
756
+ const alias = relative(configBase, output);
757
+ const existing = resolved.options.paths?.["@finesoft/front"];
758
+ if (existing && !(existing.length === 1 && path.resolve(configBase, existing[0]) === output)) return [];
759
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
760
+ if (pkg.name === "@finesoft/front") return [];
761
+ const deps = {
762
+ ...pkg.dependencies,
763
+ ...pkg.devDependencies,
764
+ ...pkg.peerDependencies
765
+ };
766
+ const installed = (name) => {
767
+ if (!deps[name]) return false;
768
+ try {
769
+ require.resolve(`${name}/package.json`);
770
+ return true;
771
+ } catch {
772
+ try {
773
+ require.resolve(name);
774
+ return true;
775
+ } catch {
776
+ return false;
777
+ }
778
+ }
779
+ };
780
+ const entry = (name) => JSON.stringify(relative(path.dirname(output), path.join(path.dirname(front), `${name}.mjs`)));
781
+ const renderers = [
782
+ "react",
783
+ "vue",
784
+ "svelte"
785
+ ].filter(installed);
786
+ const lines = [
787
+ "// Generated by @finesoft/front. Re-run finesoft-types after changing dependencies.",
788
+ `export * from ${entry("portable")};`,
789
+ `export { generateFrontTypes, generateControllerTypes } from ${entry("typegen")};`,
790
+ `export type { FrontTypeOptions, ControllerTypeOptions, ControllerTypeResult } from ${entry("typegen")};`
791
+ ];
792
+ if (installed("@types/node") || installed("@hono/node-server")) lines.push(`export * from ${entry("node")};`);
793
+ if (installed("vite") || installed("vite-plus")) lines.push(`export * from ${entry("vite")};`);
794
+ if (renderers.length) {
795
+ lines.push(`import type { NativeAPI } from ${entry("native-contract")};`);
796
+ for (const name of renderers) lines.push(`import type * as ${name} from ${entry(name)};`);
797
+ lines.push(`export interface NativeBindings { ${renderers.map((name) => `${name}: typeof ${name};`).join(" ")} }`, "export type Renderer = keyof NativeBindings;", `interface NativeViewTypes { ${renderers.map((name) => `${name}: ${name}.NativeView;`).join(" ")} }`, "export type NativeView<R extends Renderer> = NativeViewTypes[R];", "export type NativeViews<R extends Renderer> = Readonly<Record<string, NativeView<R>>>;", "export declare const Outlet: NativeAPI<NativeBindings>[\"Outlet\"];", "export declare const useSnapshot: NativeAPI<NativeBindings>[\"useSnapshot\"];");
798
+ }
799
+ const changed = [];
800
+ const write = (file, content) => {
801
+ assertEditablePath(root, file);
802
+ if (fs.existsSync(file) && fs.readFileSync(file, "utf8") === content) return;
803
+ fs.mkdirSync(path.dirname(file), { recursive: true });
804
+ fs.writeFileSync(file, content);
805
+ changed.push(file);
806
+ };
807
+ write(output, lines.join("\n") + "\n");
808
+ if (!existing) {
809
+ const source = c.parseJsonText(configFile, input);
810
+ const object = source.statements[0]?.expression;
811
+ if (!object || !c.isObjectLiteralExpression(object)) throw Error(`Invalid tsconfig: ${configFile}`);
812
+ const property = (value, name) => value.properties.find((item) => c.isPropertyAssignment(item) && (c.isStringLiteral(item.name) || c.isIdentifier(item.name)) && item.name.text === name);
813
+ const compilerOptions = property(object, "compilerOptions");
814
+ const compilerObject = compilerOptions && c.isObjectLiteralExpression(compilerOptions.initializer) ? compilerOptions.initializer : void 0;
815
+ const paths = compilerObject && property(compilerObject, "paths");
816
+ const pathsObject = paths && c.isObjectLiteralExpression(paths.initializer) ? paths.initializer : void 0;
817
+ const target = pathsObject ?? compilerObject ?? object;
818
+ const inherited = !pathsObject && resolved.options.paths ? resolved.options.paths : {};
819
+ const pathsBase = resolved.options.baseUrl ?? (typeof resolved.options.pathsBasePath === "string" ? resolved.options.pathsBasePath : path.dirname(configFile));
820
+ const mapping = Object.entries({
821
+ ...inherited,
822
+ "@finesoft/front": [alias]
823
+ }).map(([name, values]) => `${JSON.stringify(name)}: ${JSON.stringify(name === "@finesoft/front" ? values : values.map((value) => relative(configBase, path.resolve(pathsBase, value))))}`).join(",\n");
824
+ const addition = pathsObject ? mapping : compilerObject ? `"paths": { ${mapping} }` : `"compilerOptions": { "paths": { ${mapping} } }`;
825
+ const last = target.properties.at(-1);
826
+ const position = last?.end ?? target.getStart(source) + 1;
827
+ const insertion = last ? `,\n${addition}` : `\n${addition}\n`;
828
+ write(configFile, input.slice(0, position) + insertion + input.slice(position));
829
+ }
830
+ return changed;
831
+ }
832
+ //#endregion
833
+ export { createControllerTypeWatcher as n, generateControllerTypes as r, generateFrontTypes as t };
834
+
835
+ //# sourceMappingURL=public-types-BcJM-AYc.mjs.map