@semacode/cli 3.0.0 → 3.8.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.
Files changed (55) hide show
  1. package/dist/agentEntryPoints.js +78 -64
  2. package/dist/cliGrammar.js +5 -5
  3. package/dist/cliHelpTexto.js +6 -6
  4. package/dist/docs.part01.d.ts +4 -1
  5. package/dist/docs.part01.js +8 -0
  6. package/dist/docs.part02.js +9 -1
  7. package/dist/drift.part01.d.ts +8 -2
  8. package/dist/drift.part04.d.ts +14 -2
  9. package/dist/drift.part04.js +35 -2
  10. package/dist/drift.part05.d.ts +6 -0
  11. package/dist/drift.part05.js +68 -0
  12. package/dist/drift.part06.js +60 -3
  13. package/dist/drift.part09.d.ts +2 -2
  14. package/dist/drift.part09.js +13 -3
  15. package/dist/drift.part11.js +24 -3
  16. package/dist/drift.part14.js +4 -0
  17. package/dist/exemplosOficiais.d.ts +2 -1
  18. package/dist/exemplosOficiais.js +9 -3
  19. package/dist/geracaoCore.d.ts +1 -0
  20. package/dist/geracaoCore.js +5 -0
  21. package/dist/importador.part01.d.ts +1 -1
  22. package/dist/importador.part04.d.ts +5 -1
  23. package/dist/importador.part04.js +40 -2
  24. package/dist/importador.part06.d.ts +2 -0
  25. package/dist/importador.part06.js +97 -1
  26. package/dist/importador.part09.js +17 -2
  27. package/dist/index.part04.js +13 -6
  28. package/dist/index.part07.d.ts +8 -2
  29. package/dist/index.part07.js +146 -4
  30. package/dist/index.part08.js +3 -3
  31. package/dist/initCommand.d.ts +1 -0
  32. package/dist/initCommand.js +15 -3
  33. package/dist/initTemplatesBase.js +78 -78
  34. package/dist/projetoConfig.js +5 -0
  35. package/dist/projetoLegado.js +40 -0
  36. package/dist/tipos.d.ts +1 -1
  37. package/dist/typescript-http-body.js +9 -0
  38. package/dist/typescript-http-modelos.d.ts +1 -1
  39. package/dist/typescript-http-rotas.d.ts +1 -0
  40. package/dist/typescript-http-rotas.js +198 -0
  41. package/dist/typescript-http-semantica.d.ts +3 -3
  42. package/dist/typescript-http-semantica.js +65 -7
  43. package/dist/typescript-http.d.ts +1 -1
  44. package/dist/typescript-http.js +1 -1
  45. package/dist/verificacaoCache.d.ts +25 -0
  46. package/dist/verificacaoCache.js +80 -0
  47. package/docs/syntax.md +14 -0
  48. package/exemplos/express_crud.sema +139 -0
  49. package/exemplos/fastify_crud.sema +146 -0
  50. package/exemplos/koa_crud.sema +95 -0
  51. package/node_modules/@sema/nucleo/dist/ir/conversor.part01.js +37 -38
  52. package/node_modules/@sema/nucleo/dist/ir/modelos.d.ts +1 -0
  53. package/node_modules/@sema/nucleo/dist/semantico/analisador.part01.d.ts +7 -0
  54. package/node_modules/@sema/nucleo/dist/semantico/analisador.part01.js +33 -10
  55. package/package.json +1 -1
@@ -17,6 +17,18 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
17
17
  if (/@nestjs\/common|@nestjs\/core/.test(packageJsonRaiz)) {
18
18
  encontrados.add("nestjs");
19
19
  }
20
+ if (/"express"\s*:/.test(packageJsonRaiz)) {
21
+ encontrados.add("express");
22
+ encontrados.add("typescript");
23
+ }
24
+ if (/"fastify"\s*:/.test(packageJsonRaiz)) {
25
+ encontrados.add("fastify");
26
+ encontrados.add("typescript");
27
+ }
28
+ if (/"koa"\s*:|@koa\/router|"koa-router"/.test(packageJsonRaiz)) {
29
+ encontrados.add("koa");
30
+ encontrados.add("typescript");
31
+ }
20
32
  if (/typescript/.test(packageJsonRaiz)) {
21
33
  encontrados.add("typescript");
22
34
  }
@@ -43,6 +55,8 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
43
55
  const arquivosJava = await listarArquivosRecursivosLimitado(diretorio, [".java"], 5, 20);
44
56
  const arquivosGo = await listarArquivosRecursivosLimitado(diretorio, [".go"], 5, 20);
45
57
  const arquivosRust = await listarArquivosRecursivosLimitado(diretorio, [".rs"], 5, 20);
58
+ const arquivosVueSvelte = await listarArquivosRecursivosLimitado(diretorio, [".vue", ".svelte"], 5, 40);
59
+ const relacoesVueSvelte = arquivosVueSvelte.map((arquivo) => path.relative(diretorio, arquivo).replace(/\\/g, "/"));
46
60
  const arquivosCppBrutos = await listarArquivosRecursivosLimitado(diretorio, [".cpp", ".cc", ".cxx", ".hpp", ".h"], 5, 30);
