relq 1.0.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.
Files changed (305) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +862 -0
  3. package/dist/addons/buffer.js +1869 -0
  4. package/dist/addons/pg-cursor.js +1425 -0
  5. package/dist/addons/pg-format.js +2248 -0
  6. package/dist/addons/pg.js +4790 -0
  7. package/dist/bin/relq.js +2 -0
  8. package/dist/cjs/cache/index.cjs +9 -0
  9. package/dist/cjs/cache/query-cache.cjs +311 -0
  10. package/dist/cjs/cli/commands/add.cjs +82 -0
  11. package/dist/cjs/cli/commands/commit.cjs +145 -0
  12. package/dist/cjs/cli/commands/diff.cjs +84 -0
  13. package/dist/cjs/cli/commands/export.cjs +333 -0
  14. package/dist/cjs/cli/commands/fetch.cjs +59 -0
  15. package/dist/cjs/cli/commands/generate.cjs +242 -0
  16. package/dist/cjs/cli/commands/history.cjs +165 -0
  17. package/dist/cjs/cli/commands/import.cjs +524 -0
  18. package/dist/cjs/cli/commands/init.cjs +437 -0
  19. package/dist/cjs/cli/commands/introspect.cjs +142 -0
  20. package/dist/cjs/cli/commands/log.cjs +62 -0
  21. package/dist/cjs/cli/commands/migrate.cjs +167 -0
  22. package/dist/cjs/cli/commands/pull.cjs +410 -0
  23. package/dist/cjs/cli/commands/push.cjs +165 -0
  24. package/dist/cjs/cli/commands/rollback.cjs +169 -0
  25. package/dist/cjs/cli/commands/status.cjs +110 -0
  26. package/dist/cjs/cli/commands/sync.cjs +79 -0
  27. package/dist/cjs/cli/index.cjs +275 -0
  28. package/dist/cjs/cli/utils/change-tracker.cjs +446 -0
  29. package/dist/cjs/cli/utils/commit-manager.cjs +239 -0
  30. package/dist/cjs/cli/utils/config-loader.cjs +127 -0
  31. package/dist/cjs/cli/utils/env-loader.cjs +62 -0
  32. package/dist/cjs/cli/utils/fast-introspect.cjs +398 -0
  33. package/dist/cjs/cli/utils/git-utils.cjs +404 -0
  34. package/dist/cjs/cli/utils/migration-generator.cjs +269 -0
  35. package/dist/cjs/cli/utils/relqignore.cjs +114 -0
  36. package/dist/cjs/cli/utils/repo-manager.cjs +515 -0
  37. package/dist/cjs/cli/utils/schema-comparator.cjs +313 -0
  38. package/dist/cjs/cli/utils/schema-diff.cjs +284 -0
  39. package/dist/cjs/cli/utils/schema-hash.cjs +108 -0
  40. package/dist/cjs/cli/utils/schema-introspect.cjs +455 -0
  41. package/dist/cjs/cli/utils/snapshot-manager.cjs +223 -0
  42. package/dist/cjs/cli/utils/spinner.cjs +108 -0
  43. package/dist/cjs/cli/utils/sql-generator.cjs +520 -0
  44. package/dist/cjs/cli/utils/sql-parser.cjs +999 -0
  45. package/dist/cjs/cli/utils/type-generator.cjs +2061 -0
  46. package/dist/cjs/condition/array-condition-builder.cjs +503 -0
  47. package/dist/cjs/condition/array-numeric-condition-builder.cjs +186 -0
  48. package/dist/cjs/condition/array-specialized-condition-builder.cjs +206 -0
  49. package/dist/cjs/condition/array-string-condition-builder.cjs +146 -0
  50. package/dist/cjs/condition/base-condition-builder.cjs +2 -0
  51. package/dist/cjs/condition/condition-collector.cjs +284 -0
  52. package/dist/cjs/condition/fulltext-condition-builder.cjs +61 -0
  53. package/dist/cjs/condition/geometric-condition-builder.cjs +208 -0
  54. package/dist/cjs/condition/index.cjs +25 -0
  55. package/dist/cjs/condition/jsonb-condition-builder.cjs +160 -0
  56. package/dist/cjs/condition/network-condition-builder.cjs +230 -0
  57. package/dist/cjs/condition/range-condition-builder.cjs +82 -0
  58. package/dist/cjs/config/config.cjs +190 -0
  59. package/dist/cjs/config/index.cjs +9 -0
  60. package/dist/cjs/constants/pg-values.cjs +68 -0
  61. package/dist/cjs/copy/copy-builder.cjs +316 -0
  62. package/dist/cjs/copy/index.cjs +6 -0
  63. package/dist/cjs/core/query-builder.cjs +440 -0
  64. package/dist/cjs/core/relq-client.cjs +1831 -0
  65. package/dist/cjs/core/typed-kuery-client.cjs +2 -0
  66. package/dist/cjs/count/count-builder.cjs +88 -0
  67. package/dist/cjs/count/index.cjs +5 -0
  68. package/dist/cjs/cte/cte-builder.cjs +89 -0
  69. package/dist/cjs/cte/index.cjs +5 -0
  70. package/dist/cjs/ddl/function.cjs +48 -0
  71. package/dist/cjs/ddl/index.cjs +7 -0
  72. package/dist/cjs/ddl/sql.cjs +54 -0
  73. package/dist/cjs/delete/delete-builder.cjs +135 -0
  74. package/dist/cjs/delete/index.cjs +5 -0
  75. package/dist/cjs/errors/relq-errors.cjs +329 -0
  76. package/dist/cjs/examples/fulltext-search-test.cjs +122 -0
  77. package/dist/cjs/explain/explain-builder.cjs +99 -0
  78. package/dist/cjs/explain/index.cjs +5 -0
  79. package/dist/cjs/function/create-function-builder.cjs +196 -0
  80. package/dist/cjs/function/index.cjs +6 -0
  81. package/dist/cjs/functions/advanced-functions.cjs +241 -0
  82. package/dist/cjs/functions/case-builder.cjs +66 -0
  83. package/dist/cjs/functions/geometric-functions.cjs +104 -0
  84. package/dist/cjs/functions/index.cjs +184 -0
  85. package/dist/cjs/functions/network-functions.cjs +86 -0
  86. package/dist/cjs/functions/sql-functions.cjs +431 -0
  87. package/dist/cjs/index.cjs +164 -0
  88. package/dist/cjs/indexing/create-index-builder.cjs +187 -0
  89. package/dist/cjs/indexing/drop-index-builder.cjs +89 -0
  90. package/dist/cjs/indexing/index-types.cjs +2 -0
  91. package/dist/cjs/indexing/index.cjs +8 -0
  92. package/dist/cjs/insert/conflict-builder.cjs +173 -0
  93. package/dist/cjs/insert/index.cjs +5 -0
  94. package/dist/cjs/insert/insert-builder.cjs +254 -0
  95. package/dist/cjs/introspect/index.cjs +229 -0
  96. package/dist/cjs/maintenance/index.cjs +6 -0
  97. package/dist/cjs/maintenance/vacuum-builder.cjs +166 -0
  98. package/dist/cjs/pubsub/index.cjs +7 -0
  99. package/dist/cjs/pubsub/listen-notify-builder.cjs +57 -0
  100. package/dist/cjs/pubsub/listener-connection.cjs +180 -0
  101. package/dist/cjs/raw/index.cjs +5 -0
  102. package/dist/cjs/raw/raw-query-builder.cjs +27 -0
  103. package/dist/cjs/schema/index.cjs +15 -0
  104. package/dist/cjs/schema/schema-builder.cjs +1167 -0
  105. package/dist/cjs/schema-builder.cjs +21 -0
  106. package/dist/cjs/schema-definition/column-types.cjs +829 -0
  107. package/dist/cjs/schema-definition/index.cjs +62 -0
  108. package/dist/cjs/schema-definition/introspection.cjs +620 -0
  109. package/dist/cjs/schema-definition/partitions.cjs +129 -0
  110. package/dist/cjs/schema-definition/pg-enum.cjs +76 -0
  111. package/dist/cjs/schema-definition/pg-function.cjs +91 -0
  112. package/dist/cjs/schema-definition/pg-sequence.cjs +56 -0
  113. package/dist/cjs/schema-definition/pg-trigger.cjs +108 -0
  114. package/dist/cjs/schema-definition/relations.cjs +98 -0
  115. package/dist/cjs/schema-definition/sql-expressions.cjs +202 -0
  116. package/dist/cjs/schema-definition/table-definition.cjs +636 -0
  117. package/dist/cjs/select/aggregate-builder.cjs +179 -0
  118. package/dist/cjs/select/index.cjs +5 -0
  119. package/dist/cjs/select/select-builder.cjs +233 -0
  120. package/dist/cjs/sequence/index.cjs +7 -0
  121. package/dist/cjs/sequence/sequence-builder.cjs +264 -0
  122. package/dist/cjs/table/alter-table-builder.cjs +146 -0
  123. package/dist/cjs/table/constraint-builder.cjs +102 -0
  124. package/dist/cjs/table/create-table-builder.cjs +248 -0
  125. package/dist/cjs/table/index.cjs +17 -0
  126. package/dist/cjs/table/partition-builder.cjs +131 -0
  127. package/dist/cjs/table/truncate-builder.cjs +70 -0
  128. package/dist/cjs/transaction/index.cjs +6 -0
  129. package/dist/cjs/transaction/transaction-builder.cjs +78 -0
  130. package/dist/cjs/trigger/create-trigger-builder.cjs +174 -0
  131. package/dist/cjs/trigger/index.cjs +6 -0
  132. package/dist/cjs/types/aggregate-types.cjs +2 -0
  133. package/dist/cjs/types/config-types.cjs +40 -0
  134. package/dist/cjs/types/inference-types.cjs +18 -0
  135. package/dist/cjs/types/pagination-types.cjs +7 -0
  136. package/dist/cjs/types/result-types.cjs +2 -0
  137. package/dist/cjs/types/schema-types.cjs +2 -0
  138. package/dist/cjs/types/subscription-types.cjs +2 -0
  139. package/dist/cjs/types.cjs +2 -0
  140. package/dist/cjs/update/array-update-builder.cjs +205 -0
  141. package/dist/cjs/update/index.cjs +13 -0
  142. package/dist/cjs/update/update-builder.cjs +195 -0
  143. package/dist/cjs/utils/case-converter.cjs +58 -0
  144. package/dist/cjs/utils/environment-detection.cjs +120 -0
  145. package/dist/cjs/utils/index.cjs +10 -0
  146. package/dist/cjs/utils/pool-defaults.cjs +106 -0
  147. package/dist/cjs/utils/type-coercion.cjs +118 -0
  148. package/dist/cjs/view/create-view-builder.cjs +180 -0
  149. package/dist/cjs/view/index.cjs +7 -0
  150. package/dist/cjs/window/index.cjs +5 -0
  151. package/dist/cjs/window/window-builder.cjs +80 -0
  152. package/dist/config.cjs +1 -0
  153. package/dist/config.d.ts +655 -0
  154. package/dist/config.js +1 -0
  155. package/dist/esm/cache/index.js +1 -0
  156. package/dist/esm/cache/query-cache.js +303 -0
  157. package/dist/esm/cli/commands/add.js +78 -0
  158. package/dist/esm/cli/commands/commit.js +109 -0
  159. package/dist/esm/cli/commands/diff.js +81 -0
  160. package/dist/esm/cli/commands/export.js +297 -0
  161. package/dist/esm/cli/commands/fetch.js +56 -0
  162. package/dist/esm/cli/commands/generate.js +206 -0
  163. package/dist/esm/cli/commands/history.js +129 -0
  164. package/dist/esm/cli/commands/import.js +488 -0
  165. package/dist/esm/cli/commands/init.js +401 -0
  166. package/dist/esm/cli/commands/introspect.js +106 -0
  167. package/dist/esm/cli/commands/log.js +59 -0
  168. package/dist/esm/cli/commands/migrate.js +131 -0
  169. package/dist/esm/cli/commands/pull.js +374 -0
  170. package/dist/esm/cli/commands/push.js +129 -0
  171. package/dist/esm/cli/commands/rollback.js +133 -0
  172. package/dist/esm/cli/commands/status.js +107 -0
  173. package/dist/esm/cli/commands/sync.js +76 -0
  174. package/dist/esm/cli/index.js +240 -0
  175. package/dist/esm/cli/utils/change-tracker.js +405 -0
  176. package/dist/esm/cli/utils/commit-manager.js +191 -0
  177. package/dist/esm/cli/utils/config-loader.js +86 -0
  178. package/dist/esm/cli/utils/env-loader.js +57 -0
  179. package/dist/esm/cli/utils/fast-introspect.js +362 -0
  180. package/dist/esm/cli/utils/git-utils.js +347 -0
  181. package/dist/esm/cli/utils/migration-generator.js +263 -0
  182. package/dist/esm/cli/utils/relqignore.js +74 -0
  183. package/dist/esm/cli/utils/repo-manager.js +444 -0
  184. package/dist/esm/cli/utils/schema-comparator.js +307 -0
  185. package/dist/esm/cli/utils/schema-diff.js +276 -0
  186. package/dist/esm/cli/utils/schema-hash.js +69 -0
  187. package/dist/esm/cli/utils/schema-introspect.js +418 -0
  188. package/dist/esm/cli/utils/snapshot-manager.js +179 -0
  189. package/dist/esm/cli/utils/spinner.js +101 -0
  190. package/dist/esm/cli/utils/sql-generator.js +504 -0
  191. package/dist/esm/cli/utils/sql-parser.js +992 -0
  192. package/dist/esm/cli/utils/type-generator.js +2058 -0
  193. package/dist/esm/condition/array-condition-builder.js +495 -0
  194. package/dist/esm/condition/array-numeric-condition-builder.js +182 -0
  195. package/dist/esm/condition/array-specialized-condition-builder.js +200 -0
  196. package/dist/esm/condition/array-string-condition-builder.js +142 -0
  197. package/dist/esm/condition/base-condition-builder.js +1 -0
  198. package/dist/esm/condition/condition-collector.js +275 -0
  199. package/dist/esm/condition/fulltext-condition-builder.js +53 -0
  200. package/dist/esm/condition/geometric-condition-builder.js +200 -0
  201. package/dist/esm/condition/index.js +7 -0
  202. package/dist/esm/condition/jsonb-condition-builder.js +152 -0
  203. package/dist/esm/condition/network-condition-builder.js +222 -0
  204. package/dist/esm/condition/range-condition-builder.js +74 -0
  205. package/dist/esm/config/config.js +150 -0
  206. package/dist/esm/config/index.js +1 -0
  207. package/dist/esm/constants/pg-values.js +63 -0
  208. package/dist/esm/copy/copy-builder.js +308 -0
  209. package/dist/esm/copy/index.js +1 -0
  210. package/dist/esm/core/query-builder.js +426 -0
  211. package/dist/esm/core/relq-client.js +1791 -0
  212. package/dist/esm/core/typed-kuery-client.js +1 -0
  213. package/dist/esm/count/count-builder.js +81 -0
  214. package/dist/esm/count/index.js +1 -0
  215. package/dist/esm/cte/cte-builder.js +82 -0
  216. package/dist/esm/cte/index.js +1 -0
  217. package/dist/esm/ddl/function.js +45 -0
  218. package/dist/esm/ddl/index.js +2 -0
  219. package/dist/esm/ddl/sql.js +51 -0
  220. package/dist/esm/delete/delete-builder.js +128 -0
  221. package/dist/esm/delete/index.js +1 -0
  222. package/dist/esm/errors/relq-errors.js +310 -0
  223. package/dist/esm/examples/fulltext-search-test.js +117 -0
  224. package/dist/esm/explain/explain-builder.js +95 -0
  225. package/dist/esm/explain/index.js +1 -0
  226. package/dist/esm/function/create-function-builder.js +188 -0
  227. package/dist/esm/function/index.js +1 -0
  228. package/dist/esm/functions/advanced-functions.js +231 -0
  229. package/dist/esm/functions/case-builder.js +58 -0
  230. package/dist/esm/functions/geometric-functions.js +97 -0
  231. package/dist/esm/functions/index.js +171 -0
  232. package/dist/esm/functions/network-functions.js +79 -0
  233. package/dist/esm/functions/sql-functions.js +421 -0
  234. package/dist/esm/index.js +34 -0
  235. package/dist/esm/indexing/create-index-builder.js +180 -0
  236. package/dist/esm/indexing/drop-index-builder.js +81 -0
  237. package/dist/esm/indexing/index-types.js +1 -0
  238. package/dist/esm/indexing/index.js +2 -0
  239. package/dist/esm/insert/conflict-builder.js +162 -0
  240. package/dist/esm/insert/index.js +1 -0
  241. package/dist/esm/insert/insert-builder.js +247 -0
  242. package/dist/esm/introspect/index.js +224 -0
  243. package/dist/esm/maintenance/index.js +1 -0
  244. package/dist/esm/maintenance/vacuum-builder.js +158 -0
  245. package/dist/esm/pubsub/index.js +1 -0
  246. package/dist/esm/pubsub/listen-notify-builder.js +48 -0
  247. package/dist/esm/pubsub/listener-connection.js +173 -0
  248. package/dist/esm/raw/index.js +1 -0
  249. package/dist/esm/raw/raw-query-builder.js +20 -0
  250. package/dist/esm/schema/index.js +1 -0
  251. package/dist/esm/schema/schema-builder.js +1150 -0
  252. package/dist/esm/schema-builder.js +2 -0
  253. package/dist/esm/schema-definition/column-types.js +738 -0
  254. package/dist/esm/schema-definition/index.js +10 -0
  255. package/dist/esm/schema-definition/introspection.js +614 -0
  256. package/dist/esm/schema-definition/partitions.js +123 -0
  257. package/dist/esm/schema-definition/pg-enum.js +70 -0
  258. package/dist/esm/schema-definition/pg-function.js +85 -0
  259. package/dist/esm/schema-definition/pg-sequence.js +50 -0
  260. package/dist/esm/schema-definition/pg-trigger.js +102 -0
  261. package/dist/esm/schema-definition/relations.js +90 -0
  262. package/dist/esm/schema-definition/sql-expressions.js +193 -0
  263. package/dist/esm/schema-definition/table-definition.js +630 -0
  264. package/dist/esm/select/aggregate-builder.js +172 -0
  265. package/dist/esm/select/index.js +1 -0
  266. package/dist/esm/select/select-builder.js +226 -0
  267. package/dist/esm/sequence/index.js +1 -0
  268. package/dist/esm/sequence/sequence-builder.js +255 -0
  269. package/dist/esm/table/alter-table-builder.js +138 -0
  270. package/dist/esm/table/constraint-builder.js +95 -0
  271. package/dist/esm/table/create-table-builder.js +241 -0
  272. package/dist/esm/table/index.js +5 -0
  273. package/dist/esm/table/partition-builder.js +121 -0
  274. package/dist/esm/table/truncate-builder.js +63 -0
  275. package/dist/esm/transaction/index.js +1 -0
  276. package/dist/esm/transaction/transaction-builder.js +70 -0
  277. package/dist/esm/trigger/create-trigger-builder.js +166 -0
  278. package/dist/esm/trigger/index.js +1 -0
  279. package/dist/esm/types/aggregate-types.js +1 -0
  280. package/dist/esm/types/config-types.js +36 -0
  281. package/dist/esm/types/inference-types.js +12 -0
  282. package/dist/esm/types/pagination-types.js +4 -0
  283. package/dist/esm/types/result-types.js +1 -0
  284. package/dist/esm/types/schema-types.js +1 -0
  285. package/dist/esm/types/subscription-types.js +1 -0
  286. package/dist/esm/types.js +1 -0
  287. package/dist/esm/update/array-update-builder.js +192 -0
  288. package/dist/esm/update/index.js +2 -0
  289. package/dist/esm/update/update-builder.js +188 -0
  290. package/dist/esm/utils/case-converter.js +55 -0
  291. package/dist/esm/utils/environment-detection.js +113 -0
  292. package/dist/esm/utils/index.js +2 -0
  293. package/dist/esm/utils/pool-defaults.js +100 -0
  294. package/dist/esm/utils/type-coercion.js +110 -0
  295. package/dist/esm/view/create-view-builder.js +171 -0
  296. package/dist/esm/view/index.js +1 -0
  297. package/dist/esm/window/index.js +1 -0
  298. package/dist/esm/window/window-builder.js +73 -0
  299. package/dist/index.cjs +1 -0
  300. package/dist/index.d.ts +10341 -0
  301. package/dist/index.js +1 -0
  302. package/dist/schema-builder.cjs +1 -0
  303. package/dist/schema-builder.d.ts +2272 -0
  304. package/dist/schema-builder.js +1 -0
  305. package/package.json +55 -0
