better-supabase 0.0.0 → 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 +88 -1
- package/bin/better-supabase.js +3 -0
- package/dist/casing-da0uRTqt.js +14 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +15 -0
- package/dist/cli/index.d.ts +226 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli-CoByWYja.js +6472 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +112 -0
- package/dist/compiler-DL18ukPe.d.ts +14 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +3 -0
- package/dist/config-D-aR2ZoN.js +276 -0
- package/dist/define-CTfWw-SS.d.ts +662 -0
- package/dist/define-QGkq04oe.js +1068 -0
- package/dist/edge/index.d.ts +43 -0
- package/dist/edge/index.js +103 -0
- package/dist/entitlements-BBg61DQZ.d.ts +20 -0
- package/dist/entitlements-BC_khwRy.js +17 -0
- package/dist/env/index.d.ts +2 -0
- package/dist/env/index.js +2 -0
- package/dist/env-BEToab0I.js +204 -0
- package/dist/errors-Bl3rkOzo.js +196 -0
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.js +2 -0
- package/dist/events-2OJsVG_d.d.ts +84 -0
- package/dist/events-BuzTgn2D.js +135 -0
- package/dist/executor-Bazhgsgq.d.ts +195 -0
- package/dist/executor-Bje4suQF.d.ts +25 -0
- package/dist/executor-Ceq60hPM.js +89 -0
- package/dist/hono/index.d.ts +45 -0
- package/dist/hono/index.js +70 -0
- package/dist/index-BD6wKthp.d.ts +597 -0
- package/dist/index-DQVvHzjW.d.ts +74 -0
- package/dist/index-DRid0qIv.d.ts +76 -0
- package/dist/index-Dup-nSe9.d.ts +117 -0
- package/dist/index-Dy6kuF0P.d.ts +71 -0
- package/dist/index-Hf8PP9sP.d.ts +116 -0
- package/dist/index-JXHBuS53.d.ts +144 -0
- package/dist/index-Nxo3TJcm.d.ts +208 -0
- package/dist/index-Sw43J8HG.d.ts +62 -0
- package/dist/index-cEeweTeM.d.ts +38 -0
- package/dist/index.d.ts +110 -0
- package/dist/index.js +42 -0
- package/dist/invalidate-DbxuL2jL.js +18 -0
- package/dist/jobs/index.d.ts +173 -0
- package/dist/jobs/index.js +477 -0
- package/dist/json-schema-BbwO2mZg.js +181 -0
- package/dist/lint/index.d.ts +75 -0
- package/dist/lint/index.js +167 -0
- package/dist/list/index.d.ts +2 -0
- package/dist/list/index.js +410 -0
- package/dist/live-6b9i72Ux.d.ts +180 -0
- package/dist/live-D8rV1Ldw.js +168 -0
- package/dist/mcp/index.d.ts +105 -0
- package/dist/mcp/index.js +330 -0
- package/dist/mfa-CpogP66z.js +521 -0
- package/dist/mfa-TVia554c.d.ts +44 -0
- package/dist/next/image/index.d.ts +34 -0
- package/dist/next/image/index.js +40 -0
- package/dist/next/index.d.ts +210 -0
- package/dist/next/index.js +426 -0
- package/dist/openapi/index.d.ts +2 -0
- package/dist/openapi/index.js +332 -0
- package/dist/orpc/index.d.ts +46 -0
- package/dist/orpc/index.js +50 -0
- package/dist/otel/index.d.ts +40 -0
- package/dist/otel/index.js +168 -0
- package/dist/path-Cv__-OTj.d.ts +17 -0
- package/dist/plugin-BqR6wKMB.js +23 -0
- package/dist/plugin-HvGqCurB.d.ts +118 -0
- package/dist/plugins/actor/index.d.ts +13 -0
- package/dist/plugins/actor/index.js +43 -0
- package/dist/plugins/rules/index.d.ts +88 -0
- package/dist/plugins/rules/index.js +174 -0
- package/dist/plugins/soft-delete/index.d.ts +38 -0
- package/dist/plugins/soft-delete/index.js +69 -0
- package/dist/plugins/tenant/index.d.ts +43 -0
- package/dist/plugins/tenant/index.js +73 -0
- package/dist/plugins/timestamps/index.d.ts +9 -0
- package/dist/plugins/timestamps/index.js +37 -0
- package/dist/plugins/validation/index.d.ts +24 -0
- package/dist/plugins/validation/index.js +59 -0
- package/dist/pool-BFeqzwQS.d.ts +37 -0
- package/dist/postgres/index.d.ts +28 -0
- package/dist/postgres/index.js +79 -0
- package/dist/postgrest-Dm-vhY6R.d.ts +43 -0
- package/dist/problem-DPu6Fh-t.js +97 -0
- package/dist/problem-DlgB0lYl.d.ts +47 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +3 -0
- package/dist/query-ChvDbsDE.js +186 -0
- package/dist/react/index.d.ts +7 -0
- package/dist/react/index.js +5 -0
- package/dist/react/server.d.ts +13 -0
- package/dist/react/server.js +25 -0
- package/dist/react/session.d.ts +2 -0
- package/dist/react/session.js +3 -0
- package/dist/react-CrOvrnIP.js +241 -0
- package/dist/read-set-DhX2d04c.js +216 -0
- package/dist/realtime/index.d.ts +3 -0
- package/dist/realtime/index.js +253 -0
- package/dist/resolve-DW8h9NPm.d.ts +155 -0
- package/dist/resource-CYiu6Ygi.js +175 -0
- package/dist/resource-SkPTRJDD.d.ts +65 -0
- package/dist/respond-4-dhoV7x.js +380 -0
- package/dist/respond-PoTpy5DT.d.ts +169 -0
- package/dist/result-CTkCZ6JA.d.ts +198 -0
- package/dist/result-DWXatkd6.js +103 -0
- package/dist/scope-CDHcS7dE.js +90 -0
- package/dist/seed-CzecODcJ.js +101 -0
- package/dist/server/index.d.ts +42 -0
- package/dist/server/index.js +68 -0
- package/dist/session-C3osL21k.d.ts +21 -0
- package/dist/session-CD-3orO_.js +19 -0
- package/dist/shared-_6-uPL7E.js +57 -0
- package/dist/simplify-oHiDKRVQ.js +665 -0
- package/dist/spec-pins-CJPVghHy.d.ts +20 -0
- package/dist/spec-pins-t6sPaHvl.js +20 -0
- package/dist/sql-M8l3fnS0.js +260 -0
- package/dist/ssr/index.d.ts +5 -0
- package/dist/ssr/index.js +4 -0
- package/dist/standard-CyhRzctb.d.ts +7 -0
- package/dist/standard-FUi3Z3-7.js +21 -0
- package/dist/stats-3iy8u3_P.js +566 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage-BWXVUXGk.js +461 -0
- package/dist/tables-rZxRxNPW.js +59 -0
- package/dist/template-BeOGnynM.js +93 -0
- package/dist/template-mbvlysOo.d.ts +6 -0
- package/dist/testing/index.d.ts +294 -0
- package/dist/testing/index.js +751 -0
- package/dist/types-6cYK9pbJ.js +29 -0
- package/dist/types-C2fslP1z.d.ts +195 -0
- package/dist/version-Cqq1m3UY.js +4 -0
- package/dist/view-B0ltUmTi.d.ts +43 -0
- package/dist/view-B6F9QbG-.js +35 -0
- package/dist/webhooks/index.d.ts +152 -0
- package/dist/webhooks/index.js +2 -0
- package/dist/webhooks-DbMTJJX2.js +139 -0
- package/package.json +246 -17
- package/schemas/config-v1.json +328 -0
- package/schemas/doctor-report-v1.json +105 -0
- package/schemas/snapshot-v2.json +1002 -0
- package/skills/better-supabase/SKILL.md +84 -0
- package/skills/better-supabase/references/plugins.md +56 -0
- package/skills/better-supabase/references/troubleshooting.md +39 -0
- package/skills/better-supabase-api/SKILL.md +67 -0
- package/skills/better-supabase-api/references/adapters.md +108 -0
- package/skills/better-supabase-testing/SKILL.md +65 -0
- package/index.js +0 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
//#region src/generators/shared.ts
|
|
2
|
+
const INTEGER = /* @__PURE__ */ new Set([
|
|
3
|
+
"int2",
|
|
4
|
+
"int4",
|
|
5
|
+
"int8"
|
|
6
|
+
]);
|
|
7
|
+
const NUMBER = /* @__PURE__ */ new Set([
|
|
8
|
+
"float4",
|
|
9
|
+
"float8",
|
|
10
|
+
"numeric",
|
|
11
|
+
"oid",
|
|
12
|
+
"money"
|
|
13
|
+
]);
|
|
14
|
+
const DATETIME = /* @__PURE__ */ new Set(["timestamptz"]);
|
|
15
|
+
function scalarKind(column) {
|
|
16
|
+
if (column.enum) return {
|
|
17
|
+
kind: "enum",
|
|
18
|
+
values: column.enum
|
|
19
|
+
};
|
|
20
|
+
if (column.json) return { kind: "json" };
|
|
21
|
+
if (column.codec === "date") return { kind: "dateObject" };
|
|
22
|
+
if (column.codec === "bigint") return { kind: "bigint" };
|
|
23
|
+
if (column.codec === "string") return { kind: "string" };
|
|
24
|
+
if (column.type === "uuid") return { kind: "uuid" };
|
|
25
|
+
if (INTEGER.has(column.type)) return { kind: "integer" };
|
|
26
|
+
if (NUMBER.has(column.type)) return { kind: "number" };
|
|
27
|
+
if (column.type === "bool") return { kind: "boolean" };
|
|
28
|
+
if (DATETIME.has(column.type)) return { kind: "datetime" };
|
|
29
|
+
if (column.type === "date") return { kind: "date" };
|
|
30
|
+
if (column.type === "record" || column.type === "void") return { kind: "unknown" };
|
|
31
|
+
return { kind: "string" };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The generated row type of a field (its element type for arrays). Storage
|
|
35
|
+
* path columns validate as strings and are cast to their `StoragePath` brand.
|
|
36
|
+
*/
|
|
37
|
+
function rowType(table, plan) {
|
|
38
|
+
const type = `NonNullable<RowOf<'${table}'>['${plan.name}']>`;
|
|
39
|
+
return plan.column.array ? `${type}[number]` : type;
|
|
40
|
+
}
|
|
41
|
+
/** The fields a variant accepts; read-only columns are left out of writes. */
|
|
42
|
+
function fieldsFor(table, variant, input) {
|
|
43
|
+
const fields = [];
|
|
44
|
+
for (const [name, column] of Object.entries(table.columns)) {
|
|
45
|
+
if (variant === "Insert" && (column.generated || column.insertable === false)) continue;
|
|
46
|
+
if (variant === "Update" && (column.generated || column.updatable === false)) continue;
|
|
47
|
+
fields.push({
|
|
48
|
+
name,
|
|
49
|
+
column,
|
|
50
|
+
scalar: scalarKind(column),
|
|
51
|
+
nullable: column.nullable,
|
|
52
|
+
optional: variant === "Update" || variant === "Insert" && (column.nullable || column.hasDefault),
|
|
53
|
+
customJson: Boolean(column.json && input.config.json[`${table.name}.${column.db}`])
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return fields;
|
|
57
|
+
}
|
|
58
|
+
function variantsFor(table) {
|
|
59
|
+
return table.kind === "view" ? ["Row"] : [
|
|
60
|
+
"Row",
|
|
61
|
+
"Insert",
|
|
62
|
+
"Update"
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
function tableEntries(input) {
|
|
66
|
+
return Object.entries(input.meta.tables).sort(([a], [b]) => a.localeCompare(b));
|
|
67
|
+
}
|
|
68
|
+
function propertyKey(name) {
|
|
69
|
+
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
|
|
70
|
+
}
|
|
71
|
+
/** `customerTags` + `Insert` → `customerTagsInsert`. */
|
|
72
|
+
function schemaName(table, variant) {
|
|
73
|
+
return `${table.replace(/^[A-Z]/, (c) => c.toLowerCase())}${variant}`;
|
|
74
|
+
}
|
|
75
|
+
/** Output path next to the main module: `generated.ts` → `generated.zod.ts`. */
|
|
76
|
+
function siblingPath(output, suffix) {
|
|
77
|
+
return output.replace(/(\.[cm]?ts)?$/, `.${suffix}`);
|
|
78
|
+
}
|
|
79
|
+
const HEADER = "// Generated by better-supabase. Do not edit; run `better-supabase gen`.\n";
|
|
80
|
+
/** `./metadata.ts#metadataSchema` → `{ from: './metadata.ts', name: 'metadataSchema' }`. */
|
|
81
|
+
function parseImport(spec) {
|
|
82
|
+
const [from, name] = spec.split("#");
|
|
83
|
+
if (!from || !name) throw new Error(`Expected "path#export", got "${spec}"`);
|
|
84
|
+
return {
|
|
85
|
+
from,
|
|
86
|
+
name
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/generators/json-schema.ts
|
|
91
|
+
function scalar(kind) {
|
|
92
|
+
switch (kind.kind) {
|
|
93
|
+
case "uuid": return {
|
|
94
|
+
type: "string",
|
|
95
|
+
format: "uuid"
|
|
96
|
+
};
|
|
97
|
+
case "string": return { type: "string" };
|
|
98
|
+
case "integer": return { type: "integer" };
|
|
99
|
+
case "number": return { type: "number" };
|
|
100
|
+
case "boolean": return { type: "boolean" };
|
|
101
|
+
case "datetime": return {
|
|
102
|
+
type: "string",
|
|
103
|
+
format: "date-time"
|
|
104
|
+
};
|
|
105
|
+
case "date": return {
|
|
106
|
+
type: "string",
|
|
107
|
+
format: "date"
|
|
108
|
+
};
|
|
109
|
+
case "dateObject": return {
|
|
110
|
+
type: "string",
|
|
111
|
+
format: "date-time"
|
|
112
|
+
};
|
|
113
|
+
case "bigint": return {
|
|
114
|
+
type: "string",
|
|
115
|
+
pattern: "^-?\\d+$"
|
|
116
|
+
};
|
|
117
|
+
case "json":
|
|
118
|
+
case "unknown": return {};
|
|
119
|
+
case "enum": return {
|
|
120
|
+
type: "string",
|
|
121
|
+
enum: [...kind.values]
|
|
122
|
+
};
|
|
123
|
+
default: return kind;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function property(plan) {
|
|
127
|
+
let schema = scalar(plan.scalar);
|
|
128
|
+
if (plan.column.array) schema = {
|
|
129
|
+
type: "array",
|
|
130
|
+
items: schema
|
|
131
|
+
};
|
|
132
|
+
if (!plan.nullable) return schema;
|
|
133
|
+
if (Object.keys(schema).length === 0) return schema;
|
|
134
|
+
const { enum: values, ...rest } = schema;
|
|
135
|
+
if (Array.isArray(values)) return {
|
|
136
|
+
...rest,
|
|
137
|
+
type: ["string", "null"],
|
|
138
|
+
enum: [...values, null]
|
|
139
|
+
};
|
|
140
|
+
return typeof schema["type"] === "string" ? {
|
|
141
|
+
...schema,
|
|
142
|
+
type: [schema["type"], "null"]
|
|
143
|
+
} : { anyOf: [schema, { type: "null" }] };
|
|
144
|
+
}
|
|
145
|
+
/** JSON Schema (2020-12) `$defs` for every table: `customersRow`, `customersInsert`, ... */
|
|
146
|
+
function buildJsonSchema(input, id) {
|
|
147
|
+
const defs = {};
|
|
148
|
+
for (const [key, table] of tableEntries(input)) for (const variant of variantsFor(table)) {
|
|
149
|
+
const properties = {};
|
|
150
|
+
const required = [];
|
|
151
|
+
for (const plan of fieldsFor(table, variant, input)) {
|
|
152
|
+
properties[plan.name] = property(plan);
|
|
153
|
+
if (!plan.optional) required.push(plan.name);
|
|
154
|
+
}
|
|
155
|
+
defs[`${key}${variant}`] = {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties,
|
|
158
|
+
...required.length > 0 ? { required } : {},
|
|
159
|
+
additionalProperties: false
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
164
|
+
...id ? { $id: id } : {},
|
|
165
|
+
$defs: defs
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/** Writes the JSON Schema document; `createOpenApi` reuses the same `$defs`. */
|
|
169
|
+
function jsonSchema(options = {}) {
|
|
170
|
+
return {
|
|
171
|
+
name: "json-schema",
|
|
172
|
+
generate(input) {
|
|
173
|
+
return [{
|
|
174
|
+
path: options.output ?? siblingPath(input.config.output, "schema.json"),
|
|
175
|
+
contents: `${JSON.stringify(buildJsonSchema(input, options.id), null, 2)}\n`
|
|
176
|
+
}];
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
|
181
|
+
export { parseImport as a, schemaName as c, variantsFor as d, fieldsFor as i, siblingPath as l, jsonSchema as n, propertyKey as o, HEADER as r, rowType as s, buildJsonSchema as t, tableEntries as u };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//#region src/lint/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Static checks for better-supabase calls, written against the ESLint rule
|
|
4
|
+
* API so it loads in ESLint (flat config) and in oxlint's `jsPlugins`:
|
|
5
|
+
*
|
|
6
|
+
* ```js
|
|
7
|
+
* import betterSupabase from 'better-supabase/lint';
|
|
8
|
+
* export default [betterSupabase.configs.recommended];
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Rules match calls by method name (`.findMany(...)`, `.deleteMany(...)`)
|
|
12
|
+
* with an inline object literal; spread or variable arguments are skipped
|
|
13
|
+
* rather than guessed at. The runtime `rules()` plugin covers the rest.
|
|
14
|
+
*/
|
|
15
|
+
interface Node {
|
|
16
|
+
readonly type: string;
|
|
17
|
+
}
|
|
18
|
+
export interface CallExpression extends Node {
|
|
19
|
+
readonly type: "CallExpression";
|
|
20
|
+
readonly callee: Node;
|
|
21
|
+
readonly arguments: readonly Node[];
|
|
22
|
+
}
|
|
23
|
+
export interface RuleContext {
|
|
24
|
+
readonly options: readonly unknown[];
|
|
25
|
+
report(descriptor: {
|
|
26
|
+
readonly node: Node;
|
|
27
|
+
readonly messageId: string;
|
|
28
|
+
readonly data?: Readonly<Record<string, string>>;
|
|
29
|
+
}): void;
|
|
30
|
+
}
|
|
31
|
+
export interface RuleModule {
|
|
32
|
+
readonly meta: {
|
|
33
|
+
readonly type: "problem" | "suggestion";
|
|
34
|
+
readonly docs: {
|
|
35
|
+
readonly description: string;
|
|
36
|
+
readonly url: string;
|
|
37
|
+
};
|
|
38
|
+
readonly messages: Readonly<Record<string, string>>;
|
|
39
|
+
readonly schema: readonly unknown[];
|
|
40
|
+
};
|
|
41
|
+
create(context: RuleContext): {
|
|
42
|
+
CallExpression(node: CallExpression): void;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export type RuleId = "no-unbounded-find-many" | "no-delete-many-without-where" | "max-limit" | "require-order-by" | "unbounded-read";
|
|
46
|
+
/** The part of `supabase/snapshot.json` that `largeTables` reads. */
|
|
47
|
+
export interface LintSnapshot {
|
|
48
|
+
readonly extras: {
|
|
49
|
+
readonly tables: readonly {
|
|
50
|
+
readonly schema: string;
|
|
51
|
+
readonly name: string;
|
|
52
|
+
readonly large?: boolean;
|
|
53
|
+
}[];
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The tables `gen` marked as large (an estimated 10,000 rows or more), as
|
|
58
|
+
* `schema.name`, for the `unbounded-read` rule's `tables` option.
|
|
59
|
+
*/
|
|
60
|
+
export declare function largeTables(snapshot: LintSnapshot): string[];
|
|
61
|
+
export declare const rules: Readonly<Record<RuleId, RuleModule>>;
|
|
62
|
+
export declare const plugin: {
|
|
63
|
+
readonly meta: {
|
|
64
|
+
readonly name: string;
|
|
65
|
+
};
|
|
66
|
+
readonly rules: Readonly<Record<RuleId, RuleModule>>;
|
|
67
|
+
readonly configs: {
|
|
68
|
+
readonly recommended: {
|
|
69
|
+
readonly plugins: Record<string, unknown>;
|
|
70
|
+
readonly rules: Readonly<Partial<Record<`better-supabase/${RuleId}`, "warn" | "error">>>;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
75
|
+
export { plugin as default };
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
//#region src/lint/index.ts
|
|
2
|
+
const DOCS = "https://bettersupabase.com/docs/plugins/lint";
|
|
3
|
+
function methodName(node) {
|
|
4
|
+
const callee = node.callee;
|
|
5
|
+
if (callee.type !== "MemberExpression") return void 0;
|
|
6
|
+
const member = callee;
|
|
7
|
+
if (member.computed || member.property.type !== "Identifier") return void 0;
|
|
8
|
+
return member.property.name;
|
|
9
|
+
}
|
|
10
|
+
/** `users` in `db.users.findMany(...)`. */
|
|
11
|
+
function receiverName(node) {
|
|
12
|
+
const callee = node.callee;
|
|
13
|
+
if (callee.object.type !== "MemberExpression") return void 0;
|
|
14
|
+
const receiver = callee.object;
|
|
15
|
+
if (receiver.computed || receiver.property.type !== "Identifier") return void 0;
|
|
16
|
+
return receiver.property.name;
|
|
17
|
+
}
|
|
18
|
+
/** Matches `order_items`, `orderItems` and `public.order_items` alike. */
|
|
19
|
+
const tableKey = (name) => (name.split(".").at(-1) ?? name).replace(/_/g, "").toLowerCase();
|
|
20
|
+
function keyName(property) {
|
|
21
|
+
if (property.computed) return void 0;
|
|
22
|
+
if (property.key.type === "Identifier") return property.key.name;
|
|
23
|
+
if (property.key.type === "Literal") {
|
|
24
|
+
const value = property.key.value;
|
|
25
|
+
return typeof value === "string" ? value : void 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The call's first argument as a map of literal keys, `{}` when the call has
|
|
30
|
+
* no arguments, or `undefined` when it cannot be read statically.
|
|
31
|
+
*/
|
|
32
|
+
function argsOf(node) {
|
|
33
|
+
const [first] = node.arguments;
|
|
34
|
+
if (first === void 0) return /* @__PURE__ */ new Map();
|
|
35
|
+
if (first.type !== "ObjectExpression") return void 0;
|
|
36
|
+
const keys = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const property of first.properties) {
|
|
38
|
+
if (property.type !== "Property") return void 0;
|
|
39
|
+
const name = keyName(property);
|
|
40
|
+
if (name === void 0) return void 0;
|
|
41
|
+
keys.set(name, property.value);
|
|
42
|
+
}
|
|
43
|
+
return keys;
|
|
44
|
+
}
|
|
45
|
+
function numberOf(node) {
|
|
46
|
+
if (node?.type !== "Literal") return void 0;
|
|
47
|
+
const value = node.value;
|
|
48
|
+
return typeof value === "number" ? value : void 0;
|
|
49
|
+
}
|
|
50
|
+
function rule(description, name, messages, create, schema = []) {
|
|
51
|
+
return {
|
|
52
|
+
meta: {
|
|
53
|
+
type: "problem",
|
|
54
|
+
docs: {
|
|
55
|
+
description,
|
|
56
|
+
url: `${DOCS}#${name}`
|
|
57
|
+
},
|
|
58
|
+
messages,
|
|
59
|
+
schema
|
|
60
|
+
},
|
|
61
|
+
create
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const FIND_METHODS = /* @__PURE__ */ new Set([
|
|
65
|
+
"findMany",
|
|
66
|
+
"findFirst",
|
|
67
|
+
"paginate"
|
|
68
|
+
]);
|
|
69
|
+
/**
|
|
70
|
+
* The tables `gen` marked as large (an estimated 10,000 rows or more), as
|
|
71
|
+
* `schema.name`, for the `unbounded-read` rule's `tables` option.
|
|
72
|
+
*/
|
|
73
|
+
function largeTables(snapshot) {
|
|
74
|
+
return snapshot.extras.tables.filter((table) => table.large === true).map((table) => `${table.schema}.${table.name}`);
|
|
75
|
+
}
|
|
76
|
+
const rules = {
|
|
77
|
+
"no-unbounded-find-many": rule("Require a limit on findMany", "no-unbounded-find-many", { unbounded: "findMany without limit reads every visible row." }, (context) => ({ CallExpression(node) {
|
|
78
|
+
if (methodName(node) !== "findMany") return;
|
|
79
|
+
const args = argsOf(node);
|
|
80
|
+
if (args && !args.has("limit")) context.report({
|
|
81
|
+
node,
|
|
82
|
+
messageId: "unbounded"
|
|
83
|
+
});
|
|
84
|
+
} })),
|
|
85
|
+
"no-delete-many-without-where": rule("Require where on deleteMany", "no-delete-many-without-where", { missing: "deleteMany without where removes every visible row." }, (context) => ({ CallExpression(node) {
|
|
86
|
+
if (methodName(node) !== "deleteMany") return;
|
|
87
|
+
const args = argsOf(node);
|
|
88
|
+
if (args && !args.has("where")) context.report({
|
|
89
|
+
node,
|
|
90
|
+
messageId: "missing"
|
|
91
|
+
});
|
|
92
|
+
} })),
|
|
93
|
+
"max-limit": rule("Cap literal limits", "max-limit", { tooLarge: "limit {{limit}} is above the maximum of {{max}}." }, (context) => {
|
|
94
|
+
const max = context.options[0]?.max ?? 1e3;
|
|
95
|
+
return { CallExpression(node) {
|
|
96
|
+
const method = methodName(node);
|
|
97
|
+
if (method === void 0 || !FIND_METHODS.has(method)) return;
|
|
98
|
+
const limit = numberOf(argsOf(node)?.get("limit"));
|
|
99
|
+
if (limit !== void 0 && limit > max) context.report({
|
|
100
|
+
node,
|
|
101
|
+
messageId: "tooLarge",
|
|
102
|
+
data: {
|
|
103
|
+
limit: String(limit),
|
|
104
|
+
max: String(max)
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
} };
|
|
108
|
+
}, [{
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: { max: {
|
|
111
|
+
type: "integer",
|
|
112
|
+
minimum: 1
|
|
113
|
+
} },
|
|
114
|
+
additionalProperties: false
|
|
115
|
+
}]),
|
|
116
|
+
"require-order-by": rule("Require orderBy when paging with offset", "require-order-by", { unordered: "offset without orderBy returns rows in no stable order." }, (context) => ({ CallExpression(node) {
|
|
117
|
+
if (methodName(node) !== "findMany") return;
|
|
118
|
+
const args = argsOf(node);
|
|
119
|
+
if (args?.has("offset") && !args.has("orderBy")) context.report({
|
|
120
|
+
node,
|
|
121
|
+
messageId: "unordered"
|
|
122
|
+
});
|
|
123
|
+
} })),
|
|
124
|
+
"unbounded-read": rule("Require limit or paginate on large tables", "unbounded-read", { unbounded: "findMany on {{table}} without limit returns at most db-max-rows rows and drops the rest; add limit or use paginate()." }, (context) => {
|
|
125
|
+
const option = context.options[0];
|
|
126
|
+
const large = new Set((option?.tables ?? []).map(tableKey));
|
|
127
|
+
return { CallExpression(node) {
|
|
128
|
+
if (methodName(node) !== "findMany") return;
|
|
129
|
+
const table = receiverName(node);
|
|
130
|
+
if (table === void 0) return;
|
|
131
|
+
if (!option?.strict && !large.has(tableKey(table))) return;
|
|
132
|
+
const args = argsOf(node);
|
|
133
|
+
if (args && !args.has("limit")) context.report({
|
|
134
|
+
node,
|
|
135
|
+
messageId: "unbounded",
|
|
136
|
+
data: { table }
|
|
137
|
+
});
|
|
138
|
+
} };
|
|
139
|
+
}, [{
|
|
140
|
+
type: "object",
|
|
141
|
+
properties: {
|
|
142
|
+
tables: {
|
|
143
|
+
type: "array",
|
|
144
|
+
items: { type: "string" }
|
|
145
|
+
},
|
|
146
|
+
strict: { type: "boolean" }
|
|
147
|
+
},
|
|
148
|
+
additionalProperties: false
|
|
149
|
+
}])
|
|
150
|
+
};
|
|
151
|
+
const plugin = {
|
|
152
|
+
meta: { name: "better-supabase" },
|
|
153
|
+
rules,
|
|
154
|
+
configs: { recommended: {
|
|
155
|
+
plugins: {},
|
|
156
|
+
rules: {
|
|
157
|
+
"better-supabase/no-delete-many-without-where": "error",
|
|
158
|
+
"better-supabase/no-unbounded-find-many": "warn",
|
|
159
|
+
"better-supabase/max-limit": "warn",
|
|
160
|
+
"better-supabase/require-order-by": "warn",
|
|
161
|
+
"better-supabase/unbounded-read": "warn"
|
|
162
|
+
}
|
|
163
|
+
} }
|
|
164
|
+
};
|
|
165
|
+
plugin.configs.recommended.plugins["better-supabase"] = plugin;
|
|
166
|
+
//#endregion
|
|
167
|
+
export { plugin as default, plugin, largeTables, rules };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as ListPage, c as ListQuery, d as ListSearchParams, f as OpenApiParameter, h as defineListQuery, i as ListExtra, l as ListQueryConfig, m as UNSET, n as FacetInfo, o as ListParseResult, p as ParserSpec, r as ListDefinition, s as ListParsers, t as FacetCounts, u as ListQueryInput } from "../index-JXHBuS53.js";
|
|
2
|
+
export { FacetCounts, FacetInfo, ListDefinition, ListExtra, ListPage, ListParseResult, ListParsers, ListQuery, ListQueryConfig, ListQueryInput, ListSearchParams, OpenApiParameter, ParserSpec, UNSET, defineListQuery };
|