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 CHANGED
@@ -42,24 +42,23 @@ export default [
42
42
  overrides: {
43
43
  types: {
44
44
  // Map PostgreSQL types to Slonik token types
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",
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: "sql.+(type\\(*\\)|typeAlias\\(*\\)|unsafe)",
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: "DateSqlToken",
135
- timestamp: "TimestampSqlToken",
133
+ date: 'DateSqlToken',
134
+ timestamp: 'TimestampSqlToken',
136
135
  timestamptz: "TimestampSqlToken",
137
- interval: "IntervalSqlToken",
136
+ interval: 'IntervalSqlToken',
138
137
 
139
138
  // JSON types
140
- json: "JsonSqlToken",
141
- jsonb: "JsonBinarySqlToken",
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/db/sql.ts
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: "DateSqlToken",
213
- timestamp: "TimestampSqlToken",
214
- json: "JsonSqlToken",
215
- jsonb: "JsonBinarySqlToken",
216
- uuid: "UuidSqlToken",
217
- "int4[]": 'ArraySqlToken<"int4">',
218
- "text[]": 'ArraySqlToken<"text">',
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: "sql.+(type\\(*\\)|typeAlias\\(*\\)|unsafe)",
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 "./db/sql";
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 distDir = node_url.fileURLToPath(new URL("../../dist", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
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(path__default.join(distDir, `./workers/${params.name}.worker.mjs`), {
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) {