@@ -0,0 +1,405 @@
1
+ import * as crypto from 'crypto';
2
+ export function generateChangeId(type, objectType, objectName, parentName) {
3
+ const input = `${type}:${objectType}:${parentName || ''}:${objectName}:${Date.now()}`;
4
+ return crypto.createHash('sha1').update(input).digest('hex').substring(0, 12);
5
+ }
6
+ export function generateChangeSQL(change) {
7
+ switch (change.objectType) {
8
+ case 'EXTENSION':
9
+ return generateExtensionSQL(change);
10
+ case 'ENUM':
11
+ return generateEnumSQL(change);
12
+ case 'ENUM_VALUE':
13
+ return generateEnumValueSQL(change);
14
+ case 'DOMAIN':
15
+ return generateDomainSQL(change);
16
+ case 'COMPOSITE_TYPE':
17
+ return generateCompositeTypeSQL(change);
18
+ case 'SEQUENCE':
19
+ return generateSequenceSQL(change);
20
+ case 'TABLE':
21
+ return generateTableSQL(change);
22
+ case 'COLUMN':
23
+ return generateColumnSQL(change);
24
+ case 'INDEX':
25
+ return generateIndexSQL(change);
26
+ case 'CONSTRAINT':
27
+ case 'PRIMARY_KEY':
28
+ case 'FOREIGN_KEY':
29
+ case 'CHECK':
30
+ case 'EXCLUSION':
31
+ return generateConstraintSQL(change);
32
+ case 'PARTITION':
33
+ return generatePartitionSQL(change);
34
+ case 'VIEW':
35
+ return generateViewSQL(change);
36
+ case 'MATERIALIZED_VIEW':
37
+ return generateMaterializedViewSQL(change);
38
+ case 'FUNCTION':
39
+ case 'PROCEDURE':
40
+ return generateFunctionSQL(change);
41
+ case 'TRIGGER':
42
+ return generateTriggerSQL(change);
43
+ case 'FOREIGN_SERVER':
44
+ return generateForeignServerSQL(change);
45
+ case 'FOREIGN_TABLE':
46
+ return generateForeignTableSQL(change);
47
+ default:
48
+ return `-- Unsupported object type: ${change.objectType}`;
49
+ }
50
+ }
51
+ function generateExtensionSQL(change) {
52
+ if (change.type === 'CREATE') {
53
+ return `CREATE EXTENSION IF NOT EXISTS "${change.objectName}";`;
54
+ }
55
+ else if (change.type === 'DROP') {
56
+ return `DROP EXTENSION IF EXISTS "${change.objectName}";`;
57
+ }
58
+ return '';
59
+ }
60
+ function generateEnumSQL(change) {
61
+ const data = change.after;
62
+ if (change.type === 'CREATE' && data) {
63
+ const values = data.values.map(v => `'${v}'`).join(', ');
64
+ return `CREATE TYPE "${data.name}" AS ENUM (${values});`;
65
+ }
66
+ else if (change.type === 'DROP') {
67
+ return `DROP TYPE IF EXISTS "${change.objectName}" CASCADE;`;
68
+ }
69
+ return '';
70
+ }
71
+ function generateEnumValueSQL(change) {
72
+ const data = change.after;
73
+ if (change.type === 'CREATE' && data) {
74
+ const afterClause = data.after ? ` AFTER '${data.after}'` : '';
75
+ return `ALTER TYPE "${data.enumName}" ADD VALUE IF NOT EXISTS '${data.value}'${afterClause};`;
76
+ }
77
+ return '';
78
+ }
79
+ function generateDomainSQL(change) {
80
+ const data = change.after;
81
+ if (change.type === 'CREATE' && data) {
82
+ let sql = `CREATE DOMAIN "${data.name}" AS ${data.baseType}`;
83
+ if (data.default)
84
+ sql += ` DEFAULT ${data.default}`;
85
+ if (data.notNull)
86
+ sql += ' NOT NULL';
87
+ if (data.check)
88
+ sql += ` CHECK (${data.check})`;
89
+ return sql + ';';
90
+ }
91
+ else if (change.type === 'DROP') {
92
+ return `DROP DOMAIN IF EXISTS "${change.objectName}" CASCADE;`;
93
+ }
94
+ return '';
95
+ }
96
+ function generateCompositeTypeSQL(change) {
97
+ const data = change.after;
98
+ if (change.type === 'CREATE' && data) {
99
+ const attrs = data.attributes.map(a => ` "${a.name}" ${a.type}`).join(',\n');
100
+ return `CREATE TYPE "${data.name}" AS (\n${attrs}\n);`;
101
+ }
102
+ else if (change.type === 'DROP') {
103
+ return `DROP TYPE IF EXISTS "${change.objectName}" CASCADE;`;
104
+ }
105
+ return '';
106
+ }
107
+ function generateSequenceSQL(change) {
108
+ const data = change.after;
109
+ if (change.type === 'CREATE' && data) {
110
+ let sql = `CREATE SEQUENCE IF NOT EXISTS "${data.name}"`;
111
+ if (data.startWith)
112
+ sql += ` START WITH ${data.startWith}`;
113
+ if (data.incrementBy)
114
+ sql += ` INCREMENT BY ${data.incrementBy}`;
115
+ return sql + ';';
116
+ }
117
+ else if (change.type === 'DROP') {
118
+ return `DROP SEQUENCE IF EXISTS "${change.objectName}" CASCADE;`;
119
+ }
120
+ return '';
121
+ }
122
+ function generateTableSQL(change) {
123
+ const data = change.after;
124
+ if (change.type === 'CREATE' && data) {
125
+ const colDefs = [];
126
+ const constraintDefs = [];
127
+ for (const col of data.columns) {
128
+ let def = ` "${col.name}" ${col.dataType}`;
129
+ if (col.isPrimaryKey)
130
+ def += ' PRIMARY KEY';
131
+ if (!col.isNullable && !col.isPrimaryKey)
132
+ def += ' NOT NULL';
133
+ if (col.isUnique && !col.isPrimaryKey)
134
+ def += ' UNIQUE';
135
+ if (col.defaultValue)
136
+ def += ` DEFAULT ${col.defaultValue}`;
137
+ if (col.references) {
138
+ def += ` REFERENCES "${col.references.table}"("${col.references.column}")`;
139
+ }
140
+ colDefs.push(def);
141
+ }
142
+ for (const con of data.constraints || []) {
143
+ constraintDefs.push(` CONSTRAINT "${con.name}" ${con.definition}`);
144
+ }
145
+ const allDefs = [...colDefs, ...constraintDefs].join(',\n');
146
+ let sql = `CREATE TABLE "${data.name}" (\n${allDefs}\n)`;
147
+ if (data.isPartitioned && data.partitionType && data.partitionKey?.length) {
148
+ sql += ` PARTITION BY ${data.partitionType} (${data.partitionKey.join(', ')})`;
149
+ }
150
+ return sql + ';';
151
+ }
152
+ else if (change.type === 'DROP') {
153
+ return `DROP TABLE IF EXISTS "${change.objectName}" CASCADE;`;
154
+ }
155
+ return '';
156
+ }
157
+ function generateColumnSQL(change) {
158
+ const tableName = change.parentName;
159
+ if (!tableName)
160
+ return '';
161
+ const data = change.after;
162
+ if (change.type === 'CREATE' && data) {
163
+ let sql = `ALTER TABLE "${tableName}" ADD COLUMN "${data.name}" ${data.dataType}`;
164
+ if (!data.isNullable)
165
+ sql += ' NOT NULL';
166
+ if (data.defaultValue)
167
+ sql += ` DEFAULT ${data.defaultValue}`;
168
+ return sql + ';';
169
+ }
170
+ else if (change.type === 'DROP') {
171
+ return `ALTER TABLE "${tableName}" DROP COLUMN IF EXISTS "${change.objectName}" CASCADE;`;
172
+ }
173
+ else if (change.type === 'ALTER' && data) {
174
+ const before = change.before;
175
+ const lines = [];
176
+ if (before?.dataType !== data.dataType) {
177
+ lines.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${data.name}" TYPE ${data.dataType};`);
178
+ }
179
+ if (before?.isNullable !== data.isNullable) {
180
+ if (data.isNullable) {
181
+ lines.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${data.name}" DROP NOT NULL;`);
182
+ }
183
+ else {
184
+ lines.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${data.name}" SET NOT NULL;`);
185
+ }
186
+ }
187
+ if (before?.defaultValue !== data.defaultValue) {
188
+ if (data.defaultValue) {
189
+ lines.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${data.name}" SET DEFAULT ${data.defaultValue};`);
190
+ }
191
+ else {
192
+ lines.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${data.name}" DROP DEFAULT;`);
193
+ }
194
+ }
195
+ return lines.join('\n');
196
+ }
197
+ return '';
198
+ }
199
+ function generateIndexSQL(change) {
200
+ const data = change.after;
201
+ if (change.type === 'CREATE' && data) {
202
+ const unique = data.isUnique ? 'UNIQUE ' : '';
203
+ const using = data.type && data.type !== 'btree' ? ` USING ${data.type}` : '';
204
+ const cols = data.columns.map(c => `"${c}"`).join(', ');
205
+ let sql = `CREATE ${unique}INDEX IF NOT EXISTS "${data.name}" ON "${data.tableName}"${using} (${cols})`;
206
+ if (data.where)
207
+ sql += ` WHERE ${data.where}`;
208
+ return sql + ';';
209
+ }
210
+ else if (change.type === 'DROP') {
211
+ return `DROP INDEX IF EXISTS "${change.objectName}";`;
212
+ }
213
+ return '';
214
+ }
215
+ function generateConstraintSQL(change) {
216
+ const tableName = change.parentName;
217
+ if (!tableName)
218
+ return '';
219
+ const data = change.after;
220
+ if (change.type === 'CREATE' && data) {
221
+ return `ALTER TABLE "${tableName}" ADD CONSTRAINT "${data.name}" ${data.definition};`;
222
+ }
223
+ else if (change.type === 'DROP') {
224
+ return `ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${change.objectName}";`;
225
+ }
226
+ return '';
227
+ }
228
+ function generatePartitionSQL(change) {
229
+ const data = change.after;
230
+ if (change.type === 'CREATE' && data) {
231
+ return `CREATE TABLE "${data.name}" PARTITION OF "${data.parentTable}" ${data.bound};`;
232
+ }
233
+ else if (change.type === 'DROP') {
234
+ return `DROP TABLE IF EXISTS "${change.objectName}";`;
235
+ }
236
+ return '';
237
+ }
238
+ function generateViewSQL(change) {
239
+ const data = change.after;
240
+ if (change.type === 'CREATE' && data) {
241
+ return `CREATE OR REPLACE VIEW "${data.name}" AS ${data.definition};`;
242
+ }
243
+ else if (change.type === 'DROP') {
244
+ return `DROP VIEW IF EXISTS "${change.objectName}" CASCADE;`;
245
+ }
246
+ return '';
247
+ }
248
+ function generateMaterializedViewSQL(change) {
249
+ const data = change.after;
250
+ if (change.type === 'CREATE' && data) {
251
+ return `CREATE MATERIALIZED VIEW IF NOT EXISTS "${data.name}" AS ${data.definition};`;
252
+ }
253
+ else if (change.type === 'DROP') {
254
+ return `DROP MATERIALIZED VIEW IF EXISTS "${change.objectName}" CASCADE;`;
255
+ }
256
+ return '';
257
+ }
258
+ function generateFunctionSQL(change) {
259
+ const data = change.after;
260
+ if (change.type === 'CREATE' && data) {
261
+ const args = data.args || '';
262
+ const volatility = data.volatility || 'VOLATILE';
263
+ return `CREATE OR REPLACE FUNCTION "${data.name}"(${args})
264
+ RETURNS ${data.returns}
265
+ LANGUAGE ${data.language}
266
+ ${volatility}
267
+ AS $$
268
+ ${data.body}
269
+ $$;`;
270
+ }
271
+ else if (change.type === 'DROP') {
272
+ return `DROP FUNCTION IF EXISTS "${change.objectName}" CASCADE;`;
273
+ }
274
+ return '';
275
+ }
276
+ function generateTriggerSQL(change) {
277
+ const data = change.after;
278
+ if (change.type === 'CREATE' && data) {
279
+ return `CREATE TRIGGER "${data.name}"
280
+ ${data.timing} ${data.event} ON "${data.tableName}"
281
+ FOR EACH ROW EXECUTE FUNCTION ${data.functionName}();`;
282
+ }
283
+ else if (change.type === 'DROP' && change.parentName) {
284
+ return `DROP TRIGGER IF EXISTS "${change.objectName}" ON "${change.parentName}";`;
285
+ }
286
+ return '';
287
+ }
288
+ function generateForeignServerSQL(change) {
289
+ const data = change.after;
290
+ if (change.type === 'CREATE' && data) {
291
+ let sql = `CREATE SERVER IF NOT EXISTS "${data.name}" FOREIGN DATA WRAPPER ${data.fdw}`;
292
+ if (data.options && Object.keys(data.options).length > 0) {
293
+ const opts = Object.entries(data.options)
294
+ .map(([k, v]) => `${k} '${v}'`)
295
+ .join(', ');
296
+ sql += ` OPTIONS (${opts})`;
297
+ }
298
+ return sql + ';';
299
+ }
300
+ else if (change.type === 'DROP') {
301
+ return `DROP SERVER IF EXISTS "${change.objectName}" CASCADE;`;
302
+ }
303
+ return '';
304
+ }
305
+ function generateForeignTableSQL(change) {
306
+ const data = change.after;
307
+ if (change.type === 'CREATE' && data) {
308
+ const cols = data.columns.map(c => ` "${c.name}" ${c.type}`).join(',\n');
309
+ let sql = `CREATE FOREIGN TABLE IF NOT EXISTS "${data.name}" (\n${cols}\n) SERVER "${data.serverName}"`;
310
+ if (data.options && Object.keys(data.options).length > 0) {
311
+ const opts = Object.entries(data.options)
312
+ .map(([k, v]) => `${k} '${v}'`)
313
+ .join(', ');
314
+ sql += ` OPTIONS (${opts})`;
315
+ }
316
+ return sql + ';';
317
+ }
318
+ else if (change.type === 'DROP') {
319
+ return `DROP FOREIGN TABLE IF EXISTS "${change.objectName}";`;
320
+ }
321
+ return '';
322
+ }
323
+ export function createChange(type, objectType, objectName, before, after, parentName) {
324
+ const id = generateChangeId(type, objectType, objectName, parentName);
325
+ const change = {
326
+ id,
327
+ type,
328
+ objectType,
329
+ objectName,
330
+ parentName,
331
+ before,
332
+ after,
333
+ sql: '',
334
+ detectedAt: new Date().toISOString(),
335
+ };
336
+ change.sql = generateChangeSQL(change);
337
+ return change;
338
+ }
339
+ export function getChangeDisplayName(change) {
340
+ const prefix = change.type === 'CREATE' ? '+' :
341
+ change.type === 'DROP' ? '-' :
342
+ change.type === 'ALTER' ? '~' :
343
+ '>';
344
+ if (change.parentName) {
345
+ return `${prefix} ${change.objectType} ${change.parentName}.${change.objectName}`;
346
+ }
347
+ return `${prefix} ${change.objectType} ${change.objectName}`;
348
+ }
349
+ export function sortChangesByDependency(changes) {
350
+ const order = {
351
+ 'EXTENSION': 1,
352
+ 'SCHEMA': 2,
353
+ 'ENUM': 3,
354
+ 'DOMAIN': 4,
355
+ 'COMPOSITE_TYPE': 5,
356
+ 'SEQUENCE': 6,
357
+ 'FOREIGN_SERVER': 7,
358
+ 'TABLE': 10,
359
+ 'PARTITION': 11,
360
+ 'COLUMN': 12,
361
+ 'INDEX': 13,
362
+ 'CONSTRAINT': 14,
363
+ 'PRIMARY_KEY': 14,
364
+ 'FOREIGN_KEY': 15,
365
+ 'CHECK': 14,
366
+ 'EXCLUSION': 14,
367
+ 'VIEW': 20,
368
+ 'MATERIALIZED_VIEW': 21,
369
+ 'FUNCTION': 30,
370
+ 'PROCEDURE': 31,
371
+ 'TRIGGER': 40,
372
+ 'ENUM_VALUE': 3,
373
+ 'FOREIGN_TABLE': 8,
374
+ };
375
+ return [...changes].sort((a, b) => {
376
+ if (a.type === 'CREATE' && b.type === 'DROP')
377
+ return -1;
378
+ if (a.type === 'DROP' && b.type === 'CREATE')
379
+ return 1;
380
+ return (order[a.objectType] || 50) - (order[b.objectType] || 50);
381
+ });
382
+ }
383
+ export function generateCombinedSQL(changes) {
384
+ const sorted = sortChangesByDependency(changes);
385
+ const lines = [
386
+ '--',
387
+ '-- Generated by Relq CLI',
388
+ `-- Generated at: ${new Date().toISOString()}`,
389
+ '--',
390
+ '',
391
+ ];
392
+ let currentSection = '';
393
+ for (const change of sorted) {
394
+ const section = change.objectType;
395
+ if (section !== currentSection) {
396
+ lines.push(`-- ${section}s`);
397
+ currentSection = section;
398
+ }
399
+ if (change.sql) {
400
+ lines.push(change.sql);
401
+ lines.push('');
402
+ }
403
+ }
404
+ return lines.join('\n');
405
+ }
@@ -0,0 +1,191 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { normalizeSchema, generateSchemaHash } from "./schema-hash.js";
4
+ const RELQ_DIR = '.relq';
5
+ const COMMITS_FILE = 'commits.json';
6
+ const HEAD_FILE = 'HEAD';
7
+ export function ensureRelqDir(baseDir = process.cwd()) {
8
+ const relqPath = path.join(baseDir, RELQ_DIR);
9
+ if (!fs.existsSync(relqPath)) {
10
+ fs.mkdirSync(relqPath, { recursive: true });
11
+ }
12
+ return relqPath;
13
+ }
14
+ export function getLocalCommits(baseDir = process.cwd()) {
15
+ const filePath = path.join(baseDir, RELQ_DIR, COMMITS_FILE);
16
+ if (!fs.existsSync(filePath)) {
17
+ return [];
18
+ }
19
+ try {
20
+ const data = fs.readFileSync(filePath, 'utf-8');
21
+ return JSON.parse(data);
22
+ }
23
+ catch {
24
+ return [];
25
+ }
26
+ }
27
+ export function saveLocalCommits(commits, baseDir = process.cwd()) {
28
+ ensureRelqDir(baseDir);
29
+ const filePath = path.join(baseDir, RELQ_DIR, COMMITS_FILE);
30
+ fs.writeFileSync(filePath, JSON.stringify(commits, null, 2), 'utf-8');
31
+ }
32
+ export function getLocalHead(baseDir = process.cwd()) {
33
+ const filePath = path.join(baseDir, RELQ_DIR, HEAD_FILE);
34
+ if (!fs.existsSync(filePath)) {
35
+ return null;
36
+ }
37
+ return fs.readFileSync(filePath, 'utf-8').trim() || null;
38
+ }
39
+ export function setLocalHead(hash, baseDir = process.cwd()) {
40
+ ensureRelqDir(baseDir);
41
+ const filePath = path.join(baseDir, RELQ_DIR, HEAD_FILE);
42
+ fs.writeFileSync(filePath, hash, 'utf-8');
43
+ }
44
+ export function addLocalCommit(commit, limit = 1000, baseDir = process.cwd()) {
45
+ const commits = getLocalCommits(baseDir);
46
+ commits.push(commit);
47
+ while (commits.length > limit) {
48
+ commits.shift();
49
+ }
50
+ saveLocalCommits(commits, baseDir);
51
+ setLocalHead(commit.hash, baseDir);
52
+ }
53
+ export function getLatestLocalCommit(baseDir = process.cwd()) {
54
+ const commits = getLocalCommits(baseDir);
55
+ return commits.length > 0 ? commits[commits.length - 1] : null;
56
+ }
57
+ const CREATE_TABLE_SQL = `
58
+ CREATE TABLE IF NOT EXISTS _relq_commits (
59
+ id SERIAL PRIMARY KEY,
60
+ hash VARCHAR(40) UNIQUE NOT NULL,
61
+ parent_hash VARCHAR(40),
62
+ schema_snapshot JSONB NOT NULL,
63
+ author VARCHAR(255) NOT NULL,
64
+ message TEXT,
65
+ created_at TIMESTAMPTZ DEFAULT NOW()
66
+ );
67
+
68
+ CREATE INDEX IF NOT EXISTS idx_relq_commits_created
69
+ ON _relq_commits(created_at DESC);
70
+ `;
71
+ export async function ensureCommitsTable(connection) {
72
+ const { Pool } = await import("../../addon/pg.js");
73
+ const pool = new Pool({
74
+ host: connection.host,
75
+ port: connection.port || 5432,
76
+ database: connection.database,
77
+ user: connection.user,
78
+ password: connection.password,
79
+ connectionString: connection.url,
80
+ ssl: connection.ssl,
81
+ });
82
+ try {
83
+ await pool.query(CREATE_TABLE_SQL);
84
+ }
85
+ finally {
86
+ await pool.end();
87
+ }
88
+ }
89
+ export async function getRemoteCommits(connection, limit = 100) {
90
+ const { Pool } = await import("../../addon/pg.js");
91
+ const pool = new Pool({
92
+ host: connection.host,
93
+ port: connection.port || 5432,
94
+ database: connection.database,
95
+ user: connection.user,
96
+ password: connection.password,
97
+ connectionString: connection.url,
98
+ ssl: connection.ssl,
99
+ });
100
+ try {
101
+ await ensureCommitsTable(connection);
102
+ const result = await pool.query(`
103
+ SELECT id, hash, parent_hash, schema_snapshot, author, message, created_at
104
+ FROM _relq_commits
105
+ ORDER BY created_at DESC
106
+ LIMIT $1
107
+ `, [limit]);
108
+ return result.rows.map((row) => ({
109
+ id: row.id,
110
+ hash: row.hash,
111
+ parentHash: row.parent_hash,
112
+ schemaSnapshot: row.schema_snapshot,
113
+ author: row.author,
114
+ message: row.message,
115
+ createdAt: new Date(row.created_at),
116
+ }));
117
+ }
118
+ finally {
119
+ await pool.end();
120
+ }
121
+ }
122
+ export async function getLatestRemoteCommit(connection) {
123
+ const commits = await getRemoteCommits(connection, 1);
124
+ return commits.length > 0 ? commits[0] : null;
125
+ }
126
+ export async function addRemoteCommit(connection, commit, limit = 1000) {
127
+ const { Pool } = await import("../../addon/pg.js");
128
+ const pool = new Pool({
129
+ host: connection.host,
130
+ port: connection.port || 5432,
131
+ database: connection.database,
132
+ user: connection.user,
133
+ password: connection.password,
134
+ connectionString: connection.url,
135
+ ssl: connection.ssl,
136
+ });
137
+ try {
138
+ await ensureCommitsTable(connection);
139
+ await pool.query(`
140
+ INSERT INTO _relq_commits (hash, parent_hash, schema_snapshot, author, message)
141
+ VALUES ($1, $2, $3, $4, $5)
142
+ ON CONFLICT (hash) DO NOTHING
143
+ `, [
144
+ commit.hash,
145
+ commit.parentHash || null,
146
+ JSON.stringify(commit.schemaSnapshot),
147
+ commit.author,
148
+ commit.message || null,
149
+ ]);
150
+ await pool.query(`
151
+ DELETE FROM _relq_commits
152
+ WHERE id NOT IN (
153
+ SELECT id FROM _relq_commits
154
+ ORDER BY created_at DESC
155
+ LIMIT $1
156
+ )
157
+ `, [limit]);
158
+ }
159
+ finally {
160
+ await pool.end();
161
+ }
162
+ }
163
+ export async function createCommit(connection, schema, author, message, commitLimit = 1000, baseDir = process.cwd()) {
164
+ const hash = generateSchemaHash(schema);
165
+ const normalized = normalizeSchema(schema);
166
+ const parentHash = getLocalHead(baseDir);
167
+ const commit = {
168
+ hash,
169
+ parentHash,
170
+ schemaSnapshot: normalized,
171
+ author,
172
+ message,
173
+ createdAt: new Date(),
174
+ };
175
+ addLocalCommit(commit, commitLimit, baseDir);
176
+ await addRemoteCommit(connection, commit, commitLimit);
177
+ return commit;
178
+ }
179
+ export async function checkSyncStatus(connection, baseDir = process.cwd()) {
180
+ const localHead = getLocalHead(baseDir);
181
+ const remoteCommit = await getLatestRemoteCommit(connection);
182
+ const remoteHead = remoteCommit?.hash || null;
183
+ const localCommits = getLocalCommits(baseDir);
184
+ const remoteCommits = await getRemoteCommits(connection, 100);
185
+ const inSync = localHead === remoteHead;
186
+ const localHashes = new Set(localCommits.map(c => c.hash));
187
+ const remoteHashes = new Set(remoteCommits.map(c => c.hash));
188
+ const localAhead = [...localHashes].filter(h => !remoteHashes.has(h)).length;
189
+ const remoteAhead = [...remoteHashes].filter(h => !localHashes.has(h)).length;
190
+ return { inSync, localHead, remoteHead, localAhead, remoteAhead };
191
+ }
@@ -0,0 +1,86 @@
1
+ import { loadConfig as baseLoadConfig } from "../../config/config.js";
2
+ import { loadEnvConfig } from "./env-loader.js";
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ const CONFIG_FILENAMES = [
6
+ 'relq.config.ts',
7
+ 'relq.config.js',
8
+ 'relq.config.mjs',
9
+ ];
10
+ export function findConfigFile(cwd = process.cwd()) {
11
+ for (const filename of CONFIG_FILENAMES) {
12
+ const filepath = path.join(cwd, filename);
13
+ if (fs.existsSync(filepath)) {
14
+ return filepath;
15
+ }
16
+ }
17
+ return null;
18
+ }
19
+ export function findConfigFileRecursive(startDir = process.cwd()) {
20
+ let currentDir = path.resolve(startDir);
21
+ const root = path.parse(currentDir).root;
22
+ while (currentDir !== root) {
23
+ const found = findConfigFile(currentDir);
24
+ if (found) {
25
+ return { path: found, directory: currentDir };
26
+ }
27
+ currentDir = path.dirname(currentDir);
28
+ }
29
+ return null;
30
+ }
31
+ export function configExists(cwd = process.cwd()) {
32
+ return findConfigFile(cwd) !== null;
33
+ }
34
+ export async function loadConfigWithEnv(configPath) {
35
+ let config;
36
+ if (configPath) {
37
+ config = await baseLoadConfig(configPath);
38
+ }
39
+ else {
40
+ const foundPath = findConfigFile();
41
+ if (foundPath) {
42
+ config = await baseLoadConfig(foundPath);
43
+ }
44
+ else {
45
+ config = {};
46
+ }
47
+ }
48
+ if (!config.connection?.host && !config.connection?.url) {
49
+ const envConfig = loadEnvConfig();
50
+ if (envConfig) {
51
+ config = {
52
+ ...config,
53
+ connection: {
54
+ ...config.connection,
55
+ ...envConfig,
56
+ },
57
+ };
58
+ }
59
+ }
60
+ return config;
61
+ }
62
+ export function validateConfig(config) {
63
+ const errors = [];
64
+ if (!config.connection?.host && !config.connection?.url) {
65
+ errors.push('No database connection configured. Set connection in relq.config.ts or use DATABASE_* env vars.');
66
+ }
67
+ const hasSchemaPath = typeof config.schema === 'string';
68
+ const hasSchemaDir = typeof config.schema === 'object' && config.schema?.directory;
69
+ const hasTypeGenOutput = config.typeGeneration?.output;
70
+ const hasGenerateOutDir = config.generate?.outDir;
71
+ if (!hasSchemaPath && !hasSchemaDir && !hasTypeGenOutput && !hasGenerateOutDir) {
72
+ errors.push('No schema output path configured. Set schema, generate.outDir, or typeGeneration.output in relq.config.ts.');
73
+ }
74
+ return errors;
75
+ }
76
+ export function requireValidConfig(config) {
77
+ const errors = validateConfig(config);
78
+ if (errors.length > 0) {
79
+ console.error('Configuration errors:');
80
+ for (const error of errors) {
81
+ console.error(` • ${error}`);
82
+ }
83
+ console.error('\nRun "relq init" to create a configuration file.');
84
+ process.exit(1);
85
+ }
86
+ }