Skip to content

feat(plugin-drizzle): add Drizzle plugin#492

Draft
Newbie012 wants to merge 1 commit into
mainfrom
feat/plugin-drizzle
Draft

feat(plugin-drizzle): add Drizzle plugin#492
Newbie012 wants to merge 1 commit into
mainfrom
feat/plugin-drizzle

Conversation

@Newbie012

@Newbie012 Newbie012 commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Draft: held until full Drizzle support is in place — not release-ready yet.

Stacked on #491.

Adds the Drizzle plugin (@ts-safeql/plugin-drizzle):

  • Validates full raw sql queries and raw-sql fragments embedded in Drizzle queries, compiling via Drizzle's own .toSQL().
  • Includes docs/plugins/extending-to-other-orms.md documenting how the plugin API generalizes (Kysely + Drizzle as worked examples).

Lockfile/root devDep updated for drizzle-orm.

@vercel

vercel Bot commented Jun 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
safeql Ready Ready Preview, Comment Jun 21, 2026 8:41am

@changeset-bot

changeset-bot Bot commented Jun 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a1eb125

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@ts-safeql/plugin-drizzle Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 810e09af-6b95-4e19-92dc-9537b9ff5e64

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/plugin-drizzle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds @ts-safeql/plugin-drizzle, a new plugin that validates Drizzle's sql template tag and its statically resolvable helpers (sql.raw, sql.identifier, sql.placeholder, nested fragments) using the same onTarget/onExpression plugin hooks introduced by the Kysely plugin. It also ships a documentation page explaining how the plugin API generalises across query libraries.

  • onTarget classifies each sql tagged-template as a standalone query (validate) or a fragment (skip) by walking the AST parent chain against curated querySinkMethods and fragmentMethods sets.
  • onExpression handles each interpolation statically: inlining sql.raw literals, quoting sql.identifier strings, emitting $N for primitives and sql.placeholder, and recursing into nested sql fragments; non-primitive interpolations (column/table objects) return false to skip the query rather than guess.
  • One gap identified: mapWith is documented in a comment as a fragment-marking method but is absent from fragmentMethods, causing sql\\count(*)\.mapWith(Number) expressions to be misclassified as standalone queries.

Confidence Score: 3/5

The plugin is functionally sound for the common cases tested, but mapWith — a frequently used Drizzle column-selection helper — is omitted from the fragment guard set, so any code using sql\...`.mapWith(mapper)` will be incorrectly treated as a standalone query and trigger false-positive lint errors.

The mapWith omission is directly documented in a comment inside isStandaloneQuery but not reflected in fragmentMethods. Because .mapWith() is a common Drizzle pattern for typed column projections, this omission would surface lint errors for real user code the moment the plugin is adopted. The rest of the plugin logic — onTarget, onExpression, helper dispatch, nested-fragment recursion, primitive-type guard — is well-structured and the test suite covers the documented cases cleanly.

packages/plugins/drizzle/src/plugin.ts (the fragmentMethods set and isStandaloneQuery) and packages/plugins/drizzle/src/plugin.test.ts (missing .mapWith() test case).

Important Files Changed

Filename Overview
packages/plugins/drizzle/src/plugin.ts Core Drizzle plugin logic; mapWith is referenced in a comment as a fragment sentinel but is absent from fragmentMethods, causing .mapWith() expressions to be incorrectly treated as standalone queries.
packages/plugins/drizzle/src/plugin.test.ts Good coverage of onTarget and onExpression paths; missing a .mapWith() fragment test case that would have caught the fragmentMethods gap.
packages/plugins/drizzle/package.json Package metadata is well-formed with correct peer dependency range (>=0.30.0) and dual CJS/ESM exports.
packages/plugins/drizzle/build.config.ts Standard unbuild config matching the Kysely plugin pattern; externals list is complete.
docs/plugins/extending-to-other-orms.md Clear, accurate documentation mapping plugin hooks to library patterns; no issues.
.changeset/drizzle-support.md Accurate changeset entry for the new @ts-safeql/plugin-drizzle minor version.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[ESLint visits TaggedTemplateExpression] --> B{onTarget}
    B -- "not drizzle-orm sql" --> C[undefined: ignore]
    B -- "drizzle sql found" --> D{isStandaloneQuery?}
    D -- "nested / .where / .having / .as / fragment method" --> E[false: skip]
    D -- "db.execute / db.all / ..." --> F[TargetMatch: validate]
    D -- "top-level / unknown parent" --> F
    F --> G[SafeQL core builds SQL text spanning each interpolation]
    G --> H{onExpression per span}
    H -- "nested sql fragment" --> I[buildTemplateSQL recursion]
    H -- "sql.raw static" --> J[inline literal]
    H -- "sql.identifier static" --> K[quoted identifier]
    H -- "sql.placeholder" --> L[$N]
    H -- "primitive type" --> L
    H -- "non-primitive / unresolved" --> M[false: skipped]
    I --> N{all spans resolved?}
    N -- yes --> O[spliced SQL text]
    N -- no --> M
    J & K & L & O --> P[assembled SQL text to SafeQL DB validation]
