@cosmicdrift/kumiko-framework 0.319.0 → 0.321.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 (71) hide show
  1. package/package.json +8 -4
  2. package/src/__tests__/schema-cli.integration.test.ts +11 -0
  3. package/src/__tests__/upgrade-cli.test.ts +50 -3
  4. package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +6 -0
  5. package/src/api/__tests__/auth-routes-mfa-verify.test.ts +11 -0
  6. package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +8 -4
  7. package/src/api/__tests__/client-ip.test.ts +164 -0
  8. package/src/api/__tests__/extra-routes.integration.test.ts +50 -1
  9. package/src/api/__tests__/http-route-rate-limit.integration.test.ts +73 -2
  10. package/src/api/__tests__/pat-route-guard.integration.test.ts +126 -0
  11. package/src/api/__tests__/post-auth-landing.test.ts +100 -0
  12. package/src/api/__tests__/request-id-middleware.test.ts +35 -2
  13. package/src/api/__tests__/schema-route.integration.test.ts +157 -0
  14. package/src/api/api-constants.ts +2 -0
  15. package/src/api/auth-routes.ts +92 -95
  16. package/src/api/client-ip.ts +118 -0
  17. package/src/api/extra-route.ts +3 -0
  18. package/src/api/index.ts +17 -0
  19. package/src/api/pat-route-guard.ts +51 -0
  20. package/src/api/post-auth-landing.ts +105 -0
  21. package/src/api/request-context.ts +4 -2
  22. package/src/api/request-id-middleware.ts +44 -14
  23. package/src/api/server.ts +130 -40
  24. package/src/changes.json +121 -0
  25. package/src/db/queries/event-store-transfer.ts +73 -0
  26. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +47 -0
  27. package/src/engine/__tests__/metric-navigate-tab.test.ts +167 -0
  28. package/src/engine/__tests__/project-app-schema-for-roles.test.ts +500 -0
  29. package/src/engine/__tests__/steps-webhook-send.test.ts +2 -2
  30. package/src/engine/__tests__/toolbar-navigate-tab.test.ts +233 -0
  31. package/src/engine/boot-validator/pii-retention.ts +18 -2
  32. package/src/engine/boot-validator/screens.ts +75 -18
  33. package/src/engine/feature-ast/__tests__/fixtures/resolve-import/alias-reexport.ts +1 -0
  34. package/src/engine/feature-ast/__tests__/fixtures/resolve-import/cycle-a.ts +1 -0
  35. package/src/engine/feature-ast/__tests__/fixtures/resolve-import/cycle-b.ts +1 -0
  36. package/src/engine/feature-ast/__tests__/fixtures/resolve-import/entry.ts +3 -0
  37. package/src/engine/feature-ast/__tests__/fixtures/resolve-import/local-rebind.ts +3 -0
  38. package/src/engine/feature-ast/__tests__/fixtures/resolve-import/origin.ts +1 -0
  39. package/src/engine/feature-ast/__tests__/fixtures/resolve-import/star-barrel.ts +1 -0
  40. package/src/engine/feature-ast/__tests__/parse.test.ts +4 -4
  41. package/src/engine/feature-ast/__tests__/resolve-import.test.ts +138 -0
  42. package/src/engine/feature-ast/extractors/index.ts +4 -0
  43. package/src/engine/feature-ast/extractors/resolve-import.ts +187 -0
  44. package/src/engine/feature-ast/extractors/shared.ts +26 -19
  45. package/src/engine/feature-ast/parse.ts +13 -20
  46. package/src/engine/index.ts +1 -0
  47. package/src/engine/project-app-schema-for-roles.ts +950 -0
  48. package/src/engine/steps/webhook-send.ts +4 -2
  49. package/src/engine/types/handlers.ts +1 -1
  50. package/src/engine/types/index.ts +5 -1
  51. package/src/entrypoint/index.ts +14 -1
  52. package/src/event-store/__tests__/transfer.integration.test.ts +236 -0
  53. package/src/event-store/index.ts +6 -0
  54. package/src/event-store/transfer.ts +84 -0
  55. package/src/files/__tests__/files.integration.test.ts +105 -1
  56. package/src/files/file-routes.ts +54 -2
  57. package/src/files/index.ts +1 -1
  58. package/src/http/index.ts +10 -2
  59. package/src/jobs/__tests__/jobs.integration.test.ts +105 -3
  60. package/src/jobs/job-runner.ts +6 -1
  61. package/src/pipeline/multi-stream-apply-context.ts +4 -0
  62. package/src/rate-limit/__tests__/middleware.integration.test.ts +24 -7
  63. package/src/rate-limit/middleware.ts +32 -8
  64. package/src/schema-cli.ts +5 -1
  65. package/src/stack/__tests__/push-entity-projection-tables-column-sync.integration.test.ts +107 -0
  66. package/src/stack/push-entity-projection-tables.ts +52 -7
  67. package/src/stack/table-helpers.ts +17 -9
  68. package/src/stack/test-stack.ts +28 -1
  69. package/src/testing/real-providers.ts +2 -0
  70. package/src/ui-types/index.ts +1 -1
  71. package/src/upgrade-cli.ts +58 -18
