supabase-strict-check 0.1.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.
- package/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +65 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +19 -0
- package/dist/lib/catalog.d.ts +5 -0
- package/dist/lib/catalog.js +293 -0
- package/dist/lib/checker.d.ts +70 -0
- package/dist/lib/checker.js +791 -0
- package/dist/lib/program.d.ts +6 -0
- package/dist/lib/program.js +145 -0
- package/dist/lib/scope.d.ts +12 -0
- package/dist/lib/scope.js +30 -0
- package/dist/lib/select.d.ts +6 -0
- package/dist/lib/select.js +127 -0
- package/dist/lib/types.d.ts +54 -0
- package/dist/lib/types.js +6 -0
- package/dist/run.d.ts +1 -0
- package/dist/run.js +71 -0
- package/dist/utils/ast.d.ts +50 -0
- package/dist/utils/ast.js +335 -0
- package/dist/utils/clients.d.ts +14 -0
- package/dist/utils/clients.js +135 -0
- package/dist/utils/files.d.ts +6 -0
- package/dist/utils/files.js +144 -0
- package/dist/utils/paths.d.ts +6 -0
- package/dist/utils/paths.js +64 -0
- package/package.json +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FishTank
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# supabase-strict-check
|
|
2
|
+
|
|
3
|
+
Checker for PostgREST/Supabase client queries against generated `Database` types.
|
|
4
|
+
|
|
5
|
+
It walks TypeScript source, resolves `.from()`, `.select()`, filters, inserts/updates, and RPC calls, and reports columns, relations, and payloads that do not match the generated types file.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g supabase-strict-check
|
|
11
|
+
supabase-strict-check --target=src --types=src/types/supabase.ts
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Flags
|
|
15
|
+
|
|
16
|
+
| Flag | Meaning |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `--target` | Directory of TypeScript source to scan (`.ts`, `.tsx`) |
|
|
19
|
+
| `--types` | Path to the generated Supabase types file (`export type Database = { ... }`) |
|
|
20
|
+
|
|
21
|
+
Omit both flags to be prompted (TTY only).
|
|
22
|
+
|
|
23
|
+
## License
|
|
24
|
+
|
|
25
|
+
MIT. See [LICENSE](LICENSE).
|
|
26
|
+
|
|
27
|
+
## Contributing
|
|
28
|
+
|
|
29
|
+
See [CONTRIBUTING](CONTRIBUTING.md)
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveCliPaths = resolveCliPaths;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const promises_1 = __importDefault(require("node:readline/promises"));
|
|
10
|
+
const node_process_1 = require("node:process");
|
|
11
|
+
function flagValue(argv, name) {
|
|
12
|
+
const eq = `--${name}=`;
|
|
13
|
+
for (let i = 0; i < argv.length; i++) {
|
|
14
|
+
const arg = argv[i];
|
|
15
|
+
if (arg.startsWith(eq))
|
|
16
|
+
return arg.slice(eq.length);
|
|
17
|
+
if (arg === `--${name}`)
|
|
18
|
+
return argv[i + 1];
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
function resolveFromCwd(cwd, value) {
|
|
23
|
+
return node_path_1.default.resolve(cwd, value);
|
|
24
|
+
}
|
|
25
|
+
async function ask(cwd, kind) {
|
|
26
|
+
if (!node_process_1.stdin.isTTY || !node_process_1.stdout.isTTY) {
|
|
27
|
+
throw new Error(kind === "directory"
|
|
28
|
+
? "missing --target= (path to directory from pwd)"
|
|
29
|
+
: "missing --types= (path to supabase types file from pwd)");
|
|
30
|
+
}
|
|
31
|
+
const rl = promises_1.default.createInterface({ input: node_process_1.stdin, output: node_process_1.stdout });
|
|
32
|
+
try {
|
|
33
|
+
const label = kind === "directory"
|
|
34
|
+
? `path to directory (allows '..' and '.', eg: '../../src')
|
|
35
|
+
\nFrom \`${cwd}\`
|
|
36
|
+
\n> `
|
|
37
|
+
: `path to supabase types (allows '..' and '.', eg: '../../src/types/supabase.types.ts')
|
|
38
|
+
\nFrom \`${cwd}\`
|
|
39
|
+
\n> `;
|
|
40
|
+
const answer = (await rl.question(label)).trim();
|
|
41
|
+
if (!answer) {
|
|
42
|
+
throw new Error(kind === "directory" ? "directory path is required" : "types path is required");
|
|
43
|
+
}
|
|
44
|
+
return answer;
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
rl.close();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function resolveCliPaths(argv = process.argv.slice(2)) {
|
|
51
|
+
const cwd = process.cwd();
|
|
52
|
+
const targetArg = flagValue(argv, "target") ?? await ask(cwd, "directory");
|
|
53
|
+
const target = resolveFromCwd(cwd, targetArg);
|
|
54
|
+
if (!node_fs_1.default.existsSync(target) || !node_fs_1.default.statSync(target).isDirectory()) {
|
|
55
|
+
throw new Error(`not a directory: ${targetArg} (resolved ${target})`);
|
|
56
|
+
}
|
|
57
|
+
console.log(`Targeted directory: ${target}`);
|
|
58
|
+
const typesArg = flagValue(argv, "types") ?? await ask(cwd, "types");
|
|
59
|
+
const types = resolveFromCwd(cwd, typesArg);
|
|
60
|
+
if (!node_fs_1.default.existsSync(types) || !node_fs_1.default.statSync(types).isFile()) {
|
|
61
|
+
throw new Error(`not a types file: ${typesArg} (resolved ${types})`);
|
|
62
|
+
}
|
|
63
|
+
console.log(`Types file: ${types}`);
|
|
64
|
+
return { cwd, target, types };
|
|
65
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.main = main;
|
|
5
|
+
/**
|
|
6
|
+
* Static PostgREST/Supabase checker.
|
|
7
|
+
*
|
|
8
|
+
* npm start -- --target=src --types=src/types/supabase.ts
|
|
9
|
+
*
|
|
10
|
+
* Paths are from pwd. Omit flags to be prompted.
|
|
11
|
+
*/
|
|
12
|
+
const run_1 = require("./run");
|
|
13
|
+
function main() {
|
|
14
|
+
(0, run_1.run)().catch((error) => {
|
|
15
|
+
console.error(error instanceof Error ? error.message : error);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
main();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Catalog, Relation, Relationship } from "./types";
|
|
2
|
+
export declare function loadCatalog(typesPath: string): Catalog;
|
|
3
|
+
export declare function resolveRelation(catalog: Catalog, schema: string, name: string): Relation | undefined;
|
|
4
|
+
export declare function lookupTarget(catalog: Catalog, fromSchema: string, referencedRelation: string): Relation | undefined;
|
|
5
|
+
export declare function pointsTo(catalog: Catalog, rel: Relationship, fromSchema: string, target: Relation): boolean;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadCatalog = loadCatalog;
|
|
7
|
+
exports.resolveRelation = resolveRelation;
|
|
8
|
+
exports.lookupTarget = lookupTarget;
|
|
9
|
+
exports.pointsTo = pointsTo;
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
12
|
+
const types_1 = require("./types");
|
|
13
|
+
function propName(name) {
|
|
14
|
+
if (typescript_1.default.isIdentifier(name) || typescript_1.default.isStringLiteral(name) || typescript_1.default.isNumericLiteral(name)) {
|
|
15
|
+
return name.text;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function typeName(member) {
|
|
20
|
+
if (!typescript_1.default.isPropertySignature(member) || !member.name)
|
|
21
|
+
return null;
|
|
22
|
+
return propName(member.name);
|
|
23
|
+
}
|
|
24
|
+
function asTypeLiteral(node) {
|
|
25
|
+
return node && typescript_1.default.isTypeLiteralNode(node) ? node : null;
|
|
26
|
+
}
|
|
27
|
+
function stringFromType(node) {
|
|
28
|
+
if (!node)
|
|
29
|
+
return null;
|
|
30
|
+
if (typescript_1.default.isLiteralTypeNode(node) && typescript_1.default.isStringLiteral(node.literal))
|
|
31
|
+
return node.literal.text;
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function boolFromType(node) {
|
|
35
|
+
if (!node || !typescript_1.default.isLiteralTypeNode(node))
|
|
36
|
+
return false;
|
|
37
|
+
return node.literal.kind === typescript_1.default.SyntaxKind.TrueKeyword;
|
|
38
|
+
}
|
|
39
|
+
function stringsFromTuple(node) {
|
|
40
|
+
if (!node || !typescript_1.default.isTupleTypeNode(node))
|
|
41
|
+
return [];
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const el of node.elements) {
|
|
44
|
+
const value = stringFromType(el);
|
|
45
|
+
if (value != null)
|
|
46
|
+
out.push(value);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
function memberType(literal, key) {
|
|
51
|
+
for (const member of literal.members) {
|
|
52
|
+
if (typeName(member) === key && typescript_1.default.isPropertySignature(member)) {
|
|
53
|
+
return member.type;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
function stringLiterals(node) {
|
|
59
|
+
if (!node)
|
|
60
|
+
return [];
|
|
61
|
+
if (typescript_1.default.isLiteralTypeNode(node) && typescript_1.default.isStringLiteral(node.literal))
|
|
62
|
+
return [node.literal.text];
|
|
63
|
+
if (typescript_1.default.isUnionTypeNode(node)) {
|
|
64
|
+
return node.types.flatMap((t) => stringLiterals(t));
|
|
65
|
+
}
|
|
66
|
+
if (typescript_1.default.isParenthesizedTypeNode(node))
|
|
67
|
+
return stringLiterals(node.type);
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
/** Database["public"]["Enums"]["status"] */
|
|
71
|
+
function enumIndex(node) {
|
|
72
|
+
if (!node || !typescript_1.default.isIndexedAccessTypeNode(node))
|
|
73
|
+
return null;
|
|
74
|
+
const name = stringFromType(node.indexType);
|
|
75
|
+
const inner = node.objectType;
|
|
76
|
+
if (!name || !typescript_1.default.isIndexedAccessTypeNode(inner))
|
|
77
|
+
return null;
|
|
78
|
+
const enumsKey = stringFromType(inner.indexType);
|
|
79
|
+
if (enumsKey !== "Enums")
|
|
80
|
+
return null;
|
|
81
|
+
const schemaNode = inner.objectType;
|
|
82
|
+
if (!typescript_1.default.isIndexedAccessTypeNode(schemaNode))
|
|
83
|
+
return null;
|
|
84
|
+
const schema = stringFromType(schemaNode.indexType);
|
|
85
|
+
if (!schema)
|
|
86
|
+
return null;
|
|
87
|
+
return { schema, name };
|
|
88
|
+
}
|
|
89
|
+
function parseRelationships(node) {
|
|
90
|
+
if (!node || !typescript_1.default.isTupleTypeNode(node))
|
|
91
|
+
return [];
|
|
92
|
+
const rels = [];
|
|
93
|
+
for (const el of node.elements) {
|
|
94
|
+
const literal = asTypeLiteral(el);
|
|
95
|
+
if (!literal)
|
|
96
|
+
continue;
|
|
97
|
+
const foreignKeyName = stringFromType(memberType(literal, "foreignKeyName"));
|
|
98
|
+
const referencedRelation = stringFromType(memberType(literal, "referencedRelation"));
|
|
99
|
+
if (!foreignKeyName || !referencedRelation)
|
|
100
|
+
continue;
|
|
101
|
+
rels.push({
|
|
102
|
+
foreignKeyName,
|
|
103
|
+
columns: stringsFromTuple(memberType(literal, "columns")),
|
|
104
|
+
isOneToOne: boolFromType(memberType(literal, "isOneToOne")),
|
|
105
|
+
referencedRelation,
|
|
106
|
+
referencedColumns: stringsFromTuple(memberType(literal, "referencedColumns")),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return rels;
|
|
110
|
+
}
|
|
111
|
+
function parseColumns(row, catalog) {
|
|
112
|
+
const names = new Set();
|
|
113
|
+
const required = new Set();
|
|
114
|
+
const valueDomain = new Map();
|
|
115
|
+
const literal = asTypeLiteral(row);
|
|
116
|
+
if (!literal)
|
|
117
|
+
return { names, required, valueDomain };
|
|
118
|
+
for (const member of literal.members) {
|
|
119
|
+
const name = typeName(member);
|
|
120
|
+
if (!name || !typescript_1.default.isPropertySignature(member))
|
|
121
|
+
continue;
|
|
122
|
+
names.add(name);
|
|
123
|
+
if (!member.questionToken)
|
|
124
|
+
required.add(name);
|
|
125
|
+
const domain = stringLiterals(member.type);
|
|
126
|
+
if (domain.length > 0) {
|
|
127
|
+
valueDomain.set(name, domain);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const ref = enumIndex(member.type);
|
|
131
|
+
if (ref) {
|
|
132
|
+
const values = catalog.enums.get(ref.schema)?.get(ref.name);
|
|
133
|
+
if (values)
|
|
134
|
+
valueDomain.set(name, values);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return { names, required, valueDomain };
|
|
138
|
+
}
|
|
139
|
+
function parseRelationBlock(schema, name, kind, node, catalog) {
|
|
140
|
+
const literal = asTypeLiteral(node);
|
|
141
|
+
if (!literal)
|
|
142
|
+
return null;
|
|
143
|
+
const row = parseColumns(memberType(literal, "Row"), catalog);
|
|
144
|
+
const insert = parseColumns(memberType(literal, "Insert"), catalog);
|
|
145
|
+
const update = parseColumns(memberType(literal, "Update"), catalog);
|
|
146
|
+
return {
|
|
147
|
+
schema,
|
|
148
|
+
name,
|
|
149
|
+
kind,
|
|
150
|
+
columns: row.names,
|
|
151
|
+
insertColumns: insert.names,
|
|
152
|
+
insertRequired: insert.required,
|
|
153
|
+
updateColumns: update.names,
|
|
154
|
+
relationships: parseRelationships(memberType(literal, "Relationships")),
|
|
155
|
+
valueDomain: row.valueDomain.size ? row.valueDomain : insert.valueDomain,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function parseNamedBlock(schema, kind, node, catalog) {
|
|
159
|
+
const literal = asTypeLiteral(node);
|
|
160
|
+
if (!literal)
|
|
161
|
+
return;
|
|
162
|
+
for (const member of literal.members) {
|
|
163
|
+
const name = typeName(member);
|
|
164
|
+
if (!name || !typescript_1.default.isPropertySignature(member) || !member.type)
|
|
165
|
+
continue;
|
|
166
|
+
const relation = parseRelationBlock(schema, name, kind, member.type, catalog);
|
|
167
|
+
if (!relation)
|
|
168
|
+
continue;
|
|
169
|
+
catalog.relations.set((0, types_1.relKey)(schema, name), relation);
|
|
170
|
+
const list = catalog.byName.get(name) ?? [];
|
|
171
|
+
list.push(relation);
|
|
172
|
+
catalog.byName.set(name, list);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function parseRpc(name, node) {
|
|
176
|
+
const literal = asTypeLiteral(node);
|
|
177
|
+
if (!literal)
|
|
178
|
+
return null;
|
|
179
|
+
const args = memberType(literal, "Args");
|
|
180
|
+
if (!args)
|
|
181
|
+
return { name, argNames: new Set(), requiredArgs: new Set(), argsNever: true };
|
|
182
|
+
if (args.kind === typescript_1.default.SyntaxKind.NeverKeyword) {
|
|
183
|
+
return { name, argNames: new Set(), requiredArgs: new Set(), argsNever: true };
|
|
184
|
+
}
|
|
185
|
+
const body = asTypeLiteral(args);
|
|
186
|
+
const argNames = new Set();
|
|
187
|
+
const requiredArgs = new Set();
|
|
188
|
+
if (body) {
|
|
189
|
+
for (const member of body.members) {
|
|
190
|
+
const arg = typeName(member);
|
|
191
|
+
if (!arg || !typescript_1.default.isPropertySignature(member))
|
|
192
|
+
continue;
|
|
193
|
+
argNames.add(arg);
|
|
194
|
+
if (!member.questionToken)
|
|
195
|
+
requiredArgs.add(arg);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return { name, argNames, requiredArgs, argsNever: false };
|
|
199
|
+
}
|
|
200
|
+
function parseFunctions(schema, node, catalog) {
|
|
201
|
+
const map = catalog.functions.get(schema) ?? new Map();
|
|
202
|
+
const literal = asTypeLiteral(node);
|
|
203
|
+
if (literal) {
|
|
204
|
+
for (const member of literal.members) {
|
|
205
|
+
const name = typeName(member);
|
|
206
|
+
if (!name || !typescript_1.default.isPropertySignature(member) || !member.type)
|
|
207
|
+
continue;
|
|
208
|
+
const fn = parseRpc(name, member.type);
|
|
209
|
+
if (fn)
|
|
210
|
+
map.set(name, fn);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catalog.functions.set(schema, map);
|
|
214
|
+
}
|
|
215
|
+
function parseEnums(schema, node, catalog) {
|
|
216
|
+
const map = catalog.enums.get(schema) ?? new Map();
|
|
217
|
+
const literal = asTypeLiteral(node);
|
|
218
|
+
if (literal) {
|
|
219
|
+
for (const member of literal.members) {
|
|
220
|
+
const name = typeName(member);
|
|
221
|
+
if (!name || !typescript_1.default.isPropertySignature(member))
|
|
222
|
+
continue;
|
|
223
|
+
const values = stringLiterals(member.type);
|
|
224
|
+
if (values.length)
|
|
225
|
+
map.set(name, values);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catalog.enums.set(schema, map);
|
|
229
|
+
}
|
|
230
|
+
function loadCatalog(typesPath) {
|
|
231
|
+
const text = node_fs_1.default.readFileSync(typesPath, "utf8");
|
|
232
|
+
const sf = typescript_1.default.createSourceFile(typesPath, text, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TS);
|
|
233
|
+
const catalog = {
|
|
234
|
+
schemas: new Set(),
|
|
235
|
+
relations: new Map(),
|
|
236
|
+
byName: new Map(),
|
|
237
|
+
functions: new Map(),
|
|
238
|
+
enums: new Map(),
|
|
239
|
+
};
|
|
240
|
+
let database = null;
|
|
241
|
+
for (const stmt of sf.statements) {
|
|
242
|
+
if (typescript_1.default.isTypeAliasDeclaration(stmt) && stmt.name.text === "Database" && typescript_1.default.isTypeLiteralNode(stmt.type)) {
|
|
243
|
+
database = stmt.type;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (!database) {
|
|
248
|
+
throw new Error(`Could not find 'export type Database = { ... }' in ${typesPath}`);
|
|
249
|
+
}
|
|
250
|
+
for (const member of database.members) {
|
|
251
|
+
const schema = typeName(member);
|
|
252
|
+
if (!schema || schema === "__InternalSupabase")
|
|
253
|
+
continue;
|
|
254
|
+
if (!typescript_1.default.isPropertySignature(member) || !member.type)
|
|
255
|
+
continue;
|
|
256
|
+
const body = asTypeLiteral(member.type);
|
|
257
|
+
if (!body)
|
|
258
|
+
continue;
|
|
259
|
+
catalog.schemas.add(schema);
|
|
260
|
+
parseEnums(schema, memberType(body, "Enums"), catalog);
|
|
261
|
+
}
|
|
262
|
+
for (const member of database.members) {
|
|
263
|
+
const schema = typeName(member);
|
|
264
|
+
if (!schema || schema === "__InternalSupabase")
|
|
265
|
+
continue;
|
|
266
|
+
if (!typescript_1.default.isPropertySignature(member) || !member.type)
|
|
267
|
+
continue;
|
|
268
|
+
const body = asTypeLiteral(member.type);
|
|
269
|
+
if (!body)
|
|
270
|
+
continue;
|
|
271
|
+
parseNamedBlock(schema, "table", memberType(body, "Tables"), catalog);
|
|
272
|
+
parseNamedBlock(schema, "view", memberType(body, "Views"), catalog);
|
|
273
|
+
parseFunctions(schema, memberType(body, "Functions"), catalog);
|
|
274
|
+
}
|
|
275
|
+
if (catalog.relations.size === 0) {
|
|
276
|
+
throw new Error(`Parsed 0 tables/views from ${typesPath}`);
|
|
277
|
+
}
|
|
278
|
+
return catalog;
|
|
279
|
+
}
|
|
280
|
+
function resolveRelation(catalog, schema, name) {
|
|
281
|
+
return catalog.relations.get((0, types_1.relKey)(schema, name))
|
|
282
|
+
?? catalog.byName.get(name)?.find((r) => r.schema === schema)
|
|
283
|
+
?? (catalog.byName.get(name)?.length === 1 ? catalog.byName.get(name)[0] : undefined);
|
|
284
|
+
}
|
|
285
|
+
function lookupTarget(catalog, fromSchema, referencedRelation) {
|
|
286
|
+
return resolveRelation(catalog, fromSchema, referencedRelation);
|
|
287
|
+
}
|
|
288
|
+
function pointsTo(catalog, rel, fromSchema, target) {
|
|
289
|
+
const referenced = lookupTarget(catalog, fromSchema, rel.referencedRelation);
|
|
290
|
+
if (referenced)
|
|
291
|
+
return referenced.schema === target.schema && referenced.name === target.name;
|
|
292
|
+
return rel.referencedRelation === target.name;
|
|
293
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { Catalog, QueryError, Relation } from "./types";
|
|
3
|
+
export interface PendingAnyPayload {
|
|
4
|
+
fn: ts.FunctionLikeDeclaration;
|
|
5
|
+
paramIndex: number;
|
|
6
|
+
relation: Relation;
|
|
7
|
+
kind: string;
|
|
8
|
+
allowed: Set<string>;
|
|
9
|
+
checkRequired: boolean;
|
|
10
|
+
origin: ts.Node;
|
|
11
|
+
hits: number;
|
|
12
|
+
seenCalls: Set<string>;
|
|
13
|
+
}
|
|
14
|
+
export interface CheckerOptions {
|
|
15
|
+
catalog: Catalog;
|
|
16
|
+
sf: ts.SourceFile;
|
|
17
|
+
consts: Map<string, string>;
|
|
18
|
+
tsChecker?: ts.TypeChecker;
|
|
19
|
+
clientNames?: Set<string>;
|
|
20
|
+
subst?: Map<string, string>;
|
|
21
|
+
instantiated?: Set<string>;
|
|
22
|
+
pendingAny?: PendingAnyPayload[];
|
|
23
|
+
errors?: QueryError[];
|
|
24
|
+
warnings?: QueryError[];
|
|
25
|
+
}
|
|
26
|
+
export declare class Checker {
|
|
27
|
+
readonly errors: QueryError[];
|
|
28
|
+
readonly warnings: QueryError[];
|
|
29
|
+
private readonly catalog;
|
|
30
|
+
private readonly sf;
|
|
31
|
+
private readonly consts;
|
|
32
|
+
private readonly tsChecker?;
|
|
33
|
+
private readonly clientNames;
|
|
34
|
+
private readonly subst;
|
|
35
|
+
private readonly instantiated;
|
|
36
|
+
private readonly pendingAny;
|
|
37
|
+
private resolvingPending;
|
|
38
|
+
constructor(opts: CheckerOptions);
|
|
39
|
+
private evalOpts;
|
|
40
|
+
private str;
|
|
41
|
+
fail(node: ts.Node, message: string): void;
|
|
42
|
+
warn(node: ts.Node, message: string): void;
|
|
43
|
+
checkFile(): void;
|
|
44
|
+
followAnyPayloads(): void;
|
|
45
|
+
private spawn;
|
|
46
|
+
private visit;
|
|
47
|
+
private maybeInstantiate;
|
|
48
|
+
private applyPendingAtCall;
|
|
49
|
+
private queuePending;
|
|
50
|
+
private substFromCall;
|
|
51
|
+
private checkTail;
|
|
52
|
+
private skipGenericParam;
|
|
53
|
+
private resolvePayload;
|
|
54
|
+
private collectKeys;
|
|
55
|
+
private checkRpc;
|
|
56
|
+
private checkMethod;
|
|
57
|
+
private checkPayload;
|
|
58
|
+
private payloadValue;
|
|
59
|
+
private checkLiteralDomain;
|
|
60
|
+
private checkOnConflict;
|
|
61
|
+
private checkMatch;
|
|
62
|
+
private checkOr;
|
|
63
|
+
private checkFilterColumn;
|
|
64
|
+
private checkSelect;
|
|
65
|
+
private walkSelect;
|
|
66
|
+
private assertColumnOrPath;
|
|
67
|
+
private resolveEmbed;
|
|
68
|
+
private embedCandidates;
|
|
69
|
+
}
|
|
70
|
+
export declare function flushUnhitAnyPayloads(pending: PendingAnyPayload[], errors: QueryError[]): void;
|