@nitra/cursor 12.8.5 → 12.8.7

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.
Files changed (202) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/n-cursor.js +5 -5
  3. package/package.json +1 -1
  4. package/rules/abie/js/http_route_base.mdc +25 -0
  5. package/rules/abie/js/ua_http_route.mdc +1 -1
  6. package/rules/abie/main.mdc +12 -0
  7. package/rules/adr/js/hooks.mdc +32 -0
  8. package/rules/adr/js/madr_format.mdc +96 -0
  9. package/rules/adr/js/settings_policy.mdc +34 -0
  10. package/rules/adr/main.mdc +13 -95
  11. package/rules/bun/js/bunfig.mdc +12 -0
  12. package/rules/bun/js/layout.mdc +60 -0
  13. package/rules/bun/js/lint.mdc +9 -0
  14. package/rules/bun/js/package_json.mdc +19 -0
  15. package/rules/bun/main.mdc +9 -61
  16. package/rules/capacitor/js/ios_spm.mdc +69 -0
  17. package/rules/capacitor/js/version.mdc +29 -0
  18. package/rules/capacitor/main.mdc +8 -22
  19. package/rules/changelog/js/agent-workflow.mdc +15 -0
  20. package/rules/changelog/js/changelog-format.mdc +33 -0
  21. package/rules/changelog/js/comparison-models.mdc +40 -0
  22. package/rules/changelog/main.mdc +4 -98
  23. package/rules/ci4/js/marksman_config.mdc +31 -0
  24. package/rules/ci4/js/vscode_extensions.mdc +33 -0
  25. package/rules/ci4/main.mdc +14 -14
  26. package/rules/docker/js/compile.mdc +44 -0
  27. package/rules/docker/js/hadolint.mdc +50 -0
  28. package/rules/docker/js/mirror.mdc +13 -0
  29. package/rules/docker/js/multistage.mdc +13 -0
  30. package/rules/docker/js/native-addon.mdc +43 -0
  31. package/rules/docker/js/nginx-tag.mdc +7 -0
  32. package/rules/docker/js/nginx-user.mdc +37 -0
  33. package/rules/docker/js/non-root.mdc +39 -0
  34. package/rules/docker/main.mdc +15 -196
  35. package/rules/ga/js/lint_toolchain.mdc +15 -0
  36. package/rules/ga/js/required_workflows.mdc +35 -0
  37. package/rules/ga/js/vscode.mdc +17 -0
  38. package/rules/ga/js/workflow_common.mdc +108 -0
  39. package/rules/ga/js/workflows.mdc +32 -0
  40. package/rules/ga/js/zizmor.mdc +7 -0
  41. package/rules/ga/main.mdc +17 -125
  42. package/rules/graphql/js/tooling.mdc +13 -0
  43. package/rules/graphql/js/vscode_extensions.mdc +13 -0
  44. package/rules/graphql/main.mdc +3 -22
  45. package/rules/hasura/js/internal_urls.mdc +27 -0
  46. package/rules/hasura/js/migrations.mdc +13 -0
  47. package/rules/hasura/js/svc_hl.mdc +17 -0
  48. package/rules/hasura/main.mdc +8 -30
  49. package/rules/image-avif/js/avif_generation.mdc +26 -0
  50. package/rules/image-avif/js/package_json_optout.mdc +21 -0
  51. package/rules/image-avif/main.mdc +7 -34
  52. package/rules/image-compress/js/package_json.mdc +7 -0
  53. package/rules/image-compress/js/package_setup.mdc +13 -0
  54. package/rules/image-compress/main.mdc +4 -12
  55. package/rules/js/docs/index.md +3 -3
  56. package/rules/js/js/dep-policy.mdc +17 -0
  57. package/rules/js/js/eslint-config.mdc +28 -0
  58. package/rules/js/js/extensions.mdc +8 -0
  59. package/rules/js/js/file-extensions.mdc +12 -0
  60. package/rules/js/js/for-in.mdc +26 -0
  61. package/rules/js/js/jscpd.mdc +42 -0
  62. package/rules/js/js/knip.mdc +15 -0
  63. package/rules/js/js/lint-js-workflow.mdc +58 -0
  64. package/rules/js/js/oxlintrc.mdc +20 -0
  65. package/rules/js/js/package-json.mdc +31 -0
  66. package/rules/js/js/tests.mdc +9 -0
  67. package/rules/js/js/utils-lib-structure.mdc +15 -0
  68. package/rules/js/main.mdc +21 -214
  69. package/rules/js-bun-db/js/bun-sql-migration.mdc +15 -0
  70. package/rules/js-bun-db/js/connection.mdc +42 -0
  71. package/rules/js-bun-db/js/pg-format-identifiers.mdc +102 -0
  72. package/rules/js-bun-db/js/pg-format-shim.mdc +99 -0
  73. package/rules/js-bun-db/js/pg-leftover.mdc +27 -0
  74. package/rules/js-bun-db/js/pg-listen-notify.mdc +51 -0
  75. package/rules/js-bun-db/js/query-safety.mdc +117 -0
  76. package/rules/js-bun-db/js/sql-array.mdc +88 -0
  77. package/rules/js-bun-db/js/unsafe.mdc +65 -0
  78. package/rules/js-bun-db/main.mdc +15 -605
  79. package/rules/js-bun-redis/js/imports.mdc +47 -0
  80. package/rules/js-bun-redis/js/package_json.mdc +44 -0
  81. package/rules/js-bun-redis/main.mdc +3 -11
  82. package/rules/js-mssql/js/mssql-in-list.mdc +38 -0
  83. package/rules/js-mssql/js/mssql-pool.mdc +56 -0
  84. package/rules/js-mssql/js/mssql-query-template.mdc +33 -0
  85. package/rules/js-mssql/js/mssql-tvp.mdc +75 -0
  86. package/rules/js-mssql/js/mssql-version.mdc +7 -0
  87. package/rules/js-mssql/main.mdc +10 -198
  88. package/rules/js-run/js/check-env.mdc +35 -0
  89. package/rules/js-run/js/conn-aliases.mdc +109 -0
  90. package/rules/js-run/js/jsconfig.mdc +20 -0
  91. package/rules/js-run/js/otel-configmap.mdc +6 -0
  92. package/rules/js-run/js/pino.mdc +6 -0
  93. package/rules/js-run/js/project-structure.mdc +11 -0
  94. package/rules/js-run/js/runtime.mdc +14 -0
  95. package/rules/js-run/js/scope.mdc +11 -0
  96. package/rules/js-run/js/settimeout.mdc +11 -0
  97. package/rules/js-run/js/temporal.mdc +5 -0
  98. package/rules/js-run/main.mdc +16 -218
  99. package/rules/k8s/js/configmap.mdc +41 -0
  100. package/rules/k8s/js/deployment_resources.mdc +49 -0
  101. package/rules/k8s/js/hasura_httproute.mdc +91 -0
  102. package/rules/k8s/js/hpa_apiversion.mdc +27 -0
  103. package/rules/k8s/js/ingress_gateway.mdc +16 -0
  104. package/rules/k8s/js/kustomize_structure.mdc +144 -0
  105. package/rules/k8s/js/lint_k8s.mdc +72 -0
  106. package/rules/k8s/js/multidoc_yaml.mdc +5 -0
  107. package/rules/k8s/js/network_policy.mdc +136 -0
  108. package/rules/k8s/js/schema_modeline.mdc +57 -0
  109. package/rules/k8s/js/service.mdc +44 -0
  110. package/rules/k8s/js/topology_hpa_pdb.mdc +181 -0
  111. package/rules/k8s/main.mdc +30 -843
  112. package/rules/nginx-default-tpl/js/dockerfile.mdc +36 -0
  113. package/rules/nginx-default-tpl/js/http-route.mdc +41 -0
  114. package/rules/nginx-default-tpl/js/ini-keys.mdc +21 -0
  115. package/rules/nginx-default-tpl/js/template-structure.mdc +86 -0
  116. package/rules/nginx-default-tpl/js/vscode.mdc +37 -0
  117. package/rules/nginx-default-tpl/main.mdc +6 -112
  118. package/rules/npm-module/js/docs/index.md +5 -5
  119. package/rules/npm-module/js/docs/rule_meta.md +6 -6
  120. package/rules/npm-module/js/docs/skill_meta.md +8 -8
  121. package/rules/npm-module/js/header_doc_pointer.mdc +18 -0
  122. package/rules/npm-module/js/package_structure.mdc +62 -0
  123. package/rules/npm-module/js/rule_meta.mdc +11 -0
  124. package/rules/npm-module/js/skill_meta.mdc +11 -0
  125. package/rules/npm-module/main.mdc +10 -55
  126. package/rules/php/js/lint_php_yml.mdc +12 -0
  127. package/rules/php/js/tooling.mdc +66 -0
  128. package/rules/php/main.mdc +7 -66
  129. package/rules/python/js/lint_python_yml.mdc +23 -0
  130. package/rules/python/js/pyproject_toml.mdc +32 -0
  131. package/rules/python/js/tooling.mdc +23 -0
  132. package/rules/python/main.mdc +9 -33
  133. package/rules/rego/js/rego-lint.mdc +31 -0
  134. package/rules/rego/js/vscode_extensions.mdc +11 -0
  135. package/rules/rego/js/vscode_settings.mdc +13 -0
  136. package/rules/rego/main.mdc +8 -24
  137. package/rules/rust/js/coverage.mdc +28 -0
  138. package/rules/rust/js/lint.mdc +22 -0
  139. package/rules/rust/js/tauri_composition.mdc +8 -0
  140. package/rules/rust/js/vscode_extensions.mdc +12 -0
  141. package/rules/rust/main.mdc +8 -38
  142. package/rules/security/js/rego_policies.mdc +15 -0
  143. package/rules/security/js/sample_secret.mdc +19 -0
  144. package/rules/security/js/trufflehog.mdc +21 -0
  145. package/rules/security/main.mdc +7 -35
  146. package/rules/style/js/admin-table.mdc +88 -0
  147. package/rules/style/js/colors.mdc +21 -0
  148. package/rules/style/js/gap.mdc +22 -0
  149. package/rules/style/js/quasar-fixes.mdc +32 -0
  150. package/rules/style/js/quasar.mdc +7 -0
  151. package/rules/style/js/tooling.mdc +85 -0
  152. package/rules/style/main.mdc +13 -253
  153. package/rules/tauri/js/cargo_mutants_config.mdc +39 -0
  154. package/rules/tauri/js/tool_surface.mdc +21 -0
  155. package/rules/tauri/js/tooling.mdc +25 -0
  156. package/rules/tauri/main.mdc +8 -78
  157. package/rules/test/js/cargo_mutants_config.mdc +18 -0
  158. package/rules/test/js/docs/index.md +7 -7
  159. package/rules/test/js/location.mdc +52 -0
  160. package/rules/test/js/no-console-store-restore.mdc +11 -0
  161. package/rules/test/js/no-process-chdir.mdc +15 -0
  162. package/rules/test/js/no-relative-fs-path.mdc +22 -0
  163. package/rules/test/js/sandbox-aware-test.mdc +28 -0
  164. package/rules/test/js/stryker_config.mdc +26 -0
  165. package/rules/test/js/vitest-config-pool-forks.mdc +33 -0
  166. package/rules/test/main.mdc +18 -184
  167. package/rules/text/js/ci-lint-text.mdc +15 -0
  168. package/rules/text/js/cspell.mdc +81 -0
  169. package/rules/text/js/dotenv-linter.mdc +16 -0
  170. package/rules/text/js/forbidden-prettier.mdc +13 -0
  171. package/rules/text/js/markdownlint.mdc +25 -0
  172. package/rules/text/js/oxfmt.mdc +35 -0
  173. package/rules/text/js/package-json.mdc +26 -0
  174. package/rules/text/js/shellcheck.mdc +18 -0
  175. package/rules/text/js/v8r.mdc +23 -0
  176. package/rules/text/js/vscode.mdc +86 -0
  177. package/rules/text/main.mdc +20 -237
  178. package/rules/vue/js/composition-api.mdc +82 -0
  179. package/rules/vue/js/nheader-layout.mdc +171 -0
  180. package/rules/vue/js/node-imports.mdc +25 -0
  181. package/rules/vue/js/quasar-ui.mdc +32 -0
  182. package/rules/vue/js/structure.mdc +101 -0
  183. package/rules/vue/js/testing.mdc +32 -0
  184. package/rules/vue/js/tfm-translations.mdc +26 -0
  185. package/rules/vue/js/vite-config.mdc +126 -0
  186. package/rules/vue/js/vite-env.mdc +55 -0
  187. package/rules/vue/js/vue-imports.mdc +25 -0
  188. package/rules/vue/main.mdc +16 -640
  189. package/scripts/auto-rules.mjs +6 -6
  190. package/scripts/auto-skills.mjs +3 -3
  191. package/scripts/docs/auto-rules.md +17 -31
  192. package/scripts/docs/auto-skills.md +18 -163
  193. package/scripts/docs/index.md +16 -16
  194. package/scripts/lib/docs/index.md +36 -36
  195. package/scripts/lib/docs/mirror-parity.md +7 -7
  196. package/scripts/lib/docs/rule-meta.md +12 -12
  197. package/scripts/lib/docs/skill-meta.md +9 -9
  198. package/scripts/lib/docs/worktree-notice.md +10 -8
  199. package/scripts/lib/rule-meta.mjs +6 -6
  200. package/scripts/lib/skill-meta.mjs +6 -6
  201. package/scripts/lib/worktree-notice.mjs +2 -2
  202. package/scripts/utils/docs/index.md +14 -14
