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,418 @@
1
+ export async function introspectDatabase(connection, onProgress, options) {
2
+ const { includeFunctions = false, includeTriggers = false } = options || {};
3
+ const { Pool } = await import("../../addon/pg.js");
4
+ onProgress?.('connecting', connection.database);
5
+ const pool = new Pool({
6
+ host: connection.host,
7
+ port: connection.port || 5432,
8
+ database: connection.database,
9
+ user: connection.user,
10
+ password: connection.password,
11
+ connectionString: connection.url,
12
+ ssl: connection.ssl,
13
+ });
14
+ try {
15
+ onProgress?.('fetching_tables');
16
+ const tablesResult = await pool.query(`
17
+ SELECT
18
+ t.table_name,
19
+ t.table_schema,
20
+ (SELECT reltuples::bigint FROM pg_class WHERE relname = t.table_name) as row_count
21
+ FROM information_schema.tables t
22
+ WHERE t.table_schema = 'public'
23
+ AND t.table_type = 'BASE TABLE'
24
+ ORDER BY t.table_name;
25
+ `);
26
+ const tables = [];
27
+ const partitionNamesResult = await pool.query(`
28
+ SELECT c.relname as name
29
+ FROM pg_class c
30
+ JOIN pg_namespace n ON c.relnamespace = n.oid
31
+ WHERE n.nspname = 'public' AND c.relispartition = true;
32
+ `);
33
+ const partitionTableNames = new Set(partitionNamesResult.rows.map((r) => r.name));
34
+ const nonPartitionTables = tablesResult.rows.filter((r) => !partitionTableNames.has(r.table_name) && !r.table_name.startsWith('_relq'));
35
+ const totalTables = nonPartitionTables.length;
36
+ let tableIndex = 0;
37
+ for (let i = 0; i < tablesResult.rows.length; i++) {
38
+ const row = tablesResult.rows[i];
39
+ const tableName = row.table_name;
40
+ const tableSchema = row.table_schema;
41
+ const rowCount = parseInt(row.row_count) || 0;
42
+ const isPartition = partitionTableNames.has(tableName);
43
+ const isInternal = tableName.startsWith('_relq');
44
+ if (!isPartition && !isInternal) {
45
+ tableIndex++;
46
+ onProgress?.('parsing_table', `${tableName} (${tableIndex}/${totalTables})`);
47
+ }
48
+ const columnsResult = await pool.query(`
49
+ SELECT
50
+ c.column_name,
51
+ c.data_type,
52
+ c.udt_name,
53
+ c.is_nullable,
54
+ c.column_default,
55
+ c.character_maximum_length,
56
+ c.numeric_precision,
57
+ c.numeric_scale,
58
+ COALESCE(pk.is_pk, false) as is_primary_key,
59
+ COALESCE(uq.is_unique, false) as is_unique,
60
+ fk.foreign_table,
61
+ fk.foreign_column,
62
+ col_description(pgc.oid, c.ordinal_position::int) as column_comment
63
+ FROM information_schema.columns c
64
+ JOIN pg_class pgc ON pgc.relname = c.table_name
65
+ JOIN pg_namespace pgn ON pgn.oid = pgc.relnamespace AND pgn.nspname = c.table_schema
66
+ LEFT JOIN (
67
+ SELECT kcu.column_name, true as is_pk
68
+ FROM information_schema.table_constraints tc
69
+ JOIN information_schema.key_column_usage kcu
70
+ ON tc.constraint_name = kcu.constraint_name
71
+ WHERE tc.table_name = $1 AND tc.constraint_type = 'PRIMARY KEY'
72
+ ) pk ON pk.column_name = c.column_name
73
+ LEFT JOIN (
74
+ SELECT kcu.column_name, true as is_unique
75
+ FROM information_schema.table_constraints tc
76
+ JOIN information_schema.key_column_usage kcu
77
+ ON tc.constraint_name = kcu.constraint_name
78
+ WHERE tc.table_name = $1 AND tc.constraint_type = 'UNIQUE'
79
+ ) uq ON uq.column_name = c.column_name
80
+ LEFT JOIN (
81
+ SELECT
82
+ kcu.column_name,
83
+ ccu.table_name as foreign_table,
84
+ ccu.column_name as foreign_column
85
+ FROM information_schema.table_constraints tc
86
+ JOIN information_schema.key_column_usage kcu
87
+ ON tc.constraint_name = kcu.constraint_name
88
+ JOIN information_schema.constraint_column_usage ccu
89
+ ON tc.constraint_name = ccu.constraint_name
90
+ WHERE tc.table_name = $1 AND tc.constraint_type = 'FOREIGN KEY'
91
+ ) fk ON fk.column_name = c.column_name
92
+ WHERE c.table_name = $1 AND c.table_schema = $2
93
+ ORDER BY c.ordinal_position;
94
+ `, [tableName, tableSchema]);
95
+ const columns = columnsResult.rows.map(col => ({
96
+ name: col.column_name,
97
+ dataType: col.udt_name || col.data_type,
98
+ isNullable: col.is_nullable === 'YES',
99
+ defaultValue: col.column_default,
100
+ isPrimaryKey: col.is_primary_key,
101
+ isUnique: col.is_unique,
102
+ maxLength: col.character_maximum_length,
103
+ precision: col.numeric_precision,
104
+ scale: col.numeric_scale,
105
+ references: col.foreign_table ? {
106
+ table: col.foreign_table,
107
+ column: col.foreign_column,
108
+ } : null,
109
+ comment: col.column_comment || undefined,
110
+ }));
111
+ const indexesResult = await pool.query(`
112
+ SELECT
113
+ i.relname as index_name,
114
+ array_agg(a.attname ORDER BY k.n) as columns,
115
+ ix.indisunique as is_unique,
116
+ ix.indisprimary as is_primary,
117
+ am.amname as index_type
118
+ FROM pg_index ix
119
+ JOIN pg_class t ON t.oid = ix.indrelid
120
+ JOIN pg_class i ON i.oid = ix.indexrelid
121
+ JOIN pg_am am ON am.oid = i.relam
122
+ JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, n) ON true
123
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
124
+ WHERE t.relname = $1
125
+ GROUP BY i.relname, ix.indisunique, ix.indisprimary, am.amname
126
+ ORDER BY i.relname;
127
+ `, [tableName]);
128
+ const indexes = indexesResult.rows.map(idx => ({
129
+ name: idx.index_name,
130
+ columns: idx.columns,
131
+ isUnique: idx.is_unique,
132
+ isPrimary: idx.is_primary,
133
+ type: idx.index_type,
134
+ }));
135
+ const constraintsResult = await pool.query(`
136
+ SELECT
137
+ con.conname as name,
138
+ CASE con.contype
139
+ WHEN 'p' THEN 'PRIMARY KEY'
140
+ WHEN 'f' THEN 'FOREIGN KEY'
141
+ WHEN 'u' THEN 'UNIQUE'
142
+ WHEN 'c' THEN 'CHECK'
143
+ WHEN 'x' THEN 'EXCLUDE'
144
+ END as type,
145
+ array_agg(a.attname ORDER BY u.attposition) as columns,
146
+ pg_get_constraintdef(con.oid) as definition,
147
+ conf.relname as referenced_table
148
+ FROM pg_constraint con
149
+ JOIN pg_class c ON c.oid = con.conrelid
150
+ JOIN pg_namespace n ON n.oid = c.relnamespace
151
+ LEFT JOIN pg_class conf ON conf.oid = con.confrelid
152
+ LEFT JOIN unnest(con.conkey) WITH ORDINALITY u(attnum, attposition) ON true
153
+ LEFT JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = u.attnum
154
+ WHERE c.relname = $1 AND n.nspname = 'public'
155
+ GROUP BY con.oid, con.conname, con.contype, conf.relname
156
+ ORDER BY con.conname;
157
+ `, [tableName]);
158
+ const constraints = constraintsResult.rows.map(c => ({
159
+ name: c.name,
160
+ type: c.type,
161
+ columns: c.columns || [],
162
+ definition: c.definition || '',
163
+ referencedTable: c.referenced_table,
164
+ }));
165
+ const partitionCheckResult = await pool.query(`
166
+ SELECT
167
+ c.relkind = 'p' as is_partitioned,
168
+ CASE pt.partstrat
169
+ WHEN 'r' THEN 'RANGE'
170
+ WHEN 'l' THEN 'LIST'
171
+ WHEN 'h' THEN 'HASH'
172
+ END as partition_type,
173
+ array_agg(a.attname) as partition_key
174
+ FROM pg_class c
175
+ JOIN pg_namespace n ON n.oid = c.relnamespace
176
+ LEFT JOIN pg_partitioned_table pt ON pt.partrelid = c.oid
177
+ LEFT JOIN unnest(pt.partattrs) WITH ORDINALITY pk(attnum, ord) ON true
178
+ LEFT JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = pk.attnum
179
+ WHERE c.relname = $1 AND n.nspname = 'public'
180
+ GROUP BY c.relkind, pt.partstrat;
181
+ `, [tableName]);
182
+ const partitionInfo = partitionCheckResult.rows[0] || {};
183
+ let partitionKey;
184
+ if (partitionInfo.partition_key) {
185
+ partitionKey = Array.isArray(partitionInfo.partition_key)
186
+ ? partitionInfo.partition_key.filter(Boolean)
187
+ : typeof partitionInfo.partition_key === 'string'
188
+ ? [partitionInfo.partition_key]
189
+ : undefined;
190
+ }
191
+ tables.push({
192
+ name: tableName,
193
+ schema: tableSchema,
194
+ columns,
195
+ indexes,
196
+ constraints,
197
+ rowCount,
198
+ isPartitioned: partitionInfo.is_partitioned || false,
199
+ partitionType: partitionInfo.partition_type,
200
+ partitionKey,
201
+ });
202
+ }
203
+ onProgress?.('fetching_extensions');
204
+ const extensionsResult = await pool.query(`
205
+ SELECT extname FROM pg_extension WHERE extname != 'plpgsql';
206
+ `);
207
+ const extensions = extensionsResult.rows.map(r => r.extname);
208
+ let functions = [];
209
+ if (includeFunctions) {
210
+ onProgress?.('fetching_functions');
211
+ const functionsResult = await pool.query(`
212
+ SELECT
213
+ p.proname as name,
214
+ n.nspname as schema,
215
+ pg_get_function_result(p.oid) as return_type,
216
+ pg_get_function_arguments(p.oid) as arg_types,
217
+ l.lanname as language,
218
+ pg_get_functiondef(p.oid) as definition,
219
+ p.prokind = 'a' as is_aggregate,
220
+ p.provolatile as volatility
221
+ FROM pg_proc p
222
+ JOIN pg_namespace n ON p.pronamespace = n.oid
223
+ JOIN pg_language l ON p.prolang = l.oid
224
+ WHERE n.nspname = 'public'
225
+ AND p.prokind IN ('f', 'a')
226
+ ORDER BY p.proname;
227
+ `);
228
+ functions = functionsResult.rows.map(f => ({
229
+ name: f.name,
230
+ schema: f.schema,
231
+ returnType: f.return_type,
232
+ argTypes: f.arg_types ? f.arg_types.split(', ') : [],
233
+ language: f.language,
234
+ definition: f.definition || '',
235
+ isAggregate: f.is_aggregate || false,
236
+ volatility: f.volatility === 'i' ? 'IMMUTABLE' : f.volatility === 's' ? 'STABLE' : 'VOLATILE',
237
+ }));
238
+ }
239
+ let triggers = [];
240
+ if (includeTriggers) {
241
+ const triggersResult = await pool.query(`
242
+ SELECT
243
+ t.tgname as name,
244
+ c.relname as table_name,
245
+ CASE
246
+ WHEN t.tgtype & 2 > 0 THEN 'BEFORE'
247
+ WHEN t.tgtype & 64 > 0 THEN 'INSTEAD OF'
248
+ ELSE 'AFTER'
249
+ END as timing,
250
+ CASE
251
+ WHEN t.tgtype & 4 > 0 THEN 'INSERT'
252
+ WHEN t.tgtype & 8 > 0 THEN 'DELETE'
253
+ WHEN t.tgtype & 16 > 0 THEN 'UPDATE'
254
+ ELSE 'UNKNOWN'
255
+ END as event,
256
+ p.proname as function_name,
257
+ pg_get_triggerdef(t.oid) as definition,
258
+ t.tgenabled != 'D' as is_enabled
259
+ FROM pg_trigger t
260
+ JOIN pg_class c ON t.tgrelid = c.oid
261
+ JOIN pg_namespace n ON c.relnamespace = n.oid
262
+ JOIN pg_proc p ON t.tgfoid = p.oid
263
+ WHERE n.nspname = 'public'
264
+ AND NOT t.tgisinternal
265
+ ORDER BY c.relname, t.tgname;
266
+ `);
267
+ triggers = triggersResult.rows.map(t => ({
268
+ name: t.name,
269
+ tableName: t.table_name,
270
+ timing: t.timing,
271
+ event: t.event,
272
+ functionName: t.function_name,
273
+ definition: t.definition || '',
274
+ isEnabled: t.is_enabled,
275
+ }));
276
+ }
277
+ const policiesResult = await pool.query(`
278
+ SELECT
279
+ pol.polname as name,
280
+ c.relname as table_name,
281
+ CASE pol.polcmd
282
+ WHEN '*' THEN 'ALL'
283
+ WHEN 'r' THEN 'SELECT'
284
+ WHEN 'a' THEN 'INSERT'
285
+ WHEN 'w' THEN 'UPDATE'
286
+ WHEN 'd' THEN 'DELETE'
287
+ END as command,
288
+ array_agg(r.rolname) as roles,
289
+ pg_get_expr(pol.polqual, pol.polrelid) as using_expr,
290
+ pg_get_expr(pol.polwithcheck, pol.polrelid) as with_check
291
+ FROM pg_policy pol
292
+ JOIN pg_class c ON pol.polrelid = c.oid
293
+ JOIN pg_namespace n ON c.relnamespace = n.oid
294
+ LEFT JOIN unnest(pol.polroles) WITH ORDINALITY pr(oid, ord) ON true
295
+ LEFT JOIN pg_roles r ON r.oid = pr.oid
296
+ WHERE n.nspname = 'public'
297
+ GROUP BY pol.polname, c.relname, pol.polcmd, pol.polqual, pol.polwithcheck, pol.polrelid
298
+ ORDER BY c.relname, pol.polname;
299
+ `);
300
+ const policies = policiesResult.rows.map(p => ({
301
+ name: p.name,
302
+ tableName: p.table_name,
303
+ command: p.command,
304
+ roles: p.roles?.filter(Boolean) || [],
305
+ using: p.using_expr,
306
+ withCheck: p.with_check,
307
+ }));
308
+ const partitionsResult = await pool.query(`
309
+ SELECT
310
+ c.relname as name,
311
+ parent.relname as parent_table,
312
+ CASE pt.partstrat
313
+ WHEN 'r' THEN 'RANGE'
314
+ WHEN 'l' THEN 'LIST'
315
+ WHEN 'h' THEN 'HASH'
316
+ END as partition_type,
317
+ pg_get_expr(c.relpartbound, c.oid) as partition_bound
318
+ FROM pg_class c
319
+ JOIN pg_inherits i ON c.oid = i.inhrelid
320
+ JOIN pg_class parent ON parent.oid = i.inhparent
321
+ JOIN pg_namespace n ON c.relnamespace = n.oid
322
+ LEFT JOIN pg_partitioned_table pt ON pt.partrelid = parent.oid
323
+ WHERE n.nspname = 'public'
324
+ AND c.relispartition
325
+ ORDER BY parent.relname, c.relname;
326
+ `);
327
+ const partitions = partitionsResult.rows.map(p => ({
328
+ name: p.name,
329
+ parentTable: p.parent_table,
330
+ partitionType: p.partition_type || 'RANGE',
331
+ partitionKey: [],
332
+ partitionBound: p.partition_bound || '',
333
+ }));
334
+ const foreignServersResult = await pool.query(`
335
+ SELECT
336
+ s.srvname as name,
337
+ f.fdwname as foreign_data_wrapper,
338
+ s.srvoptions as options
339
+ FROM pg_foreign_server s
340
+ JOIN pg_foreign_data_wrapper f ON f.oid = s.srvfdw
341
+ ORDER BY s.srvname;
342
+ `);
343
+ const foreignServers = foreignServersResult.rows.map(s => ({
344
+ name: s.name,
345
+ foreignDataWrapper: s.foreign_data_wrapper,
346
+ options: parseOptions(s.options),
347
+ }));
348
+ const foreignTablesResult = await pool.query(`
349
+ SELECT
350
+ c.relname as name,
351
+ n.nspname as schema,
352
+ s.srvname as server_name,
353
+ ft.ftoptions as options
354
+ FROM pg_foreign_table ft
355
+ JOIN pg_class c ON c.oid = ft.ftrelid
356
+ JOIN pg_namespace n ON n.oid = c.relnamespace
357
+ JOIN pg_foreign_server s ON s.oid = ft.ftserver
358
+ WHERE n.nspname = 'public'
359
+ ORDER BY c.relname;
360
+ `);
361
+ const foreignTables = foreignTablesResult.rows.map(t => ({
362
+ name: t.name,
363
+ schema: t.schema,
364
+ serverName: t.server_name,
365
+ columns: [],
366
+ options: parseOptions(t.options),
367
+ }));
368
+ return {
369
+ tables,
370
+ enums: [],
371
+ domains: [],
372
+ compositeTypes: [],
373
+ sequences: [],
374
+ functions,
375
+ triggers,
376
+ policies,
377
+ partitions,
378
+ foreignServers,
379
+ foreignTables,
380
+ extensions
381
+ };
382
+ }
383
+ finally {
384
+ await pool.end();
385
+ }
386
+ }
387
+ function parseOptions(options) {
388
+ if (!options)
389
+ return {};
390
+ const result = {};
391
+ for (const opt of options) {
392
+ const [key, value] = opt.split('=');
393
+ if (key)
394
+ result[key] = value || '';
395
+ }
396
+ return result;
397
+ }
398
+ export async function tableHasData(connection, tableName) {
399
+ const { Pool } = await import("../../addon/pg.js");
400
+ const pool = new Pool({
401
+ host: connection.host,
402
+ port: connection.port || 5432,
403
+ database: connection.database,
404
+ user: connection.user,
405
+ password: connection.password,
406
+ connectionString: connection.url,
407
+ });
408
+ try {
409
+ const result = await pool.query(`SELECT EXISTS(SELECT 1 FROM "${tableName}" LIMIT 1) as has_data`);
410
+ return result.rows[0]?.has_data || false;
411
+ }
412
+ catch {
413
+ return false;
414
+ }
415
+ finally {
416
+ await pool.end();
417
+ }
418
+ }
@@ -0,0 +1,179 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ const SNAPSHOT_VERSION = 1;
4
+ const DEFAULT_SNAPSHOT_PATH = '.relq/snapshot.json';
5
+ export function loadSnapshot(snapshotPath) {
6
+ const filePath = snapshotPath || DEFAULT_SNAPSHOT_PATH;
7
+ if (!fs.existsSync(filePath)) {
8
+ return null;
9
+ }
10
+ try {
11
+ const content = fs.readFileSync(filePath, 'utf-8');
12
+ const snapshot = JSON.parse(content);
13
+ if (snapshot.version !== SNAPSHOT_VERSION) {
14
+ console.warn(`Snapshot version mismatch: expected ${SNAPSHOT_VERSION}, got ${snapshot.version}`);
15
+ }
16
+ return snapshot;
17
+ }
18
+ catch (error) {
19
+ console.error('Failed to load snapshot:', error);
20
+ return null;
21
+ }
22
+ }
23
+ export function saveSnapshot(schema, snapshotPath, database) {
24
+ const filePath = snapshotPath || DEFAULT_SNAPSHOT_PATH;
25
+ const dir = path.dirname(filePath);
26
+ if (!fs.existsSync(dir)) {
27
+ fs.mkdirSync(dir, { recursive: true });
28
+ }
29
+ const snapshot = databaseSchemaToSnapshot(schema, database);
30
+ fs.writeFileSync(filePath, JSON.stringify(snapshot, null, 2), 'utf-8');
31
+ }
32
+ export function databaseSchemaToSnapshot(schema, database) {
33
+ const tables = {};
34
+ for (const table of schema.tables) {
35
+ tables[table.name] = tableToSnapshot(table);
36
+ }
37
+ return {
38
+ version: SNAPSHOT_VERSION,
39
+ generatedAt: new Date().toISOString(),
40
+ database,
41
+ tables,
42
+ appliedMigrations: [],
43
+ extensions: schema.extensions || [],
44
+ };
45
+ }
46
+ function tableToSnapshot(table) {
47
+ const columns = {};
48
+ const indexes = {};
49
+ const constraints = {};
50
+ for (const col of table.columns) {
51
+ columns[col.name] = {
52
+ name: col.name,
53
+ dataType: col.dataType,
54
+ isNullable: col.isNullable,
55
+ defaultValue: col.defaultValue,
56
+ isPrimaryKey: col.isPrimaryKey,
57
+ isUnique: col.isUnique,
58
+ maxLength: col.maxLength,
59
+ precision: col.precision,
60
+ scale: col.scale,
61
+ references: col.references,
62
+ };
63
+ }
64
+ for (const idx of table.indexes || []) {
65
+ indexes[idx.name] = {
66
+ name: idx.name,
67
+ columns: Array.isArray(idx.columns) ? idx.columns : [idx.columns],
68
+ isUnique: idx.isUnique,
69
+ isPrimary: idx.isPrimary,
70
+ type: idx.type,
71
+ };
72
+ }
73
+ for (const con of table.constraints || []) {
74
+ constraints[con.name] = {
75
+ name: con.name,
76
+ type: con.type,
77
+ columns: con.columns || [],
78
+ definition: con.definition,
79
+ referencedTable: con.referencedTable,
80
+ };
81
+ }
82
+ return {
83
+ name: table.name,
84
+ schema: table.schema,
85
+ columns,
86
+ indexes,
87
+ constraints,
88
+ isPartitioned: table.isPartitioned,
89
+ partitionType: table.partitionType,
90
+ partitionKey: table.partitionKey,
91
+ };
92
+ }
93
+ export function snapshotToDatabaseSchema(snapshot) {
94
+ const tables = [];
95
+ for (const [, tableSnapshot] of Object.entries(snapshot.tables)) {
96
+ tables.push(snapshotToTable(tableSnapshot));
97
+ }
98
+ return {
99
+ tables,
100
+ enums: [],
101
+ domains: [],
102
+ compositeTypes: [],
103
+ sequences: [],
104
+ functions: [],
105
+ triggers: [],
106
+ policies: [],
107
+ partitions: [],
108
+ foreignServers: [],
109
+ foreignTables: [],
110
+ extensions: snapshot.extensions,
111
+ };
112
+ }
113
+ function snapshotToTable(snapshot) {
114
+ const columns = Object.values(snapshot.columns);
115
+ const indexes = Object.values(snapshot.indexes);
116
+ const constraints = Object.values(snapshot.constraints).map(c => ({
117
+ ...c,
118
+ type: c.type,
119
+ }));
120
+ return {
121
+ name: snapshot.name,
122
+ schema: snapshot.schema,
123
+ columns,
124
+ indexes,
125
+ constraints,
126
+ rowCount: 0,
127
+ isPartitioned: snapshot.isPartitioned || false,
128
+ partitionType: snapshot.partitionType,
129
+ partitionKey: snapshot.partitionKey,
130
+ };
131
+ }
132
+ export function createBackup(filePath, backupDir) {
133
+ if (!fs.existsSync(filePath)) {
134
+ return null;
135
+ }
136
+ const dir = backupDir || '.relq/backups';
137
+ if (!fs.existsSync(dir)) {
138
+ fs.mkdirSync(dir, { recursive: true });
139
+ }
140
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
141
+ const fileName = path.basename(filePath);
142
+ const backupPath = path.join(dir, `${fileName}.${timestamp}.bak`);
143
+ fs.copyFileSync(filePath, backupPath);
144
+ return backupPath;
145
+ }
146
+ export function listBackups(filePath, backupDir) {
147
+ const dir = backupDir || '.relq/backups';
148
+ if (!fs.existsSync(dir)) {
149
+ return [];
150
+ }
151
+ const fileName = path.basename(filePath);
152
+ const files = fs.readdirSync(dir);
153
+ return files
154
+ .filter(f => f.startsWith(fileName) && f.endsWith('.bak'))
155
+ .map(f => path.join(dir, f))
156
+ .sort()
157
+ .reverse();
158
+ }
159
+ export function restoreBackup(backupPath, targetPath) {
160
+ if (!fs.existsSync(backupPath)) {
161
+ throw new Error(`Backup not found: ${backupPath}`);
162
+ }
163
+ fs.copyFileSync(backupPath, targetPath);
164
+ }
165
+ export function addMigrationToSnapshot(snapshotPath, migrationName) {
166
+ const snapshot = loadSnapshot(snapshotPath);
167
+ if (!snapshot) {
168
+ console.warn('No snapshot found, creating new one');
169
+ return;
170
+ }
171
+ if (!snapshot.appliedMigrations.includes(migrationName)) {
172
+ snapshot.appliedMigrations.push(migrationName);
173
+ fs.writeFileSync(snapshotPath || DEFAULT_SNAPSHOT_PATH, JSON.stringify(snapshot, null, 2), 'utf-8');
174
+ }
175
+ }
176
+ export function getAppliedMigrations(snapshotPath) {
177
+ const snapshot = loadSnapshot(snapshotPath);
178
+ return snapshot?.appliedMigrations || [];
179
+ }
@@ -0,0 +1,101 @@
1
+ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
2
+ const SPINNER_INTERVAL = 80;
3
+ export function createSpinner() {
4
+ let intervalId = null;
5
+ let frameIndex = 0;
6
+ let currentMessage = '';
7
+ let isSpinning = false;
8
+ const clearLine = () => {
9
+ process.stdout.write('\r\x1b[K');
10
+ };
11
+ const render = () => {
12
+ if (!isSpinning)
13
+ return;
14
+ clearLine();
15
+ const frame = SPINNER_FRAMES[frameIndex];
16
+ process.stdout.write(`\x1b[36m${frame}\x1b[0m ${currentMessage}`);
17
+ frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
18
+ };
19
+ return {
20
+ start(message) {
21
+ if (isSpinning)
22
+ this.stop();
23
+ currentMessage = message;
24
+ isSpinning = true;
25
+ frameIndex = 0;
26
+ intervalId = setInterval(render, SPINNER_INTERVAL);
27
+ render();
28
+ },
29
+ update(message) {
30
+ currentMessage = message;
31
+ },
32
+ succeed(message) {
33
+ this.stop();
34
+ console.log(`\x1b[32m✓\x1b[0m ${message || currentMessage}`);
35
+ },
36
+ fail(message) {
37
+ this.stop();
38
+ console.log(`\x1b[31m✗\x1b[0m ${message || currentMessage}`);
39
+ },
40
+ info(message) {
41
+ this.stop();
42
+ console.log(`\x1b[34mℹ\x1b[0m ${message || currentMessage}`);
43
+ },
44
+ warn(message) {
45
+ this.stop();
46
+ console.log(`\x1b[33m⚠\x1b[0m ${message || currentMessage}`);
47
+ },
48
+ stop() {
49
+ if (intervalId) {
50
+ clearInterval(intervalId);
51
+ intervalId = null;
52
+ }
53
+ if (isSpinning) {
54
+ clearLine();
55
+ isSpinning = false;
56
+ }
57
+ }
58
+ };
59
+ }
60
+ export const colors = {
61
+ reset: '\x1b[0m',
62
+ bold: (text) => `\x1b[1m${text}\x1b[0m`,
63
+ dim: (text) => `\x1b[2m${text}\x1b[0m`,
64
+ red: (text) => `\x1b[31m${text}\x1b[0m`,
65
+ green: (text) => `\x1b[32m${text}\x1b[0m`,
66
+ yellow: (text) => `\x1b[33m${text}\x1b[0m`,
67
+ blue: (text) => `\x1b[34m${text}\x1b[0m`,
68
+ magenta: (text) => `\x1b[35m${text}\x1b[0m`,
69
+ cyan: (text) => `\x1b[36m${text}\x1b[0m`,
70
+ gray: (text) => `\x1b[90m${text}\x1b[0m`,
71
+ white: (text) => `\x1b[37m${text}\x1b[0m`,
72
+ success: (text) => `\x1b[32m${text}\x1b[0m`,
73
+ error: (text) => `\x1b[31m${text}\x1b[0m`,
74
+ warning: (text) => `\x1b[33m${text}\x1b[0m`,
75
+ info: (text) => `\x1b[34m${text}\x1b[0m`,
76
+ muted: (text) => `\x1b[90m${text}\x1b[0m`,
77
+ };
78
+ export function progressBar(current, total, width = 30) {
79
+ const percentage = Math.min(100, Math.round((current / total) * 100));
80
+ const filled = Math.round((percentage / 100) * width);
81
+ const empty = width - filled;
82
+ const bar = '█'.repeat(filled) + '░'.repeat(empty);
83
+ return `\x1b[36m${bar}\x1b[0m ${percentage}%`;
84
+ }
85
+ export function formatBytes(bytes) {
86
+ if (bytes === 0)
87
+ return '0 B';
88
+ const k = 1024;
89
+ const sizes = ['B', 'KB', 'MB', 'GB'];
90
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
91
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
92
+ }
93
+ export function formatDuration(ms) {
94
+ if (ms < 1000)
95
+ return `${ms}ms`;
96
+ if (ms < 60000)
97
+ return `${(ms / 1000).toFixed(1)}s`;
98
+ const minutes = Math.floor(ms / 60000);
99
+ const seconds = Math.round((ms % 60000) / 1000);
100
+ return `${minutes}m ${seconds}s`;
101
+ }