Loading
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
packages/plugins/drizzle/src/plugin.ts:89-97
**`mapWith` mentioned in comment but missing from `fragmentMethods`**

The comment on line 89 says `'.as(...)' / '.mapWith(...)' etc. mark a fragment, never a query`, but `mapWith` is not in the `fragmentMethods` set. When a user writes `sql\`count(*)\`.mapWith(Number)` (a very common Drizzle pattern for typed column selections), `isStandaloneQuery` walks up through the `PropertyAccessExpression .mapWith`, doesn't find it in `fragmentMethods`, advances `current` to the PAE, and then breaks at the `CallExpression`. At that point `parent.arguments.some(arg => arg === current)` is false (the PAE is the callee, not an argument), so the function falls through to `return true` — treating the raw fragment as a standalone query. SafeQL would then attempt to validate `count(*)` as a complete SQL statement, producing a false positive lint error for any code using `.mapWith()`.

### Issue 2 of 3
packages/plugins/drizzle/src/plugin.ts:26-42
Add `mapWith` to `fragmentMethods` to match the intent described in the comment and prevent false-positive validation of column-selection fragments.

```suggestion
// Methods that consume a `sql` tag as a *fragment* (skip — not a standalone query).
const fragmentMethods = new Set([
  "where",
  "having",
  "and",
  "or",
  "not",
  "on",
  "orderBy",
  "groupBy",
  "set",
  "leftJoin",
  "rightJoin",
  "innerJoin",
  "fullJoin",
  "as",
  "mapWith",
]);
```

### Issue 3 of 3
packages/plugins/drizzle/src/plugin.test.ts:17-43
**No test coverage for `.mapWith()` fragments**

The `targetCases` array covers `.as()` but has no test for `sql\`count(*)\`.mapWith(Number)` or similar. Without it, the `mapWith`-missing-from-`fragmentMethods` bug went undetected. A case like `{ name: ".mapWith() fragment is skipped", source: imp("sql\`count(*)\`.mapWith(Number)"), output: { skipped: true } }` would catch this and the class of regressions it represents.

Reviews (1): Last reviewed commit: "feat(plugin-drizzle): add Drizzle plugin" | Re-trigger Greptile

@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from c515f57 to 4869b29 Compare June 14, 2026 22:01
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch from cae02e3 to f4e5f68 Compare June 14, 2026 22:34
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from 4869b29 to c930fd5 Compare June 14, 2026 22:34
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch from f4e5f68 to 47089fe Compare June 15, 2026 07:38
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from c930fd5 to ace493b Compare June 15, 2026 07:39
@Newbie012 Newbie012 marked this pull request as draft June 15, 2026 07:44
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch from 47089fe to f4b3789 Compare June 15, 2026 09:25
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from ace493b to c3921fe Compare June 15, 2026 09:26
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch from f4b3789 to 730a69b Compare June 15, 2026 09:57
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from c3921fe to 258e151 Compare June 15, 2026 10:04
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch from 730a69b to 2831c89 Compare June 15, 2026 10:43
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from 258e151 to acc53f1 Compare June 15, 2026 10:44
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch from 2831c89 to cb1f1a8 Compare June 15, 2026 11:11
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from acc53f1 to dc25fee Compare June 15, 2026 11:11
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch 2 times, most recently from abaed1c to fecb952 Compare June 15, 2026 16:56
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from dc25fee to 18abb38 Compare June 15, 2026 16:57
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch from fecb952 to ca072e9 Compare June 15, 2026 17:13
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from 18abb38 to 38f198a Compare June 15, 2026 17:14
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch from ca072e9 to 5a301ca Compare June 17, 2026 19:59
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from 38f198a to 3aebc42 Compare June 17, 2026 19:59
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch from 5a301ca to 173309b Compare June 17, 2026 20:18
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from 3aebc42 to 6abe902 Compare June 17, 2026 20:18
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch from 173309b to e4bdca6 Compare June 18, 2026 08:20
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from 6abe902 to ec14381 Compare June 18, 2026 08:20
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch from e4bdca6 to 752a0cc Compare June 18, 2026 08:44
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from ec14381 to 04df80a Compare June 18, 2026 08:44
@Newbie012 Newbie012 force-pushed the feat/plugin-kysely branch from 752a0cc to 1b1e0df Compare June 18, 2026 15:42
@Newbie012 Newbie012 force-pushed the feat/plugin-drizzle branch from 04df80a to 7d5ddb9 Compare June 18, 2026 15:42
@ts-safeql ts-safeql deleted a comment from greptile-apps Bot Jun 20, 2026
@ts-safeql ts-safeql deleted a comment from greptile-apps Bot Jun 20, 2026
@ts-safeql ts-safeql deleted a comment from greptile-apps Bot Jun 20, 2026
@Newbie012 Newbie012 changed the base branch from feat/plugin-kysely to main June 20, 2026 20:51
Validate raw `sql` queries and the raw-sql fragments embedded in Drizzle
queries against the live database, compiling via Drizzle's own `.toSQL()`.
Includes docs on extending SafeQL's plugin API to other ORMs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant