sql-dev-inspector 0.0.0-stage → 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 (46) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/LICENSE +21 -0
  3. package/README.VI.md +341 -0
  4. package/README.md +267 -2
  5. package/dist/client/nuxt-plugin.client.d.ts +2 -0
  6. package/dist/client/nuxt-plugin.client.js +11 -0
  7. package/dist/client/widget.d.ts +40 -0
  8. package/dist/client/widget.js +485 -0
  9. package/dist/i18n/index.d.ts +26 -0
  10. package/dist/i18n/index.js +48 -0
  11. package/dist/i18n/messages.d.ts +119 -0
  12. package/dist/i18n/messages.js +331 -0
  13. package/dist/index.d.ts +8 -0
  14. package/dist/index.js +7 -0
  15. package/dist/inspector-server.d.ts +11 -0
  16. package/dist/inspector-server.js +95 -0
  17. package/dist/nitro-plugin.d.ts +19 -0
  18. package/dist/nitro-plugin.js +30 -0
  19. package/dist/node.d.ts +27 -0
  20. package/dist/node.js +52 -0
  21. package/dist/nuxt.d.ts +20 -0
  22. package/dist/nuxt.js +21 -0
  23. package/dist/report.d.ts +24 -0
  24. package/dist/report.js +182 -0
  25. package/dist/runtime.d.ts +21 -0
  26. package/dist/runtime.js +42 -0
  27. package/dist/setup.d.ts +8 -0
  28. package/dist/setup.js +44 -0
  29. package/dist/sql-analyzer.d.ts +12 -0
  30. package/dist/sql-analyzer.js +214 -0
  31. package/dist/store.d.ts +36 -0
  32. package/dist/store.js +86 -0
  33. package/dist/terminal.d.ts +5 -0
  34. package/dist/terminal.js +64 -0
  35. package/dist/tracer.d.ts +36 -0
  36. package/dist/tracer.js +143 -0
  37. package/dist/types.d.ts +79 -0
  38. package/dist/types.js +1 -0
  39. package/dist/ui/client.d.ts +1 -0
  40. package/dist/ui/client.js +472 -0
  41. package/dist/ui/page.d.ts +6 -0
  42. package/dist/ui/page.js +73 -0
  43. package/dist/ui/styles.d.ts +7 -0
  44. package/dist/ui/styles.js +207 -0
  45. package/docs/PUBLISH-AND-INSTALL.md +267 -0
  46. package/package.json +80 -4
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Styles of the inspector page. The page runs on its own document (served by the inspector,
3
+ * shown standalone or inside the floating panel's iframe), so nothing here can reach the host
4
+ * application. Everything is still scoped to our own elements and every native control is
5
+ * restyled, so no browser-default widget shows through.
6
+ */
7
+ const DARK = `
8
+ --bg: #0d1117; --surface: #151b23; --surface-2: #1b232d; --surface-3: #242e3a;
9
+ --border: #283241; --border-strong: #3a4757;
10
+ --text: #e6edf3; --text-2: #aab5c3; --text-3: #7d8896;
11
+ --accent: #5aa2ff; --accent-weak: rgba(90, 162, 255, .14); --accent-text: #0d1117;
12
+ --ok: #4ac26b; --ok-weak: rgba(74, 194, 107, .14);
13
+ --warn: #e3a008; --warn-weak: rgba(227, 160, 8, .14);
14
+ --err: #ff6b6b; --err-weak: rgba(255, 107, 107, .15);
15
+ --shadow: 0 8px 24px rgba(0, 0, 0, .45);
16
+ --syn-kw: #c792ea; --syn-str: #8bd49c; --syn-num: #f2a65e; --syn-id: #7cb7ff; --syn-fn: #e6c07b; --syn-com: #7d8896;
17
+ `;
18
+ export const STYLES = `
19
+ :root {
20
+ --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Hiragino Sans", "Yu Gothic UI", "Apple SD Gothic Neo", sans-serif;
21
+ --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
22
+ --radius: 8px; --radius-sm: 6px;
23
+ --bg: #f5f7fa; --surface: #ffffff; --surface-2: #f1f4f8; --surface-3: #e6ebf1;
24
+ --border: #e2e7ee; --border-strong: #cdd5df;
25
+ --text: #121821; --text-2: #4a5666; --text-3: #6e7a8a;
26
+ --accent: #1f6feb; --accent-weak: #e7f0ff; --accent-text: #ffffff;
27
+ --ok: #1a7f37; --ok-weak: #e6f5ea;
28
+ --warn: #9a6700; --warn-weak: #fff4d6;
29
+ --err: #cf222e; --err-weak: #ffebe9;
30
+ --shadow: 0 8px 24px rgba(20, 30, 50, .14);
31
+ --syn-kw: #8250df; --syn-str: #0a7a3d; --syn-num: #b35900; --syn-id: #0550ae; --syn-fn: #8a5a00; --syn-com: #6e7a8a;
32
+ color-scheme: light;
33
+ }
34
+ @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { ${DARK} color-scheme: dark; } }
35
+ :root[data-theme="dark"] { ${DARK} color-scheme: dark; }
36
+
37
+ *, *::before, *::after { box-sizing: border-box; }
38
+ html, body { height: 100%; }
39
+ body { margin: 0; background: var(--bg); color: var(--text); font: 13px/1.5 var(--font); -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; overflow: hidden; }
40
+ [hidden] { display: none !important; }
41
+ button, input, select { font: inherit; color: inherit; }
42
+ button { appearance: none; background: none; border: 0; padding: 0; cursor: pointer; }
43
+ svg { width: 16px; height: 16px; flex: none; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
44
+ :focus { outline: none; }
45
+ :focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-radius: 4px; }
46
+ .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
47
+ * { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
48
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
49
+ ::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 8px; border: 2px solid transparent; background-clip: content-box; }
50
+ ::-webkit-scrollbar-track { background: transparent; }
51
+
52
+ /* ── Shell ─────────────────────────────────────────────────────────── */
53
+ .app { display: flex; flex-direction: column; height: 100%; }
54
+ .topbar { display: flex; align-items: center; gap: 12px; height: 52px; padding: 0 16px; background: var(--surface); border-bottom: 1px solid var(--border); flex: none; }
55
+ .brand { display: flex; align-items: center; gap: 10px; font-weight: 650; font-size: 14px; letter-spacing: -.01em; white-space: nowrap; }
56
+ .brand-mark { display: grid; place-items: center; width: 28px; height: 28px; border-radius: 8px; background: linear-gradient(135deg, #1f6feb, #7a5af8); color: #fff; }
57
+ .brand-mark svg { width: 16px; height: 16px; stroke: #fff; }
58
+ .live { display: inline-flex; align-items: center; gap: 6px; padding: 2px 9px 2px 8px; border-radius: 999px; font-size: 11.5px; font-weight: 600; color: var(--text-2); background: var(--surface-2); border: 1px solid var(--border); white-space: nowrap; }
59
+ .live i { width: 7px; height: 7px; border-radius: 50%; background: var(--err); }
60
+ .live.on i { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-weak); }
61
+ .spacer { flex: 1; }
62
+ .field { position: relative; display: flex; align-items: center; }
63
+ .field > svg { position: absolute; left: 10px; color: var(--text-3); pointer-events: none; }
64
+ .input { height: 32px; width: 260px; padding: 0 34px 0 32px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); color: var(--text); transition: border-color .15s, background .15s, box-shadow .15s; }
65
+ .input::placeholder { color: var(--text-3); }
66
+ .input:hover { border-color: var(--border-strong); }
67
+ .input:focus { border-color: var(--accent); background: var(--surface); box-shadow: 0 0 0 3px var(--accent-weak); outline: none; }
68
+ .kbd { font: 600 11px/1 var(--mono); min-width: 18px; padding: 3px 5px; text-align: center; border: 1px solid var(--border-strong); border-bottom-width: 2px; border-radius: 4px; background: var(--surface); color: var(--text-2); }
69
+ .field .kbd { position: absolute; right: 8px; }
70
+ .btn { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font-weight: 550; white-space: nowrap; transition: background .15s, border-color .15s; }
71
+ .btn:hover { background: var(--surface-2); border-color: var(--border-strong); }
72
+ .btn:active { background: var(--surface-3); }
73
+ .btn.icon { width: 32px; padding: 0; justify-content: center; }
74
+ .btn.ghost { border-color: transparent; background: transparent; }
75
+ .btn.ghost:hover { background: var(--surface-2); }
76
+ .btn.sm { height: 26px; padding: 0 9px; font-size: 12px; }
77
+ .dd { position: relative; display: inline-flex; }
78
+ .dd-btn { display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font-weight: 550; white-space: nowrap; transition: background .15s, border-color .15s; }
79
+ .dd-btn:hover { background: var(--surface-2); border-color: var(--border-strong); }
80
+ .dd-btn[aria-expanded="true"] { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-weak); }
81
+ .dd-btn svg { color: var(--text-3); } .dd-btn svg:last-child { width: 12px; height: 12px; }
82
+ .dd-menu { position: absolute; z-index: 40; right: 0; top: calc(100% + 6px); min-width: 188px; margin: 0; padding: 4px; list-style: none; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); box-shadow: var(--shadow); }
83
+ .dd-item { display: flex; align-items: center; gap: 10px; height: 34px; padding: 0 10px; border-radius: 6px; color: var(--text); cursor: pointer; user-select: none; }
84
+ .dd-item:hover, .dd-item:focus-visible { background: var(--surface-2); outline: none; }
85
+ .dd-item .check { width: 14px; height: 14px; color: var(--accent); visibility: hidden; }
86
+ .dd-item[aria-selected="true"] { font-weight: 650; }
87
+ .dd-item[aria-selected="true"] .check { visibility: visible; }
88
+ .dd-item .code { margin-left: auto; color: var(--text-3); font: 600 11px var(--mono); text-transform: uppercase; }
89
+ .seg { display: inline-flex; padding: 2px; gap: 2px; border-radius: var(--radius-sm); background: var(--surface-2); border: 1px solid var(--border); }
90
+ .seg button { height: 26px; padding: 0 10px; border-radius: 4px; color: var(--text-2); font-weight: 550; font-size: 12px; }
91
+ .seg button:hover { color: var(--text); }
92
+ .seg button[aria-pressed="true"] { background: var(--surface); color: var(--text); box-shadow: 0 1px 2px rgba(0, 0, 0, .12); }
93
+ .switch { display: inline-flex; align-items: center; gap: 8px; color: var(--text-2); font-weight: 550; font-size: 12px; cursor: pointer; }
94
+ .switch .track { position: relative; width: 28px; height: 16px; border-radius: 999px; background: var(--border-strong); transition: background .15s; }
95
+ .switch .track::after { content: ""; position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, .3); transition: transform .15s; }
96
+ .switch[aria-checked="true"] .track { background: var(--accent); }
97
+ .switch[aria-checked="true"] .track::after { transform: translateX(12px); }
98
+
99
+ .layout { flex: 1; display: grid; grid-template-columns: minmax(280px, 360px) 1fr; min-height: 0; }
100
+ .list { display: flex; flex-direction: column; min-height: 0; background: var(--surface); border-right: 1px solid var(--border); }
101
+ .list-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--border); }
102
+ .list-head h2 { margin: 0; font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-3); font-weight: 650; }
103
+ .count { padding: 0 7px; border-radius: 999px; background: var(--surface-3); color: var(--text-2); font-size: 11px; font-weight: 650; line-height: 18px; }
104
+ .list-body { flex: 1; overflow: auto; }
105
+ .req { position: relative; display: block; width: 100%; text-align: left; padding: 10px 12px 10px 15px; border-bottom: 1px solid var(--border); }
106
+ .req::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: transparent; }
107
+ .req.has-warn::before { background: var(--warn); } .req.has-err::before { background: var(--err); }
108
+ .req:hover { background: var(--surface-2); }
109
+ .req[aria-selected="true"] { background: var(--accent-weak); }
110
+ .req-top { display: flex; align-items: center; gap: 8px; min-width: 0; }
111
+ .req-url { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); font-size: 12px; }
112
+ .req-meta { display: flex; align-items: center; gap: 10px; margin-top: 3px; color: var(--text-3); font-size: 11.5px; }
113
+ .req-meta .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--border-strong); }
114
+
115
+ .method { font: 700 10.5px/1 var(--mono); letter-spacing: .02em; padding: 3px 6px; border-radius: 4px; background: var(--surface-3); color: var(--text-2); }
116
+ .method.GET { background: var(--ok-weak); color: var(--ok); } .method.POST { background: var(--accent-weak); color: var(--accent); }
117
+ .method.PUT, .method.PATCH { background: var(--warn-weak); color: var(--warn); } .method.DELETE { background: var(--err-weak); color: var(--err); }
118
+ .status { font: 650 11.5px/1 var(--mono); color: var(--text-2); }
119
+ .status.s2 { color: var(--ok); } .status.s3 { color: var(--accent); } .status.s4 { color: var(--warn); } .status.s5 { color: var(--err); }
120
+ .chip { display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 8px; border-radius: 999px; font-size: 11px; font-weight: 650; background: var(--surface-3); color: var(--text-2); white-space: nowrap; }
121
+ .chip.ok { background: var(--ok-weak); color: var(--ok); } .chip.warn { background: var(--warn-weak); color: var(--warn); } .chip.err { background: var(--err-weak); color: var(--err); } .chip.accent { background: var(--accent-weak); color: var(--accent); }
122
+ .chip svg { width: 12px; height: 12px; }
123
+
124
+ .detail { overflow: auto; min-width: 0; }
125
+ .detail-inner { max-width: 1180px; margin: 0 auto; padding: 20px 24px 40px; }
126
+ .detail-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
127
+ .detail-head h1 { margin: 0; font: 600 15px/1.4 var(--mono); word-break: break-all; flex: 1; min-width: 200px; }
128
+ .back { display: none; }
129
+ .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 20px; }
130
+ .kpi { padding: 12px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
131
+ .kpi-label { color: var(--text-3); font-size: 11.5px; font-weight: 600; }
132
+ .kpi-value { margin-top: 2px; font-size: 22px; font-weight: 650; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
133
+ .kpi-sub { color: var(--text-3); font-size: 11.5px; }
134
+ .section { margin-bottom: 22px; }
135
+ .section-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
136
+ .section-head h2 { margin: 0; font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-3); font-weight: 650; }
137
+ .card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
138
+ .insight { display: flex; gap: 10px; width: 100%; text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--border); }
139
+ .insight:last-child { border-bottom: 0; }
140
+ .insight:hover { background: var(--surface-2); }
141
+ .insight .ico { margin-top: 1px; }
142
+ .insight.warn .ico { color: var(--warn); } .insight.error .ico { color: var(--err); } .insight.info .ico { color: var(--text-3); }
143
+ .insight-title { font-weight: 650; }
144
+ .insight-msg { color: var(--text-2); font-size: 12.5px; word-break: break-word; }
145
+ .insight-msg code { font: 12px var(--mono); }
146
+ .empty-ok { padding: 14px; color: var(--text-2); display: flex; gap: 8px; align-items: center; }
147
+ .empty-ok svg { color: var(--ok); }
148
+ .empty { display: grid; place-items: center; align-content: center; gap: 6px; height: 100%; min-height: 240px; padding: 32px; text-align: center; color: var(--text-2); }
149
+ .empty svg.big { width: 44px; height: 44px; stroke-width: 1.3; color: var(--text-3); }
150
+ .empty h3 { margin: 6px 0 0; font-size: 14px; color: var(--text); }
151
+ .empty p { margin: 0; max-width: 340px; color: var(--text-3); }
152
+
153
+ /* ── Query tree ───────────────────────────────────────────────────── */
154
+ .tree-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
155
+ .axis { display: flex; justify-content: space-between; margin: 0 12px 4px auto; width: var(--wf, 220px); color: var(--text-3); font: 11px var(--mono); }
156
+ .tx { margin: 6px 0; border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: var(--radius); background: color-mix(in srgb, var(--accent-weak) 35%, var(--surface)); }
157
+ .tx.rolled-back { border-left-color: var(--err); } .tx.committed { border-left-color: var(--ok); } .tx.open { border-left-color: var(--warn); }
158
+ .tx > .tx-head { display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 10px; text-align: left; font-weight: 650; }
159
+ .tx-head .dim { font-weight: 500; }
160
+ .tx-children { padding: 0 8px 8px; }
161
+ .q { margin: 4px 0; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
162
+ .q.hl { box-shadow: 0 0 0 2px var(--accent); border-color: var(--accent); }
163
+ .q.err { border-color: var(--err); }
164
+ .q-head { display: grid; grid-template-columns: 1fr var(--wf, 220px); align-items: center; gap: 12px; width: 100%; padding: 7px 12px; text-align: left; }
165
+ .q-main { min-width: 0; }
166
+ .q-line { display: flex; align-items: center; gap: 8px; min-width: 0; }
167
+ .chev { transition: transform .15s; color: var(--text-3); }
168
+ .q.open .chev, .tx.expanded > .tx-head .chev { transform: rotate(90deg); }
169
+ .kind { font: 700 10.5px/1 var(--mono); padding: 3px 6px; border-radius: 4px; background: var(--surface-3); color: var(--text-2); min-width: 54px; text-align: center; }
170
+ .kind.select { background: var(--accent-weak); color: var(--accent); } .kind.insert { background: var(--ok-weak); color: var(--ok); }
171
+ .kind.update { background: var(--warn-weak); color: var(--warn); } .kind.delete { background: var(--err-weak); color: var(--err); }
172
+ .tables { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
173
+ .dim { color: var(--text-3); }
174
+ .num { font: 12px var(--mono); font-variant-numeric: tabular-nums; color: var(--text-2); white-space: nowrap; }
175
+ .preview { margin-top: 3px; font: 11.5px/1.4 var(--mono); color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
176
+ .wf { position: relative; height: 8px; border-radius: 4px; background: var(--surface-3); }
177
+ .wf i { position: absolute; top: 0; bottom: 0; min-width: 2px; border-radius: 4px; background: var(--accent); }
178
+ .wf i.slow { background: var(--warn); } .wf i.err { background: var(--err); }
179
+ .q-body { display: none; padding: 10px 12px 12px; border-top: 1px solid var(--border); background: var(--surface-2); border-radius: 0 0 var(--radius) var(--radius); }
180
+ .q.open .q-body { display: block; }
181
+ .q-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; color: var(--text-3); font-size: 12px; }
182
+ .q-tools .spacer { flex: 1; }
183
+ .sql { margin: 0; padding: 12px 14px; max-height: 380px; overflow: auto; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); font: 12px/1.6 var(--mono); white-space: pre-wrap; word-break: break-word; tab-size: 2; }
184
+ .sql .kw { color: var(--syn-kw); font-weight: 600; } .sql .str { color: var(--syn-str); } .sql .num { color: var(--syn-num); font: inherit; }
185
+ .sql .id { color: var(--syn-id); } .sql .fn { color: var(--syn-fn); } .sql .com { color: var(--syn-com); font-style: italic; }
186
+ .params { margin-top: 8px; font: 12px var(--mono); color: var(--text-2); word-break: break-all; }
187
+ .error-box { margin-top: 8px; padding: 8px 10px; border-radius: var(--radius-sm); background: var(--err-weak); color: var(--err); font: 12px var(--mono); }
188
+
189
+ .toast { position: fixed; left: 50%; bottom: 20px; transform: translate(-50%, 12px); padding: 8px 14px; border-radius: 999px; background: var(--text); color: var(--bg); font-weight: 600; font-size: 12.5px; box-shadow: var(--shadow); opacity: 0; pointer-events: none; transition: opacity .18s, transform .18s; }
190
+ .toast.show { opacity: 1; transform: translate(-50%, 0); }
191
+ .footer { display: flex; align-items: center; gap: 14px; height: 28px; padding: 0 16px; border-top: 1px solid var(--border); background: var(--surface); color: var(--text-3); font-size: 11.5px; flex: none; overflow: hidden; white-space: nowrap; }
192
+ .footer span { display: inline-flex; align-items: center; gap: 5px; }
193
+
194
+ /* Narrow (the docked panel can be ~400px wide): one pane at a time */
195
+ .narrow .topbar { gap: 8px; padding: 0 10px; }
196
+ .narrow .brand span.name, .narrow .footer, .narrow .live span, .narrow .btn .label { display: none; }
197
+ .narrow .input { width: 150px; }
198
+ .narrow .layout { display: block; }
199
+ .narrow .list, .narrow .detail { height: 100%; border: 0; }
200
+ .narrow[data-view="detail"] .list { display: none; } .narrow[data-view="list"] .detail { display: none; }
201
+ .narrow .back { display: inline-flex; }
202
+ .narrow .detail-inner { padding: 14px 12px 28px; }
203
+ .narrow .kpis { grid-template-columns: repeat(2, 1fr); }
204
+ .narrow .q-head { grid-template-columns: 1fr; } .narrow .wf, .narrow .axis { display: none; }
205
+
206
+ @media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }
207
+ `;
@@ -0,0 +1,267 @@
1
+ # Đưa lên Git public & hướng dẫn cài đặt cho mọi người
2
+
3
+ Tài liệu gồm 3 phần:
4
+
5
+ - **Phần A** — chuẩn bị gói trước khi công khai, và **cái gì đẩy lên git / cái gì không** (A4)
6
+ - **Phần B** — đưa mã nguồn lên Git public (GitHub) và (tuỳ chọn) publish lên npm
7
+ - **Phần C** — hướng dẫn cho người khác cài vào dự án của họ
8
+
9
+ > ⚠️ **Công khai là hành động khó thu hồi.** Mã đã public có thể bị clone, cache, index (kể cả sau khi xoá).
10
+ > Các bước dưới đây **bạn tự thực hiện**; mỗi bước đều ghi rõ tác động. Đừng bỏ qua checklist ở A3.
11
+
12
+ ---
13
+
14
+ ## Phần A. Chuẩn bị
15
+
16
+ ### A1. Thay các chỗ giữ chỗ (placeholder)
17
+
18
+ Tìm và thay trong gói:
19
+
20
+ | Nơi | Cần thay | Ghi chú |
21
+ |---|---|---|
22
+ | `package.json` → `name` | `sql-dev-inspector` | Tên mặc định. Có thể đổi (ví dụ `@your-scope/sql-dev-inspector`); nếu đổi, sửa cả README và mọi `import "sql-dev-inspector/..."` |
23
+ | `package.json` → `author` | `<YOUR NAME OR ORGANIZATION>` | Tên cá nhân hoặc tổ chức hiển thị công khai |
24
+ | `package.json` → `repository.url`, `homepage`, `bugs.url` | `<YOUR-ORG>` | URL repo GitHub sẽ tạo ở B1 |
25
+ | `LICENSE` | `<YOUR NAME OR ORGANIZATION>` | Chủ bản quyền |
26
+
27
+ Lệnh tìm chỗ còn sót:
28
+
29
+ ```bash
30
+ grep -rn "YOUR" --exclude-dir=node_modules --exclude-dir=dist .
31
+ ```
32
+
33
+ ### A2. Chọn giấy phép
34
+
35
+ Gói mặc định dùng **MIT** (cho phép dùng/sửa/phân phối tự do, kể cả thương mại). Nếu tổ chức của bạn yêu cầu giấy phép khác hoặc phải được pháp chế/quản lý duyệt trước khi mở mã nguồn, **hỏi trước khi công khai**, rồi sửa `LICENSE` và `"license"` trong `package.json` cho khớp.
36
+
37
+ ### A3. Checklist trước khi public
38
+
39
+ - [ ] Không có bí mật trong mã: không `.env`, token, key, mật khẩu, URL nội bộ. (Gói này không chứa; vẫn nên chạy lại lệnh dưới.)
40
+ - [ ] Không có tên dự án / khách hàng / hạ tầng nội bộ trong mã, test, README, lịch sử git.
41
+ - [ ] Đã đồng ý với giấy phép (A2) và tên tác giả (A1).
42
+ - [ ] `npm install && npm run typecheck && npm test && npm run build` đều thành công.
43
+
44
+ ```bash
45
+ grep -rniE "password|secret|token|api[_-]?key|BEGIN (RSA|PRIVATE)" --exclude-dir=node_modules --exclude-dir=dist . | head
46
+ ```
47
+
48
+ (Kết quả có thể có vài chữ "token" vô hại trong mã phân tích SQL; hãy đọc từng dòng.)
49
+
50
+ ### A4. Cái gì đẩy lên Git, cái gì không
51
+
52
+ Có **ba nơi** khác nhau mà mã đi tới. Mỗi nơi nhận một phần khác nhau:
53
+
54
+ | Nơi | Là gì | Ai thấy |
55
+ |---|---|---|
56
+ | **1. Repo public `sql-dev-inspector`** | Mã nguồn của tool (thư mục `sql-dev-inspector/` trong file zip) | Mọi người |
57
+ | **2. npm registry** (tuỳ chọn, B4) | Bản đã build để cài | Mọi người |
58
+ | **3. Repo nội bộ của dự án (ví dụ Alps)** | Dự án dùng tool | Chỉ nội bộ — **không bao giờ public** |
59
+
60
+ #### 1. Repo public `sql-dev-inspector`
61
+
62
+ | ✅ Đẩy lên (được `git add .` lấy) | Vì sao |
63
+ |---|---|
64
+ | `src/` | Mã nguồn |
65
+ | `test/` | Test, kèm MySQL giả — người đóng góp cần để chạy test |
66
+ | `docs/` | Tài liệu (file này) |
67
+ | `README.md` (English), `README.VI.md` (tiếng Việt), `CHANGELOG.md`, `LICENSE` | Tài liệu và giấy phép |
68
+ | `package.json` | Metadata, scripts, dependencies |
69
+ | `package-lock.json` | Khoá phiên bản — sinh ra sau lần `npm install` đầu tiên, nên commit |
70
+ | `tsconfig.json`, `tsconfig.build.json`, `vitest.config.ts` | Cấu hình build / test |
71
+ | `.gitignore` | Để người khác không vô tình commit rác |
72
+ | `.github/workflows/ci.yml` | CI chạy test mỗi lần push / PR |
73
+
74
+ | ❌ KHÔNG đẩy lên | Vì sao | Đã được chặn bởi |
75
+ |---|---|---|
76
+ | `node_modules/` | Sinh lại bằng `npm install` | `.gitignore` |
77
+ | `dist/` | Sản phẩm build; chỉ đi lên **npm**, không đi lên git | `.gitignore` |
78
+ | `*.tgz`, file `.zip` | Gói phân phối. Muốn chia sẻ thì đính kèm vào *GitHub Release*, đừng commit | `.gitignore` (`*.tgz`) — zip không nằm trong repo |
79
+ | `.env*`, token, key | Bí mật (gói này không có, giữ nguyên như vậy) | tự kiểm tra ở A3 |
80
+ | `.claude/`, `.vite/`, `coverage/`, `.DS_Store`, `*.log` | File của công cụ / máy cá nhân | `.gitignore` |
81
+
82
+ Kiểm tra trước khi commit:
83
+
84
+ ```bash
85
+ git status # chỉ thấy các file ✅ ở bảng trên
86
+ git ls-files # danh sách file sẽ nằm trong repo
87
+ git check-ignore -v dist node_modules # xác nhận 2 thư mục này bị bỏ qua
88
+ ```
89
+
90
+ #### 2. Cái gì lên npm (khi `npm publish`)
91
+
92
+ Do `files` trong `package.json` quyết định, **không phải** `.gitignore`: chỉ `dist/`, `docs/`, `README.md`, `README.VI.md`, `LICENSE`, `CHANGELOG.md` (+ `package.json`). `src/`, `test/`, cấu hình CI **không** lên npm. Xem trước bằng:
93
+
94
+ ```bash
95
+ npm publish --dry-run # hoặc: npm pack --dry-run
96
+ ```
97
+
98
+ #### 3. Repo nội bộ của dự án dùng tool (ví dụ Alps)
99
+
100
+ | ✅ Commit vào repo dự án | ❌ Không commit |
101
+ |---|---|
102
+ | Dòng cấu hình bật tool (`modules: ["sql-dev-inspector/nuxt"]` hoặc middleware) | File `.zip` / `.tgz` của tool (`tools/release/`) |
103
+ | Dòng `sql-dev-inspector` trong `devDependencies` + lockfile | `node_modules/`, `dist/`, `.nuxt/` |
104
+ | | Mọi thứ trong thư mục scratch / thử nghiệm |
105
+
106
+ Hai giai đoạn:
107
+
108
+ - **Trước khi tool có repo public / npm** (hiện tại): mã nguồn tool nằm tạm trong repo dự án ở `tools/sql-inspector/`, `nuxt.config.ts` trỏ tới `./tools/sql-inspector/src/nuxt`. Commit cả thư mục `tools/sql-inspector/` (trừ `dist/`, `node_modules/`) và thêm `tools/release/` vào `.gitignore` của dự án.
109
+ - **Sau khi tool đã publish** (khuyến nghị): xoá thư mục `tools/sql-inspector/` khỏi repo dự án, `npm i -D sql-dev-inspector`, đổi `nuxt.config.ts` sang `"sql-dev-inspector/nuxt"`. Từ đó repo public là **nguồn duy nhất**; không giữ hai bản mã nguồn song song.
110
+
111
+ > ⚠️ Không bao giờ đổi repo dự án nội bộ sang Public chỉ để chia sẻ tool. Tool có repo riêng (Phần B) chính là để làm việc này.
112
+
113
+ ---
114
+
115
+ ## Phần B. Lên Git public
116
+
117
+ ### B1. Tạo repo trống trên GitHub
118
+
119
+ 1. Vào GitHub → **New repository**.
120
+ 2. Đặt tên (khuyến nghị trùng tên package), **không** tick "Add a README / .gitignore / license" (gói đã có sẵn).
121
+ 3. Chọn **Public** → *tác động: bất kỳ ai trên Internet đều xem và clone được mã này.* (Nếu chưa chắc, chọn **Private** trước; đổi sang Public sau ở *Settings → General → Danger Zone → Change visibility*.)
122
+ 4. Bấm **Create repository**, copy URL (ví dụ `https://github.com/<your-org>/sql-dev-inspector.git`).
123
+
124
+ ### B2. Đẩy mã lên
125
+
126
+ Giải nén file zip (hoặc dùng thư mục `sql-dev-inspector/` trong gói), rồi:
127
+
128
+ ```bash
129
+ cd sql-dev-inspector
130
+ git init -b main
131
+ git add .
132
+ git status # ← kiểm tra danh sách file: không có node_modules, dist, .env
133
+ git commit -m "feat: initial release 1.0.0"
134
+ git remote add origin https://github.com/<your-org>/sql-dev-inspector.git
135
+ git push -u origin main
136
+ ```
137
+
138
+ *Tác động của `git push`: mã nguồn được gửi lên GitHub; nếu repo là Public thì công khai ngay.*
139
+
140
+ > **Đang ở trong monorepo và muốn giữ lịch sử commit riêng của thư mục này?** Dùng `git subtree split` thay vì copy:
141
+ > ```bash
142
+ > git subtree split --prefix=tools/sql-inspector -b sql-inspector-only
143
+ > git push https://github.com/<your-org>/sql-dev-inspector.git sql-inspector-only:main
144
+ > ```
145
+ > Lưu ý: cách này đẩy **toàn bộ lịch sử** của thư mục đó; kiểm tra lịch sử không chứa thông tin nội bộ trước khi push.
146
+
147
+ ### B3. Thiết lập repo sau khi đẩy lên
148
+
149
+ 1. **CI**: file `.github/workflows/ci.yml` chạy typecheck + build + test trên Node 20/22/24 mỗi lần push / PR. Kiểm tra tab *Actions*.
150
+ 2. **Mô tả & topics** (*About* ở trang repo): `mysql`, `mysql2`, `sql`, `devtools`, `drizzle`, `nuxt`, `express`.
151
+ 3. **Bảo vệ nhánh `main`** (*Settings → Branches*): yêu cầu PR + CI xanh trước khi merge (khuyến nghị nếu có nhiều người đóng góp).
152
+ 4. **Tag phiên bản**:
153
+ ```bash
154
+ git tag v1.0.0
155
+ git push origin v1.0.0
156
+ ```
157
+ Rồi *Releases → Draft a new release* chọn tag `v1.0.0`, dán nội dung `CHANGELOG.md`.
158
+
159
+ ### B4. (Tuỳ chọn) Publish lên npm
160
+
161
+ Chỉ làm khi muốn mọi người `npm install sql-dev-inspector` không cần trỏ vào GitHub.
162
+
163
+ *Tác động: tên package bị chiếm công khai trên npm; mỗi phiên bản đã publish **không thể ghi đè** (chỉ có thể deprecate, và unpublish bị giới hạn).*
164
+
165
+ ```bash
166
+ npm view sql-dev-inspector # nếu trả về thông tin => tên đã bị chiếm, hãy đổi tên (A1)
167
+ npm login # đăng nhập tài khoản npm (nên bật 2FA)
168
+ npm publish --dry-run # xem trước danh sách file sẽ lên npm (chỉ dist, docs, README.md, README.VI.md, LICENSE, CHANGELOG)
169
+ npm publish --access public # publish thật (chạy typecheck/build/test qua prepublishOnly)
170
+ ```
171
+
172
+ Với package có scope (`@your-scope/...`) bắt buộc `--access public` thì mới public.
173
+
174
+ Phát hành phiên bản mới:
175
+
176
+ ```bash
177
+ # sửa CHANGELOG.md, rồi:
178
+ npm version patch # hoặc minor / major (tự tạo commit + tag)
179
+ git push && git push --tags
180
+ npm publish --access public
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Phần C. Hướng dẫn cài đặt cho mọi người
186
+
187
+ > Dán nguyên phần này vào wiki / Slack / README nội bộ nếu cần.
188
+
189
+ ### C1. Chọn cách cài
190
+
191
+ | Cách | Khi nào dùng | Lệnh |
192
+ |---|---|---|
193
+ | **npm registry** | Đã publish (B4) | `npm i -D sql-dev-inspector` |
194
+ | **Từ GitHub** | Chưa publish npm, repo đã public | `npm i -D github:<your-org>/sql-dev-inspector` |
195
+ | **Từ GitHub, cố định phiên bản** | Nên dùng thay cho nhánh `main` | `npm i -D github:<your-org>/sql-dev-inspector#v1.0.0` |
196
+ | **File `.tgz`** | Offline / chưa có repo | `npm i -D ./sql-dev-inspector-1.0.0.tgz` |
197
+ | **Link thư mục local** | Đang sửa tool và thử trong dự án khác | `npm i -D ../sql-dev-inspector` (hoặc `pnpm add -D link:../sql-dev-inspector`) |
198
+
199
+ Với pnpm / yarn thay `npm i -D` bằng `pnpm add -D` / `yarn add -D`.
200
+ Cài từ GitHub sẽ tự chạy `prepare` (build `dist/`) nên cần mạng để tải `typescript`.
201
+
202
+ Điều kiện: Node.js ≥ 20, dự án có `mysql2` ≥ 3 (`npm i mysql2` nếu chưa có).
203
+
204
+ ### C2. Bật trong dự án
205
+
206
+ **Nuxt** — `nuxt.config.ts`:
207
+
208
+ ```ts
209
+ export default defineNuxtConfig({
210
+ modules: ["sql-dev-inspector/nuxt"]
211
+ })
212
+ ```
213
+
214
+ **Nitro** — `nitro.config.ts`:
215
+
216
+ ```ts
217
+ export default defineNitroConfig({ plugins: ["sql-dev-inspector/nitro"] })
218
+ ```
219
+
220
+ **Express / Connect**:
221
+
222
+ ```ts
223
+ import { createSqlInspector } from "sql-dev-inspector"
224
+ const sqlInspector = createSqlInspector()
225
+ app.use(sqlInspector.middleware) // trước mọi route có chạy SQL
226
+ ```
227
+
228
+ **Node `http` thuần**:
229
+
230
+ ```ts
231
+ const sqlInspector = createSqlInspector()
232
+ http.createServer((req, res) => {
233
+ if (sqlInspector.handle(req, res)) return
234
+ sqlInspector.track(req, res, () => app(req, res))
235
+ })
236
+ ```
237
+
238
+ **Job / cron**: `await sqlInspector.task("tên-job", async () => { … })`.
239
+
240
+ ### C3. Chạy và kiểm tra
241
+
242
+ 1. Chạy dev server với `NODE_ENV=development` (`nuxt dev`, `next dev`, `vite`… tự đặt; với script Node thuần: `NODE_ENV=development node server.js`).
243
+ 2. Terminal in dòng `[sql-inspector] bật …` (Nuxt/Nitro) — hoặc không in gì cho tới khi có request chạy SQL.
244
+ 3. Gọi một API, terminal in cây SQL; mở `http://localhost:<port>/__sql-inspector/`.
245
+ 4. Nuxt: nút **SQL** ở góc phải dưới; phím tắt `Alt+Shift+S` (mở/đóng), `Alt+Shift+D` (ẩn/hiện nút).
246
+
247
+ Không thấy gì? Xem mục *Xử lý sự cố* trong [README](../README.VI.md#xử-lý-sự-cố).
248
+
249
+ ### C4. Tắt / gỡ
250
+
251
+ - Tắt tạm: chạy với `SQL_INSPECTOR=0`.
252
+ - Tắt từ nút nổi: menu *⚙ Tuỳ chỉnh → Tắt SQL Inspector*.
253
+ - Gỡ hẳn: xoá dòng cấu hình (module / plugin / middleware) rồi `npm rm sql-dev-inspector`.
254
+
255
+ ### C5. An toàn
256
+
257
+ - Tool **không chạy ở production** (`NODE_ENV=production` là hard stop), nhưng vẫn nên giữ ở `devDependencies` nếu dự án chỉ dùng cho Nuxt/Nitro dev.
258
+ - SQL hiển thị gồm **giá trị thật** (có thể là dữ liệu cá nhân): đừng chia sẻ màn hình/ảnh chụp khi trỏ vào DB thật; đừng mở cổng dev ra mạng chung khi đang bật tool.
259
+
260
+ ### C6. Cập nhật phiên bản
261
+
262
+ ```bash
263
+ npm update sql-dev-inspector # từ npm
264
+ npm i -D github:<your-org>/sql-dev-inspector#v1.1.0 # từ GitHub: đổi tag
265
+ ```
266
+
267
+ Xem thay đổi ở [CHANGELOG.md](../CHANGELOG.md).
package/package.json CHANGED
@@ -1,6 +1,82 @@
1
1
  {
2
2
  "name": "sql-dev-inspector",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "type": "module",
4
+ "version": "1.0.0",
5
+ "description": "Dev-only SQL inspector for Node apps that talk to MySQL through mysql2 (Drizzle, Knex, Sequelize, TypeORM…): see which SQL each request ran, how transactions and subqueries nest, N+1 and slow queries. Terminal tree, web UI and a floating in-app launcher.",
6
+ "author": "Nhu Duy Thin",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/githubthinnd/sql-dev-inspector#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/githubthinnd/sql-dev-inspector.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/githubthinnd/sql-dev-inspector/issues"
15
+ },
16
+ "keywords": [
17
+ "mysql",
18
+ "mysql2",
19
+ "sql",
20
+ "inspector",
21
+ "debug",
22
+ "devtools",
23
+ "drizzle",
24
+ "nuxt",
25
+ "nitro",
26
+ "express",
27
+ "n+1",
28
+ "transaction"
29
+ ],
30
+ "sideEffects": false,
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js"
35
+ },
36
+ "./nuxt": {
37
+ "types": "./dist/nuxt.d.ts",
38
+ "import": "./dist/nuxt.js"
39
+ },
40
+ "./nitro": {
41
+ "types": "./dist/nitro-plugin.d.ts",
42
+ "import": "./dist/nitro-plugin.js"
43
+ },
44
+ "./client": {
45
+ "types": "./dist/client/widget.d.ts",
46
+ "import": "./dist/client/widget.js"
47
+ },
48
+ "./package.json": "./package.json"
49
+ },
50
+ "main": "./dist/index.js",
51
+ "types": "./dist/index.d.ts",
52
+ "files": [
53
+ "CHANGELOG.md",
54
+ "LICENSE",
55
+ "README.VI.md",
56
+ "README.md",
57
+ "dist",
58
+ "docs"
59
+ ],
60
+ "engines": {
61
+ "node": ">=20"
62
+ },
63
+ "scripts": {
64
+ "build": "tsc -p tsconfig.build.json",
65
+ "prepare": "tsc -p tsconfig.build.json",
66
+ "clean": "rm -rf dist",
67
+ "typecheck": "tsc -p tsconfig.json --noEmit",
68
+ "test": "vitest run",
69
+ "prepublishOnly": "npm run clean && npm run build && npm test"
70
+ },
71
+ "peerDependencies": {
72
+ "mysql2": ">=3.0.0"
73
+ },
74
+ "devDependencies": {
75
+ "@types/node": "^24.0.0",
76
+ "drizzle-orm": "^0.45.4",
77
+ "happy-dom": "^20.8.4",
78
+ "mysql2": "^3.20.0",
79
+ "typescript": "^5.9.0",
80
+ "vitest": "^4.1.10"
81
+ }
82
+ }