@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.
- package/LICENSE +21 -0
- package/README.md +18 -2
- package/dist/analyze.d.ts +17 -0
- package/dist/analyze.js +99 -0
- package/dist/ast/node.d.ts +29 -0
- package/dist/ast/node.js +85 -0
- package/dist/ast/walk.d.ts +8 -0
- package/dist/ast/walk.js +40 -0
- package/dist/diagnostics.d.ts +5 -0
- package/dist/diagnostics.js +12 -0
- package/dist/fs.d.ts +13 -0
- package/dist/fs.js +51 -0
- package/dist/graph-edges.d.ts +3 -0
- package/dist/graph-edges.js +148 -0
- package/dist/graph-ids.d.ts +6 -0
- package/dist/graph-ids.js +22 -0
- package/dist/graph.d.ts +15 -0
- package/dist/graph.js +162 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +17 -0
- package/dist/json.d.ts +8 -0
- package/dist/json.js +31 -0
- package/dist/module-graph.d.ts +33 -0
- package/dist/module-graph.js +86 -0
- package/dist/module-info.d.ts +27 -0
- package/dist/module-info.js +143 -0
- package/dist/node.d.ts +16 -0
- package/dist/node.js +41 -0
- package/dist/parser.d.ts +16 -0
- package/dist/parser.js +1 -0
- package/dist/passes/clients.d.ts +22 -0
- package/dist/passes/clients.js +167 -0
- package/dist/passes/config.d.ts +16 -0
- package/dist/passes/config.js +229 -0
- package/dist/passes/file-facts.d.ts +13 -0
- package/dist/passes/file-facts.js +130 -0
- package/dist/passes/i18n.d.ts +14 -0
- package/dist/passes/i18n.js +55 -0
- package/dist/passes/job-relations-diagnostics.d.ts +9 -0
- package/dist/passes/job-relations-diagnostics.js +38 -0
- package/dist/passes/job-relations.d.ts +18 -0
- package/dist/passes/job-relations.js +52 -0
- package/dist/passes/jobs.d.ts +20 -0
- package/dist/passes/jobs.js +133 -0
- package/dist/passes/jsx-text.d.ts +28 -0
- package/dist/passes/jsx-text.js +121 -0
- package/dist/passes/jsx.d.ts +24 -0
- package/dist/passes/jsx.js +135 -0
- package/dist/passes/path-expression.d.ts +26 -0
- package/dist/passes/path-expression.js +265 -0
- package/dist/passes/route-config.d.ts +24 -0
- package/dist/passes/route-config.js +152 -0
- package/dist/passes/route-description.d.ts +13 -0
- package/dist/passes/route-description.js +23 -0
- package/dist/passes/route-elevation.d.ts +18 -0
- package/dist/passes/route-elevation.js +136 -0
- package/dist/passes/route-handler.d.ts +27 -0
- package/dist/passes/route-handler.js +87 -0
- package/dist/passes/route-location.d.ts +16 -0
- package/dist/passes/route-location.js +58 -0
- package/dist/passes/route-relation-resolve.d.ts +51 -0
- package/dist/passes/route-relation-resolve.js +172 -0
- package/dist/passes/route-relations-diagnostics.d.ts +15 -0
- package/dist/passes/route-relations-diagnostics.js +79 -0
- package/dist/passes/route-relations-follow.d.ts +16 -0
- package/dist/passes/route-relations-follow.js +116 -0
- package/dist/passes/route-relations-usage.d.ts +50 -0
- package/dist/passes/route-relations-usage.js +44 -0
- package/dist/passes/route-relations-walk.d.ts +25 -0
- package/dist/passes/route-relations-walk.js +199 -0
- package/dist/passes/route-relations.d.ts +17 -0
- package/dist/passes/route-relations.js +43 -0
- package/dist/passes/route-table.d.ts +21 -0
- package/dist/passes/route-table.js +104 -0
- package/dist/passes/route-tables.d.ts +10 -0
- package/dist/passes/route-tables.js +42 -0
- package/dist/passes/routes.d.ts +18 -0
- package/dist/passes/routes.js +152 -0
- package/dist/passes/schema-column-types.d.ts +9 -0
- package/dist/passes/schema-column-types.js +55 -0
- package/dist/passes/schema-columns.d.ts +35 -0
- package/dist/passes/schema-columns.js +197 -0
- package/dist/passes/schema-dsl.d.ts +24 -0
- package/dist/passes/schema-dsl.js +117 -0
- package/dist/passes/schema-file-links-ddl.d.ts +18 -0
- package/dist/passes/schema-file-links-ddl.js +106 -0
- package/dist/passes/schema-file-links.d.ts +19 -0
- package/dist/passes/schema-file-links.js +144 -0
- package/dist/passes/schema-foreign-keys.d.ts +21 -0
- package/dist/passes/schema-foreign-keys.js +80 -0
- package/dist/passes/schema-policies.d.ts +15 -0
- package/dist/passes/schema-policies.js +201 -0
- package/dist/passes/schema-re-exports.d.ts +5 -0
- package/dist/passes/schema-re-exports.js +77 -0
- package/dist/passes/schema-relations.d.ts +8 -0
- package/dist/passes/schema-relations.js +182 -0
- package/dist/passes/schema-scope.d.ts +7 -0
- package/dist/passes/schema-scope.js +20 -0
- package/dist/passes/schema-tables.d.ts +13 -0
- package/dist/passes/schema-tables.js +149 -0
- package/dist/passes/schema.d.ts +9 -0
- package/dist/passes/schema.js +55 -0
- package/dist/passes/screen-components.d.ts +22 -0
- package/dist/passes/screen-components.js +143 -0
- package/dist/passes/screen-intrinsics.d.ts +18 -0
- package/dist/passes/screen-intrinsics.js +21 -0
- package/dist/passes/screen-preview.d.ts +29 -0
- package/dist/passes/screen-preview.js +174 -0
- package/dist/passes/screens.d.ts +12 -0
- package/dist/passes/screens.js +278 -0
- package/dist/passes/static-read.d.ts +47 -0
- package/dist/passes/static-read.js +151 -0
- package/dist/passes/views.d.ts +20 -0
- package/dist/passes/views.js +101 -0
- package/dist/passes/workflow-tables.d.ts +18 -0
- package/dist/passes/workflow-tables.js +71 -0
- package/dist/passes/workflows.d.ts +8 -0
- package/dist/passes/workflows.js +160 -0
- package/dist/path-resolver.d.ts +20 -0
- package/dist/path-resolver.js +134 -0
- package/dist/revision.d.ts +2 -0
- package/dist/revision.js +23 -0
- package/dist/source.d.ts +4 -0
- package/dist/source.js +13 -0
- package/dist/static-eval.d.ts +36 -0
- package/dist/static-eval.js +215 -0
- package/dist/types-graph.d.ts +144 -0
- package/dist/types-graph.js +4 -0
- package/dist/types-jobs.d.ts +28 -0
- package/dist/types-jobs.js +14 -0
- package/dist/types-relations.d.ts +49 -0
- package/dist/types-relations.js +11 -0
- package/dist/types-workflows.d.ts +36 -0
- package/dist/types-workflows.js +3 -0
- package/dist/types.d.ts +226 -0
- package/dist/types.js +1 -0
- package/package.json +49 -4
- package/src/analyze.ts +119 -0
- package/src/ast/node.ts +102 -0
- package/src/ast/walk.ts +37 -0
- package/src/diagnostics.ts +22 -0
- package/src/fs.ts +59 -0
- package/src/graph-edges.ts +167 -0
- package/src/graph-ids.ts +31 -0
- package/src/graph.ts +194 -0
- package/src/index.ts +26 -0
- package/src/json.ts +34 -0
- package/src/module-graph.ts +117 -0
- package/src/module-info.ts +162 -0
- package/src/node.ts +54 -0
- package/src/parser.ts +19 -0
- package/src/passes/clients.ts +233 -0
- package/src/passes/config.ts +278 -0
- package/src/passes/file-facts.ts +142 -0
- package/src/passes/i18n.ts +66 -0
- package/src/passes/job-relations-diagnostics.ts +60 -0
- package/src/passes/job-relations.ts +76 -0
- package/src/passes/jobs.ts +181 -0
- package/src/passes/jsx-text.ts +126 -0
- package/src/passes/jsx.ts +142 -0
- package/src/passes/path-expression.ts +297 -0
- package/src/passes/route-config.ts +188 -0
- package/src/passes/route-description.ts +33 -0
- package/src/passes/route-elevation.ts +161 -0
- package/src/passes/route-handler.ts +105 -0
- package/src/passes/route-location.ts +62 -0
- package/src/passes/route-relation-resolve.ts +228 -0
- package/src/passes/route-relations-diagnostics.ts +115 -0
- package/src/passes/route-relations-follow.ts +146 -0
- package/src/passes/route-relations-usage.ts +98 -0
- package/src/passes/route-relations-walk.ts +255 -0
- package/src/passes/route-relations.ts +62 -0
- package/src/passes/route-table.ts +123 -0
- package/src/passes/route-tables.ts +50 -0
- package/src/passes/routes.ts +188 -0
- package/src/passes/schema-column-types.ts +62 -0
- package/src/passes/schema-columns.ts +287 -0
- package/src/passes/schema-dsl.ts +136 -0
- package/src/passes/schema-file-links-ddl.ts +125 -0
- package/src/passes/schema-file-links.ts +213 -0
- package/src/passes/schema-foreign-keys.ts +121 -0
- package/src/passes/schema-policies.ts +270 -0
- package/src/passes/schema-re-exports.ts +95 -0
- package/src/passes/schema-relations.ts +239 -0
- package/src/passes/schema-scope.ts +29 -0
- package/src/passes/schema-tables.ts +216 -0
- package/src/passes/schema.ts +67 -0
- package/src/passes/screen-components.ts +162 -0
- package/src/passes/screen-intrinsics.ts +29 -0
- package/src/passes/screen-preview.ts +295 -0
- package/src/passes/screens.ts +291 -0
- package/src/passes/static-read.ts +196 -0
- package/src/passes/views.ts +125 -0
- package/src/passes/workflow-tables.ts +87 -0
- package/src/passes/workflows.ts +213 -0
- package/src/path-resolver.ts +146 -0
- package/src/revision.ts +25 -0
- package/src/source.ts +15 -0
- package/src/static-eval.ts +242 -0
- package/src/types-graph.ts +176 -0
- package/src/types-jobs.ts +40 -0
- package/src/types-relations.ts +64 -0
- package/src/types-workflows.ts +42 -0
- package/src/types.ts +299 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The DDL `@facilis.dev/db`'s `fileLinks` expands to, expressed as analyzer facts (D-053).
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from the call-site scan: this module mirrors the framework's fixed table shape,
|
|
5
|
+
* while `schema-file-links.ts` interprets the project's arguments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ColumnDefaultFact, ColumnFact, PolicyFact, SourceRef } from "../types.ts";
|
|
9
|
+
import type { TableDraft } from "./schema-tables.ts";
|
|
10
|
+
|
|
11
|
+
const FACILIS_FILES = "facilis_files";
|
|
12
|
+
export const FACILIS_FILES_BINDING = "facilisFiles";
|
|
13
|
+
|
|
14
|
+
interface ColumnInput {
|
|
15
|
+
readonly property: string;
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly builder: string;
|
|
18
|
+
readonly type: string;
|
|
19
|
+
readonly notNull: boolean;
|
|
20
|
+
readonly primaryKey: boolean;
|
|
21
|
+
readonly default: ColumnDefaultFact | null;
|
|
22
|
+
readonly source: SourceRef;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function column(input: ColumnInput): ColumnFact {
|
|
26
|
+
return { ...input, unique: false, unknown: [] };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The generated link table's columns; `parentColumn` is the `column` option. */
|
|
30
|
+
export function linkColumns(parentColumn: string, source: SourceRef): ColumnFact[] {
|
|
31
|
+
return [
|
|
32
|
+
column({
|
|
33
|
+
property: "id",
|
|
34
|
+
name: "id",
|
|
35
|
+
builder: "uuid",
|
|
36
|
+
type: "uuid",
|
|
37
|
+
notNull: true,
|
|
38
|
+
primaryKey: true,
|
|
39
|
+
default: { kind: "random", value: null },
|
|
40
|
+
source,
|
|
41
|
+
}),
|
|
42
|
+
column({
|
|
43
|
+
property: "parentId",
|
|
44
|
+
name: parentColumn,
|
|
45
|
+
builder: "uuid",
|
|
46
|
+
type: "uuid",
|
|
47
|
+
notNull: true,
|
|
48
|
+
primaryKey: false,
|
|
49
|
+
default: null,
|
|
50
|
+
source,
|
|
51
|
+
}),
|
|
52
|
+
column({
|
|
53
|
+
property: "fileId",
|
|
54
|
+
name: "file_id",
|
|
55
|
+
builder: "uuid",
|
|
56
|
+
type: "uuid",
|
|
57
|
+
notNull: true,
|
|
58
|
+
primaryKey: false,
|
|
59
|
+
default: null,
|
|
60
|
+
source,
|
|
61
|
+
}),
|
|
62
|
+
column({
|
|
63
|
+
property: "tenantId",
|
|
64
|
+
name: "tenant_id",
|
|
65
|
+
builder: "tenantColumn",
|
|
66
|
+
type: "uuid",
|
|
67
|
+
notNull: true,
|
|
68
|
+
primaryKey: false,
|
|
69
|
+
default: null,
|
|
70
|
+
source,
|
|
71
|
+
}),
|
|
72
|
+
column({
|
|
73
|
+
property: "createdAt",
|
|
74
|
+
name: "created_at",
|
|
75
|
+
builder: "timestamp",
|
|
76
|
+
type: "timestamp with time zone",
|
|
77
|
+
notNull: true,
|
|
78
|
+
primaryKey: false,
|
|
79
|
+
default: { kind: "now", value: null },
|
|
80
|
+
source,
|
|
81
|
+
}),
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function linkPolicies(source: SourceRef): PolicyFact[] {
|
|
86
|
+
return [
|
|
87
|
+
{ name: "tenant_isolation", command: "ALL", restrictive: true, source, unknown: [] },
|
|
88
|
+
{ name: "parent_access", command: "ALL", restrictive: false, source, unknown: [] },
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* `facilis_files` as an FK target only. The link DDL references it, but a release-ref map
|
|
94
|
+
* does not carry `@facilis.dev/db`'s source, so this lookup-only draft lets the FK resolve; it is
|
|
95
|
+
* never emitted as one of the project's tables.
|
|
96
|
+
*/
|
|
97
|
+
export function facilisFilesDraft(source: SourceRef): TableDraft {
|
|
98
|
+
return {
|
|
99
|
+
binding: FACILIS_FILES_BINDING,
|
|
100
|
+
references: [],
|
|
101
|
+
fact: {
|
|
102
|
+
name: FACILIS_FILES,
|
|
103
|
+
binding: FACILIS_FILES_BINDING,
|
|
104
|
+
columns: [
|
|
105
|
+
column({
|
|
106
|
+
property: "id",
|
|
107
|
+
name: "id",
|
|
108
|
+
builder: "uuid",
|
|
109
|
+
type: "uuid",
|
|
110
|
+
notNull: true,
|
|
111
|
+
primaryKey: true,
|
|
112
|
+
default: { kind: "random", value: null },
|
|
113
|
+
source,
|
|
114
|
+
}),
|
|
115
|
+
],
|
|
116
|
+
primaryKey: ["id"],
|
|
117
|
+
foreignKeys: [],
|
|
118
|
+
policies: [],
|
|
119
|
+
rlsEnabled: false,
|
|
120
|
+
scope: null,
|
|
121
|
+
source,
|
|
122
|
+
unknown: [],
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fileLinks(parent, { table, column })` -> the synthetic link table it generates (D-053).
|
|
3
|
+
*
|
|
4
|
+
* The call is framework DSL rather than a `pgTable(...)` literal, so the table pass cannot
|
|
5
|
+
* see the link table it returns. The options come from the static subset and the parent
|
|
6
|
+
* table from the declaring file's own drafts; a value the interpreter cannot read becomes an
|
|
7
|
+
* explicit unknown instead of aborting the pass.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { callArguments, identifierName, unwrapExpression, type AstNode } from "../ast/node.ts";
|
|
11
|
+
import type { ModuleGraph, ProjectFile } from "../module-graph.ts";
|
|
12
|
+
import type { StaticContext } from "../static-eval.ts";
|
|
13
|
+
import type { Diagnostic, SourceRef, TableFact, UnknownFact } from "../types.ts";
|
|
14
|
+
import { optionsOf } from "./schema-column-types.ts";
|
|
15
|
+
import type { ColumnReference } from "./schema-columns.ts";
|
|
16
|
+
import {
|
|
17
|
+
createDslResolver,
|
|
18
|
+
nodeRef,
|
|
19
|
+
schemaDiagnostics,
|
|
20
|
+
schemaUnknown,
|
|
21
|
+
topLevelDeclarators,
|
|
22
|
+
} from "./schema-dsl.ts";
|
|
23
|
+
import {
|
|
24
|
+
FACILIS_FILES_BINDING,
|
|
25
|
+
facilisFilesDraft,
|
|
26
|
+
linkColumns,
|
|
27
|
+
linkPolicies,
|
|
28
|
+
} from "./schema-file-links-ddl.ts";
|
|
29
|
+
import type { TableDraft } from "./schema-tables.ts";
|
|
30
|
+
|
|
31
|
+
const DB_PACKAGE = "facilis-db";
|
|
32
|
+
const FILE_LINKS = "fileLinks";
|
|
33
|
+
const DEFAULT_PARENT_COLUMN = "parent_id";
|
|
34
|
+
|
|
35
|
+
export interface FileLinkScan {
|
|
36
|
+
readonly drafts: readonly TableDraft[];
|
|
37
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
38
|
+
/** Lookup-only targets of the generated FKs; see `facilisFilesDraft`. */
|
|
39
|
+
readonly externalTables: readonly TableDraft[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface LinkOptions {
|
|
43
|
+
/** null when the name is not a static non-empty string. */
|
|
44
|
+
readonly table: string | null;
|
|
45
|
+
readonly parentColumn: string;
|
|
46
|
+
readonly unknowns: readonly UnknownFact[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readOptions(
|
|
50
|
+
call: AstNode,
|
|
51
|
+
binding: string | null,
|
|
52
|
+
context: StaticContext,
|
|
53
|
+
source: SourceRef,
|
|
54
|
+
): LinkOptions {
|
|
55
|
+
const label = binding ?? "unknown";
|
|
56
|
+
const options = optionsOf(callArguments(call)[1], context);
|
|
57
|
+
const unknowns: UnknownFact[] = [];
|
|
58
|
+
if (options === null) {
|
|
59
|
+
unknowns.push(
|
|
60
|
+
schemaUnknown('fileLinks options of "' + label + '" are not a static object', source),
|
|
61
|
+
);
|
|
62
|
+
return { table: null, parentColumn: DEFAULT_PARENT_COLUMN, unknowns };
|
|
63
|
+
}
|
|
64
|
+
const rawTable = options["table"];
|
|
65
|
+
let table: string | null = null;
|
|
66
|
+
if (typeof rawTable === "string" && rawTable.length > 0) {
|
|
67
|
+
table = rawTable;
|
|
68
|
+
} else {
|
|
69
|
+
unknowns.push(
|
|
70
|
+
schemaUnknown('fileLinks table name of "' + label + '" is not a static string', source),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const rawColumn = options["column"];
|
|
74
|
+
let parentColumn = DEFAULT_PARENT_COLUMN;
|
|
75
|
+
if (rawColumn !== undefined) {
|
|
76
|
+
if (typeof rawColumn === "string" && rawColumn.length > 0) {
|
|
77
|
+
parentColumn = rawColumn;
|
|
78
|
+
} else {
|
|
79
|
+
unknowns.push(
|
|
80
|
+
schemaUnknown('fileLinks column name of "' + label + '" is not a static string', source),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { table, parentColumn, unknowns };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface ParentTarget {
|
|
88
|
+
readonly binding: string;
|
|
89
|
+
readonly property: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readParent(
|
|
93
|
+
call: AstNode,
|
|
94
|
+
drafts: readonly TableDraft[],
|
|
95
|
+
binding: string | null,
|
|
96
|
+
source: SourceRef,
|
|
97
|
+
): { target: ParentTarget | null; unknowns: readonly UnknownFact[] } {
|
|
98
|
+
const label = binding ?? "unknown";
|
|
99
|
+
const parentNode = unwrapExpression(callArguments(call)[0]);
|
|
100
|
+
const parentBinding = parentNode?.type === "Identifier" ? identifierName(parentNode) : undefined;
|
|
101
|
+
if (parentBinding === undefined) {
|
|
102
|
+
return {
|
|
103
|
+
target: null,
|
|
104
|
+
unknowns: [schemaUnknown('fileLinks parent of "' + label + '" is not an identifier', source)],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const draft = drafts.find((entry) => entry.binding === parentBinding);
|
|
108
|
+
if (draft === undefined) {
|
|
109
|
+
return {
|
|
110
|
+
target: null,
|
|
111
|
+
unknowns: [
|
|
112
|
+
schemaUnknown(
|
|
113
|
+
'fileLinks parent "' + parentBinding + '" is not a table declared in this file',
|
|
114
|
+
source,
|
|
115
|
+
),
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const key = draft.fact.primaryKey[0];
|
|
120
|
+
if (draft.fact.primaryKey.length !== 1 || key === undefined) {
|
|
121
|
+
return {
|
|
122
|
+
target: null,
|
|
123
|
+
unknowns: [
|
|
124
|
+
schemaUnknown(
|
|
125
|
+
'fileLinks parent "' + parentBinding + '" needs a single-column primary key',
|
|
126
|
+
source,
|
|
127
|
+
),
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return { target: { binding: parentBinding, property: key }, unknowns: [] };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildLinkDraft(input: {
|
|
135
|
+
readonly call: AstNode;
|
|
136
|
+
readonly binding: string | null;
|
|
137
|
+
readonly drafts: readonly TableDraft[];
|
|
138
|
+
readonly context: StaticContext;
|
|
139
|
+
readonly source: SourceRef;
|
|
140
|
+
}): { draft: TableDraft; external: TableDraft; unknowns: readonly UnknownFact[] } {
|
|
141
|
+
const { call, binding, drafts, context, source } = input;
|
|
142
|
+
const options = readOptions(call, binding, context, source);
|
|
143
|
+
const parent = readParent(call, drafts, binding, source);
|
|
144
|
+
const references: ColumnReference[] = [];
|
|
145
|
+
if (parent.target !== null) {
|
|
146
|
+
references.push({
|
|
147
|
+
column: "parentId",
|
|
148
|
+
target: {
|
|
149
|
+
binding: parent.target.binding,
|
|
150
|
+
property: parent.target.property,
|
|
151
|
+
onDelete: "cascade",
|
|
152
|
+
onUpdate: null,
|
|
153
|
+
source,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
// The generated fileId column references facilis_files regardless of how the parent reads.
|
|
158
|
+
references.push({
|
|
159
|
+
column: "fileId",
|
|
160
|
+
target: {
|
|
161
|
+
binding: FACILIS_FILES_BINDING,
|
|
162
|
+
property: "id",
|
|
163
|
+
onDelete: "cascade",
|
|
164
|
+
onUpdate: null,
|
|
165
|
+
source,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
const unknowns: UnknownFact[] = [...options.unknowns, ...parent.unknowns];
|
|
169
|
+
const fact: TableFact = {
|
|
170
|
+
name: options.table ?? binding ?? "unknown",
|
|
171
|
+
binding,
|
|
172
|
+
columns: linkColumns(options.parentColumn, source),
|
|
173
|
+
primaryKey: ["id"],
|
|
174
|
+
foreignKeys: [],
|
|
175
|
+
policies: linkPolicies(source),
|
|
176
|
+
rlsEnabled: true,
|
|
177
|
+
scope: { column: "tenantId", kind: "tenant" },
|
|
178
|
+
source,
|
|
179
|
+
unknown: unknowns,
|
|
180
|
+
};
|
|
181
|
+
return { draft: { fact, binding, references }, external: facilisFilesDraft(source), unknowns };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Read the top-level `fileLinks(...)` declarators of one schema file into table drafts. */
|
|
185
|
+
export function collectFileLinkDrafts(
|
|
186
|
+
graph: ModuleGraph,
|
|
187
|
+
file: ProjectFile,
|
|
188
|
+
drafts: readonly TableDraft[],
|
|
189
|
+
): FileLinkScan {
|
|
190
|
+
const resolver = createDslResolver(graph, file);
|
|
191
|
+
const context: StaticContext = { graph, file: file.path };
|
|
192
|
+
const links: TableDraft[] = [];
|
|
193
|
+
const externalTables: TableDraft[] = [];
|
|
194
|
+
const diagnostics: Diagnostic[] = [];
|
|
195
|
+
for (const declarator of topLevelDeclarators(file.program)) {
|
|
196
|
+
const binding = identifierName(declarator.id) ?? null;
|
|
197
|
+
const init = unwrapExpression(declarator.init);
|
|
198
|
+
if (init?.type !== "CallExpression") continue;
|
|
199
|
+
const symbol = resolver(init.callee);
|
|
200
|
+
if (symbol?.pkg !== DB_PACKAGE || symbol.name !== FILE_LINKS) continue;
|
|
201
|
+
const result = buildLinkDraft({
|
|
202
|
+
call: init,
|
|
203
|
+
binding,
|
|
204
|
+
drafts,
|
|
205
|
+
context,
|
|
206
|
+
source: nodeRef(file, init),
|
|
207
|
+
});
|
|
208
|
+
links.push(result.draft);
|
|
209
|
+
externalTables.push(result.external);
|
|
210
|
+
diagnostics.push(...schemaDiagnostics(result.unknowns));
|
|
211
|
+
}
|
|
212
|
+
return { drafts: links, externalTables, diagnostics };
|
|
213
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { diagnostic } from "../diagnostics.ts";
|
|
2
|
+
import type { ModuleGraph } from "../module-graph.ts";
|
|
3
|
+
import type { Diagnostic, ForeignKeyFact, TableFact } from "../types.ts";
|
|
4
|
+
import type { TableDraft } from "./schema-tables.ts";
|
|
5
|
+
|
|
6
|
+
export interface TableLookup {
|
|
7
|
+
/** `file#binding` -> table name; the key keeps same-named bindings in different modules apart. */
|
|
8
|
+
readonly byBinding: ReadonlyMap<string, string>;
|
|
9
|
+
readonly byName: ReadonlyMap<string, TableDraft>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function buildTableLookup(drafts: readonly TableDraft[]): TableLookup {
|
|
13
|
+
const byBinding = new Map<string, string>();
|
|
14
|
+
const byName = new Map<string, TableDraft>();
|
|
15
|
+
for (const draft of drafts) {
|
|
16
|
+
if (draft.binding !== null) {
|
|
17
|
+
byBinding.set(draft.fact.source.file + "#" + draft.binding, draft.fact.name);
|
|
18
|
+
}
|
|
19
|
+
byName.set(draft.fact.name, draft);
|
|
20
|
+
}
|
|
21
|
+
return { byBinding, byName };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Add lookups for tables a DSL call expands to but the analysis does not emit: a generated
|
|
26
|
+
* `fileLinks` FK points at the framework's `facilis_files`, whose source is absent from a
|
|
27
|
+
* release-ref map. The extra tables resolve FK targets only; they never reach `tables`.
|
|
28
|
+
*/
|
|
29
|
+
export function withFrameworkTables(
|
|
30
|
+
lookup: TableLookup,
|
|
31
|
+
tables: readonly TableDraft[],
|
|
32
|
+
): TableLookup {
|
|
33
|
+
if (tables.length === 0) return lookup;
|
|
34
|
+
const byBinding = new Map(lookup.byBinding);
|
|
35
|
+
const byName = new Map(lookup.byName);
|
|
36
|
+
for (const table of tables) {
|
|
37
|
+
if (!byName.has(table.fact.name)) byName.set(table.fact.name, table);
|
|
38
|
+
if (table.binding === null) continue;
|
|
39
|
+
const key = table.fact.source.file + "#" + table.binding;
|
|
40
|
+
if (!byBinding.has(key)) byBinding.set(key, table.fact.name);
|
|
41
|
+
}
|
|
42
|
+
return { byBinding, byName };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Resolve a table identifier used in the DSL (local const or any project/framework import). */
|
|
46
|
+
export function resolveTableBinding(
|
|
47
|
+
graph: ModuleGraph,
|
|
48
|
+
filePath: string,
|
|
49
|
+
local: string,
|
|
50
|
+
lookup: TableLookup,
|
|
51
|
+
): string | null {
|
|
52
|
+
const binding = graph.resolveBinding(filePath, local);
|
|
53
|
+
if (binding !== null) {
|
|
54
|
+
const table = lookup.byBinding.get(binding.file + "#" + binding.name);
|
|
55
|
+
if (table !== undefined) return table;
|
|
56
|
+
}
|
|
57
|
+
return lookup.byBinding.get(filePath + "#" + local) ?? null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function finalizeTables(
|
|
61
|
+
graph: ModuleGraph,
|
|
62
|
+
drafts: readonly TableDraft[],
|
|
63
|
+
lookup: TableLookup,
|
|
64
|
+
): { tables: TableFact[]; diagnostics: Diagnostic[] } {
|
|
65
|
+
const tables: TableFact[] = [];
|
|
66
|
+
const diagnostics: Diagnostic[] = [];
|
|
67
|
+
for (const draft of drafts) {
|
|
68
|
+
const foreignKeys: ForeignKeyFact[] = [];
|
|
69
|
+
for (const reference of draft.references) {
|
|
70
|
+
const table = draft.fact.name;
|
|
71
|
+
const targetTable = resolveTableBinding(
|
|
72
|
+
graph,
|
|
73
|
+
draft.fact.source.file,
|
|
74
|
+
reference.target.binding,
|
|
75
|
+
lookup,
|
|
76
|
+
);
|
|
77
|
+
if (targetTable === null) {
|
|
78
|
+
diagnostics.push(
|
|
79
|
+
diagnostic(
|
|
80
|
+
"fk-target-known",
|
|
81
|
+
"warning",
|
|
82
|
+
'table "' + table + '" references unknown table "' + reference.target.binding + '"',
|
|
83
|
+
reference.target.source,
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const targetDraft = lookup.byName.get(targetTable);
|
|
89
|
+
const knownColumn = targetDraft?.fact.columns.some(
|
|
90
|
+
(column) => column.property === reference.target.property,
|
|
91
|
+
);
|
|
92
|
+
if (knownColumn !== true) {
|
|
93
|
+
diagnostics.push(
|
|
94
|
+
diagnostic(
|
|
95
|
+
"fk-target-known",
|
|
96
|
+
"warning",
|
|
97
|
+
'table "' +
|
|
98
|
+
table +
|
|
99
|
+
'" references unknown column "' +
|
|
100
|
+
targetTable +
|
|
101
|
+
"." +
|
|
102
|
+
reference.target.property +
|
|
103
|
+
'"',
|
|
104
|
+
reference.target.source,
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
foreignKeys.push({
|
|
110
|
+
columns: [reference.column],
|
|
111
|
+
targetTable,
|
|
112
|
+
targetColumns: [reference.target.property],
|
|
113
|
+
onDelete: reference.target.onDelete,
|
|
114
|
+
onUpdate: reference.target.onUpdate,
|
|
115
|
+
source: reference.target.source,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
tables.push({ ...draft.fact, foreignKeys });
|
|
119
|
+
}
|
|
120
|
+
return { tables, diagnostics };
|
|
121
|
+
}
|