eslint-plugin-slonik 1.6.0 → 1.7.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/README.md +39 -31
- package/dist/index.cjs +12 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -9
- package/dist/index.d.mts +1 -9
- package/dist/index.d.ts +1 -9
- package/dist/index.mjs +12 -11
- package/dist/index.mjs.map +1 -1
- package/dist/shared/{eslint-plugin-slonik.uSZXmGoY.d.cts → eslint-plugin-slonik.ZtKj5lnb.d.cts} +0 -14
- package/dist/shared/{eslint-plugin-slonik.uSZXmGoY.d.mts → eslint-plugin-slonik.ZtKj5lnb.d.mts} +0 -14
- package/dist/shared/{eslint-plugin-slonik.uSZXmGoY.d.ts → eslint-plugin-slonik.ZtKj5lnb.d.ts} +0 -14
- package/dist/workers/check-sql.worker.cjs +133 -37
- package/dist/workers/check-sql.worker.cjs.map +1 -1
- package/dist/workers/check-sql.worker.d.cts +11 -4
- package/dist/workers/check-sql.worker.d.mts +11 -4
- package/dist/workers/check-sql.worker.d.ts +11 -4
- package/dist/workers/check-sql.worker.mjs +132 -37
- package/dist/workers/check-sql.worker.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,24 +42,23 @@ export default [
|
|
|
42
42
|
overrides: {
|
|
43
43
|
types: {
|
|
44
44
|
// Map PostgreSQL types to Slonik token types
|
|
45
|
-
date:
|
|
46
|
-
timestamp:
|
|
47
|
-
interval:
|
|
48
|
-
json:
|
|
49
|
-
jsonb:
|
|
50
|
-
uuid:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
date: 'DateSqlToken',
|
|
46
|
+
timestamp: 'TimestampSqlToken',
|
|
47
|
+
interval: 'IntervalSqlToken',
|
|
48
|
+
json: 'JsonSqlToken',
|
|
49
|
+
jsonb: 'JsonBinarySqlToken',
|
|
50
|
+
uuid: 'UuidSqlToken',
|
|
51
|
+
'int4[]': 'ArraySqlToken<"int4">',
|
|
52
|
+
'text[]': 'ArraySqlToken<"text">',
|
|
53
|
+
'uuid[]': 'ArraySqlToken<"uuid">',
|
|
54
|
+
'numeric[]': 'ArraySqlToken<"numeric">',
|
|
55
|
+
'real[]': 'VectorSqlToken',
|
|
56
56
|
},
|
|
57
57
|
},
|
|
58
58
|
targets: [
|
|
59
59
|
{
|
|
60
60
|
// Match Slonik's typed query methods
|
|
61
|
-
tag:
|
|
62
|
-
skipTypeAnnotations: true,
|
|
61
|
+
tag: 'sql.+(type\\(*\\)|typeAlias\\(*\\)|unsafe)',
|
|
63
62
|
},
|
|
64
63
|
],
|
|
65
64
|
}),
|
|
@@ -131,14 +130,14 @@ When using Slonik, you'll want to map PostgreSQL types to Slonik's token types:
|
|
|
131
130
|
overrides: {
|
|
132
131
|
types: {
|
|
133
132
|
// Date/Time types
|
|
134
|
-
date:
|
|
135
|
-
timestamp:
|
|
133
|
+
date: 'DateSqlToken',
|
|
134
|
+
timestamp: 'TimestampSqlToken',
|
|
136
135
|
timestamptz: "TimestampSqlToken",
|
|
137
|
-
interval:
|
|
136
|
+
interval: 'IntervalSqlToken',
|
|
138
137
|
|
|
139
138
|
// JSON types
|
|
140
|
-
json:
|
|
141
|
-
jsonb:
|
|
139
|
+
json: 'JsonSqlToken',
|
|
140
|
+
jsonb: 'JsonBinarySqlToken',
|
|
142
141
|
|
|
143
142
|
// UUID
|
|
144
143
|
uuid: "UuidSqlToken",
|
|
@@ -167,7 +166,6 @@ targets: [
|
|
|
167
166
|
{
|
|
168
167
|
// Matches: sql.type(...)``, sql.typeAlias(...)``, sql.unsafe``
|
|
169
168
|
tag: "sql.+(type\\(*\\)|typeAlias\\(*\\)|unsafe)",
|
|
170
|
-
skipTypeAnnotations: true,
|
|
171
169
|
},
|
|
172
170
|
]
|
|
173
171
|
```
|
|
@@ -184,7 +182,7 @@ pnpm add -D eslint-plugin-slonik libpg-query
|
|
|
184
182
|
### 2. Create your SQL tag with type aliases
|
|
185
183
|
|
|
186
184
|
```ts
|
|
187
|
-
// src/
|
|
185
|
+
// src/slonik.ts
|
|
188
186
|
import { createSqlTag } from "slonik";
|
|
189
187
|
import { z } from "zod";
|
|
190
188
|
|
|
@@ -209,19 +207,18 @@ export default tseslint.config(
|
|
|
209
207
|
databaseUrl: process.env.DATABASE_URL,
|
|
210
208
|
overrides: {
|
|
211
209
|
types: {
|
|
212
|
-
date:
|
|
213
|
-
timestamp:
|
|
214
|
-
json:
|
|
215
|
-
jsonb:
|
|
216
|
-
uuid:
|
|
217
|
-
|
|
218
|
-
|
|
210
|
+
date: 'DateSqlToken',
|
|
211
|
+
timestamp: 'TimestampSqlToken',
|
|
212
|
+
json: 'JsonSqlToken',
|
|
213
|
+
jsonb: 'JsonBinarySqlToken',
|
|
214
|
+
uuid: 'UuidSqlToken',
|
|
215
|
+
'int4[]': 'ArraySqlToken<"int4">',
|
|
216
|
+
'text[]': 'ArraySqlToken<"text">',
|
|
219
217
|
},
|
|
220
218
|
},
|
|
221
219
|
targets: [
|
|
222
220
|
{
|
|
223
|
-
tag:
|
|
224
|
-
skipTypeAnnotations: true,
|
|
221
|
+
tag: 'sql.+(type\\(*\\)|typeAlias\\(*\\)|unsafe)',
|
|
225
222
|
},
|
|
226
223
|
],
|
|
227
224
|
})
|
|
@@ -231,8 +228,7 @@ export default tseslint.config(
|
|
|
231
228
|
### 4. Write validated queries
|
|
232
229
|
|
|
233
230
|
```ts
|
|
234
|
-
import { sql } from "./
|
|
235
|
-
import { pool } from "./db/pool";
|
|
231
|
+
import { pool, sql } from "./slonik";
|
|
236
232
|
|
|
237
233
|
// ✅ Valid - query matches schema
|
|
238
234
|
const users = await pool.many(
|
|
@@ -274,6 +270,18 @@ This plugin is specifically designed for Slonik and includes:
|
|
|
274
270
|
4. **Identifier support** — Converts `sql.identifier()` to quoted identifiers
|
|
275
271
|
5. **Graceful degradation** — Skips validation for runtime-dependent constructs instead of erroring
|
|
276
272
|
|
|
273
|
+
## How It Works
|
|
274
|
+
|
|
275
|
+
ESLint rules must be synchronous, but SQL validation requires async operations like database connections. This plugin solves this using [`synckit`](https://github.com/un-ts/synckit), which enables synchronous calls to async worker threads.
|
|
276
|
+
|
|
277
|
+
The architecture:
|
|
278
|
+
|
|
279
|
+
1. **Worker Thread** — Runs all async operations (database connections, migrations, type generation) in a separate thread
|
|
280
|
+
2. **Synchronous Bridge** — Uses `synckit` to block the main thread until the worker completes, making async operations appear synchronous to ESLint
|
|
281
|
+
3. **Connection Pooling** — Reuses database connections across lint runs for performance
|
|
282
|
+
|
|
283
|
+
Under the hood, `synckit` uses Node.js Worker Threads with `Atomics.wait()` to block the main thread until the worker signals completion via `Atomics.notify()`.
|
|
284
|
+
|
|
277
285
|
## Development
|
|
278
286
|
|
|
279
287
|
### Prerequisites
|
package/dist/index.cjs
CHANGED
|
@@ -1072,9 +1072,16 @@ function checkType(params) {
|
|
|
1072
1072
|
`);
|
|
1073
1073
|
}
|
|
1074
1074
|
|
|
1075
|
-
const
|
|
1075
|
+
const currentDir = node_url.fileURLToPath(new URL(".", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
|
|
1076
|
+
const isSourceDir = currentDir.includes(path__default.sep + "src" + path__default.sep);
|
|
1077
|
+
function getWorkerPath(name) {
|
|
1078
|
+
if (isSourceDir) {
|
|
1079
|
+
return path__default.join(currentDir, "../../dist/workers", `${name}.worker.mjs`);
|
|
1080
|
+
}
|
|
1081
|
+
return path__default.join(currentDir, "workers", `${name}.worker.mjs`);
|
|
1082
|
+
}
|
|
1076
1083
|
function defineWorker(params) {
|
|
1077
|
-
return synckit.createSyncFn(
|
|
1084
|
+
return synckit.createSyncFn(getWorkerPath(params.name), {
|
|
1078
1085
|
tsRunner: "tsx",
|
|
1079
1086
|
timeout: params.timeout
|
|
1080
1087
|
});
|
|
@@ -1101,11 +1108,7 @@ const zBaseTarget = z__default.object({
|
|
|
1101
1108
|
* - `"pascal"` - `user_id` → `UserId`
|
|
1102
1109
|
* - `"screaming snake"` - `user_id` → `USER_ID`
|
|
1103
1110
|
*/
|
|
1104
|
-
fieldTransform: z__default.enum(["snake", "pascal", "camel", "screaming snake"]).optional()
|
|
1105
|
-
/**
|
|
1106
|
-
* Whether or not to skip type annotation.
|
|
1107
|
-
*/
|
|
1108
|
-
skipTypeAnnotations: z__default.boolean().optional()
|
|
1111
|
+
fieldTransform: z__default.enum(["snake", "pascal", "camel", "screaming snake"]).optional()
|
|
1109
1112
|
});
|
|
1110
1113
|
const zWrapperTarget = z__default.object({ wrapper: zStringOrRegex, maxDepth: z__default.number().optional() }).merge(zBaseTarget);
|
|
1111
1114
|
const zTagTarget = z__default.object({ tag: zStringOrRegex }).merge(zBaseTarget);
|
|
@@ -1302,16 +1305,14 @@ function reportCheck(params) {
|
|
|
1302
1305
|
If you believe this query should be supported, please open an issue at https://github.com/gajus/eslint-plugin-slonik/issues`
|
|
1303
1306
|
);
|
|
1304
1307
|
return;
|
|
1308
|
+
}).with({ _tag: "ConnectionFailedError" }, () => {
|
|
1309
|
+
return;
|
|
1305
1310
|
}).exhaustive();
|
|
1306
1311
|
},
|
|
1307
1312
|
({ result, checker, parser }) => {
|
|
1308
1313
|
if (result === null) {
|
|
1309
1314
|
return;
|
|
1310
1315
|
}
|
|
1311
|
-
const shouldSkipTypeAnnotations = target.skipTypeAnnotations === true;
|
|
1312
|
-
if (shouldSkipTypeAnnotations) {
|
|
1313
|
-
return;
|
|
1314
|
-
}
|
|
1315
1316
|
const isMissingTypeAnnotations = typeParameter === void 0;
|
|
1316
1317
|
if (isMissingTypeAnnotations) {
|
|
1317
1318
|
if (result.output === null) {
|