47
61
  const arquivosCpp = arquivosCppBrutos.filter((arquivo) => !/(^|[\\/])(windows|linux|macos|runner|flutter|ephemeral|build|vendor)([\\/]|$)/i.test(arquivo));
48
62
  const arquivosPhp = (await listarArquivosRecursivosLimitado(diretorio, [".php"], 5, 30))
@@ -64,6 +78,12 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
64
78
  const relacoesTs = arquivosTsJs.map((arquivo) => path.relative(diretorio, arquivo).replace(/\\/g, "/"));
65
79
  const temNest = textosPackage.some((texto) => /@nestjs\/common|@nestjs\/core/.test(texto ?? ""))
66
80
  || amostrasTs.some((texto) => /@nestjs\/common|@nestjs\/core|@Controller\(|@Get\(|@Post\(|@Put\(|@Patch\(|@Delete\(/.test(texto ?? ""));
81
+ const temExpress = textosPackage.some((texto) => /"express"\s*:/.test(texto ?? ""))
82
+ || amostrasTs.some((texto) => /(?:from\s+["']express["']|require\(\s*["']express["']\s*\)|\bexpress\s*\(\s*\))/.test(texto ?? ""));
83
+ const temFastify = textosPackage.some((texto) => /"fastify"\s*:/.test(texto ?? ""))
84
+ || amostrasTs.some((texto) => /(?:from\s+["']fastify["']|require\(\s*["']fastify["']\s*\)|\bfastify\s*\(\s*\{)/.test(texto ?? ""));
85
+ const temKoa = textosPackage.some((texto) => /"koa"\s*:|@koa\/router|"koa-router"/.test(texto ?? ""))
86
+ || amostrasTs.some((texto) => /(?:from\s+["'](?:@koa\/router|koa-router|koa)["']|require\(\s*["'](?:@koa\/router|koa-router|koa)["']\s*\)|new\s+Router\s*\()/.test(texto ?? ""));
67
87
  const temNext = textosPackage.some((texto) => /"next"\s*:/.test(texto ?? ""))
68
88
  || nextConfigs.length > 0
69
89
  || relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?app\/api\/.+\/route\.(?:ts|tsx|js|jsx)$/.test(relacao));
@@ -77,6 +97,11 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
77
97
  const temSuperficieAngularConsumer = relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?app\/.+\.component\.(?:ts|js)$/.test(relacao))
78
98
  || relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?app(?:\/.+)?\/[^/]+\.routes\.(?:ts|js)$/.test(relacao));
79
99
  const temBridgeAngularConsumer = relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?app\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|js)$/.test(relacao));
100
+ const temSvelteKitConsumer = relacoesVueSvelte.some((relacao) => /(?:^|\/)(?:src\/)?routes\/(?:[^/]+\/)*\+(?:page|layout|error|server)\.svelte$/i.test(relacao))
101
+ || relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?routes\/(?:[^/]+\/)*\+(?:page|server)\.(?:ts|js)$/i.test(relacao))
102
+ || textosPackage.some((texto) => /@sveltejs\/kit/.test(texto ?? ""));
103
+ const temNuxtConsumer = relacoesVueSvelte.some((relacao) => /^(?:app\/)?pages\/.+\.vue$/i.test(relacao))
104
+ || textosPackage.some((texto) => /"nuxt"\s*:/.test(texto ?? ""));
80
105
  const temAngularConsumer = temSuperficieAngularConsumer
81
106
  || ((textosPackage.some((texto) => /@angular\/core|@angular\/router/.test(texto ?? "")) || angularConfigs.length > 0) && temBridgeAngularConsumer);
82
107
  const temFirebase = marcadoresFirebaseProjeto.length > 0
@@ -86,6 +111,15 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
86
111
  if (temNest) {
87
112
  encontrados.add("nestjs");
88
113
  }
114
+ if (temExpress) {
115
+ encontrados.add("express");
116
+ }
117
+ if (temFastify) {
118
+ encontrados.add("fastify");
119
+ }
120
+ if (temKoa) {
121
+ encontrados.add("koa");
122
+ }
89
123
  if (temNext) {
90
124
  encontrados.add("nextjs");
91
125
  }
@@ -98,6 +132,12 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
98
132
  if (temAngularConsumer) {
99
133
  encontrados.add("angular-consumer");
100
134
  }
135
+ if (temSvelteKitConsumer) {
136
+ encontrados.add("sveltekit-consumer");
137
+ }
138
+ if (temNuxtConsumer) {
139
+ encontrados.add("nuxt-consumer");
140
+ }
101
141
  if (temFirebase) {
102
142
  encontrados.add("firebase");
103
143
  }
package/dist/tipos.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export type EstruturaSaida = "flat" | "modulos" | "backend";
2
- export type FonteLegado = "nestjs" | "fastapi" | "flask" | "nextjs" | "nextjs-consumer" | "react-vite-consumer" | "angular-consumer" | "flutter-consumer" | "firebase" | "typescript" | "javascript" | "python" | "dart" | "lua" | "dotnet" | "java" | "go" | "rust" | "cpp" | "php";
2
+ export type FonteLegado = "nestjs" | "express" | "fastify" | "koa" | "fastapi" | "flask" | "nextjs" | "nextjs-consumer" | "react-vite-consumer" | "angular-consumer" | "flutter-consumer" | "sveltekit-consumer" | "nuxt-consumer" | "firebase" | "typescript" | "javascript" | "python" | "dart" | "lua" | "dotnet" | "java" | "go" | "rust" | "cpp" | "php";
3
3
  export type ModoAdocao = "incremental";
@@ -8,6 +8,15 @@ export function resolverMetadadosOrigemBody(expr, sourceFile, requestNames, body
8
8
  if (ts.isIdentifier(normalizado) && bodyAliases.has(normalizado.text)) {
9
9
  return bodyAliases.get(normalizado.text);
10
10
  }
11
+ if (ts.isPropertyAccessExpression(normalizado)
12
+ && normalizado.name.text === "body"
13
+ && ts.isIdentifier(normalizado.expression)
14
+ && requestNames.has(normalizado.expression.text)) {
15
+ return {
16
+ tipoTexto: typeNodeDeclarado?.getText(sourceFile),
17
+ campos: [],
18
+ };
19
+ }
11
20
  if (ehCallRequest(expr, requestNames, "json")) {
12
21
  return extrairMetadadosTipoExplicito(expr, sourceFile, typeNodeDeclarado);
13
22
  }
@@ -4,7 +4,7 @@ export interface ParametroRotaTypeScript {
4
4
  tipoSema: "Texto" | "Inteiro" | "Decimal" | "Id";
5
5
  }
6
6
  export interface RotaTypeScriptExtraida {
7
- origem: "nextjs" | "firebase";
7
+ origem: "nextjs" | "firebase" | "express" | "fastify" | "koa";
8
8
  metodo: string;
9
9
  caminho: string;
10
10
  simbolo: string;
@@ -6,4 +6,5 @@ export declare function inferirCaminhoNext(relacaoArquivo: string): {
6
6
  } | undefined;
7
7
  export declare function identificarRotasNodeHttp(sourceFile: ts.SourceFile, nomeSimbolo: string, corpo: ts.Block): RotaTypeScriptExtraida[];
8
8
  export declare function extrairRotasNodeWorker(sourceFile: ts.SourceFile, relacaoArquivo: string): RotaTypeScriptExtraida[];
9
+ export declare function extrairRotasExpressFastify(sourceFile: ts.SourceFile): RotaTypeScriptExtraida[];
9
10
  export declare function extrairRotasTypeScriptHttp(sourceFile: ts.SourceFile, relacaoArquivo: string): RotaTypeScriptExtraida[];
@@ -109,6 +109,203 @@ export function extrairRotasNodeWorker(sourceFile, relacaoArquivo) {
109
109
  }
110
110
  return deduplicarRotas(rotas);
111
111
  }
112
+ function combinarCaminhoPrefixo(base, sufixo) {
113
+ const prefixo = (base ?? "").replace(/\/+$/u, "");
114
+ const caminho = sufixo.startsWith("/") ? sufixo : `/${sufixo}`;
115
+ const combinado = `${prefixo}${caminho}`.replace(/\/{2,}/gu, "/");
116
+ return combinado.length > 1 && combinado.endsWith("/") ? combinado.slice(0, -1) : combinado;
117
+ }
118
+ function extrairPrefixoRegister(node) {
119
+ const opcoes = node.arguments[1];
120
+ if (!opcoes || !ts.isObjectLiteralExpression(opcoes)) {
121
+ return undefined;
122
+ }
123
+ for (const propriedade of opcoes.properties) {
124
+ if (ts.isPropertyAssignment(propriedade)
125
+ && ts.isIdentifier(propriedade.name)
126
+ && propriedade.name.text === "prefix"
127
+ && propriedade.initializer
128
+ && ts.isStringLiteralLike(propriedade.initializer)) {
129
+ return propriedade.initializer.text;
130
+ }
131
+ }
132
+ return undefined;
133
+ }
134
+ export function extrairRotasExpressFastify(sourceFile) {
135
+ const textoArquivo = sourceFile.getFullText();
136
+ const importouExpress = /(?:from\s+["']express["']|require\(\s*["']express["']\s*\))/.test(textoArquivo);
137
+ const importouFastify = /(?:from\s+["']fastify["']|require\(\s*["']fastify["']\s*\))/.test(textoArquivo);
138
+ const importouKoa = /(?:from\s+["'](?:@koa\/router|koa-router|koa)["']|require\(\s*["'](?:@koa\/router|koa-router|koa)["']\s*\))/.test(textoArquivo);
139
+ if (!importouExpress && !importouFastify && !importouKoa) {
140
+ return [];
141
+ }
142
+ const arestasMontagem = [];
143
+ const prefixoDireto = new Map();
144
+ const prefixoPorParametro = new Map();
145
+ const prefixoResolvido = new Map();
146
+ const resolverPrefixo = (receptor, profundidade = 0) => {
147
+ if (prefixoResolvido.has(receptor)) {
148
+ return prefixoResolvido.get(receptor);
149
+ }
150
+ if (profundidade > 8) {
151
+ return "";
152
+ }
153
+ let prefixo = prefixoDireto.get(receptor) ?? "";
154
+ const aresta = arestasMontagem.find((item) => item.filho === receptor);
155
+ if (aresta) {
156
+ prefixo = combinarCaminhoPrefixo(resolverPrefixo(aresta.pai, profundidade + 1), aresta.prefixo);
157
+ }
158
+ prefixoResolvido.set(receptor, prefixo);
159
+ return prefixo;
160
+ };
161
+ const visitarMontagens = (node) => {
162
+ if (ts.isVariableStatement(node)) {
163
+ for (const declaracao of node.declarationList.declarations) {
164
+ if (!ts.isIdentifier(declaracao.name) || !declaracao.initializer) {
165
+ continue;
166
+ }
167
+ const init = declaracao.initializer;
168
+ const argumentoFastify = ts.isCallExpression(init)
169
+ && ts.isIdentifier(init.expression)
170
+ && /fastify/i.test(init.expression.text)
171
+ && init.arguments[0]
172
+ && ts.isObjectLiteralExpression(init.arguments[0])
173
+ ? init.arguments[0]
174
+ : undefined;
175
+ if (argumentoFastify) {
176
+ for (const propriedade of argumentoFastify.properties) {
177
+ if (ts.isPropertyAssignment(propriedade)
178
+ && ts.isIdentifier(propriedade.name)
179
+ && propriedade.name.text === "prefix"
180
+ && propriedade.initializer
181
+ && ts.isStringLiteralLike(propriedade.initializer)) {
182
+ prefixoDireto.set(declaracao.name.text, propriedade.initializer.text);
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression)) {
189
+ const receptor = node.expression.expression.text;
190
+ const nomeMetodo = node.expression.name.text.toLowerCase();
191
+ if (nomeMetodo === "use"
192
+ && node.arguments[0]
193
+ && ts.isStringLiteralLike(node.arguments[0])
194
+ && node.arguments[0].text.startsWith("/")
195
+ && node.arguments[1]
196
+ && ts.isIdentifier(node.arguments[1])) {
197
+ arestasMontagem.push({ pai: receptor, filho: node.arguments[1].text, prefixo: node.arguments[0].text });
198
+ }
199
+ else if (nomeMetodo === "prefix"
200
+ && node.arguments[0]
201
+ && ts.isStringLiteralLike(node.arguments[0])
202
+ && node.arguments[0].text.startsWith("/")) {
203
+ prefixoDireto.set(receptor, combinarCaminhoPrefixo(node.arguments[0].text, prefixoDireto.get(receptor) ?? ""));
204
+ }
205
+ else if (nomeMetodo === "register" && importouFastify) {
206
+ const prefixo = extrairPrefixoRegister(node);
207
+ const alvo = node.arguments[0];
208
+ if (prefixo && alvo) {
209
+ const funcao = ts.isFunctionExpression(alvo) || ts.isArrowFunction(alvo) ? alvo : undefined;
210
+ const parametro = funcao?.parameters[0];
211
+ if (parametro && ts.isIdentifier(parametro.name)) {
212
+ prefixoPorParametro.set(parametro.name.text, prefixo);
213
+ }
214
+ }
215
+ }
216
+ }
217
+ node.forEachChild(visitarMontagens);
218
+ };
219
+ visitarMontagens(sourceFile);
220
+ const rotas = [];
221
+ const escolherOrigemChamada = (receptor) => {
222
+ if (/fastify/i.test(receptor)) {
223
+ return "fastify";
224
+ }
225
+ if (importouExpress) {
226
+ return "express";
227
+ }
228
+ if (importouKoa) {
229
+ return "koa";
230
+ }
231
+ if (importouFastify || prefixoPorParametro.has(receptor)) {
232
+ return "fastify";
233
+ }
234
+ return undefined;
235
+ };
236
+ const visitar = (node) => {
237
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression)) {
238
+ const receptor = node.expression.expression.text;
239
+ const nomeMetodo = node.expression.name.text.toLowerCase();
240
+ const prefixoReceptor = prefixoPorParametro.get(receptor) ?? resolverPrefixo(receptor);
241
+ if (nomeMetodo === "route" && node.arguments[0] && ts.isObjectLiteralExpression(node.arguments[0]) && importouFastify) {
242
+ let metodo;
243
+ let caminho;
244
+ for (const propriedade of node.arguments[0].properties) {
245
+ if (!ts.isPropertyAssignment(propriedade) || !ts.isIdentifier(propriedade.name)) {
246
+ continue;
247
+ }
248
+ const valor = propriedade.initializer && ts.isStringLiteralLike(propriedade.initializer)
249
+ ? propriedade.initializer.text
250
+ : undefined;
251
+ if (propriedade.name.text === "method" && valor) {
252
+ metodo = valor.toUpperCase();
253
+ }
254
+ if (propriedade.name.text === "url" && valor) {
255
+ caminho = valor;
256
+ }
257
+ }
258
+ if (metodo && caminho?.startsWith("/") && METODOS_HTTP.has(metodo)) {
259
+ rotas.push({
260
+ origem: "fastify",
261
+ metodo,
262
+ caminho: converterCaminhoParametros(combinarCaminhoPrefixo(prefixoReceptor, caminho)),
263
+ simbolo: receptor,
264
+ parametros: extrairParametrosCaminhoDoisPontos(caminho),
265
+ });
266
+ }
267
+ }
268
+ else if (nomeMetodo === "all" || nomeMetodo === "del" || METODOS_HTTP.has(nomeMetodo.toUpperCase())) {
269
+ const caminho = node.arguments[0] && ts.isStringLiteralLike(node.arguments[0])
270
+ ? node.arguments[0].text
271
+ : undefined;
272
+ const origem = escolherOrigemChamada(receptor);
273
+ const handler = node.arguments[1] && ts.isIdentifier(node.arguments[1]) ? node.arguments[1].text : undefined;
274
+ if (caminho?.startsWith("/") && origem) {
275
+ const metodos = nomeMetodo === "all"
276
+ ? ["GET", "POST", "PUT", "PATCH", "DELETE"]
277
+ : [nomeMetodo === "del" ? "DELETE" : nomeMetodo.toUpperCase()];
278
+ for (const metodo of metodos) {
279
+ rotas.push({
280
+ origem,
281
+ metodo,
282
+ caminho: converterCaminhoParametros(combinarCaminhoPrefixo(prefixoReceptor, caminho)),
283
+ simbolo: handler ?? receptor,
284
+ parametros: extrairParametrosCaminhoDoisPontos(combinarCaminhoPrefixo(prefixoReceptor, caminho)),
285
+ });
286
+ }
287
+ }
288
+ }
289
+ }
290
+ node.forEachChild(visitar);
291
+ };
292
+ visitar(sourceFile);
293
+ return deduplicarRotas(rotas);
294
+ }
295
+ function converterCaminhoParametros(caminho) {
296
+ return caminho.replace(/:([A-Za-z_]\w*)/g, "{$1}");
297
+ }
298
+ function extrairParametrosCaminhoDoisPontos(caminho) {
299
+ const parametros = [];
300
+ for (const correspondencia of caminho.matchAll(/:([A-Za-z_]\w*)/g)) {
301
+ const nome = correspondencia[1];
302
+ parametros.push({
303
+ nome,
304
+ tipoSema: /(^|_)id$/i.test(nome) ? "Id" : "Texto",
305
+ });
306
+ }
307
+ return parametros;
308
+ }
112
309
  export function extrairRotasTypeScriptHttp(sourceFile, relacaoArquivo) {
113
310
  const rotas = [];
114
311
  const caminhoNext = inferirCaminhoNext(relacaoArquivo);
@@ -124,6 +321,7 @@ export function extrairRotasTypeScriptHttp(sourceFile, relacaoArquivo) {
124
321
  }
125
322
  }
126
323
  rotas.push(...extrairRotasNodeWorker(sourceFile, relacaoArquivo));
324
+ rotas.push(...extrairRotasExpressFastify(sourceFile));
127
325
  return deduplicarRotas(rotas);
128
326
  }
129
327
  //# sourceMappingURL=typescript-http-rotas.js.map
@@ -1,11 +1,11 @@
1
1
  import ts from "typescript";
2
2
  import { type CampoInferidoTypeScriptHttp, type ExportacaoTypeScriptHttp, type SemanticaHandlerTypeScriptHttp, type ValorLocalTypeScriptHttp } from "./typescript-http-modelos.js";
3
- export declare function localizarExportacaoEmStatement(statement: ts.Statement, nomeExportado: string, sourceFile: ts.SourceFile): ExportacaoTypeScriptHttp | undefined;
3
+ export declare function localizarExportacaoEmStatement(statement: ts.Statement, nomeExportado: string, sourceFile: ts.SourceFile, permitirNaoExportada?: boolean): ExportacaoTypeScriptHttp | undefined;
4
4
  export declare function inferirTipoPorContexto(node: ts.Node, nomeCampo: string): string | undefined;
5
5
  export declare function extrairRetornoHttp(expr: ts.Expression, sourceFile: ts.SourceFile, valoresLocais: Map<string, ValorLocalTypeScriptHttp>): {
6
6
  campos: CampoInferidoTypeScriptHttp[];
7
7
  tipoTexto?: string;
8
8
  status?: number;
9
9
  } | undefined;
10
- export declare function localizarExportacaoTypeScriptHttp(sourceFile: ts.SourceFile, nomeExportado: string): ExportacaoTypeScriptHttp | undefined;
11
- export declare function inferirSemanticaHandlerTypeScriptHttp(sourceFile: ts.SourceFile, nomeExportado: string): SemanticaHandlerTypeScriptHttp | undefined;
10
+ export declare function localizarExportacaoTypeScriptHttp(sourceFile: ts.SourceFile, nomeExportado: string, permitirNaoExportada?: boolean): ExportacaoTypeScriptHttp | undefined;
11
+ export declare function inferirSemanticaHandlerTypeScriptHttp(sourceFile: ts.SourceFile, nomeExportado: string, permitirNaoExportada?: boolean): SemanticaHandlerTypeScriptHttp | undefined;
@@ -4,8 +4,10 @@ import ts from "typescript";
4
4
  import { deduplicarCampos, deduplicarNumeros, desembrulharExpressao, } from "./typescript-http-modelos.js";
5
5
  import { ehCallRequest, ehNewUrl, ehSearchParamsSource, extrairCamposObjetoLiteral, extrairMetadadosTipoExplicito, extrairStatusHttp, obterNomeSchema, tipoInferidoPorNomeCampo, } from "./typescript-http-expressoes.js";
6
6
  import { extrairCamposBindingPattern, extrairSchemasZodLocais, resolverMetadadosOrigemBody, } from "./typescript-http-body.js";
7
- export function localizarExportacaoEmStatement(statement, nomeExportado, sourceFile) {
8
- if (ts.isFunctionDeclaration(statement) && statement.name?.text === nomeExportado && statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
7
+ export function localizarExportacaoEmStatement(statement, nomeExportado, sourceFile, permitirNaoExportada = false) {
8
+ const declarada = (statement) => permitirNaoExportada
9
+ || statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) === true;
10
+ if (ts.isFunctionDeclaration(statement) && statement.name?.text === nomeExportado && declarada(statement)) {
9
11
  return {
10
12
  corpo: statement.body,
11
13
  retorno: statement.type?.getText(sourceFile),
@@ -13,7 +15,7 @@ export function localizarExportacaoEmStatement(statement, nomeExportado, sourceF
13
15
  declaracao: statement,
14
16
  };
15
17
  }
16
- if (!ts.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
18
+ if (!ts.isVariableStatement(statement) || !declarada(statement)) {
17
19
  return undefined;
18
20
  }
19
21
  for (const declaracao of statement.declarationList.declarations) {
@@ -110,17 +112,17 @@ export function extrairRetornoHttp(expr, sourceFile, valoresLocais) {
110
112
  }
111
113
  return undefined;
112
114
  }
113
- export function localizarExportacaoTypeScriptHttp(sourceFile, nomeExportado) {
115
+ export function localizarExportacaoTypeScriptHttp(sourceFile, nomeExportado, permitirNaoExportada = false) {
114
116
  for (const node of sourceFile.statements) {
115
- const localizada = localizarExportacaoEmStatement(node, nomeExportado, sourceFile);
117
+ const localizada = localizarExportacaoEmStatement(node, nomeExportado, sourceFile, permitirNaoExportada);
116
118
  if (localizada) {
117
119
  return localizada;
118
120
  }
119
121
  }
120
122
  return undefined;
121
123
  }
122
- export function inferirSemanticaHandlerTypeScriptHttp(sourceFile, nomeExportado) {
123
- const exportacao = localizarExportacaoTypeScriptHttp(sourceFile, nomeExportado);
124
+ export function inferirSemanticaHandlerTypeScriptHttp(sourceFile, nomeExportado, permitirNaoExportada = false) {
125
+ const exportacao = localizarExportacaoTypeScriptHttp(sourceFile, nomeExportado, permitirNaoExportada);
124
126
  if (!exportacao?.corpo) {
125
127
  return undefined;
126
128
  }
@@ -139,6 +141,12 @@ export function inferirSemanticaHandlerTypeScriptHttp(sourceFile, nomeExportado)
139
141
  requestNames.add(parametro.name.text);
140
142
  }
141
143
  }
144
+ const responseNames = new Set();
145
+ for (const [indice, parametro] of exportacao.parametros.entries()) {
146
+ if (indice > 0 && ts.isIdentifier(parametro.name)) {
147
+ responseNames.add(parametro.name.text);
148
+ }
149
+ }
142
150
  const visitarDeclaracoes = (node) => {
143
151
  if (ts.isVariableDeclaration(node) && node.initializer) {
144
152
  const inicializadorNormalizado = desembrulharExpressao(node.initializer);
@@ -256,6 +264,56 @@ export function inferirSemanticaHandlerTypeScriptHttp(sourceFile, nomeExportado)
256
264
  bodyTipoTexto ??= tipoTexto;
257
265
  };
258
266
  const visitarSemantica = (node) => {
267
+ if (ts.isPropertyAccessExpression(node)
268
+ && ts.isPropertyAccessExpression(node.expression)
269
+ && ts.isIdentifier(node.expression.expression)
270
+ && requestNames.has(node.expression.expression.text)) {
271
+ const origemAcesso = node.expression.name.text;
272
+ const nomeCampo = node.name.text;
273
+ const ehQuery = origemAcesso === "query" || origemAcesso === "params"
274
+ || (origemAcesso === "request" && (nomeCampo === "query" || nomeCampo === "searchParams"));
275
+ const ehBody = origemAcesso === "body" || (origemAcesso === "request" && nomeCampo === "body");
276
+ if (ehQuery && origemAcesso !== "params") {
277
+ query.push({
278
+ nome: nomeCampo,
279
+ tipoTexto: inferirTipoPorContexto(node, nomeCampo) ?? "string",
280
+ obrigatorio: false,
281
+ });
282
+ }
283
+ else if (ehBody) {
284
+ body.push({
285
+ nome: nomeCampo,
286
+ tipoTexto: inferirTipoPorContexto(node, nomeCampo) ?? "string",
287
+ obrigatorio: false,
288
+ });
289
+ }
290
+ }
291
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "json") {
292
+ let alvo = node.expression.expression;
293
+ let status;
294
+ if (ts.isCallExpression(alvo)
295
+ && ts.isPropertyAccessExpression(alvo.expression)
296
+ && alvo.expression.name.text === "status"
297
+ && alvo.arguments[0]
298
+ && ts.isNumericLiteral(alvo.arguments[0])) {
299
+ status = Number(alvo.arguments[0].text);
300
+ alvo = alvo.expression.expression;
301
+ }
302
+ if (ts.isIdentifier(alvo) && responseNames.has(alvo.text)) {
303
+ const argumento = node.arguments[0];
304
+ const campos = argumento && ts.isObjectLiteralExpression(argumento)
305
+ ? extrairCamposObjetoLiteral(argumento, sourceFile)
306
+ : argumento && ts.isIdentifier(argumento) && valoresLocais.has(argumento.text)
307
+ ? valoresLocais.get(argumento.text).campos
308
+ : [];
309
+ if (typeof status === "number") {
310
+ statuses.push(status);
311
+ }
312
+ if (status === undefined || status < 400) {
313
+ response.push(...campos);
314
+ }
315
+ }
316
+ }
259
317
  if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
260
318
  const alvo = node.expression.expression;
261
319
  const metodo = node.expression.name.text;
@@ -1,3 +1,3 @@
1
1
  export type { CampoInferidoTypeScriptHttp, ExportacaoTypeScriptHttp, ParametroRotaTypeScript, RotaTypeScriptExtraida, SemanticaHandlerTypeScriptHttp, } from "./typescript-http-modelos.js";
2
- export { extrairRotasTypeScriptHttp } from "./typescript-http-rotas.js";
2
+ export { extrairRotasExpressFastify, extrairRotasTypeScriptHttp } from "./typescript-http-rotas.js";
3
3
  export { inferirSemanticaHandlerTypeScriptHttp, localizarExportacaoTypeScriptHttp, } from "./typescript-http-semantica.js";
@@ -1,5 +1,5 @@
1
1
  // SEMA-GOVERNED: sema.governanca_ia_contexto
2
2
  // Descricao: fachada de extracao HTTP TypeScript; consulte contratos/sema/governanca_ia_contexto.sema antes de editar.
3
- export { extrairRotasTypeScriptHttp } from "./typescript-http-rotas.js";
3
+ export { extrairRotasExpressFastify, extrairRotasTypeScriptHttp } from "./typescript-http-rotas.js";
4
4
  export { inferirSemanticaHandlerTypeScriptHttp, localizarExportacaoTypeScriptHttp, } from "./typescript-http-semantica.js";
5
5
  //# sourceMappingURL=typescript-http.js.map
@@ -0,0 +1,25 @@
1
+ export declare const SCHEMA_CACHE_VERIFICACAO = "sema.verificacao-cache/v1";
2
+ export interface EntradaCacheVerificacao {
3
+ schemaVersion: typeof SCHEMA_CACHE_VERIFICACAO;
4
+ chave: string;
5
+ versaoCli: string;
6
+ alvo: string;
7
+ modulo: string;
8
+ sucesso: boolean;
9
+ quantidadeTestes: number;
10
+ testesExecutados: boolean;
11
+ arquivosGerados: string[];
12
+ geradoEm: string;
13
+ }
14
+ export declare function calcularChaveVerificacao(entradas: {
15
+ versaoCli: string;
16
+ versaoNode: string;
17
+ modulo: string;
18
+ alvo: string;
19
+ framework: string;
20
+ estrutura: string;
21
+ conteudoContrato: string;
22
+ }): string;
23
+ export declare function carregarCacheVerificacao(chave: string, pastaAlvo: string, raizCache?: string): Promise<EntradaCacheVerificacao | undefined>;
24
+ export declare function carregarManifestoVerificacao(chave: string, raizCache?: string): Promise<EntradaCacheVerificacao | undefined>;
25
+ export declare function gravarCacheVerificacao(entrada: EntradaCacheVerificacao, raizCache?: string): Promise<void>;
@@ -0,0 +1,80 @@
1
+ // SEMA-GOVERNED: sema.produto.cli_verificacao
2
+ // Descrição: cache incremental de verificação fora do workspace, endereçado por conteúdo do contrato.
3
+ import { createHash } from "node:crypto";
4
+ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { resolverRaizCacheSema } from "./driftCacheFilesystem.js";
7
+ export const SCHEMA_CACHE_VERIFICACAO = "sema.verificacao-cache/v1";
8
+ export function calcularChaveVerificacao(entradas) {
9
+ return createHash("sha256")
10
+ .update(JSON.stringify({
11
+ schemaVersion: SCHEMA_CACHE_VERIFICACAO,
12
+ versaoCli: entradas.versaoCli,
13
+ versaoNode: entradas.versaoNode,
14
+ modulo: entradas.modulo,
15
+ alvo: entradas.alvo,
16
+ framework: entradas.framework,
17
+ estrutura: entradas.estrutura,
18
+ conteudoContrato: entradas.conteudoContrato,
19
+ }))
20
+ .digest("hex");
21
+ }
22
+ function resolverPastaCacheVerificacao(raizCache) {
23
+ const resolucao = resolverRaizCacheSema(raizCache ? { raizCache } : {});
24
+ return resolucao.disponivel ? path.join(resolucao.raiz, "verificacao") : undefined;
25
+ }
26
+ export async function carregarCacheVerificacao(chave, pastaAlvo, raizCache) {
27
+ const pasta = resolverPastaCacheVerificacao(raizCache);
28
+ if (!pasta) {
29
+ return undefined;
30
+ }
31
+ let entrada;
32
+ try {
33
+ entrada = JSON.parse(await readFile(path.join(pasta, `${chave}.json`), "utf8"));
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ if (entrada.schemaVersion !== SCHEMA_CACHE_VERIFICACAO || entrada.chave !== chave) {
39
+ return undefined;
40
+ }
41
+ for (const relativo of entrada.arquivosGerados) {
42
+ try {
43
+ await stat(path.join(pastaAlvo, relativo));
44
+ }
45
+ catch {
46
+ return undefined;
47
+ }
48
+ }
49
+ return entrada;
50
+ }
51
+ export async function carregarManifestoVerificacao(chave, raizCache) {
52
+ const pasta = resolverPastaCacheVerificacao(raizCache);
53
+ if (!pasta) {
54
+ return undefined;
55
+ }
56
+ try {
57
+ const entrada = JSON.parse(await readFile(path.join(pasta, `${chave}.json`), "utf8"));
58
+ if (entrada.schemaVersion !== SCHEMA_CACHE_VERIFICACAO || entrada.chave !== chave) {
59
+ return undefined;
60
+ }
61
+ return entrada;
62
+ }
63
+ catch {
64
+ return undefined;
65
+ }
66
+ }
67
+ export async function gravarCacheVerificacao(entrada, raizCache) {
68
+ const pasta = resolverPastaCacheVerificacao(raizCache);
69
+ if (!pasta) {
70
+ return;
71
+ }
72
+ try {
73
+ await mkdir(pasta, { recursive: true });
74
+ await writeFile(path.join(pasta, `${entrada.chave}.json`), `${JSON.stringify(entrada, null, 2)}\n`, "utf8");
75
+ }
76
+ catch {
77
+ // Cache é aceleração: falha de gravação nunca derruba a verificação.
78
+ }
79
+ }
80
+ //# sourceMappingURL=verificacaoCache.js.map
package/docs/syntax.md CHANGED
@@ -87,6 +87,20 @@ impl {
87
87
 
88
88
  `sema compilar --alvo javascript` defines a generation target. `impl { js: ... }` defines the live-code origin linked to the contract. They are different layers and both are valid.
89
89
 
90
+ ## Layered `impl` Roles
91
+
92
+ Layered code (route + service + persistence in the same language) can declare one `impl` per role. Append a supported role to the origin: `rota`, `servico`, `persistencia` or `repositorio`.
93
+
94
+ ```sema
95
+ impl {
96
+ ts_rota: server.routes.monitores.criarMonitor
97
+ ts_servico: server.services.monitores.criarMonitor
98
+ ts_persistencia: server.repositories.monitores.criar
99
+ }
100
+ ```
101
+
102
+ Each origin-plus-role pair must appear at most once; the bare origin (for example `ts`) stays valid for single-layer tasks, and drift resolves every role independently.
103
+
90
104
  ## Small Canonical Lists
91
105
 
92
106
  - `effects`: `persistencia`, `consulta`, `evento`, `auditoria`, `db.write`, `queue.publish`, `fs.write`, `network.egress`, `secret.read`, `shell.exec`.