@lunora/codegen 0.0.0 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +105 -0
- package/README.md +117 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/index.d.mts +1473 -0
- package/dist/index.d.ts +1473 -0
- package/dist/index.mjs +28 -0
- package/dist/packem_shared/CONTAINERS_FILENAME-0K-pjNb8.mjs +224 -0
- package/dist/packem_shared/CodegenDiagnosticError-54jWDxA9.mjs +22 -0
- package/dist/packem_shared/LUNORA_ERROR_CODES-CySpQPD3.mjs +61 -0
- package/dist/packem_shared/buildOpenApiDocument-yHVN66Xd.mjs +183 -0
- package/dist/packem_shared/buildOpenRpcDocument-BZGY1-jT.mjs +60 -0
- package/dist/packem_shared/buildSchemaSnapshot-DzLDbWk3.mjs +233 -0
- package/dist/packem_shared/createCodegenProject-DGJm0_Pk.mjs +895 -0
- package/dist/packem_shared/discover-ast-CT6BgBr4.mjs +13 -0
- package/dist/packem_shared/discoverAuthApiCalls-C35R6z0T.mjs +62 -0
- package/dist/packem_shared/discoverCrons-BL6iGuJ3.mjs +254 -0
- package/dist/packem_shared/discoverFunctions-DEgAcRuD.mjs +460 -0
- package/dist/packem_shared/discoverHttpRoutes-C978pBiG.mjs +131 -0
- package/dist/packem_shared/discoverInserts-CRQdXvHO.mjs +39 -0
- package/dist/packem_shared/discoverMaskProcedures-B64zA740.mjs +217 -0
- package/dist/packem_shared/discoverMigrations-Doj_-BAA.mjs +91 -0
- package/dist/packem_shared/discoverNondeterministicCalls-4KiPQxQU.mjs +122 -0
- package/dist/packem_shared/discoverQueries-BkIi0dBD.mjs +62 -0
- package/dist/packem_shared/discoverRlsMetadata-DpRB1HMe.mjs +280 -0
- package/dist/packem_shared/discoverSchema-BBulgGbH.mjs +542 -0
- package/dist/packem_shared/discoverStorageRulesMetadata-DAqJUxUv.mjs +97 -0
- package/dist/packem_shared/discoverWorkflows-DRDQdhfq.mjs +84 -0
- package/dist/packem_shared/emitApi-hRVC-kE7.mjs +2426 -0
- package/dist/packem_shared/emitApp-CzZ6GbrD.mjs +593 -0
- package/dist/packem_shared/lintSchema-DicbOHvH.mjs +68 -0
- package/dist/packem_shared/parse-validator-tuQtHrsr.mjs +132 -0
- package/dist/packem_shared/paths-BRd6JHuF.mjs +11 -0
- package/dist/packem_shared/schemaFromIr-DTYsLBaA.mjs +57 -0
- package/package.json +45 -17
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Node, SyntaxKind } from 'ts-morph';
|
|
2
|
+
import { listLunoraSourceFiles, lunoraRelativePath, classifyProcedureCall } from './discoverFunctions-DEgAcRuD.mjs';
|
|
3
|
+
|
|
4
|
+
const isMaskCall = (node) => {
|
|
5
|
+
if (!Node.isCallExpression(node)) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
const callee = node.getExpression();
|
|
9
|
+
if (Node.isIdentifier(callee)) {
|
|
10
|
+
return callee.getText() === "mask";
|
|
11
|
+
}
|
|
12
|
+
if (Node.isPropertyAccessExpression(callee)) {
|
|
13
|
+
return callee.getName() === "mask";
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
};
|
|
17
|
+
const memberName = (member) => {
|
|
18
|
+
if (Node.isPropertyAssignment(member) || Node.isShorthandPropertyAssignment(member) || Node.isMethodDeclaration(member) || Node.isGetAccessorDeclaration(member)) {
|
|
19
|
+
return member.getName();
|
|
20
|
+
}
|
|
21
|
+
return void 0;
|
|
22
|
+
};
|
|
23
|
+
const extractMaskColumns = (maskCall) => {
|
|
24
|
+
const argument = maskCall.getArguments()[0];
|
|
25
|
+
if (!argument || !Node.isObjectLiteralExpression(argument)) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
const pairs = [];
|
|
29
|
+
for (const tableProperty of argument.getProperties()) {
|
|
30
|
+
const table = memberName(tableProperty);
|
|
31
|
+
if (table === void 0 || !Node.isPropertyAssignment(tableProperty)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const initializer = tableProperty.getInitializer();
|
|
35
|
+
if (!initializer || !Node.isObjectLiteralExpression(initializer)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
for (const columnProperty of initializer.getProperties()) {
|
|
39
|
+
const column = memberName(columnProperty);
|
|
40
|
+
if (column !== void 0) {
|
|
41
|
+
pairs.push({ column, table });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return pairs;
|
|
46
|
+
};
|
|
47
|
+
const maskCallsInChain = (receiver) => {
|
|
48
|
+
const calls = [];
|
|
49
|
+
let node = receiver;
|
|
50
|
+
while (Node.isCallExpression(node)) {
|
|
51
|
+
const chainCallee = node.getExpression();
|
|
52
|
+
if (!Node.isPropertyAccessExpression(chainCallee)) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
if (chainCallee.getName() === "use") {
|
|
56
|
+
const argument = node.getArguments()[0];
|
|
57
|
+
if (argument && isMaskCall(argument)) {
|
|
58
|
+
calls.push(argument);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
node = chainCallee.getExpression();
|
|
62
|
+
}
|
|
63
|
+
return calls;
|
|
64
|
+
};
|
|
65
|
+
const maskFromBuilderChain = (receiver) => {
|
|
66
|
+
const calls = maskCallsInChain(receiver);
|
|
67
|
+
return { maskColumns: calls.flatMap((call) => extractMaskColumns(call)), usesMask: calls.length > 0 };
|
|
68
|
+
};
|
|
69
|
+
const READ_METHODS = /* @__PURE__ */ new Set(["findFirst", "findFirstOrThrow", "findMany", "get", "query"]);
|
|
70
|
+
const WRITE_METHODS = /* @__PURE__ */ new Set(["delete", "insert", "patch", "replace"]);
|
|
71
|
+
const isDatabaseCall = (call, methodSet) => {
|
|
72
|
+
const callee = call.getExpression();
|
|
73
|
+
if (!Node.isPropertyAccessExpression(callee) || !methodSet.has(callee.getName())) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const receiver = callee.getExpression();
|
|
77
|
+
if (Node.isPropertyAccessExpression(receiver)) {
|
|
78
|
+
return receiver.getName() === "db";
|
|
79
|
+
}
|
|
80
|
+
return Node.isIdentifier(receiver) && receiver.getText() === "db";
|
|
81
|
+
};
|
|
82
|
+
const tableArgumentOf = (call) => {
|
|
83
|
+
const argument = call.getArguments()[0];
|
|
84
|
+
return argument && Node.isStringLiteral(argument) ? argument.getLiteralText() : "";
|
|
85
|
+
};
|
|
86
|
+
const tablesAccessedIn = (declaration) => {
|
|
87
|
+
const tablesRead = /* @__PURE__ */ new Set();
|
|
88
|
+
const tablesWritten = /* @__PURE__ */ new Set();
|
|
89
|
+
for (const call of declaration.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
90
|
+
if (isDatabaseCall(call, READ_METHODS)) {
|
|
91
|
+
const table = tableArgumentOf(call);
|
|
92
|
+
if (table !== "") {
|
|
93
|
+
tablesRead.add(table);
|
|
94
|
+
}
|
|
95
|
+
} else if (isDatabaseCall(call, WRITE_METHODS)) {
|
|
96
|
+
const table = tableArgumentOf(call);
|
|
97
|
+
if (table !== "") {
|
|
98
|
+
tablesWritten.add(table);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { tablesRead: [...tablesRead], tablesWritten: [...tablesWritten] };
|
|
103
|
+
};
|
|
104
|
+
const procedureIrFromDeclaration = (declaration, relativePath) => {
|
|
105
|
+
if (!Node.isVariableDeclaration(declaration)) {
|
|
106
|
+
return void 0;
|
|
107
|
+
}
|
|
108
|
+
const initializer = declaration.getInitializer();
|
|
109
|
+
if (!initializer || !Node.isCallExpression(initializer)) {
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
const classified = classifyProcedureCall(initializer);
|
|
113
|
+
if (!classified) {
|
|
114
|
+
return void 0;
|
|
115
|
+
}
|
|
116
|
+
const chain = classified.receiver ? maskFromBuilderChain(classified.receiver) : { maskColumns: [], usesMask: false };
|
|
117
|
+
const { tablesRead, tablesWritten } = tablesAccessedIn(declaration);
|
|
118
|
+
return {
|
|
119
|
+
exportName: declaration.getName(),
|
|
120
|
+
file: relativePath,
|
|
121
|
+
maskColumns: chain.maskColumns,
|
|
122
|
+
tablesRead,
|
|
123
|
+
tablesWritten,
|
|
124
|
+
usesMask: chain.usesMask,
|
|
125
|
+
visibility: classified.visibility
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
const discoverMaskProcedures = (project, lunoraDirectory) => {
|
|
129
|
+
const procedures = [];
|
|
130
|
+
for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
|
|
131
|
+
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
132
|
+
const relativePath = lunoraRelativePath(lunoraDirectory, filePath);
|
|
133
|
+
for (const statement of sourceFile.getVariableStatements()) {
|
|
134
|
+
if (!statement.isExported()) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
for (const declaration of statement.getDeclarations()) {
|
|
138
|
+
const ir = procedureIrFromDeclaration(declaration, relativePath);
|
|
139
|
+
if (ir) {
|
|
140
|
+
procedures.push(ir);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return procedures;
|
|
146
|
+
};
|
|
147
|
+
const strategyOf = (columnProperty) => {
|
|
148
|
+
if (Node.isPropertyAssignment(columnProperty)) {
|
|
149
|
+
const initializer = columnProperty.getInitializer();
|
|
150
|
+
if (initializer && Node.isStringLiteral(initializer)) {
|
|
151
|
+
const literal = initializer.getLiteralText();
|
|
152
|
+
if (literal === "redact" || literal === "hash") {
|
|
153
|
+
return literal;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return "custom";
|
|
158
|
+
};
|
|
159
|
+
const extractMaskColumnMetadata = (maskCall) => {
|
|
160
|
+
const argument = maskCall.getArguments()[0];
|
|
161
|
+
if (!argument || !Node.isObjectLiteralExpression(argument)) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
const columns = [];
|
|
165
|
+
for (const tableProperty of argument.getProperties()) {
|
|
166
|
+
const table = memberName(tableProperty);
|
|
167
|
+
if (table === void 0 || !Node.isPropertyAssignment(tableProperty)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const initializer = tableProperty.getInitializer();
|
|
171
|
+
if (!initializer || !Node.isObjectLiteralExpression(initializer)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
for (const columnProperty of initializer.getProperties()) {
|
|
175
|
+
const column = memberName(columnProperty);
|
|
176
|
+
if (column !== void 0) {
|
|
177
|
+
columns.push({ column, strategy: strategyOf(columnProperty), table });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return columns;
|
|
182
|
+
};
|
|
183
|
+
const exportedProcedureChains = (sourceFile) => {
|
|
184
|
+
const receivers = [];
|
|
185
|
+
for (const statement of sourceFile.getVariableStatements()) {
|
|
186
|
+
if (!statement.isExported()) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
for (const declaration of statement.getDeclarations()) {
|
|
190
|
+
const initializer = declaration.getInitializer();
|
|
191
|
+
const classified = initializer && Node.isCallExpression(initializer) ? classifyProcedureCall(initializer) : void 0;
|
|
192
|
+
if (classified?.receiver) {
|
|
193
|
+
receivers.push(classified.receiver);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return receivers;
|
|
198
|
+
};
|
|
199
|
+
const discoverMaskMetadata = (project, lunoraDirectory) => {
|
|
200
|
+
const columnsByKey = /* @__PURE__ */ new Map();
|
|
201
|
+
for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
|
|
202
|
+
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
203
|
+
for (const receiver of exportedProcedureChains(sourceFile)) {
|
|
204
|
+
for (const maskCall of maskCallsInChain(receiver)) {
|
|
205
|
+
for (const column of extractMaskColumnMetadata(maskCall)) {
|
|
206
|
+
const key = `${column.table}\0${column.column}`;
|
|
207
|
+
if (!columnsByKey.has(key)) {
|
|
208
|
+
columnsByKey.set(key, column);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return { columns: [...columnsByKey.values()] };
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
export { discoverMaskProcedures as default, discoverMaskMetadata };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { relative, sep } from 'node:path';
|
|
2
|
+
import { SyntaxKind, Node } from 'ts-morph';
|
|
3
|
+
import { listLunoraSourceFiles } from './discoverFunctions-DEgAcRuD.mjs';
|
|
4
|
+
|
|
5
|
+
const TS_EXTENSION_RE = /\.ts$/u;
|
|
6
|
+
const isDefineMigration = (identifier) => {
|
|
7
|
+
const symbol = identifier.getSymbol();
|
|
8
|
+
if (!symbol) {
|
|
9
|
+
return identifier.getText() === "defineMigration";
|
|
10
|
+
}
|
|
11
|
+
for (const declaration of symbol.getDeclarations()) {
|
|
12
|
+
if (!Node.isImportSpecifier(declaration)) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (declaration.getImportDeclaration().getModuleSpecifierValue() !== "@lunora/server") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return declaration.getNameNode().getText() === "defineMigration";
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
};
|
|
22
|
+
const stringProperty = (object, name) => {
|
|
23
|
+
const property = object.getProperty(name);
|
|
24
|
+
if (!property || !Node.isPropertyAssignment(property)) {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
const initializer = property.getInitializer();
|
|
28
|
+
return initializer && Node.isStringLiteral(initializer) ? initializer.getLiteralValue() : void 0;
|
|
29
|
+
};
|
|
30
|
+
const migrationFromDeclaration = (declaration, relativePath) => {
|
|
31
|
+
const initializer = declaration.getInitializer();
|
|
32
|
+
if (initializer?.getKind() !== SyntaxKind.CallExpression) {
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
const callee = initializer.getExpression();
|
|
36
|
+
if (!Node.isIdentifier(callee) || !isDefineMigration(callee)) {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
const argument = initializer.getArguments()[0];
|
|
40
|
+
if (!argument || !Node.isObjectLiteralExpression(argument)) {
|
|
41
|
+
return void 0;
|
|
42
|
+
}
|
|
43
|
+
const id = stringProperty(argument, "id");
|
|
44
|
+
const exportName = declaration.getName();
|
|
45
|
+
if (id === void 0 || id.trim() === "") {
|
|
46
|
+
throw Object.assign(
|
|
47
|
+
new Error(`Migration "${exportName}" in "${relativePath}" must declare \`id\` as a non-empty string literal so codegen can key the registry.`),
|
|
48
|
+
{ code: "MIGRATION_ID_NOT_STATIC", name: "LunoraError", status: 500 }
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return { exportName, filePath: relativePath, id, table: stringProperty(argument, "table") ?? "" };
|
|
52
|
+
};
|
|
53
|
+
const assertUniqueIds = (migrations) => {
|
|
54
|
+
const seenIds = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const migration of migrations) {
|
|
56
|
+
const prior = seenIds.get(migration.id);
|
|
57
|
+
if (prior !== void 0) {
|
|
58
|
+
throw Object.assign(
|
|
59
|
+
new Error(
|
|
60
|
+
`Duplicate migration id "${migration.id}": declared in both "${prior}" and "${migration.filePath}". Migration ids must be unique across the project.`
|
|
61
|
+
),
|
|
62
|
+
{ code: "DUPLICATE_MIGRATION_ID", id: migration.id, name: "LunoraError", paths: [prior, migration.filePath], status: 500 }
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
seenIds.set(migration.id, migration.filePath);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const discoverMigrations = (project, lunoraDirectory) => {
|
|
69
|
+
const filePaths = listLunoraSourceFiles(lunoraDirectory);
|
|
70
|
+
const migrations = [];
|
|
71
|
+
for (const filePath of filePaths) {
|
|
72
|
+
const source = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
73
|
+
const relativePath = relative(lunoraDirectory, filePath).split(sep).join("/").replace(TS_EXTENSION_RE, "");
|
|
74
|
+
for (const statement of source.getVariableStatements()) {
|
|
75
|
+
if (!statement.isExported()) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
for (const declaration of statement.getDeclarations()) {
|
|
79
|
+
const migration = migrationFromDeclaration(declaration, relativePath);
|
|
80
|
+
if (migration) {
|
|
81
|
+
migrations.push(migration);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
migrations.sort((a, b) => a.id.localeCompare(b.id));
|
|
87
|
+
assertUniqueIds(migrations);
|
|
88
|
+
return migrations;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export { discoverMigrations as default };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Node, SyntaxKind } from 'ts-morph';
|
|
2
|
+
import { listLunoraSourceFiles, lunoraRelativePath, classifyProcedureCall } from './discoverFunctions-DEgAcRuD.mjs';
|
|
3
|
+
|
|
4
|
+
const PROPERTY_CALLS = {
|
|
5
|
+
"crypto.getRandomValues": "crypto.getRandomValues",
|
|
6
|
+
"crypto.randomUUID": "crypto.randomUUID",
|
|
7
|
+
"Date.now": "Date.now",
|
|
8
|
+
"Math.random": "Math.random"
|
|
9
|
+
};
|
|
10
|
+
const FETCH_GLOBAL_RECEIVERS = /* @__PURE__ */ new Set(["globalThis", "self"]);
|
|
11
|
+
const GLOBAL_THIS_RECEIVERS = /* @__PURE__ */ new Set(["globalThis", "self", "window"]);
|
|
12
|
+
const receiverNameOf = (receiver) => {
|
|
13
|
+
if (Node.isIdentifier(receiver)) {
|
|
14
|
+
return receiver.getText();
|
|
15
|
+
}
|
|
16
|
+
if (Node.isPropertyAccessExpression(receiver)) {
|
|
17
|
+
const inner = receiver.getExpression();
|
|
18
|
+
if (Node.isIdentifier(inner) && GLOBAL_THIS_RECEIVERS.has(inner.getText())) {
|
|
19
|
+
return receiver.getName();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return void 0;
|
|
23
|
+
};
|
|
24
|
+
const nondeterministicCalleeOf = (call) => {
|
|
25
|
+
const callee = call.getExpression();
|
|
26
|
+
if (Node.isIdentifier(callee)) {
|
|
27
|
+
const name = callee.getText();
|
|
28
|
+
return name === "fetch" || name === "Date" ? name : void 0;
|
|
29
|
+
}
|
|
30
|
+
if (!Node.isPropertyAccessExpression(callee)) {
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
const method = callee.getName();
|
|
34
|
+
const receiverName = receiverNameOf(callee.getExpression());
|
|
35
|
+
if (receiverName === void 0) {
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
if (method === "fetch" && FETCH_GLOBAL_RECEIVERS.has(receiverName)) {
|
|
39
|
+
return "fetch";
|
|
40
|
+
}
|
|
41
|
+
return PROPERTY_CALLS[`${receiverName}.${method}`];
|
|
42
|
+
};
|
|
43
|
+
const nondeterministicNewOf = (expression) => {
|
|
44
|
+
const callee = expression.getExpression();
|
|
45
|
+
if (!Node.isIdentifier(callee) || callee.getText() !== "Date") {
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
const argumentNodes = expression.getArguments();
|
|
49
|
+
const allLiteral = argumentNodes.every(
|
|
50
|
+
(argument) => Node.isNumericLiteral(argument) || Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument)
|
|
51
|
+
);
|
|
52
|
+
return argumentNodes.length > 0 && allLiteral ? void 0 : "new Date";
|
|
53
|
+
};
|
|
54
|
+
const handlerOf = (call, receiver) => {
|
|
55
|
+
if (receiver) {
|
|
56
|
+
const handler = call.getArguments()[0];
|
|
57
|
+
return handler && (Node.isArrowFunction(handler) || Node.isFunctionExpression(handler)) ? handler : void 0;
|
|
58
|
+
}
|
|
59
|
+
const first = call.getArguments()[0];
|
|
60
|
+
if (!first || !Node.isObjectLiteralExpression(first)) {
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
const handlerProperty = first.getProperty("handler");
|
|
64
|
+
if (!handlerProperty || !Node.isPropertyAssignment(handlerProperty)) {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
const initializer = handlerProperty.getInitializer();
|
|
68
|
+
return initializer && (Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer)) ? initializer : void 0;
|
|
69
|
+
};
|
|
70
|
+
const exportedProcedureHandler = (declaration) => {
|
|
71
|
+
const initializer = declaration.getInitializer();
|
|
72
|
+
if (!initializer || !Node.isCallExpression(initializer)) {
|
|
73
|
+
return void 0;
|
|
74
|
+
}
|
|
75
|
+
const classified = classifyProcedureCall(initializer);
|
|
76
|
+
if (!classified || classified.kind !== "query" && classified.kind !== "mutation") {
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
const handler = handlerOf(initializer, classified.receiver);
|
|
80
|
+
return handler ? { exportName: declaration.getName(), handler, kind: classified.kind } : void 0;
|
|
81
|
+
};
|
|
82
|
+
const callsInHandler = (procedure, file) => {
|
|
83
|
+
const found = [];
|
|
84
|
+
for (const callNode of procedure.handler.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
85
|
+
const callee = nondeterministicCalleeOf(callNode);
|
|
86
|
+
if (callee !== void 0) {
|
|
87
|
+
found.push({ callee, exportName: procedure.exportName, file, kind: procedure.kind, line: callNode.getStartLineNumber() });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const newNode of procedure.handler.getDescendantsOfKind(SyntaxKind.NewExpression)) {
|
|
91
|
+
const callee = nondeterministicNewOf(newNode);
|
|
92
|
+
if (callee !== void 0) {
|
|
93
|
+
found.push({ callee, exportName: procedure.exportName, file, kind: procedure.kind, line: newNode.getStartLineNumber() });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return found;
|
|
97
|
+
};
|
|
98
|
+
const callsInSourceFile = (sourceFile, relativePath) => {
|
|
99
|
+
const found = [];
|
|
100
|
+
for (const statement of sourceFile.getVariableStatements()) {
|
|
101
|
+
if (!statement.isExported()) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
for (const declaration of statement.getDeclarations()) {
|
|
105
|
+
const procedure = exportedProcedureHandler(declaration);
|
|
106
|
+
if (procedure) {
|
|
107
|
+
found.push(...callsInHandler(procedure, relativePath));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return found;
|
|
112
|
+
};
|
|
113
|
+
const discoverNondeterministicCalls = (project, lunoraDirectory) => {
|
|
114
|
+
const calls = [];
|
|
115
|
+
for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
|
|
116
|
+
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
117
|
+
calls.push(...callsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
|
|
118
|
+
}
|
|
119
|
+
return calls;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export { discoverNondeterministicCalls as default };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { SyntaxKind, Node } from 'ts-morph';
|
|
2
|
+
import { listLunoraSourceFiles, lunoraRelativePath } from './discoverFunctions-DEgAcRuD.mjs';
|
|
3
|
+
|
|
4
|
+
const INDEX_METHODS = /* @__PURE__ */ new Set(["withIndex", "withSearchIndex"]);
|
|
5
|
+
const isDatabaseQueryCall = (call) => {
|
|
6
|
+
const callee = call.getExpression();
|
|
7
|
+
if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "query") {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const receiver = callee.getExpression();
|
|
11
|
+
if (Node.isPropertyAccessExpression(receiver)) {
|
|
12
|
+
return receiver.getName() === "db";
|
|
13
|
+
}
|
|
14
|
+
return Node.isIdentifier(receiver) && receiver.getText() === "db";
|
|
15
|
+
};
|
|
16
|
+
const chainMethods = (queryCall) => {
|
|
17
|
+
const methods = [];
|
|
18
|
+
let node = queryCall;
|
|
19
|
+
for (; ; ) {
|
|
20
|
+
const parent = node.getParent();
|
|
21
|
+
if (!parent || !Node.isPropertyAccessExpression(parent)) {
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
const callParent = parent.getParent();
|
|
25
|
+
if (!callParent || !Node.isCallExpression(callParent)) {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
methods.push(parent.getName());
|
|
29
|
+
node = callParent;
|
|
30
|
+
}
|
|
31
|
+
return methods;
|
|
32
|
+
};
|
|
33
|
+
const tableOf = (queryCall) => {
|
|
34
|
+
const argument = queryCall.getArguments()[0];
|
|
35
|
+
return argument && Node.isStringLiteral(argument) ? argument.getLiteralText() : "";
|
|
36
|
+
};
|
|
37
|
+
const discoverQueries = (project, lunoraDirectory) => {
|
|
38
|
+
const reads = [];
|
|
39
|
+
for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
|
|
40
|
+
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
41
|
+
const relativePath = lunoraRelativePath(lunoraDirectory, filePath);
|
|
42
|
+
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
43
|
+
if (!isDatabaseQueryCall(call)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const methods = chainMethods(call);
|
|
47
|
+
if (!methods.includes("filter")) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
reads.push({
|
|
51
|
+
file: relativePath,
|
|
52
|
+
hasFilter: true,
|
|
53
|
+
hasIndex: methods.some((method) => INDEX_METHODS.has(method)),
|
|
54
|
+
line: call.getStartLineNumber(),
|
|
55
|
+
table: tableOf(call)
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return reads;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export { discoverQueries as default };
|