@@ -0,0 +1,187 @@
1
+ // Syntactic cross-file resolution for feature-ast identifier extractors.
2
+ // Deliberately avoids ts-morph's symbol/type-checker APIs (getSymbol,
3
+ // getDefinitionNodes, getModuleSpecifierSourceFile, ...) — those build a full
4
+ // TypeScript program on first use, which against this repo's workspace
5
+ // packages pulls in ~1000 source files and costs seconds instead of
6
+ // milliseconds.
7
+ import type {
8
+ Node,
9
+ ParameterDeclaration,
10
+ SourceFile,
11
+ Statement,
12
+ VariableDeclaration,
13
+ } from "ts-morph";
14
+ import { SyntaxKind, ts } from "ts-morph";
15
+
16
+ const MODULE_RESOLUTION_OPTIONS: ts.CompilerOptions = {
17
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
18
+ module: ts.ModuleKind.ESNext,
19
+ target: ts.ScriptTarget.ESNext,
20
+ };
21
+
22
+ const MAX_RESOLUTION_DEPTH = 24;
23
+
24
+ export function resolveModuleFile(fromFile: SourceFile, specifier: string): SourceFile | undefined {
25
+ const project = fromFile.getProject();
26
+ // The project's own host, not ts.sys: it honours an in-memory file system
27
+ // (Designer) and exists outside Node.
28
+ const resolved = ts.resolveModuleName(
29
+ specifier,
30
+ fromFile.getFilePath(),
31
+ MODULE_RESOLUTION_OPTIONS,
32
+ project.getModuleResolutionHost(),
33
+ );
34
+ const resolvedFileName = resolved.resolvedModule?.resolvedFileName;
35
+ if (!resolvedFileName) return undefined;
36
+ // A declaration file has no initializers to read. Installed packages that
37
+ // ship .ts sources (the @cosmicdrift/* packages in a consumer repo) stay
38
+ // resolvable, as they were via the type checker.
39
+ if (resolvedFileName.endsWith(".d.ts")) return undefined;
40
+ return fromFile.getProject().addSourceFileAtPathIfExists(resolvedFileName);
41
+ }
42
+
43
+ export function findImportBindingForLocalName(
44
+ sourceFile: SourceFile,
45
+ localName: string,
46
+ ): { readonly moduleSpecifier: string; readonly importedName: string } | undefined {
47
+ for (const importDecl of sourceFile.getImportDeclarations()) {
48
+ const named = importDecl
49
+ .getNamedImports()
50
+ .find((ni) => (ni.getAliasNode()?.getText() ?? ni.getName()) === localName);
51
+ if (!named) continue;
52
+ return {
53
+ moduleSpecifier: importDecl.getModuleSpecifierValue(),
54
+ importedName: named.getName(),
55
+ };
56
+ }
57
+ return undefined;
58
+ }
59
+
60
+ /**
61
+ * `visited` keys on `${filePath}#${name}`, not path alone — aliasing can
62
+ * legally re-export a different name from the same file — and doubles as
63
+ * the cycle guard; `depth` backstops pathological non-cyclic chains.
64
+ */
65
+ export function resolveExportedVariable(
66
+ file: SourceFile,
67
+ exportedName: string,
68
+ visited: Set<string> = new Set(),
69
+ depth = 0,
70
+ ): VariableDeclaration | undefined {
71
+ if (depth > MAX_RESOLUTION_DEPTH) return undefined;
72
+ const key = `${file.getFilePath()}#${exportedName}`;
73
+ if (visited.has(key)) return undefined;
74
+ visited.add(key);
75
+
76
+ const directDecl = file.getVariableDeclaration(exportedName);
77
+ if (directDecl?.getVariableStatement()?.hasExportKeyword()) return directDecl;
78
+
79
+ const namedExportMatch = resolveViaNamedExportSpecifier(file, exportedName, visited, depth);
80
+ if (namedExportMatch) return namedExportMatch;
81
+
82
+ return resolveViaStarExport(file, exportedName, visited, depth);
83
+ }
84
+
85
+ function resolveViaNamedExportSpecifier(
86
+ file: SourceFile,
87
+ exportedName: string,
88
+ visited: Set<string>,
89
+ depth: number,
90
+ ): VariableDeclaration | undefined {
91
+ for (const exportDecl of file.getExportDeclarations()) {
92
+ const specifier = exportDecl
93
+ .getNamedExports()
94
+ .find((spec) => (spec.getAliasNode()?.getText() ?? spec.getName()) === exportedName);
95
+ if (!specifier) continue;
96
+ const originalName = specifier.getName();
97
+
98
+ if (!exportDecl.hasModuleSpecifier()) {
99
+ const resolved = resolveLocalExportBinding(file, originalName, visited, depth);
100
+ if (resolved) return resolved;
101
+ continue;
102
+ }
103
+
104
+ const moduleSpecifierValue = exportDecl.getModuleSpecifierValue();
105
+ if (!moduleSpecifierValue) continue;
106
+ const target = resolveModuleFile(file, moduleSpecifierValue);
107
+ if (!target) continue;
108
+ const resolved = resolveExportedVariable(target, originalName, visited, depth + 1);
109
+ if (resolved) return resolved;
110
+ }
111
+ return undefined;
112
+ }
113
+
114
+ function resolveLocalExportBinding(
115
+ file: SourceFile,
116
+ originalName: string,
117
+ visited: Set<string>,
118
+ depth: number,
119
+ ): VariableDeclaration | undefined {
120
+ const localDecl = file.getVariableDeclaration(originalName);
121
+ if (localDecl) return localDecl;
122
+ const binding = findImportBindingForLocalName(file, originalName);
123
+ if (!binding) return undefined;
124
+ const target = resolveModuleFile(file, binding.moduleSpecifier);
125
+ if (!target) return undefined;
126
+ return resolveExportedVariable(target, binding.importedName, visited, depth + 1);
127
+ }
128
+
129
+ function resolveViaStarExport(
130
+ file: SourceFile,
131
+ exportedName: string,
132
+ visited: Set<string>,
133
+ depth: number,
134
+ ): VariableDeclaration | undefined {
135
+ for (const exportDecl of file.getExportDeclarations()) {
136
+ if (!exportDecl.hasModuleSpecifier()) continue;
137
+ if (exportDecl.getNamedExports().length > 0) continue;
138
+ if (exportDecl.getNamespaceExport()) continue; // `export * as ns from "./m"` is not a re-export target.
139
+ const moduleSpecifierValue = exportDecl.getModuleSpecifierValue();
140
+ if (!moduleSpecifierValue) continue;
141
+ const target = resolveModuleFile(file, moduleSpecifierValue);
142
+ if (!target) continue;
143
+ const resolved = resolveExportedVariable(target, exportedName, visited, depth + 1);
144
+ if (resolved) return resolved;
145
+ }
146
+ return undefined;
147
+ }
148
+
149
+ /** A function parameter of the same name shadows and stops the search — it has no statically readable initializer. */
150
+ export function findScopedVariableDeclaration(
151
+ identifier: Node,
152
+ name: string,
153
+ ): VariableDeclaration | undefined {
154
+ for (const ancestor of [identifier, ...identifier.getAncestors()]) {
155
+ const params = getShadowingParameters(ancestor);
156
+ if (params?.some((p) => p.getName() === name)) return undefined;
157
+
158
+ const statements = getDirectStatements(ancestor);
159
+ if (!statements) continue;
160
+ for (const stmt of statements) {
161
+ const varStmt = stmt.asKind(SyntaxKind.VariableStatement);
162
+ if (!varStmt) continue;
163
+ const decl = varStmt
164
+ .getDeclarationList()
165
+ .getDeclarations()
166
+ .find((d) => d.getName() === name);
167
+ if (decl) return decl;
168
+ }
169
+ }
170
+ return undefined;
171
+ }
172
+
173
+ function getShadowingParameters(node: Node): readonly ParameterDeclaration[] | undefined {
174
+ const fn =
175
+ node.asKind(SyntaxKind.ArrowFunction) ??
176
+ node.asKind(SyntaxKind.FunctionExpression) ??
177
+ node.asKind(SyntaxKind.FunctionDeclaration);
178
+ return fn?.getParameters();
179
+ }
180
+
181
+ function getDirectStatements(node: Node): readonly Statement[] | undefined {
182
+ const block = node.asKind(SyntaxKind.Block);
183
+ if (block) return block.getStatements();
184
+ const sourceFile = node.asKind(SyntaxKind.SourceFile);
185
+ if (sourceFile) return sourceFile.getStatements();
186
+ return undefined;
187
+ }
@@ -2,6 +2,12 @@ import type { CallExpression, Node, ObjectLiteralExpression } from "ts-morph";
2
2
  import { SyntaxKind } from "ts-morph";
3
3
  import { isPlainObject } from "../../../utils/is-plain-object";
4
4
  import type { ParseError } from "../parse";
5
+ import {
6
+ findImportBindingForLocalName,
7
+ findScopedVariableDeclaration,
8
+ resolveExportedVariable,
9
+ resolveModuleFile,
10
+ } from "./resolve-import";
5
11
 
6
12
  export type ExtractOutput<TPattern> =
7
13
  | { readonly kind: "pattern"; readonly pattern: TPattern }
@@ -200,28 +206,31 @@ function unwrapLiteralInitializer(node: Node): string | undefined {
200
206
  /**
201
207
  * Resolves a bare Identifier to the string value of its declaration's
202
208
  * initializer (`export const EXT_TENANT_DATA = "tenant-data" as const`),
203
- * following imports via ts-morph's definition lookup — works across files
204
- * and packages against a real-filesystem Project (see #1008 precedent in
205
- * parse.ts). Only descends into VariableDeclaration initializers; a
206
- * function/class/type definition or an unresolvable import (external
207
- * package, ambient declaration) yields undefined, never a throw.
209
+ * following imports syntactically across files and packages against a
210
+ * real-filesystem Project without ever building a TypeScript program (see
211
+ * resolve-import.ts). Only descends into VariableDeclaration initializers; a
212
+ * function/class/type definition or an unresolvable import yields undefined,
213
+ * never a throw.
208
214
  */
209
215
  function resolveIdentifierToStringLiteral(identifier: Node): string | undefined {
210
216
  const id = identifier.asKind(SyntaxKind.Identifier);
211
217
  if (!id) return undefined;
212
- let defs: readonly Node[];
213
- try {
214
- defs = id.getDefinitionNodes();
215
- } catch {
216
- return undefined;
217
- }
218
- for (const def of defs) {
219
- const init = def.asKind(SyntaxKind.VariableDeclaration)?.getInitializer();
220
- if (!init) continue;
221
- const value = unwrapLiteralInitializer(init);
218
+ const name = id.getText();
219
+
220
+ const scoped = findScopedVariableDeclaration(id, name);
221
+ if (scoped) {
222
+ const init = scoped.getInitializer();
223
+ const value = init ? unwrapLiteralInitializer(init) : undefined;
222
224
  if (value !== undefined) return value;
223
225
  }
224
- return undefined;
226
+
227
+ const binding = findImportBindingForLocalName(id.getSourceFile(), name);
228
+ if (!binding) return undefined;
229
+ const targetFile = resolveModuleFile(id.getSourceFile(), binding.moduleSpecifier);
230
+ if (!targetFile) return undefined;
231
+ const decl = resolveExportedVariable(targetFile, binding.importedName);
232
+ const init = decl?.getInitializer();
233
+ return init ? unwrapLiteralInitializer(init) : undefined;
225
234
  }
226
235
 
227
236
  /**
@@ -279,9 +288,7 @@ export function resolveSameFileObjectLiteral(node: Node): ObjectLiteralExpressio
279
288
  if (direct) return direct;
280
289
  const identifier = node.asKind(SyntaxKind.Identifier);
281
290
  if (!identifier) return undefined;
282
- const valueDecl = identifier.getSymbol()?.getValueDeclaration();
283
- const fromSymbol = valueDecl?.asKind(SyntaxKind.VariableDeclaration);
284
- const varDecl = fromSymbol ?? node.getSourceFile().getVariableDeclaration(identifier.getText());
291
+ const varDecl = findScopedVariableDeclaration(identifier, identifier.getText());
285
292
  return varDecl?.getInitializer()?.asKind(SyntaxKind.ObjectLiteralExpression);
286
293
  }
287
294
 
@@ -73,7 +73,9 @@ import {
73
73
  extractWorkspace,
74
74
  extractWriteHandler,
75
75
  findFunctionLiteral,
76
+ findImportBindingForLocalName,
76
77
  readObjectPropertyInitializer,
78
+ resolveModuleFile,
77
79
  resolveSameFileObjectLiteral,
78
80
  } from "./extractors";
79
81
  import type { FeaturePattern, UnknownPattern } from "./patterns";
@@ -296,13 +298,13 @@ function walkSetupCallback(
296
298
  /**
297
299
  * Resolves a bare call like `registerShowPonyScreens(r)` to the callee's
298
300
  * body + its own parameter name for the registrar slot. The callee may be
299
- * declared in the same file (function declaration, or a const initialized
300
- * with an arrow/function expression), or imported by name from another
301
- * file — resolved via the module specifier's SourceFile (#1008; only
302
- * works against a real-filesystem Project, e.g. parseFeatureFile's. The
303
- * in-memory Designer Project has no files to resolve against and
304
- * getModuleSpecifierSourceFile() returns undefined there, same as any
305
- * other unresolvable reference).
301
+ * declared in the same file, or imported by name from another file —
302
+ * resolved syntactically via resolveModuleFile, which only works against a
303
+ * real-filesystem Project; the in-memory Designer Project has no files to
304
+ * resolve against and returns undefined there, same as any other
305
+ * unresolvable reference. Does not follow re-exports in the target file — a
306
+ * wrapper function is expected to be declared directly in the module it's
307
+ * imported from.
306
308
  */
307
309
  function resolveRegistrarWrapperCall(
308
310
  call: CallExpression,
@@ -328,21 +330,12 @@ function resolveRegistrarWrapperCall(
328
330
  const local = resolveWrapperDeclarationInFile(callSourceFile, name, argIndex);
329
331
  if (local) return { ...local, sourceFile: callSourceFile };
330
332
 
331
- const importDecl = callSourceFile
332
- .getImportDeclarations()
333
- .find((d) =>
334
- d.getNamedImports().some((ni) => (ni.getAliasNode()?.getText() ?? ni.getName()) === name),
335
- );
336
- if (!importDecl) return undefined;
337
- const exportedName =
338
- importDecl
339
- .getNamedImports()
340
- .find((ni) => (ni.getAliasNode()?.getText() ?? ni.getName()) === name)
341
- ?.getName() ?? name;
342
- const targetFile = importDecl.getModuleSpecifierSourceFile();
333
+ const binding = findImportBindingForLocalName(callSourceFile, name);
334
+ if (!binding) return undefined;
335
+ const targetFile = resolveModuleFile(callSourceFile, binding.moduleSpecifier);
343
336
  if (!targetFile) return undefined; // Unresolvable: in-memory project, external package, or missing file.
344
337
 
345
- const imported = resolveWrapperDeclarationInFile(targetFile, exportedName, argIndex);
338
+ const imported = resolveWrapperDeclarationInFile(targetFile, binding.importedName, argIndex);
346
339
  return imported ? { ...imported, sourceFile: targetFile } : undefined;
347
340
  }
348
341
 
@@ -267,6 +267,7 @@ export {
267
267
  export type { ParsedRefTarget } from "./parse-ref-target";
268
268
  export { parseRefTarget, parseRefTargetEntityName } from "./parse-ref-target";
269
269
  export { buildPipelineSteps, stepsPipeline } from "./pipeline";
270
+ export { projectAppSchemaForRoles } from "./project-app-schema-for-roles";
270
271
  export { defineApply, defineMspApply, setFields } from "./projection-helpers";
271
272
  export type { BuiltinQnType, ParsedQn, QnType } from "./qualified-name";
272
273
  export { isValidQn, parseQn, QnTypes, qn, toKebab } from "./qualified-name";