@@ -0,0 +1,47 @@
1
+ ## Сканування заборонених redis-імпортів у JS/TS-джерелах
2
+
3
+ Правило сканує всі JS/TS-файли проєкту (`.js`, `.ts`, `.mjs`, `.cjs`, `.jsx`, `.tsx` тощо, без `.d.ts`) на наявність `import`, `require()` або динамічного `import()` з заборонених пакетів:
4
+
5
+ - `ioredis` та підшляхи (`ioredis/…`)
6
+ - `node-redis`
7
+ - `redis` та підшляхи (`redis/…`)
8
+ - `@redis/client`, `@redis/json`, `@redis/search`, `@redis/time-series`, `@redis/bloom`
9
+
10
+ Усі ці пакети слід замінити на **Bun native Redis**: `import { redis } from 'bun'` — документація: <https://bun.com/docs/runtime/redis>.
11
+
12
+ Сканування виконується через **oxc-parser** по AST (не regex по тексту) — статичні імпорти, `require()`-виклики і динамічні `import()` окремо.
13
+
14
+ При синтаксичних помилках у файлі результат — порожній (спочатку виправити синтаксис).
15
+
16
+ Директорії, зазначені у `.n-cursor-ignore` / `cursorignore` конфігурації, повністю пропускаються.
17
+
18
+ ### Повідомлення про порушення
19
+
20
+ ```
21
+ js-bun-redis: src/conn/redis.ts:3 — заміни 'ioredis' на Bun native Redis (import { redis } from 'bun', https://bun.com/docs/runtime/redis): import IORedis from 'ioredis'
22
+ ```
23
+
24
+ ### Міграція
25
+
26
+ ```ts
27
+ // До (ioredis)
28
+ import IORedis from 'ioredis'
29
+ const client = new IORedis({ host: 'localhost', port: 6379 })
30
+ await client.set('key', 'value')
31
+
32
+ // Після (Bun native Redis)
33
+ import { redis } from 'bun'
34
+ await redis.set('key', 'value')
35
+ ```
36
+
37
+ ```ts
38
+ // До (node-redis / redis v4)
39
+ import { createClient } from 'redis'
40
+ const client = createClient()
41
+ await client.connect()
42
+ await client.set('key', 'value')
43
+
44
+ // Після (Bun native Redis)
45
+ import { redis } from 'bun'
46
+ await redis.set('key', 'value')
47
+ ```
@@ -0,0 +1,44 @@
1
+ ## Перевірка `dependencies` у `package.json` (Rego gate)
2
+
3
+ Rego-полісі `js_bun_redis.package_json` перевіряє `package.json` кожного воркспейсу на наявність заборонених redis-залежностей у полі `dependencies`.
4
+
5
+ **Namespace:** `js_bun_redis.package_json`
6
+
7
+ Канон заборонених пакетів надходить через `--data` з файлу-шаблону:
8
+
9
+ [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
10
+
11
+ Поточний список заборонених `dependencies`:
12
+
13
+ | Пакет | Причина |
14
+ |---|---|
15
+ | `ioredis` | заміни на Bun native Redis |
16
+ | `node-redis` | заміни на Bun native Redis |
17
+ | `redis` | заміни на Bun native Redis |
18
+ | `@redis/client` | заміни на Bun native Redis |
19
+ | `@redis/json` | заміни на Bun native Redis |
20
+ | `@redis/search` | заміни на Bun native Redis |
21
+ | `@redis/time-series` | заміни на Bun native Redis |
22
+ | `@redis/bloom` | заміни на Bun native Redis |
23
+
24
+ ### Повідомлення про порушення
25
+
26
+ ```
27
+ dependencies.ioredis — заміни на Bun native Redis (js-bun-redis.mdc)
28
+ ```
29
+
30
+ ### Виправлення
31
+
32
+ ```jsonc
33
+ // package.json — видалити зі списку dependencies:
34
+ {
35
+ "dependencies": {
36
+ // "ioredis": "...", ← видалити
37
+ // "node-redis": "...", ← видалити
38
+ // "redis": "...", ← видалити
39
+ // "@redis/client": "...",← видалити
40
+ }
41
+ }
42
+ ```
43
+
44
+ Після видалення пакетів з `dependencies` виконати також сканування джерел на заборонені імпорти — перевірка `js-bun-redis-imports`.
@@ -5,16 +5,8 @@ alwaysApply: false
5
5
  version: '1.2'
6
6
  ---
7
7
 
8
- ## Підтримувані версії redis
8
+ Правило забороняє використання застарілих redis-клієнтів (`ioredis`, `node-redis`, `redis`, `@redis/*`) і вимагає переходу на **Bun native Redis** (`import { redis } from 'bun'`). Підтримувана версія Redis: **7.2+**.
9
9
 
10
- Redis 7.2+
10
+ [js-bun-redis-imports](./js/imports.mdc)
11
11
 
12
- ## Заміна на Bun native Redis
13
-
14
- Якщо в проєкті використовуються бібліотеки `ioredis`, `node-redis`, їх потрібно замінити на Bun native Redis: <https://bun.com/docs/runtime/redis>.
15
-
16
- - Видалити з `dependencies`: `ioredis`, `node-redis`.
17
- - Видалити з коду: усі `import` / `require` цих пакетів та власні обгортки над ними.
18
- - Замінити на `import { redis } from 'bun'`
19
-
20
- Канон заборонених `dependencies` (`ioredis`, `node-redis`, `redis`, `@redis/*`): [package.json.deny.json](./policy/package_json/template/package.json.deny.json).
12
+ [js-bun-redis-package_json](./js/package_json.mdc)
@@ -0,0 +1,38 @@
1
+ ## Парсинг і guard для `IN (${...})`
2
+
3
+ Якщо `IN (...)` все ж використовується (а не `JOIN` на TVP), значення в `${...}` **обов'язково** мають бути попередньо приведені числовим парсером і відфільтровані від `NaN`.
4
+
5
+ Це знімає будь-яку можливість SQL injection: SQL-метасимволи в `Number`/`parseInt(...)` перетворюються на `NaN` і відсіюються.
6
+
7
+ ### Правила
8
+
9
+ Додатково:
10
+
11
+ - значення для `IN (${...})` потрібно **винести в окрему змінну** перед запитом (не підставляти вираз напряму в `${...}`);
12
+ - цю змінну потрібно **перевірити на пустоту** і якщо список порожній — **throw error** (щоб не виконувати некоректний запит).
13
+
14
+ ### Приклади
15
+
16
+ ```javascript
17
+ // ❌ НЕ МОЖНА: значення з req.body / зовнішнього джерела без парсингу
18
+ const outIds = pgQ.rows.flatMap(x => x.req_body.Orders.map(o => o.OutletId))
19
+ await pool.query(/* sql */ String.raw`
20
+ SELECT ... WHERE so.OutletId IN (${outIds})
21
+ `)
22
+ ```
23
+
24
+ ```javascript
25
+ // ✅ МОЖНА: parseInt + filter(!isNaN) гарантує, що в SQL потраплять лише числа
26
+ // і перед запитом робимо guard на пустоту, щоб не виконувати некоректний SQL.
27
+ const outIds = pgQ.rows
28
+ .flatMap(x => x.req_body.Orders.map(o => parseInt(o.OutletId)))
29
+ .filter(n => !isNaN(n))
30
+ if (!outIds.length) throw new Error('outIds is empty')
31
+ await pool.request().query`
32
+ SELECT ... WHERE so.OutletId IN (${outIds})
33
+ `
34
+ ```
35
+
36
+ Допустимі парсери: `parseInt(...)`, `parseFloat(...)`, `Number(...)`, `BigInt(...)` або унарний `+x`. Літеральні масиви чисел (`[1, 2, 3]`) теж безпечні — без парсера, але без жодних рядків.
37
+
38
+ Це правило діє і для безпечного `pool.request().query\`...\`` (де mssql сам параметризує масив), і поготів для `pool.query(String.raw\`...\`)` чи `pool.query(\`...\`)`, де такий парсинг — єдиний бар'єр.
@@ -0,0 +1,56 @@
1
+ ## Singleton ConnectionPool і заборона shared Request
2
+
3
+ Потрібно використовувати connection pool (`sql.ConnectionPool`) як singleton, а **НЕ** створювати підключення на кожен запит.
4
+
5
+ ```javascript
6
+ // db.js
7
+ import sql from 'mssql';
8
+
9
+ let poolPromise;
10
+
11
+ export function getPool() {
12
+ if (!poolPromise) {
13
+ const db = new sql.ConnectionPool(config);
14
+ poolPromise = db.connect().catch(err => {
15
+ poolPromise = undefined; // дозволити повторну спробу
16
+ throw err;
17
+ });
18
+ }
19
+ return poolPromise;
20
+ }
21
+ ```
22
+
23
+ ```javascript
24
+ // usage
25
+ import { getPool } from './db.js';
26
+
27
+ const pool = await getPool();
28
+ const userId = 42;
29
+ const status = 'active';
30
+
31
+ // Tagged template на request — це працює
32
+ const result = await pool.request().query`
33
+ SELECT * FROM users
34
+ WHERE id = ${userId} AND status = ${status}
35
+ `;
36
+ ```
37
+
38
+ Ключове: `pool.request().query\`...\`` — бектіки після `query`, без круглих дужок. Це tagged template, де mssql параметризує значення автоматично.
39
+
40
+ ### Заборона shared Request
41
+
42
+ Заборонено робити singleton `request` на рівні модуля:
43
+
44
+ ```javascript
45
+ // ❌ НЕ МОЖНА
46
+ export const request = pool.request();
47
+ ```
48
+
49
+ `Request` має створюватися **щоразу заново**:
50
+
51
+ ```javascript
52
+ // ✅ МОЖНА
53
+ const request = pool.request();
54
+ ```
55
+
56
+ Це особливо важливо для **TVP** (табличні параметри) та будь-яких `.input(...)`.
@@ -0,0 +1,33 @@
1
+ ## Безпечний tagged template vs небезпечний `query(...)`
2
+
3
+ Різниця між `query\`...\`` і `query(\`...\`)` — критична.
4
+
5
+ ### Що НЕ робити: `query(\`...\`)`
6
+
7
+ ```javascript
8
+ // ❌ Це не tagged template — це конкатенація рядка перед викликом
9
+ await pool.request().query(`SELECT * FROM users WHERE id = ${userId}`);
10
+ // ↑ круглі дужки замість бектіків = звичайна інтерполяція = SQL injection
11
+ ```
12
+
13
+ ### Що робити: `query\`...\``
14
+
15
+ ```javascript
16
+ // ✅ Tagged template — mssql параметризує ${userId} автоматично
17
+ await pool.request().query`SELECT * FROM users WHERE id = ${userId}`;
18
+ ```
19
+
20
+ ### Коментар під час виправлення SQL injection
21
+
22
+ Коли виправляєш місце з потенційним SQL injection (заміна `query(\`...\`)` на `query\`...\`` або прибирання динамічних списків/конкатенації), **додай поруч короткий коментар** з описом причини.
23
+
24
+ Вимоги до коментаря:
25
+
26
+ - вказати **що було небезпечно** (звичайна інтерполяція в рядок, конкатенація, динамічний список);
27
+ - вказати **чому новий варіант безпечний** (tagged template / параметризація / TVP);
28
+ - 1–2 рядки, без дублювання очевидного.
29
+
30
+ ```javascript
31
+ // SQLi fix: query`...` (tagged template) параметризує значення; query(`...`) небезпечний через інтерполяцію.
32
+ await pool.request().query`SELECT * FROM users WHERE id = ${userId}`
33
+ ```
@@ -0,0 +1,75 @@
1
+ ## TVP (table-valued parameters) — рекомендований підхід для списків
2
+
3
+ TVP дозволений і рекомендований для SQL Server 2019. Для списків значень та пар ключів **найкращий** шлях по безпеці/продуктивності — TVP.
4
+
5
+ ### 1) `IN (...)` для кодів → `JOIN` на TVP
6
+
7
+ Замість складання SQL-рядка:
8
+
9
+ ```javascript
10
+ // ❌ НЕ МОЖНА: динамічний список у SQL
11
+ await pool.request().query`
12
+ SELECT *
13
+ FROM promo.SomeTable t
14
+ WHERE t.ExternalCode IN (${codes.map(c => `'${c}'`).join(',')})
15
+ `;
16
+ ```
17
+
18
+ Роби TVP з 1 колонкою:
19
+
20
+ - створити `new sql.Table()`
21
+ - `columns.add('ExternalCode', sql.NVarChar(N))`
22
+ - `rows.add(code)` після `trim()` + валідації
23
+ - `request.input('codes', table)`
24
+ - в SQL: `JOIN @codes c ON c.ExternalCode = t.ExternalCode`
25
+
26
+ Плюси:
27
+
28
+ - 0% SQL injection
29
+ - текст SQL **не росте** від довжини масиву
30
+ - SQL Server часто оптимізує `JOIN` краще, ніж гігантський `IN (...)`
31
+
32
+ ### 2) `DELETE ... VALUES (...)` / `MERGE ... VALUES (...)` → TVP з 2 колонками
33
+
34
+ Замість генерації списку рядків `(...),(...),...` у SQL (навіть якщо воно "через tagged template"):
35
+
36
+ - сформуй TVP-таблицю `@Pairs` з колонками:
37
+ - `PromoActivitiesId` (INT або BIGINT — як у схемі БД)
38
+ - `SupplierOutletId` (INT або BIGINT — як у схемі БД)
39
+
40
+ Delete:
41
+
42
+ ```sql
43
+ DELETE abi
44
+ FROM promo.ActivitiesByOutlet abi
45
+ JOIN @Pairs p
46
+ ON p.PromoActivitiesId = abi.PromoActivitiesId
47
+ AND p.SupplierOutletId = abi.SupplierOutletId;
48
+ ```
49
+
50
+ Insert (без `MERGE`, простіше і безпечніше):
51
+
52
+ ```sql
53
+ INSERT INTO promo.ActivitiesByOutlet (PromoActivitiesId, SupplierOutletId, Status)
54
+ SELECT p.PromoActivitiesId, p.SupplierOutletId, 2
55
+ FROM @Pairs p
56
+ WHERE NOT EXISTS (
57
+ SELECT 1
58
+ FROM promo.ActivitiesByOutlet abi
59
+ WHERE abi.PromoActivitiesId = p.PromoActivitiesId
60
+ AND abi.SupplierOutletId = p.SupplierOutletId
61
+ );
62
+ ```
63
+
64
+ Плюси:
65
+
66
+ - не збираєш SQL-рядок із даних
67
+ - менше шансів упіймати edge-case `MERGE` (у SQL Server історично багато "сюрпризів")
68
+
69
+ ### Мінімальна валідація перед наповненням TVP
70
+
71
+ Навіть з TVP потрібно:
72
+
73
+ - `ExternalCode`: `typeof === 'string'`, `trim()`, довжина `<= N` (під схему), відкинути пусті.
74
+ - ліміт на кількість елементів (наприклад 5k/10k — залежить від вашого потоку).
75
+ - `supplierId`/ID: число/BigInt, валідне та скінченне.
@@ -0,0 +1,7 @@
1
+ ## Версія пакета `mssql`
2
+
3
+ Якщо в проекті в будь-якому `package.json` в секції `dependencies` присутній пакет **`mssql`**, то його версія повинна бути не менше **12.5.0**.
4
+
5
+ Правило орієнтоване на **SQL Server 2019**.
6
+
7
+ Допустимі формати діапазону: `^12.5.0`, `>=12.5.0`, `12.5.0`, `workspace:*` (трактується як OK).
@@ -5,210 +5,22 @@ alwaysApply: false
5
5
  version: '1.4'
6
6
  ---
7
7
 
8
- ## Підтримувана версія SQL Server
8
+ Правило охоплює безпечне використання пакету `mssql` у Node.js: мінімальну версію залежності, singleton ConnectionPool, заборону небезпечних шаблонів запитів і захист від SQL injection через TVP або числовий парсинг IN-списків.
9
9
 
10
- Правило орієнтоване на **SQL Server 2019**.
10
+ [js-mssql-mssql-version](./js/mssql-version.mdc)
11
11
 
12
- ## Версія пакета `mssql`
12
+ [js-mssql-mssql-pool](./js/mssql-pool.mdc)
13
13
 
14
- Якщо в проекті в будь-якому `package.json` в секції `dependencies` присутній пакет **`mssql`**, то його версія повинна бути не менше **12.5.0**.
14
+ [js-mssql-mssql-query-template](./js/mssql-query-template.mdc)
15
15
 
16
- Потрібно використовувати connection pool (sql.ConnectionPool) як singleton, а НЕ створювати підключення на кожен запит.
16
+ [js-mssql-mssql-tvp](./js/mssql-tvp.mdc)
17
17
 
18
- ## Як виконувати запити (безпечно)
18
+ [js-mssql-mssql-in-list](./js/mssql-in-list.mdc)
19
19
 
20
- tagged template треба викликати на request-обʼєкті цього пулу:
20
+ ## Швидкий gate через conftest
21
21
 
22
- ```javascript
23
- javascript// db.js
24
- import sql from 'mssql';
22
+ Rego-перевірки (`policy/`) — швидкий синхронний gate для одиничного `package.json` без JS-рантайму:
25
23
 
26
- let poolPromise;
24
+ - `js_mssql.package_json` — перевіряє `dependencies.mssql >= 12.5.0` у вхідному `package.json`.
27
25
 
28
- export function getPool() {
29
- if (!poolPromise) {
30
- const db = new SQL.ConnectionPool(config);
31
- poolPromise = pool.connect().catch(err => {
32
- poolPromise = undefined; // дозволити повторну спробу
33
- throw err;
34
- });
35
- }
36
- return poolPromise;
37
- }
38
- ```
39
-
40
- ```javascript
41
- // usage
42
- import { getPool } from './db.js';
43
-
44
- const pool = await getPool();
45
- const userId = 42;
46
- const status = 'active';
47
-
48
- // Tagged template на request — це працює
49
- const result = await pool.request().query`
50
- SELECT * FROM users
51
- WHERE id = ${userId} AND status = ${status}
52
- `;
53
-
54
- ```
55
-
56
- Ключове: pool.request().query\...`— бекті́ки післяquery`, без круглих дужок. Це той самий tagged template, тільки контекст — конкретний пул, а не глобальний.
57
-
58
- ## Коментар під час виправлення SQL injection
59
-
60
- Коли виправляєш місце з потенційним **SQL injection** (наприклад, заміна `query(\`...\`)` на `query\`...\`` або прибирання динамічних списків/конкатенації), **додай поруч короткий коментар** з описом причини.
61
-
62
- Вимоги до коментаря:
63
-
64
- - вказати **що було небезпечно** (звичайна інтерполяція в рядок, конкатенація, динамічний список);
65
- - вказати **чому новий варіант безпечний** (tagged template / параметризація / TVP);
66
- - 1–2 рядки, без дублювання очевидного.
67
-
68
- Приклад:
69
-
70
- ```javascript
71
- // SQLi fix: query`...` (tagged template) параметризує значення; query(`...`) небезпечний через інтерполяцію.
72
- await pool.request().query`SELECT * FROM users WHERE id = ${userId}`
73
- ```
74
-
75
- ## Що НЕ робити
76
-
77
- ### Не робити `query(\`...\`)`
78
-
79
- javascript// ❌ Це не tagged template — це конкатенація рядка перед викликом
80
-
81
- ```javascript
82
- await pool.request().query(`SELECT * FROM users WHERE id = ${userId}`);
83
- // ↑ круглі дужки замість бекті́ків = звичайна інтерполяція = SQL injection
84
- ```
85
-
86
- Різниця між query\...`іquery(`...`)` — критична. Перше безпечне, друге — діра.
87
- Потрібно використовувати перше. Потрібно шукати в коді друге і заміняти на перше.
88
-
89
- ### Не шарити `Request` між запитами
90
-
91
- Заборонено робити singleton `request` на рівні модуля на кшталт:
92
-
93
- ```javascript
94
- // ❌ НЕ МОЖНА
95
- export const request = pool.request();
96
- ```
97
-
98
- `Request` має створюватися **щоразу заново**:
99
-
100
- ```javascript
101
- // ✅ МОЖНА
102
- const request = pool.request();
103
- ```
104
-
105
- Це особливо важливо для **TVP** (табличні параметри) та будь-яких `.input(...)`.
106
-
107
- ## TVP дозволений і рекомендований (SQL Server 2019)
108
-
109
- Для списків значень та пар ключів **найкращий** шлях по безпеці/продуктивності — **TVP** (table-valued parameters).
110
-
111
- ### 1) `IN (...)` для кодів → `JOIN` на TVP
112
-
113
- Замість складання SQL-рядка:
114
-
115
- ```javascript
116
- // ❌ НЕ МОЖНА: динамічний список у SQL
117
- await pool.request().query`
118
- SELECT *
119
- FROM promo.SomeTable t
120
- WHERE t.ExternalCode IN (${codes.map(c => `'${c}'`).join(',')})
121
- `;
122
- ```
123
-
124
- Роби TVP з 1 колонкою:
125
-
126
- - створити `new sql.Table()`
127
- - `columns.add('ExternalCode', sql.NVarChar(N))`
128
- - `rows.add(code)` після `trim()` + валідації
129
- - `request.input('codes', table)`
130
- - в SQL: `JOIN @codes c ON c.ExternalCode = t.ExternalCode`
131
-
132
- Плюси:
133
-
134
- - 0% SQL injection
135
- - текст SQL **не росте** від довжини масиву
136
- - SQL Server часто оптимізує `JOIN` краще, ніж гігантський `IN (...)`
137
-
138
- ### 2) `DELETE ... VALUES (...)` / `MERGE ... VALUES (...)` → TVP з 2 колонками
139
-
140
- Замість генерації списку рядків `(...),(...),...` у SQL (навіть якщо воно “через tagged template”):
141
-
142
- - сформуй TVP-таблицю `@Pairs` з колонками:
143
- - `PromoActivitiesId` (INT або BIGINT — як у схемі БД)
144
- - `SupplierOutletId` (INT або BIGINT — як у схемі БД)
145
-
146
- Delete:
147
-
148
- ```sql
149
- DELETE abi
150
- FROM promo.ActivitiesByOutlet abi
151
- JOIN @Pairs p
152
- ON p.PromoActivitiesId = abi.PromoActivitiesId
153
- AND p.SupplierOutletId = abi.SupplierOutletId;
154
- ```
155
-
156
- Insert (без `MERGE`, простіше і безпечніше):
157
-
158
- ```sql
159
- INSERT INTO promo.ActivitiesByOutlet (PromoActivitiesId, SupplierOutletId, Status)
160
- SELECT p.PromoActivitiesId, p.SupplierOutletId, 2
161
- FROM @Pairs p
162
- WHERE NOT EXISTS (
163
- SELECT 1
164
- FROM promo.ActivitiesByOutlet abi
165
- WHERE abi.PromoActivitiesId = p.PromoActivitiesId
166
- AND abi.SupplierOutletId = p.SupplierOutletId
167
- );
168
- ```
169
-
170
- Плюси:
171
-
172
- - не збираєш SQL-рядок із даних
173
- - менше шансів упіймати edge-case `MERGE` (у SQL Server історично багато “сюрпризів”)
174
-
175
- ## Мінімальна валідація перед наповненням TVP
176
-
177
- Навіть з TVP потрібно:
178
-
179
- - `ExternalCode`: `typeof === 'string'`, `trim()`, довжина `<= N` (під схему), відкинути пусті.
180
- - ліміт на кількість елементів (наприклад 5k/10k — залежить від вашого потоку).
181
- - `supplierId`/ID: число/BigInt, валідне та скінченне.
182
-
183
- ## Парсинг значень для `IN (${...})`
184
-
185
- Якщо `IN (...)` все ж використовується (а не `JOIN` на TVP), значення в `${...}` **обовʼязково** мають бути попередньо приведені числовим парсером і відфільтровані від `NaN`. Це знімає будь-яку можливість SQL injection: SQL-метасимволи в `Number`/`parseInt(...)` перетворюються на `NaN` і відсіюються.
186
-
187
- Додатково:
188
-
189
- - значення для `IN (${...})` потрібно **винести в окрему змінну** перед запитом (не підставляти вираз напряму в `${...}`);
190
- - цю змінну потрібно **перевірити на пустоту** і якщо список порожній — **throw error** (щоб не виконувати некоректний запит).
191
-
192
- ```javascript
193
- // ❌ НЕ МОЖНА: значення з req.body / зовнішнього джерела без парсингу
194
- const outIds = pgQ.rows.flatMap(x => x.req_body.Orders.map(o => o.OutletId))
195
- await pool.query(/* sql */ String.raw`
196
- SELECT ... WHERE so.OutletId IN (${outIds})
197
- `)
198
- ```
199
-
200
- ```javascript
201
- // ✅ МОЖНА: parseInt + filter(!isNaN) гарантує, що в SQL потраплять лише числа
202
- // і перед запитом робимо guard на пустоту, щоб не виконувати некоректний SQL.
203
- const outIds = pgQ.rows
204
- .flatMap(x => x.req_body.Orders.map(o => parseInt(o.OutletId)))
205
- .filter(n => !isNaN(n))
206
- if (!outIds.length) throw new Error('outIds is empty')
207
- await pool.request().query`
208
- SELECT ... WHERE so.OutletId IN (${outIds})
209
- `
210
- ```
211
-
212
- Допустимі парсери: `parseInt(...)`, `parseFloat(...)`, `Number(...)`, `BigInt(...)` або унарний `+x`. Літеральні масиви чисел (`[1, 2, 3]`) теж безпечні — без парсера, але без жодних рядків.
213
-
214
- Це правило діє і для безпечного `pool.request().query\`...\`` (де mssql сам параметризує масив), і поготів для `pool.query(String.raw\`...\`)` чи `pool.query(\`...\`)`, де такий парсинг — єдиний бар'єр.
26
+ JS-перевірка (`deps.mjs`) — authoritative: охоплює всі `package.json` репо, AST-скан JS/TS-джерел і повний semver-triple-compare. Rego-gate — доповнення, не заміна.
@@ -0,0 +1,35 @@
1
+ ## CheckEnv та заборона прямого `process.env`
2
+
3
+ ### CheckEnv
4
+
5
+ Усі змінні оточення, які використовуються в коді, повинні бути перевірені за допомогою `checkEnv` з пакету `@nitra/check-env`. Це гарантує, що всі необхідні змінні оточення встановлені перед запуском програми.
6
+
7
+ ```javascript title="Приклад підключення до PostgreSQL в /src/conn/pg.mjs"
8
+ import { checkEnv, env } from '@nitra/check-env'
9
+ import { SQL } from 'bun'
10
+
11
+ checkEnv(['PG_CONN'])
12
+
13
+ export const db = new SQL({ url: env.PG_CONN })
14
+
15
+ ```
16
+
17
+ ### process.env
18
+
19
+ Прямий доступ до `process.env.X` у коді заборонений — його треба замінити на `env`:
20
+
21
+ > Стосується лише backend-пакетів (див. **Область застосування**). У frontend-пакетах (`vite` у `devDependencies`) — **не змінюй** `process.env.*` і **не додавай** імпорт `node:process`.
22
+
23
+ - **обов'язкова змінна** — `import { checkEnv, env } from '@nitra/check-env'` плюс `checkEnv(['X'])`
24
+ у тому ж файлі (приклад див. вище в розділі **CheckEnv**);
25
+ - **опційна змінна** — `import { env } from 'node:process'`:
26
+
27
+ ```javascript title="Опційна змінна — env з node:process"
28
+ import { env } from 'node:process'
29
+
30
+ console.log(env.OPTIONAL_ENV_VAR)
31
+ ```
32
+
33
+ Тимчасово приглушити перевірку для конкретного рядка можна коментарем
34
+ `// @nitra/cursor ignore-next-line checkEnv` безпосередньо перед використанням
35
+ (escape-hatch для legacy-коду, не для нових файлів).