|
| 1 | +# Authoring a SafeQL Plugin |
| 2 | + |
| 3 | +## Checklist |
| 4 | + |
| 5 | +- [ ] Create a new package under `packages/plugins/<name>/` (see [creating a package](creating-package.md)) |
| 6 | +- [ ] Add `@ts-safeql/plugin-utils` as a dependency |
| 7 | +- [ ] Add `postgres` as a dependency (if using `createConnection`) |
| 8 | +- [ ] Default-export a `definePlugin()` result |
| 9 | +- [ ] Add tests under `src/plugin.test.ts` and `src/plugin.integration.test.ts` |
| 10 | +- [ ] Add a demo under `demos/plugin-<name>/` |
| 11 | +- [ ] Add a docs page at `docs/plugins/<name>.md` or update `docs/compatibility/<name>.md` |
| 12 | +- [ ] Add sidebar entry in `docs/.vitepress/config.ts` |
| 13 | + |
| 14 | +## Plugin Hooks |
| 15 | + |
| 16 | +### `createConnection` |
| 17 | + |
| 18 | +Custom database connection strategy. Returns a `postgres` Sql instance. |
| 19 | + |
| 20 | +### `connectionDefaults` |
| 21 | + |
| 22 | +Default config values merged under user config. Use for library-specific type overrides. |
| 23 | + |
| 24 | +### `onTarget({ node, context })` |
| 25 | + |
| 26 | +Called for each TaggedTemplateExpression. Return: |
| 27 | + |
| 28 | +- `TargetMatch` object to proceed with checking |
| 29 | +- `false` to skip entirely (e.g., `sql.fragment`) |
| 30 | +- `undefined` to defer to SafeQL default |
| 31 | + |
| 32 | +### `onExpression({ node, context })` |
| 33 | + |
| 34 | +Called for each interpolated expression. Return: |
| 35 | + |
| 36 | +- `string` - SQL fragment (use `$N` for placeholder) |
| 37 | +- `false` - skip the entire query |
| 38 | +- `undefined` - use default `$N::type` behavior |
| 39 | + |
| 40 | +## Key Constraints |
| 41 | + |
| 42 | +- Use `type` aliases (not `interface`) for the config type |
| 43 | +- Plugin names are auto-prefixed with `safeql-plugin-` |
| 44 | +- Package names must be prefixed with `plugin-` (e.g., `@ts-safeql/plugin-auth-aws`) |
| 45 | +- Test file naming: `plugin.test.ts` for unit tests, `plugin.integration.test.ts` for DB tests |
| 46 | + |
| 47 | +## Example Structure |
| 48 | + |
| 49 | +``` |
| 50 | +packages/plugins/my-lib/ |
| 51 | +├── src/ |
| 52 | +│ ├── index.ts # definePlugin() export |
| 53 | +│ ├── plugin.test.ts # Unit tests (PluginTestDriver) |
| 54 | +│ └── plugin.integration.test.ts # Integration tests (RuleTester) |
| 55 | +├── package.json |
| 56 | +├── tsconfig.json |
| 57 | +└── build.config.ts |
| 58 | +``` |
| 59 | + |
| 60 | +## Testing |
| 61 | + |
| 62 | +Unit tests use `PluginTestDriver` from `@ts-safeql/plugin-utils/testing`: |
| 63 | + |
| 64 | +```ts |
| 65 | +import { PluginTestDriver } from "@ts-safeql/plugin-utils/testing"; |
| 66 | +import plugin from "./plugin"; |
| 67 | + |
| 68 | +const driver = new PluginTestDriver({ plugin: plugin.factory({}) }); |
| 69 | +const result = driver.toSQL(`import { sql } from "my-lib"; sql.unsafe\`SELECT 1\``); |
| 70 | +expect(result).toEqual({ sql: "SELECT 1" }); |
| 71 | +``` |
| 72 | + |
| 73 | +Integration tests use `@typescript-eslint/rule-tester` with a real database. |
0 commit comments