@facilis.dev/analyze 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -2
  3. package/dist/analyze.d.ts +17 -0
  4. package/dist/analyze.js +99 -0
  5. package/dist/ast/node.d.ts +29 -0
  6. package/dist/ast/node.js +85 -0
  7. package/dist/ast/walk.d.ts +8 -0
  8. package/dist/ast/walk.js +40 -0
  9. package/dist/diagnostics.d.ts +5 -0
  10. package/dist/diagnostics.js +12 -0
  11. package/dist/fs.d.ts +13 -0
  12. package/dist/fs.js +51 -0
  13. package/dist/graph-edges.d.ts +3 -0
  14. package/dist/graph-edges.js +148 -0
  15. package/dist/graph-ids.d.ts +6 -0
  16. package/dist/graph-ids.js +22 -0
  17. package/dist/graph.d.ts +15 -0
  18. package/dist/graph.js +162 -0
  19. package/dist/index.d.ts +24 -0
  20. package/dist/index.js +17 -0
  21. package/dist/json.d.ts +8 -0
  22. package/dist/json.js +31 -0
  23. package/dist/module-graph.d.ts +33 -0
  24. package/dist/module-graph.js +86 -0
  25. package/dist/module-info.d.ts +27 -0
  26. package/dist/module-info.js +143 -0
  27. package/dist/node.d.ts +16 -0
  28. package/dist/node.js +41 -0
  29. package/dist/parser.d.ts +16 -0
  30. package/dist/parser.js +1 -0
  31. package/dist/passes/clients.d.ts +22 -0
  32. package/dist/passes/clients.js +167 -0
  33. package/dist/passes/config.d.ts +16 -0
  34. package/dist/passes/config.js +229 -0
  35. package/dist/passes/file-facts.d.ts +13 -0
  36. package/dist/passes/file-facts.js +130 -0
  37. package/dist/passes/i18n.d.ts +14 -0
  38. package/dist/passes/i18n.js +55 -0
  39. package/dist/passes/job-relations-diagnostics.d.ts +9 -0
  40. package/dist/passes/job-relations-diagnostics.js +38 -0
  41. package/dist/passes/job-relations.d.ts +18 -0
  42. package/dist/passes/job-relations.js +52 -0
  43. package/dist/passes/jobs.d.ts +20 -0
  44. package/dist/passes/jobs.js +133 -0
  45. package/dist/passes/jsx-text.d.ts +28 -0
  46. package/dist/passes/jsx-text.js +121 -0
  47. package/dist/passes/jsx.d.ts +24 -0
  48. package/dist/passes/jsx.js +135 -0
  49. package/dist/passes/path-expression.d.ts +26 -0
  50. package/dist/passes/path-expression.js +265 -0
  51. package/dist/passes/route-config.d.ts +24 -0
  52. package/dist/passes/route-config.js +152 -0
  53. package/dist/passes/route-description.d.ts +13 -0
  54. package/dist/passes/route-description.js +23 -0
  55. package/dist/passes/route-elevation.d.ts +18 -0
  56. package/dist/passes/route-elevation.js +136 -0
  57. package/dist/passes/route-handler.d.ts +27 -0
  58. package/dist/passes/route-handler.js +87 -0
  59. package/dist/passes/route-location.d.ts +16 -0
  60. package/dist/passes/route-location.js +58 -0
  61. package/dist/passes/route-relation-resolve.d.ts +51 -0
  62. package/dist/passes/route-relation-resolve.js +172 -0
  63. package/dist/passes/route-relations-diagnostics.d.ts +15 -0
  64. package/dist/passes/route-relations-diagnostics.js +79 -0
  65. package/dist/passes/route-relations-follow.d.ts +16 -0
  66. package/dist/passes/route-relations-follow.js +116 -0
  67. package/dist/passes/route-relations-usage.d.ts +50 -0
  68. package/dist/passes/route-relations-usage.js +44 -0
  69. package/dist/passes/route-relations-walk.d.ts +25 -0
  70. package/dist/passes/route-relations-walk.js +199 -0
  71. package/dist/passes/route-relations.d.ts +17 -0
  72. package/dist/passes/route-relations.js +43 -0
  73. package/dist/passes/route-table.d.ts +21 -0
  74. package/dist/passes/route-table.js +104 -0
  75. package/dist/passes/route-tables.d.ts +10 -0
  76. package/dist/passes/route-tables.js +42 -0
  77. package/dist/passes/routes.d.ts +18 -0
  78. package/dist/passes/routes.js +152 -0
  79. package/dist/passes/schema-column-types.d.ts +9 -0
  80. package/dist/passes/schema-column-types.js +55 -0
  81. package/dist/passes/schema-columns.d.ts +35 -0
  82. package/dist/passes/schema-columns.js +197 -0
  83. package/dist/passes/schema-dsl.d.ts +24 -0
  84. package/dist/passes/schema-dsl.js +117 -0
  85. package/dist/passes/schema-file-links-ddl.d.ts +18 -0
  86. package/dist/passes/schema-file-links-ddl.js +106 -0
  87. package/dist/passes/schema-file-links.d.ts +19 -0
  88. package/dist/passes/schema-file-links.js +144 -0
  89. package/dist/passes/schema-foreign-keys.d.ts +21 -0
  90. package/dist/passes/schema-foreign-keys.js +80 -0
  91. package/dist/passes/schema-policies.d.ts +15 -0
  92. package/dist/passes/schema-policies.js +201 -0
  93. package/dist/passes/schema-re-exports.d.ts +5 -0
  94. package/dist/passes/schema-re-exports.js +77 -0
  95. package/dist/passes/schema-relations.d.ts +8 -0
  96. package/dist/passes/schema-relations.js +182 -0
  97. package/dist/passes/schema-scope.d.ts +7 -0
  98. package/dist/passes/schema-scope.js +20 -0
  99. package/dist/passes/schema-tables.d.ts +13 -0
  100. package/dist/passes/schema-tables.js +149 -0
  101. package/dist/passes/schema.d.ts +9 -0
  102. package/dist/passes/schema.js +55 -0
  103. package/dist/passes/screen-components.d.ts +22 -0
  104. package/dist/passes/screen-components.js +143 -0
  105. package/dist/passes/screen-intrinsics.d.ts +18 -0
  106. package/dist/passes/screen-intrinsics.js +21 -0
  107. package/dist/passes/screen-preview.d.ts +29 -0
  108. package/dist/passes/screen-preview.js +174 -0
  109. package/dist/passes/screens.d.ts +12 -0
  110. package/dist/passes/screens.js +278 -0
  111. package/dist/passes/static-read.d.ts +47 -0
  112. package/dist/passes/static-read.js +151 -0
  113. package/dist/passes/views.d.ts +20 -0
  114. package/dist/passes/views.js +101 -0
  115. package/dist/passes/workflow-tables.d.ts +18 -0
  116. package/dist/passes/workflow-tables.js +71 -0
  117. package/dist/passes/workflows.d.ts +8 -0
  118. package/dist/passes/workflows.js +160 -0
  119. package/dist/path-resolver.d.ts +20 -0
  120. package/dist/path-resolver.js +134 -0
  121. package/dist/revision.d.ts +2 -0
  122. package/dist/revision.js +23 -0
  123. package/dist/source.d.ts +4 -0
  124. package/dist/source.js +13 -0
  125. package/dist/static-eval.d.ts +36 -0
  126. package/dist/static-eval.js +215 -0
  127. package/dist/types-graph.d.ts +144 -0
  128. package/dist/types-graph.js +4 -0
  129. package/dist/types-jobs.d.ts +28 -0
  130. package/dist/types-jobs.js +14 -0
  131. package/dist/types-relations.d.ts +49 -0
  132. package/dist/types-relations.js +11 -0
  133. package/dist/types-workflows.d.ts +36 -0
  134. package/dist/types-workflows.js +3 -0
  135. package/dist/types.d.ts +226 -0
  136. package/dist/types.js +1 -0
  137. package/package.json +49 -4
  138. package/src/analyze.ts +119 -0
  139. package/src/ast/node.ts +102 -0
  140. package/src/ast/walk.ts +37 -0
  141. package/src/diagnostics.ts +22 -0
  142. package/src/fs.ts +59 -0
  143. package/src/graph-edges.ts +167 -0
  144. package/src/graph-ids.ts +31 -0
  145. package/src/graph.ts +194 -0
  146. package/src/index.ts +26 -0
  147. package/src/json.ts +34 -0
  148. package/src/module-graph.ts +117 -0
  149. package/src/module-info.ts +162 -0
  150. package/src/node.ts +54 -0
  151. package/src/parser.ts +19 -0
  152. package/src/passes/clients.ts +233 -0
  153. package/src/passes/config.ts +278 -0
  154. package/src/passes/file-facts.ts +142 -0
  155. package/src/passes/i18n.ts +66 -0
  156. package/src/passes/job-relations-diagnostics.ts +60 -0
  157. package/src/passes/job-relations.ts +76 -0
  158. package/src/passes/jobs.ts +181 -0
  159. package/src/passes/jsx-text.ts +126 -0
  160. package/src/passes/jsx.ts +142 -0
  161. package/src/passes/path-expression.ts +297 -0
  162. package/src/passes/route-config.ts +188 -0
  163. package/src/passes/route-description.ts +33 -0
  164. package/src/passes/route-elevation.ts +161 -0
  165. package/src/passes/route-handler.ts +105 -0
  166. package/src/passes/route-location.ts +62 -0
  167. package/src/passes/route-relation-resolve.ts +228 -0
  168. package/src/passes/route-relations-diagnostics.ts +115 -0
  169. package/src/passes/route-relations-follow.ts +146 -0
  170. package/src/passes/route-relations-usage.ts +98 -0
  171. package/src/passes/route-relations-walk.ts +255 -0
  172. package/src/passes/route-relations.ts +62 -0
  173. package/src/passes/route-table.ts +123 -0
  174. package/src/passes/route-tables.ts +50 -0
  175. package/src/passes/routes.ts +188 -0
  176. package/src/passes/schema-column-types.ts +62 -0
  177. package/src/passes/schema-columns.ts +287 -0
  178. package/src/passes/schema-dsl.ts +136 -0
  179. package/src/passes/schema-file-links-ddl.ts +125 -0
  180. package/src/passes/schema-file-links.ts +213 -0
  181. package/src/passes/schema-foreign-keys.ts +121 -0
  182. package/src/passes/schema-policies.ts +270 -0
  183. package/src/passes/schema-re-exports.ts +95 -0
  184. package/src/passes/schema-relations.ts +239 -0
  185. package/src/passes/schema-scope.ts +29 -0
  186. package/src/passes/schema-tables.ts +216 -0
  187. package/src/passes/schema.ts +67 -0
  188. package/src/passes/screen-components.ts +162 -0
  189. package/src/passes/screen-intrinsics.ts +29 -0
  190. package/src/passes/screen-preview.ts +295 -0
  191. package/src/passes/screens.ts +291 -0
  192. package/src/passes/static-read.ts +196 -0
  193. package/src/passes/views.ts +125 -0
  194. package/src/passes/workflow-tables.ts +87 -0
  195. package/src/passes/workflows.ts +213 -0
  196. package/src/path-resolver.ts +146 -0
  197. package/src/revision.ts +25 -0
  198. package/src/source.ts +15 -0
  199. package/src/static-eval.ts +242 -0
  200. package/src/types-graph.ts +176 -0
  201. package/src/types-jobs.ts +40 -0
  202. package/src/types-relations.ts +64 -0
  203. package/src/types-workflows.ts +42 -0
  204. package/src/types.ts +299 -0
@@ -0,0 +1,8 @@
1
+ import type { ModuleGraph, ProjectFile } from "../module-graph.js";
2
+ import type { Diagnostic, RelationFact } from "../types.js";
3
+ import { type TableLookup } from "./schema-foreign-keys.js";
4
+ export interface RelationScan {
5
+ readonly relations: readonly RelationFact[];
6
+ readonly diagnostics: readonly Diagnostic[];
7
+ }
8
+ export declare function buildRelationFacts(graph: ModuleGraph, files: readonly ProjectFile[], lookup: TableLookup): RelationScan;
@@ -0,0 +1,182 @@
1
+ import { callArguments, identifierName, memberName, nodeArray, objectEntries, propertyName, propertyValue, unwrapExpression, } from "../ast/node.js";
2
+ import { callbackExpression, createDslResolver, nodeRef, schemaDiagnostics, schemaUnknown, topLevelDeclarators, } from "./schema-dsl.js";
3
+ import { resolveTableBinding } from "./schema-foreign-keys.js";
4
+ /** Map the callback's destructured `one`/`many` helpers to their local names. */
5
+ function helperBindings(callback) {
6
+ const fn = unwrapExpression(callback);
7
+ if (fn === undefined)
8
+ return null;
9
+ if (fn.type !== "ArrowFunctionExpression" && fn.type !== "FunctionExpression")
10
+ return null;
11
+ const param = unwrapExpression(nodeArray(fn, "params")[0]);
12
+ if (param?.type !== "ObjectPattern")
13
+ return null;
14
+ const one = new Set();
15
+ const many = new Set();
16
+ for (const property of objectEntries(param)) {
17
+ if (property.type === "SpreadElement")
18
+ continue;
19
+ const key = propertyName(property);
20
+ const local = identifierName(propertyValue(property));
21
+ if (local === undefined)
22
+ continue;
23
+ if (key === "one")
24
+ one.add(local);
25
+ if (key === "many")
26
+ many.add(local);
27
+ }
28
+ return { one, many };
29
+ }
30
+ function readColumnList(options, file, unknowns) {
31
+ const empty = { fields: [], references: [] };
32
+ if (options === undefined)
33
+ return empty;
34
+ const node = unwrapExpression(options);
35
+ if (node?.type !== "ObjectExpression") {
36
+ unknowns.push(schemaUnknown("relation options are not an object literal", nodeRef(file, options)));
37
+ return empty;
38
+ }
39
+ const readList = (key) => {
40
+ const property = objectEntries(node).find((entry) => propertyName(entry) === key);
41
+ if (property === undefined)
42
+ return [];
43
+ const value = unwrapExpression(propertyValue(property));
44
+ if (value?.type !== "ArrayExpression") {
45
+ unknowns.push(schemaUnknown("relation " + key + " is not an array literal", nodeRef(file, property)));
46
+ return [];
47
+ }
48
+ const columns = [];
49
+ for (const element of nodeArray(value, "elements")) {
50
+ const item = unwrapExpression(element);
51
+ const name = item?.type === "MemberExpression" ? memberName(item) : identifierName(item);
52
+ if (name === undefined) {
53
+ unknowns.push(schemaUnknown("relation " + key + " entry is not a column reference", nodeRef(file, item)));
54
+ continue;
55
+ }
56
+ columns.push(name);
57
+ }
58
+ return columns;
59
+ };
60
+ return { fields: readList("fields"), references: readList("references") };
61
+ }
62
+ function readRelationEntry(input) {
63
+ const { entry, from, graph, file, lookup, helpers } = input;
64
+ const unknowns = [];
65
+ const value = unwrapExpression(propertyValue(entry));
66
+ const source = nodeRef(file, entry);
67
+ if (value?.type !== "CallExpression") {
68
+ return {
69
+ relation: null,
70
+ diagnostics: schemaDiagnostics([
71
+ schemaUnknown("relations() entry is not a one()/many() call", source),
72
+ ]),
73
+ };
74
+ }
75
+ const calleeLocal = identifierName(unwrapExpression(value.callee));
76
+ const cardinality = calleeLocal !== undefined && helpers.one.has(calleeLocal)
77
+ ? "one"
78
+ : calleeLocal !== undefined && helpers.many.has(calleeLocal)
79
+ ? "many"
80
+ : null;
81
+ if (cardinality === null) {
82
+ return {
83
+ relation: null,
84
+ diagnostics: schemaDiagnostics([
85
+ schemaUnknown("relations() entry is not a one()/many() call", source),
86
+ ]),
87
+ };
88
+ }
89
+ const args = callArguments(value);
90
+ const targetLocal = identifierName(unwrapExpression(args[0]));
91
+ const targetTable = targetLocal === undefined ? null : resolveTableBinding(graph, file.path, targetLocal, lookup);
92
+ if (targetTable === null) {
93
+ unknowns.push(schemaUnknown("relation target table is not resolvable statically", source));
94
+ }
95
+ const columns = readColumnList(args[1], file, unknowns);
96
+ const relation = {
97
+ from,
98
+ to: targetTable ?? targetLocal ?? "unknown",
99
+ cardinality,
100
+ fields: columns.fields,
101
+ references: columns.references,
102
+ source,
103
+ unknown: unknowns,
104
+ };
105
+ return { relation, diagnostics: schemaDiagnostics(unknowns) };
106
+ }
107
+ function readRelations(input) {
108
+ const { graph, file, call, lookup } = input;
109
+ const source = nodeRef(file, call);
110
+ const args = callArguments(call);
111
+ const fromLocal = identifierName(unwrapExpression(args[0]));
112
+ const fromTable = fromLocal === undefined ? null : resolveTableBinding(graph, file.path, fromLocal, lookup);
113
+ const from = fromTable ?? fromLocal ?? "unknown";
114
+ const callback = args[1];
115
+ const body = callbackExpression(callback);
116
+ const helpers = helperBindings(callback);
117
+ if (body?.type !== "ObjectExpression" || helpers === null) {
118
+ return {
119
+ relations: [],
120
+ diagnostics: schemaDiagnostics([
121
+ schemaUnknown("relations() callback is not a destructured one/many callback", source),
122
+ ]),
123
+ };
124
+ }
125
+ const relations = [];
126
+ const diagnostics = [];
127
+ if (fromTable === null) {
128
+ const unknown = schemaUnknown("relation source table is not resolvable statically", source);
129
+ diagnostics.push(...schemaDiagnostics([unknown]));
130
+ }
131
+ for (const entry of objectEntries(body)) {
132
+ if (propertyName(entry) === undefined) {
133
+ diagnostics.push(...schemaDiagnostics([
134
+ schemaUnknown("computed relation key is outside the DSL", nodeRef(file, entry)),
135
+ ]));
136
+ continue;
137
+ }
138
+ const result = readRelationEntry({ entry, from, graph, file, lookup, helpers });
139
+ if (result.relation !== null)
140
+ relations.push(result.relation);
141
+ diagnostics.push(...result.diagnostics);
142
+ }
143
+ return { relations, diagnostics };
144
+ }
145
+ export function buildRelationFacts(graph, files, lookup) {
146
+ const relations = [];
147
+ const diagnostics = [];
148
+ for (const file of files) {
149
+ const resolver = createDslResolver(graph, file);
150
+ for (const declarator of topLevelDeclarators(file.program)) {
151
+ const call = unwrapExpression(declarator.init);
152
+ if (call?.type !== "CallExpression")
153
+ continue;
154
+ const symbol = resolver(call.callee);
155
+ if (symbol?.pkg !== "drizzle-orm" || symbol.name !== "relations")
156
+ continue;
157
+ const scan = readRelations({ graph, file, call, lookup });
158
+ relations.push(...scan.relations);
159
+ diagnostics.push(...scan.diagnostics);
160
+ }
161
+ }
162
+ return { relations: dedupeRelations(relations), diagnostics };
163
+ }
164
+ /** Duplicate declarations (e.g. split files) must not duplicate edges. */
165
+ function dedupeRelations(relations) {
166
+ const seen = new Set();
167
+ const out = [];
168
+ for (const relation of relations) {
169
+ const key = [
170
+ relation.from,
171
+ relation.to,
172
+ relation.cardinality,
173
+ relation.fields.join(","),
174
+ relation.references.join(","),
175
+ ].join("|");
176
+ if (seen.has(key))
177
+ continue;
178
+ seen.add(key);
179
+ out.push(relation);
180
+ }
181
+ return out;
182
+ }
@@ -0,0 +1,7 @@
1
+ import type { ColumnFact, ConfigFacts, ScopeFact } from "../types.js";
2
+ export interface ScopedColumn {
3
+ readonly column: ColumnFact;
4
+ readonly tenant: boolean;
5
+ }
6
+ /** Tenant scope wins over owner scope: both can be declared on the same table. */
7
+ export declare function scopeOf(columns: readonly ScopedColumn[], config: ConfigFacts): ScopeFact | null;
@@ -0,0 +1,20 @@
1
+ function normalized(value) {
2
+ return value.replaceAll("_", "").replaceAll("-", "").toLowerCase();
3
+ }
4
+ function matchesConfig(column, names) {
5
+ const property = normalized(column.property);
6
+ const sqlName = normalized(column.name);
7
+ return names.some((candidate) => {
8
+ const value = normalized(candidate);
9
+ return value === property || value === sqlName;
10
+ });
11
+ }
12
+ /** Tenant scope wins over owner scope: both can be declared on the same table. */
13
+ export function scopeOf(columns, config) {
14
+ const tenant = columns.find((entry) => entry.tenant) ??
15
+ columns.find((entry) => matchesConfig(entry.column, config.tenantColumns));
16
+ if (tenant !== undefined)
17
+ return { column: tenant.column.property, kind: "tenant" };
18
+ const owner = columns.find((entry) => matchesConfig(entry.column, config.ownerColumns));
19
+ return owner === undefined ? null : { column: owner.column.property, kind: "owner" };
20
+ }
@@ -0,0 +1,13 @@
1
+ import type { ModuleGraph, ProjectFile } from "../module-graph.js";
2
+ import type { ConfigFacts, Diagnostic, TableFact } from "../types.js";
3
+ import { type ColumnReference } from "./schema-columns.js";
4
+ export interface TableDraft {
5
+ readonly fact: TableFact;
6
+ readonly binding: string | null;
7
+ readonly references: readonly ColumnReference[];
8
+ }
9
+ export interface TableScan {
10
+ readonly drafts: readonly TableDraft[];
11
+ readonly diagnostics: readonly Diagnostic[];
12
+ }
13
+ export declare function collectTableDrafts(graph: ModuleGraph, file: ProjectFile, config: ConfigFacts): TableScan;
@@ -0,0 +1,149 @@
1
+ import { callArguments, identifierName, memberName, objectEntries, propertyName, propertyValue, unwrapExpression, } from "../ast/node.js";
2
+ import { diagnostic } from "../diagnostics.js";
3
+ import { staticString } from "../static-eval.js";
4
+ import { buildColumnFact } from "./schema-columns.js";
5
+ import { scopeOf } from "./schema-scope.js";
6
+ import { createDslResolver, nodeRef, schemaDiagnostics, schemaUnknown, topLevelDeclarators, } from "./schema-dsl.js";
7
+ import { buildPolicyFacts } from "./schema-policies.js";
8
+ /** Local `pgEnum("name", values)` consts; a column built from one is a known enum builder. */
9
+ function collectEnumBindings(file, resolver, graph) {
10
+ const enums = new Map();
11
+ for (const declarator of topLevelDeclarators(file.program)) {
12
+ const binding = identifierName(declarator.id);
13
+ const init = unwrapExpression(declarator.init);
14
+ if (binding === undefined || init?.type !== "CallExpression")
15
+ continue;
16
+ const symbol = resolver(init.callee);
17
+ if (symbol?.pkg !== "pg-core" || symbol.name !== "pgEnum")
18
+ continue;
19
+ const name = staticString(callArguments(init)[0], { graph, file: file.path });
20
+ enums.set(binding, name.ok ? name.value : binding);
21
+ }
22
+ return enums;
23
+ }
24
+ /** The `pgTable` call inside `pgTable(...).enableRLS()`, plus the chain methods after it. */
25
+ function findTableCall(init, resolver) {
26
+ let current = unwrapExpression(init);
27
+ const methods = [];
28
+ let guard = 0;
29
+ while (current?.type === "CallExpression" && guard < 32) {
30
+ guard += 1;
31
+ const callee = unwrapExpression(current.callee);
32
+ if (callee === undefined)
33
+ return null;
34
+ if (callee.type === "MemberExpression") {
35
+ const name = memberName(callee);
36
+ if (name === undefined)
37
+ return null;
38
+ methods.unshift(name);
39
+ current = unwrapExpression(callee.object);
40
+ continue;
41
+ }
42
+ const symbol = resolver(callee);
43
+ if (symbol?.pkg !== "pg-core" || symbol.name !== "pgTable")
44
+ return null;
45
+ return { call: current, methods };
46
+ }
47
+ return null;
48
+ }
49
+ function buildTableDraft(input) {
50
+ const { graph, file, resolver, enums, config, binding, tableCall } = input;
51
+ const source = nodeRef(file, tableCall.call);
52
+ const args = callArguments(tableCall.call);
53
+ const unknowns = [];
54
+ const diagnostics = [];
55
+ const nameResult = staticString(args[0], { graph, file: file.path });
56
+ let name;
57
+ if (nameResult.ok && nameResult.value.length > 0) {
58
+ name = nameResult.value;
59
+ }
60
+ else {
61
+ name = binding ?? "unknown";
62
+ unknowns.push(schemaUnknown('table name of "' + name + '" is not a static string', source));
63
+ }
64
+ const columns = [];
65
+ const references = [];
66
+ const columnsArgument = unwrapExpression(args[1]);
67
+ if (columnsArgument?.type !== "ObjectExpression") {
68
+ unknowns.push(schemaUnknown('columns of table "' + name + '" are not an object literal', source));
69
+ }
70
+ else {
71
+ for (const entry of objectEntries(columnsArgument)) {
72
+ if (entry.type === "SpreadElement") {
73
+ unknowns.push(schemaUnknown('spread columns in table "' + name + '" are outside the DSL', nodeRef(file, entry)));
74
+ continue;
75
+ }
76
+ const property = propertyName(entry);
77
+ if (property === undefined) {
78
+ unknowns.push(schemaUnknown('computed column key in table "' + name + '" is outside the DSL', nodeRef(file, entry)));
79
+ continue;
80
+ }
81
+ const result = buildColumnFact({
82
+ property,
83
+ expression: propertyValue(entry),
84
+ propertyNode: entry,
85
+ file,
86
+ graph,
87
+ resolver,
88
+ enums,
89
+ });
90
+ diagnostics.push(...result.diagnostics);
91
+ if (result.reference !== null) {
92
+ references.push({ column: property, target: result.reference });
93
+ }
94
+ columns.push({ column: result.column, tenant: result.tenant });
95
+ }
96
+ }
97
+ const policyScan = buildPolicyFacts({
98
+ extras: args[2],
99
+ file,
100
+ graph,
101
+ resolver,
102
+ });
103
+ diagnostics.push(...policyScan.diagnostics);
104
+ const rlsEnabled = tableCall.methods.includes("enableRLS");
105
+ const scope = scopeOf(columns, config);
106
+ const fact = {
107
+ name,
108
+ binding,
109
+ columns: columns.map((entry) => entry.column),
110
+ primaryKey: columns
111
+ .filter((entry) => entry.column.primaryKey)
112
+ .map((entry) => entry.column.property),
113
+ foreignKeys: [],
114
+ policies: policyScan.policies,
115
+ rlsEnabled,
116
+ scope,
117
+ source,
118
+ unknown: unknowns,
119
+ };
120
+ diagnostics.push(...schemaDiagnostics(unknowns));
121
+ if (scope !== null && (!rlsEnabled || policyScan.policies.length === 0)) {
122
+ diagnostics.push(diagnostic("rls-on-scoped-table", "error", 'scoped table "' + name + '" must call .enableRLS() and declare a policy', source));
123
+ }
124
+ return { draft: { fact, binding, references }, diagnostics };
125
+ }
126
+ export function collectTableDrafts(graph, file, config) {
127
+ const resolver = createDslResolver(graph, file);
128
+ const enums = collectEnumBindings(file, resolver, graph);
129
+ const drafts = [];
130
+ const diagnostics = [];
131
+ for (const declarator of topLevelDeclarators(file.program)) {
132
+ const binding = identifierName(declarator.id) ?? null;
133
+ const tableCall = findTableCall(declarator.init, resolver);
134
+ if (tableCall === null)
135
+ continue;
136
+ const result = buildTableDraft({
137
+ graph,
138
+ file,
139
+ resolver,
140
+ enums,
141
+ config,
142
+ binding,
143
+ tableCall,
144
+ });
145
+ drafts.push(result.draft);
146
+ diagnostics.push(...result.diagnostics);
147
+ }
148
+ return { drafts, diagnostics };
149
+ }
@@ -0,0 +1,9 @@
1
+ import type { ModuleGraph } from "../module-graph.js";
2
+ import type { ConfigFacts, Diagnostic, RelationFact, TableFact } from "../types.js";
3
+ export interface SchemaResult {
4
+ readonly tables: readonly TableFact[];
5
+ readonly relations: readonly RelationFact[];
6
+ readonly diagnostics: readonly Diagnostic[];
7
+ }
8
+ /** Interpret the Drizzle DSL under `db/**`, including framework tables reached by re-export. */
9
+ export declare function buildSchemaFacts(graph: ModuleGraph, config: ConfigFacts): SchemaResult;
@@ -0,0 +1,55 @@
1
+ import { isSchemaFile } from "./schema-dsl.js";
2
+ import { collectFileLinkDrafts } from "./schema-file-links.js";
3
+ import { buildTableLookup, finalizeTables, withFrameworkTables } from "./schema-foreign-keys.js";
4
+ import { collectTableDrafts } from "./schema-tables.js";
5
+ import { collectReExportedDrafts } from "./schema-re-exports.js";
6
+ import { buildRelationFacts } from "./schema-relations.js";
7
+ function schemaFiles(graph) {
8
+ return [...graph.files.values()]
9
+ .filter((file) => isSchemaFile(graph, file.path))
10
+ .toSorted((left, right) => left.path.localeCompare(right.path));
11
+ }
12
+ /** Tables declared twice (invalid TS, but analyzable) keep the first declaration. */
13
+ function dedupeDrafts(drafts) {
14
+ const seen = new Set();
15
+ const out = [];
16
+ for (const draft of drafts) {
17
+ if (seen.has(draft.fact.name))
18
+ continue;
19
+ seen.add(draft.fact.name);
20
+ out.push(draft);
21
+ }
22
+ return out;
23
+ }
24
+ /** Interpret the Drizzle DSL under `db/**`, including framework tables reached by re-export. */
25
+ export function buildSchemaFacts(graph, config) {
26
+ const files = schemaFiles(graph);
27
+ const collected = [];
28
+ const externalTables = [];
29
+ const diagnostics = [];
30
+ for (const file of files) {
31
+ const scan = collectTableDrafts(graph, file, config);
32
+ collected.push(...scan.drafts);
33
+ diagnostics.push(...scan.diagnostics);
34
+ // Generated link tables follow the file's own declarations, which is their source order:
35
+ // `const todoFiles = fileLinks(todos, ...)` comes after the `todos` it references.
36
+ const links = collectFileLinkDrafts(graph, file, scan.drafts);
37
+ collected.push(...links.drafts);
38
+ externalTables.push(...links.externalTables);
39
+ diagnostics.push(...links.diagnostics);
40
+ }
41
+ const seenNames = new Set(collected.map((draft) => draft.fact.name));
42
+ const visitedFiles = new Set(files.map((file) => file.path));
43
+ for (const file of files) {
44
+ const scan = collectReExportedDrafts(graph, file, config, seenNames, visitedFiles);
45
+ collected.push(...scan.drafts);
46
+ diagnostics.push(...scan.diagnostics);
47
+ }
48
+ const drafts = dedupeDrafts(collected);
49
+ const lookup = withFrameworkTables(buildTableLookup(drafts), externalTables);
50
+ const finalized = finalizeTables(graph, drafts, lookup);
51
+ diagnostics.push(...finalized.diagnostics);
52
+ const relations = buildRelationFacts(graph, files, lookup);
53
+ diagnostics.push(...relations.diagnostics);
54
+ return { tables: finalized.tables, relations: relations.relations, diagnostics };
55
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * JSX component resolution for the static page preview: find a component's returned
3
+ * expression and resolve a JSX name to a project file through the module graph.
4
+ *
5
+ * The resolver is deliberately one level deep per call; the preview builder owns the depth,
6
+ * cycle and node budgets.
7
+ */
8
+ import { type AstNode } from "../ast/node.js";
9
+ import type { ModuleGraph } from "../module-graph.js";
10
+ export interface ComponentRef {
11
+ readonly file: string;
12
+ readonly name: string;
13
+ }
14
+ export declare function lineRef(graph: ModuleGraph, file: string, node: AstNode): {
15
+ file: string;
16
+ line: number;
17
+ };
18
+ export declare function returnedExpression(body: AstNode): AstNode | undefined;
19
+ export declare function componentExpression(graph: ModuleGraph, filePath: string, name: string): AstNode | undefined;
20
+ export declare function resolveComponent(graph: ModuleGraph, originFile: string, name: string): ComponentRef | null;
21
+ export declare function firstComponentName(graph: ModuleGraph, filePath: string): string | null;
22
+ export declare function canExpand(file: string): boolean;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * JSX component resolution for the static page preview: find a component's returned
3
+ * expression and resolve a JSX name to a project file through the module graph.
4
+ *
5
+ * The resolver is deliberately one level deep per call; the preview builder owns the depth,
6
+ * cycle and node budgets.
7
+ */
8
+ import { asNode, nodeArray } from "../ast/node.js";
9
+ import { defaultExportDeclaration } from "../module-info.js";
10
+ import { sourceRef } from "../source.js";
11
+ export function lineRef(graph, file, node) {
12
+ const source = graph.file(file)?.source;
13
+ return source === undefined
14
+ ? { file, line: 1 }
15
+ : sourceRef(file, source, typeof node.start === "number" ? node.start : 0);
16
+ }
17
+ function componentDeclaration(program, name) {
18
+ const wanted = name === "default" ? null : name;
19
+ for (const statement of nodeArray(program, "body")) {
20
+ const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration"
21
+ ? asNode(statement.declaration)
22
+ : statement;
23
+ if (declaration === undefined)
24
+ continue;
25
+ if (declaration.type === "FunctionDeclaration") {
26
+ const id = asNode(declaration.id);
27
+ if (id !== undefined &&
28
+ typeof id.name === "string" &&
29
+ (wanted === null || id.name === wanted)) {
30
+ return declaration;
31
+ }
32
+ }
33
+ if (declaration.type !== "VariableDeclaration")
34
+ continue;
35
+ for (const item of nodeArray(declaration, "declarations")) {
36
+ const id = asNode(item.id);
37
+ const init = asNode(item.init);
38
+ if (id === undefined || init === undefined)
39
+ continue;
40
+ if (typeof id.name !== "string" || (wanted !== null && id.name !== wanted))
41
+ continue;
42
+ if (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")
43
+ return init;
44
+ }
45
+ }
46
+ if (name === "default")
47
+ return defaultExportDeclaration(program);
48
+ return undefined;
49
+ }
50
+ export function returnedExpression(body) {
51
+ if (body.type === "BlockStatement") {
52
+ for (const statement of nodeArray(body, "body")) {
53
+ const found = returnedExpression(statement);
54
+ if (found !== undefined)
55
+ return found;
56
+ }
57
+ return undefined;
58
+ }
59
+ if (body.type === "ReturnStatement")
60
+ return asNode(body.argument);
61
+ if (body.type === "IfStatement") {
62
+ const consequent = asNode(body.consequent);
63
+ const alternate = asNode(body.alternate);
64
+ return ((consequent === undefined ? undefined : returnedExpression(consequent)) ??
65
+ (alternate === undefined ? undefined : returnedExpression(alternate)));
66
+ }
67
+ if (body.type === "TryStatement") {
68
+ const block = asNode(body.block);
69
+ const handler = asNode(body.handler);
70
+ const finalizer = asNode(body.finalizer);
71
+ const handlerBody = handler === undefined ? undefined : asNode(handler.body);
72
+ return ((block === undefined ? undefined : returnedExpression(block)) ??
73
+ (handlerBody === undefined ? undefined : returnedExpression(handlerBody)) ??
74
+ (finalizer === undefined ? undefined : returnedExpression(finalizer)));
75
+ }
76
+ return undefined;
77
+ }
78
+ export function componentExpression(graph, filePath, name) {
79
+ const file = graph.file(filePath);
80
+ if (file === undefined)
81
+ return undefined;
82
+ const declaration = componentDeclaration(file.program, name);
83
+ if (declaration === undefined)
84
+ return undefined;
85
+ if (declaration.type === "ArrowFunctionExpression") {
86
+ const body = asNode(declaration.body);
87
+ return body === undefined
88
+ ? undefined
89
+ : body.type === "BlockStatement"
90
+ ? returnedExpression(body)
91
+ : body;
92
+ }
93
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "FunctionExpression") {
94
+ const body = asNode(declaration.body);
95
+ return body === undefined ? undefined : returnedExpression(body);
96
+ }
97
+ return declaration;
98
+ }
99
+ export function resolveComponent(graph, originFile, name) {
100
+ const local = name.includes(".") ? name.slice(0, name.indexOf(".")) : name;
101
+ const binding = graph.resolveBinding(originFile, local);
102
+ if (binding === null)
103
+ return null;
104
+ const file = graph.file(binding.file);
105
+ if (file === undefined)
106
+ return null;
107
+ return { file: file.path, name: binding.name };
108
+ }
109
+ export function firstComponentName(graph, filePath) {
110
+ const file = graph.file(filePath);
111
+ if (file === undefined)
112
+ return null;
113
+ for (const statement of nodeArray(file.program, "body")) {
114
+ const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration"
115
+ ? asNode(statement.declaration)
116
+ : statement;
117
+ if (declaration === undefined)
118
+ continue;
119
+ if (declaration.type === "FunctionDeclaration") {
120
+ const id = asNode(declaration.id);
121
+ if (id !== undefined && typeof id.name === "string")
122
+ return id.name;
123
+ }
124
+ if (declaration.type !== "VariableDeclaration")
125
+ continue;
126
+ for (const item of nodeArray(declaration, "declarations")) {
127
+ const id = asNode(item.id);
128
+ const init = asNode(item.init);
129
+ if (id === undefined || init === undefined || typeof id.name !== "string")
130
+ continue;
131
+ if (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")
132
+ return id.name;
133
+ }
134
+ }
135
+ return null;
136
+ }
137
+ export function canExpand(file) {
138
+ if (file.startsWith("src/components/ui/"))
139
+ return false;
140
+ return (file.startsWith("src/pages/") ||
141
+ file.startsWith("src/layouts/") ||
142
+ file.startsWith("src/components/"));
143
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Which intrinsic JSX elements the page preview draws, and which copy they carry.
3
+ *
4
+ * Sections are the page's layout skeleton and are always drawn; content intrinsics are the
5
+ * copy-bearing blocks and controls that make the schematic page readable (D-170). Layout-only
6
+ * wrappers such as `<div>` are flattened, and a copy-bearing text element with no readable copy
7
+ * is flattened too, so the preview never draws empty boxes for spacing wrappers.
8
+ */
9
+ import type { AstNode } from "../ast/node.js";
10
+ import { type JsxTextFields } from "./jsx-text.js";
11
+ /** What a drawable intrinsic contributes: its copy and whether it spends the content budget. */
12
+ export interface IntrinsicPlan {
13
+ /** Content intrinsics have their own node budget and only draw when they carry something. */
14
+ readonly content: boolean;
15
+ readonly fields: JsxTextFields | undefined;
16
+ }
17
+ /** Classify one intrinsic element, or null when it is flattened into its parent. */
18
+ export declare function planIntrinsic(node: AstNode, name: string): IntrinsicPlan | null;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Which intrinsic JSX elements the page preview draws, and which copy they carry.
3
+ *
4
+ * Sections are the page's layout skeleton and are always drawn; content intrinsics are the
5
+ * copy-bearing blocks and controls that make the schematic page readable (D-170). Layout-only
6
+ * wrappers such as `<div>` are flattened, and a copy-bearing text element with no readable copy
7
+ * is flattened too, so the preview never draws empty boxes for spacing wrappers.
8
+ */
9
+ import { isContentIntrinsic, isRoleIntrinsic, isSectionIntrinsic } from "./jsx.js";
10
+ import { jsxContentFields, jsxTextFields } from "./jsx-text.js";
11
+ /** Classify one intrinsic element, or null when it is flattened into its parent. */
12
+ export function planIntrinsic(node, name) {
13
+ const section = isSectionIntrinsic(name);
14
+ if (!section && !isContentIntrinsic(name))
15
+ return null;
16
+ const fields = section ? jsxTextFields(node) : jsxContentFields(node, name);
17
+ // A control or media element is a visible block even without copy; a bare span is not.
18
+ if (!section && fields === undefined && !isRoleIntrinsic(name))
19
+ return null;
20
+ return { content: !section, fields };
21
+ }