sql-dev-inspector 0.0.0-stage → 1.0.1
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.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +267 -2
- package/dist/client/nuxt-plugin.client.d.ts +2 -0
- package/dist/client/nuxt-plugin.client.js +11 -0
- package/dist/client/widget.d.ts +40 -0
- package/dist/client/widget.js +485 -0
- package/dist/i18n/index.d.ts +26 -0
- package/dist/i18n/index.js +48 -0
- package/dist/i18n/messages.d.ts +119 -0
- package/dist/i18n/messages.js +331 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/inspector-server.d.ts +11 -0
- package/dist/inspector-server.js +95 -0
- package/dist/nitro-plugin.d.ts +19 -0
- package/dist/nitro-plugin.js +30 -0
- package/dist/node.d.ts +27 -0
- package/dist/node.js +52 -0
- package/dist/nuxt.d.ts +20 -0
- package/dist/nuxt.js +21 -0
- package/dist/report.d.ts +24 -0
- package/dist/report.js +182 -0
- package/dist/runtime.d.ts +21 -0
- package/dist/runtime.js +42 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +44 -0
- package/dist/sql-analyzer.d.ts +12 -0
- package/dist/sql-analyzer.js +214 -0
- package/dist/store.d.ts +36 -0
- package/dist/store.js +86 -0
- package/dist/terminal.d.ts +5 -0
- package/dist/terminal.js +64 -0
- package/dist/tracer.d.ts +36 -0
- package/dist/tracer.js +143 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.js +1 -0
- package/dist/ui/client.d.ts +1 -0
- package/dist/ui/client.js +472 -0
- package/dist/ui/page.d.ts +6 -0
- package/dist/ui/page.js +73 -0
- package/dist/ui/styles.d.ts +7 -0
- package/dist/ui/styles.js +207 -0
- package/package.json +80 -4
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All user-facing text. English is the source of truth: every other locale must define exactly
|
|
3
|
+
* the same keys and the same `{placeholders}` (enforced by test/i18n.test.ts).
|
|
4
|
+
* To add a language: add its code to `Locale`, `LOCALES` (index.ts) and a block below.
|
|
5
|
+
*/
|
|
6
|
+
export type Locale = "en" | "vi" | "ja";
|
|
7
|
+
declare const en: {
|
|
8
|
+
readonly "app.title": "SQL Inspector";
|
|
9
|
+
readonly "search.placeholder": "Filter requests…";
|
|
10
|
+
readonly "search.aria": "Filter requests by method, URL or status";
|
|
11
|
+
readonly "filter.all": "All";
|
|
12
|
+
readonly "filter.issues": "Issues";
|
|
13
|
+
readonly "filter.errors": "Errors";
|
|
14
|
+
readonly "lang.label": "Language";
|
|
15
|
+
readonly "theme.label": "Theme";
|
|
16
|
+
readonly "theme.auto": "System";
|
|
17
|
+
readonly "theme.light": "Light";
|
|
18
|
+
readonly "theme.dark": "Dark";
|
|
19
|
+
readonly "action.clear": "Clear";
|
|
20
|
+
readonly "action.clear.title": "Clear captured history";
|
|
21
|
+
readonly "follow.label": "Follow latest";
|
|
22
|
+
readonly "requests.title": "Requests";
|
|
23
|
+
readonly "requests.empty.title": "No SQL captured yet";
|
|
24
|
+
readonly "requests.empty.body": "Call an endpoint of your app. Requests that run SQL appear here, live.";
|
|
25
|
+
readonly "requests.noMatch": "No requests match your filter";
|
|
26
|
+
readonly "requests.background": "Outside any request (startup, cron, queue)";
|
|
27
|
+
readonly "detail.empty.title": "Select a request";
|
|
28
|
+
readonly "detail.empty.body": "Pick a request on the left to see its SQL, transactions and insights.";
|
|
29
|
+
readonly "detail.back": "Requests";
|
|
30
|
+
readonly "kpi.queries": "Queries";
|
|
31
|
+
readonly "kpi.sqlTime": "SQL time";
|
|
32
|
+
readonly "kpi.share": "{percent}% of request";
|
|
33
|
+
readonly "kpi.depth": "Max subquery depth";
|
|
34
|
+
readonly "kpi.transactions": "Transactions";
|
|
35
|
+
readonly "kpi.total": "Total time";
|
|
36
|
+
readonly "kpi.status": "Status";
|
|
37
|
+
readonly "insights.title": "Insights";
|
|
38
|
+
readonly "insights.none": "No issues found.";
|
|
39
|
+
readonly "tree.title": "Query timeline";
|
|
40
|
+
readonly "tree.expandAll": "Expand all";
|
|
41
|
+
readonly "tree.collapseAll": "Collapse all";
|
|
42
|
+
readonly "tree.dropped": "{count} oldest statements were dropped (limit reached).";
|
|
43
|
+
readonly "tx.transaction": "Transaction";
|
|
44
|
+
readonly "tx.savepoint": "Savepoint {name}";
|
|
45
|
+
readonly "tx.statements": "{count} statements";
|
|
46
|
+
readonly "tx.statements.one": "{count} statement";
|
|
47
|
+
readonly "status.committed": "Committed";
|
|
48
|
+
readonly "status.rolled-back": "Rolled back";
|
|
49
|
+
readonly "status.open": "Open";
|
|
50
|
+
readonly "sql.formatted": "Formatted";
|
|
51
|
+
readonly "sql.raw": "Raw";
|
|
52
|
+
readonly "sql.copy": "Copy SQL";
|
|
53
|
+
readonly "sql.copied": "Copied";
|
|
54
|
+
readonly "sql.params": "Parameters";
|
|
55
|
+
readonly "sql.connection": "Connection {id}";
|
|
56
|
+
readonly "sql.rows": "{count} rows";
|
|
57
|
+
readonly "sql.rows.one": "{count} row";
|
|
58
|
+
readonly "sql.affected": "{count} affected";
|
|
59
|
+
readonly "sql.subquery": "Subquery ×{count} · depth {depth}";
|
|
60
|
+
readonly "sql.startedAt": "+{ms} from request start";
|
|
61
|
+
readonly "live.on": "Live";
|
|
62
|
+
readonly "live.off": "Reconnecting…";
|
|
63
|
+
readonly "shortcuts.title": "Shortcuts";
|
|
64
|
+
readonly "shortcuts.search": "Focus search";
|
|
65
|
+
readonly "shortcuts.navigate": "Previous / next request";
|
|
66
|
+
readonly "shortcuts.expand": "Expand / collapse all";
|
|
67
|
+
readonly "toast.copied": "SQL copied to clipboard";
|
|
68
|
+
readonly "toast.copyFailed": "Couldn't copy to clipboard";
|
|
69
|
+
readonly "insight.title.n-plus-one": "N+1 queries";
|
|
70
|
+
readonly "insight.title.duplicate": "Repeated query";
|
|
71
|
+
readonly "insight.title.slow": "Slow query";
|
|
72
|
+
readonly "insight.title.select-star": "SELECT *";
|
|
73
|
+
readonly "insight.title.select-in-transaction": "Read inside a transaction";
|
|
74
|
+
readonly "insight.title.write-outside-transaction": "Writes outside a transaction";
|
|
75
|
+
readonly "insight.title.deep-subquery": "Deep subquery";
|
|
76
|
+
readonly "insight.title.error": "SQL error";
|
|
77
|
+
readonly "insight.title.many-queries": "Many queries";
|
|
78
|
+
readonly "insight.msg.n-plus-one": "{count} similar queries, {total} in total — {sql}";
|
|
79
|
+
readonly "insight.msg.duplicate": "The identical query ran {count} times — {sql}";
|
|
80
|
+
readonly "insight.msg.slow": "Took {ms} (threshold {threshold}) — {sql}";
|
|
81
|
+
readonly "insight.msg.select-star": "SELECT * on {tables}";
|
|
82
|
+
readonly "insight.msg.select-in-transaction": "{count} SELECT statements ran inside a transaction (convention: only writes go in a transaction)";
|
|
83
|
+
readonly "insight.msg.select-in-transaction.one": "{count} SELECT statement ran inside a transaction (convention: only writes go in a transaction)";
|
|
84
|
+
readonly "insight.msg.write-outside-transaction": "{count} write statements ran outside a transaction — if they must be atomic, wrap them in one";
|
|
85
|
+
readonly "insight.msg.deep-subquery": "Subquery nested {depth} levels deep";
|
|
86
|
+
readonly "insight.msg.error": "SQL error: {error}";
|
|
87
|
+
readonly "insight.msg.many-queries": "This request ran {count} statements";
|
|
88
|
+
readonly "term.total": "{ms} total";
|
|
89
|
+
readonly "term.queries": "{count} queries";
|
|
90
|
+
readonly "term.queries.one": "{count} query";
|
|
91
|
+
readonly "term.inSql": "{ms} in SQL";
|
|
92
|
+
readonly "term.rows": "{count} rows";
|
|
93
|
+
readonly "term.rows.one": "{count} row";
|
|
94
|
+
readonly "term.affected": "{count} affected";
|
|
95
|
+
readonly "term.subquery": "subquery×{count} (depth {depth})";
|
|
96
|
+
readonly "term.ready": "[sql-inspector] enabled — open {path} to see SQL per request";
|
|
97
|
+
readonly "widget.title": "SQL Inspector";
|
|
98
|
+
readonly "widget.open": "Open SQL Inspector ({keys})";
|
|
99
|
+
readonly "widget.settings": "Settings";
|
|
100
|
+
readonly "widget.dock.bottom": "Dock to bottom";
|
|
101
|
+
readonly "widget.dock.right": "Dock to right";
|
|
102
|
+
readonly "widget.newTab": "Open in a new tab";
|
|
103
|
+
readonly "widget.close": "Close ({key})";
|
|
104
|
+
readonly "widget.autoOpen": "Open automatically on warnings or errors";
|
|
105
|
+
readonly "widget.showButton": "Show the floating button";
|
|
106
|
+
readonly "widget.language": "Language";
|
|
107
|
+
readonly "widget.lang.auto": "Auto (browser)";
|
|
108
|
+
readonly "widget.shortcuts": "Shortcuts";
|
|
109
|
+
readonly "widget.shortcut.toggle": "Open / close panel";
|
|
110
|
+
readonly "widget.shortcut.button": "Show / hide button";
|
|
111
|
+
readonly "widget.shortcut.close": "Close panel";
|
|
112
|
+
readonly "widget.clear": "Clear history";
|
|
113
|
+
readonly "widget.disable": "Turn off SQL Inspector";
|
|
114
|
+
readonly "widget.disable.hint": "Press {keys} to turn it back on.";
|
|
115
|
+
readonly "widget.resize": "Resize panel";
|
|
116
|
+
};
|
|
117
|
+
export type MessageKey = keyof typeof en;
|
|
118
|
+
export declare const MESSAGES: Record<Locale, Record<MessageKey, string>>;
|
|
119
|
+
export {};
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
const en = {
|
|
2
|
+
"app.title": "SQL Inspector",
|
|
3
|
+
"search.placeholder": "Filter requests…",
|
|
4
|
+
"search.aria": "Filter requests by method, URL or status",
|
|
5
|
+
"filter.all": "All",
|
|
6
|
+
"filter.issues": "Issues",
|
|
7
|
+
"filter.errors": "Errors",
|
|
8
|
+
"lang.label": "Language",
|
|
9
|
+
"theme.label": "Theme",
|
|
10
|
+
"theme.auto": "System",
|
|
11
|
+
"theme.light": "Light",
|
|
12
|
+
"theme.dark": "Dark",
|
|
13
|
+
"action.clear": "Clear",
|
|
14
|
+
"action.clear.title": "Clear captured history",
|
|
15
|
+
"follow.label": "Follow latest",
|
|
16
|
+
"requests.title": "Requests",
|
|
17
|
+
"requests.empty.title": "No SQL captured yet",
|
|
18
|
+
"requests.empty.body": "Call an endpoint of your app. Requests that run SQL appear here, live.",
|
|
19
|
+
"requests.noMatch": "No requests match your filter",
|
|
20
|
+
"requests.background": "Outside any request (startup, cron, queue)",
|
|
21
|
+
"detail.empty.title": "Select a request",
|
|
22
|
+
"detail.empty.body": "Pick a request on the left to see its SQL, transactions and insights.",
|
|
23
|
+
"detail.back": "Requests",
|
|
24
|
+
"kpi.queries": "Queries",
|
|
25
|
+
"kpi.sqlTime": "SQL time",
|
|
26
|
+
"kpi.share": "{percent}% of request",
|
|
27
|
+
"kpi.depth": "Max subquery depth",
|
|
28
|
+
"kpi.transactions": "Transactions",
|
|
29
|
+
"kpi.total": "Total time",
|
|
30
|
+
"kpi.status": "Status",
|
|
31
|
+
"insights.title": "Insights",
|
|
32
|
+
"insights.none": "No issues found.",
|
|
33
|
+
"tree.title": "Query timeline",
|
|
34
|
+
"tree.expandAll": "Expand all",
|
|
35
|
+
"tree.collapseAll": "Collapse all",
|
|
36
|
+
"tree.dropped": "{count} oldest statements were dropped (limit reached).",
|
|
37
|
+
"tx.transaction": "Transaction",
|
|
38
|
+
"tx.savepoint": "Savepoint {name}",
|
|
39
|
+
"tx.statements": "{count} statements",
|
|
40
|
+
"tx.statements.one": "{count} statement",
|
|
41
|
+
"status.committed": "Committed",
|
|
42
|
+
"status.rolled-back": "Rolled back",
|
|
43
|
+
"status.open": "Open",
|
|
44
|
+
"sql.formatted": "Formatted",
|
|
45
|
+
"sql.raw": "Raw",
|
|
46
|
+
"sql.copy": "Copy SQL",
|
|
47
|
+
"sql.copied": "Copied",
|
|
48
|
+
"sql.params": "Parameters",
|
|
49
|
+
"sql.connection": "Connection {id}",
|
|
50
|
+
"sql.rows": "{count} rows",
|
|
51
|
+
"sql.rows.one": "{count} row",
|
|
52
|
+
"sql.affected": "{count} affected",
|
|
53
|
+
"sql.subquery": "Subquery ×{count} · depth {depth}",
|
|
54
|
+
"sql.startedAt": "+{ms} from request start",
|
|
55
|
+
"live.on": "Live",
|
|
56
|
+
"live.off": "Reconnecting…",
|
|
57
|
+
"shortcuts.title": "Shortcuts",
|
|
58
|
+
"shortcuts.search": "Focus search",
|
|
59
|
+
"shortcuts.navigate": "Previous / next request",
|
|
60
|
+
"shortcuts.expand": "Expand / collapse all",
|
|
61
|
+
"toast.copied": "SQL copied to clipboard",
|
|
62
|
+
"toast.copyFailed": "Couldn't copy to clipboard",
|
|
63
|
+
"insight.title.n-plus-one": "N+1 queries",
|
|
64
|
+
"insight.title.duplicate": "Repeated query",
|
|
65
|
+
"insight.title.slow": "Slow query",
|
|
66
|
+
"insight.title.select-star": "SELECT *",
|
|
67
|
+
"insight.title.select-in-transaction": "Read inside a transaction",
|
|
68
|
+
"insight.title.write-outside-transaction": "Writes outside a transaction",
|
|
69
|
+
"insight.title.deep-subquery": "Deep subquery",
|
|
70
|
+
"insight.title.error": "SQL error",
|
|
71
|
+
"insight.title.many-queries": "Many queries",
|
|
72
|
+
"insight.msg.n-plus-one": "{count} similar queries, {total} in total — {sql}",
|
|
73
|
+
"insight.msg.duplicate": "The identical query ran {count} times — {sql}",
|
|
74
|
+
"insight.msg.slow": "Took {ms} (threshold {threshold}) — {sql}",
|
|
75
|
+
"insight.msg.select-star": "SELECT * on {tables}",
|
|
76
|
+
"insight.msg.select-in-transaction": "{count} SELECT statements ran inside a transaction (convention: only writes go in a transaction)",
|
|
77
|
+
"insight.msg.select-in-transaction.one": "{count} SELECT statement ran inside a transaction (convention: only writes go in a transaction)",
|
|
78
|
+
"insight.msg.write-outside-transaction": "{count} write statements ran outside a transaction — if they must be atomic, wrap them in one",
|
|
79
|
+
"insight.msg.deep-subquery": "Subquery nested {depth} levels deep",
|
|
80
|
+
"insight.msg.error": "SQL error: {error}",
|
|
81
|
+
"insight.msg.many-queries": "This request ran {count} statements",
|
|
82
|
+
"term.total": "{ms} total",
|
|
83
|
+
"term.queries": "{count} queries",
|
|
84
|
+
"term.queries.one": "{count} query",
|
|
85
|
+
"term.inSql": "{ms} in SQL",
|
|
86
|
+
"term.rows": "{count} rows",
|
|
87
|
+
"term.rows.one": "{count} row",
|
|
88
|
+
"term.affected": "{count} affected",
|
|
89
|
+
"term.subquery": "subquery×{count} (depth {depth})",
|
|
90
|
+
"term.ready": "[sql-inspector] enabled — open {path} to see SQL per request",
|
|
91
|
+
"widget.title": "SQL Inspector",
|
|
92
|
+
"widget.open": "Open SQL Inspector ({keys})",
|
|
93
|
+
"widget.settings": "Settings",
|
|
94
|
+
"widget.dock.bottom": "Dock to bottom",
|
|
95
|
+
"widget.dock.right": "Dock to right",
|
|
96
|
+
"widget.newTab": "Open in a new tab",
|
|
97
|
+
"widget.close": "Close ({key})",
|
|
98
|
+
"widget.autoOpen": "Open automatically on warnings or errors",
|
|
99
|
+
"widget.showButton": "Show the floating button",
|
|
100
|
+
"widget.language": "Language",
|
|
101
|
+
"widget.lang.auto": "Auto (browser)",
|
|
102
|
+
"widget.shortcuts": "Shortcuts",
|
|
103
|
+
"widget.shortcut.toggle": "Open / close panel",
|
|
104
|
+
"widget.shortcut.button": "Show / hide button",
|
|
105
|
+
"widget.shortcut.close": "Close panel",
|
|
106
|
+
"widget.clear": "Clear history",
|
|
107
|
+
"widget.disable": "Turn off SQL Inspector",
|
|
108
|
+
"widget.disable.hint": "Press {keys} to turn it back on.",
|
|
109
|
+
"widget.resize": "Resize panel"
|
|
110
|
+
};
|
|
111
|
+
const vi = {
|
|
112
|
+
"app.title": "SQL Inspector",
|
|
113
|
+
"search.placeholder": "Lọc request…",
|
|
114
|
+
"search.aria": "Lọc request theo method, URL hoặc status",
|
|
115
|
+
"filter.all": "Tất cả",
|
|
116
|
+
"filter.issues": "Có vấn đề",
|
|
117
|
+
"filter.errors": "Lỗi",
|
|
118
|
+
"lang.label": "Ngôn ngữ",
|
|
119
|
+
"theme.label": "Giao diện",
|
|
120
|
+
"theme.auto": "Theo hệ thống",
|
|
121
|
+
"theme.light": "Sáng",
|
|
122
|
+
"theme.dark": "Tối",
|
|
123
|
+
"action.clear": "Xoá",
|
|
124
|
+
"action.clear.title": "Xoá lịch sử đã ghi",
|
|
125
|
+
"follow.label": "Theo dõi mới nhất",
|
|
126
|
+
"requests.title": "Request",
|
|
127
|
+
"requests.empty.title": "Chưa ghi được câu SQL nào",
|
|
128
|
+
"requests.empty.body": "Hãy gọi một endpoint của ứng dụng. Request nào chạy SQL sẽ hiện ở đây, theo thời gian thực.",
|
|
129
|
+
"requests.noMatch": "Không có request nào khớp bộ lọc",
|
|
130
|
+
"requests.background": "Ngoài request (khởi động, cron, queue)",
|
|
131
|
+
"detail.empty.title": "Chọn một request",
|
|
132
|
+
"detail.empty.body": "Chọn một request ở bên trái để xem SQL, transaction và các cảnh báo.",
|
|
133
|
+
"detail.back": "Request",
|
|
134
|
+
"kpi.queries": "Số query",
|
|
135
|
+
"kpi.sqlTime": "Thời gian SQL",
|
|
136
|
+
"kpi.share": "{percent}% thời gian request",
|
|
137
|
+
"kpi.depth": "Subquery sâu nhất",
|
|
138
|
+
"kpi.transactions": "Transaction",
|
|
139
|
+
"kpi.total": "Tổng thời gian",
|
|
140
|
+
"kpi.status": "Trạng thái",
|
|
141
|
+
"insights.title": "Cảnh báo",
|
|
142
|
+
"insights.none": "Không phát hiện vấn đề nào.",
|
|
143
|
+
"tree.title": "Dòng thời gian query",
|
|
144
|
+
"tree.expandAll": "Mở tất cả",
|
|
145
|
+
"tree.collapseAll": "Đóng tất cả",
|
|
146
|
+
"tree.dropped": "Đã bỏ {count} câu cũ nhất (vượt giới hạn).",
|
|
147
|
+
"tx.transaction": "Transaction",
|
|
148
|
+
"tx.savepoint": "Savepoint {name}",
|
|
149
|
+
"tx.statements": "{count} câu",
|
|
150
|
+
"tx.statements.one": "{count} câu",
|
|
151
|
+
"status.committed": "Đã commit",
|
|
152
|
+
"status.rolled-back": "Đã rollback",
|
|
153
|
+
"status.open": "Đang mở",
|
|
154
|
+
"sql.formatted": "Đã format",
|
|
155
|
+
"sql.raw": "Gốc",
|
|
156
|
+
"sql.copy": "Copy SQL",
|
|
157
|
+
"sql.copied": "Đã copy",
|
|
158
|
+
"sql.params": "Tham số",
|
|
159
|
+
"sql.connection": "Connection {id}",
|
|
160
|
+
"sql.rows": "{count} dòng",
|
|
161
|
+
"sql.rows.one": "{count} dòng",
|
|
162
|
+
"sql.affected": "{count} dòng bị ảnh hưởng",
|
|
163
|
+
"sql.subquery": "Subquery ×{count} · sâu {depth}",
|
|
164
|
+
"sql.startedAt": "+{ms} so với lúc bắt đầu request",
|
|
165
|
+
"live.on": "Trực tiếp",
|
|
166
|
+
"live.off": "Đang kết nối lại…",
|
|
167
|
+
"shortcuts.title": "Phím tắt",
|
|
168
|
+
"shortcuts.search": "Chuyển tới ô tìm kiếm",
|
|
169
|
+
"shortcuts.navigate": "Request trước / sau",
|
|
170
|
+
"shortcuts.expand": "Mở / đóng tất cả",
|
|
171
|
+
"toast.copied": "Đã copy SQL vào clipboard",
|
|
172
|
+
"toast.copyFailed": "Không copy được vào clipboard",
|
|
173
|
+
"insight.title.n-plus-one": "Query N+1",
|
|
174
|
+
"insight.title.duplicate": "Query lặp",
|
|
175
|
+
"insight.title.slow": "Query chậm",
|
|
176
|
+
"insight.title.select-star": "SELECT *",
|
|
177
|
+
"insight.title.select-in-transaction": "Đọc trong transaction",
|
|
178
|
+
"insight.title.write-outside-transaction": "Ghi ngoài transaction",
|
|
179
|
+
"insight.title.deep-subquery": "Subquery sâu",
|
|
180
|
+
"insight.title.error": "Lỗi SQL",
|
|
181
|
+
"insight.title.many-queries": "Quá nhiều query",
|
|
182
|
+
"insight.msg.n-plus-one": "{count} query giống nhau, tổng {total} — {sql}",
|
|
183
|
+
"insight.msg.duplicate": "Query y hệt chạy {count} lần — {sql}",
|
|
184
|
+
"insight.msg.slow": "Mất {ms} (ngưỡng {threshold}) — {sql}",
|
|
185
|
+
"insight.msg.select-star": "SELECT * trên {tables}",
|
|
186
|
+
"insight.msg.select-in-transaction": "{count} câu SELECT chạy trong transaction (quy ước: chỉ câu ghi nằm trong transaction)",
|
|
187
|
+
"insight.msg.select-in-transaction.one": "{count} câu SELECT chạy trong transaction (quy ước: chỉ câu ghi nằm trong transaction)",
|
|
188
|
+
"insight.msg.write-outside-transaction": "{count} câu ghi chạy ngoài transaction — nếu cần nguyên tử, hãy gói chúng vào một transaction",
|
|
189
|
+
"insight.msg.deep-subquery": "Subquery lồng {depth} tầng",
|
|
190
|
+
"insight.msg.error": "Lỗi SQL: {error}",
|
|
191
|
+
"insight.msg.many-queries": "Request này chạy {count} câu SQL",
|
|
192
|
+
"term.total": "{ms} tổng",
|
|
193
|
+
"term.queries": "{count} query",
|
|
194
|
+
"term.queries.one": "{count} query",
|
|
195
|
+
"term.inSql": "{ms} trong SQL",
|
|
196
|
+
"term.rows": "{count} dòng",
|
|
197
|
+
"term.rows.one": "{count} dòng",
|
|
198
|
+
"term.affected": "{count} bị ảnh hưởng",
|
|
199
|
+
"term.subquery": "subquery×{count} (sâu {depth})",
|
|
200
|
+
"term.ready": "[sql-inspector] đã bật — mở {path} để xem SQL theo từng request",
|
|
201
|
+
"widget.title": "SQL Inspector",
|
|
202
|
+
"widget.open": "Mở SQL Inspector ({keys})",
|
|
203
|
+
"widget.settings": "Tuỳ chỉnh",
|
|
204
|
+
"widget.dock.bottom": "Dock xuống đáy",
|
|
205
|
+
"widget.dock.right": "Dock sang phải",
|
|
206
|
+
"widget.newTab": "Mở trong tab mới",
|
|
207
|
+
"widget.close": "Đóng ({key})",
|
|
208
|
+
"widget.autoOpen": "Tự mở khi có cảnh báo hoặc lỗi",
|
|
209
|
+
"widget.showButton": "Hiện nút nổi",
|
|
210
|
+
"widget.language": "Ngôn ngữ",
|
|
211
|
+
"widget.lang.auto": "Tự động (theo trình duyệt)",
|
|
212
|
+
"widget.shortcuts": "Phím tắt",
|
|
213
|
+
"widget.shortcut.toggle": "Mở / đóng panel",
|
|
214
|
+
"widget.shortcut.button": "Ẩn / hiện nút",
|
|
215
|
+
"widget.shortcut.close": "Đóng panel",
|
|
216
|
+
"widget.clear": "Xoá lịch sử",
|
|
217
|
+
"widget.disable": "Tắt SQL Inspector",
|
|
218
|
+
"widget.disable.hint": "Nhấn {keys} để bật lại.",
|
|
219
|
+
"widget.resize": "Đổi kích thước panel"
|
|
220
|
+
};
|
|
221
|
+
const ja = {
|
|
222
|
+
"app.title": "SQL Inspector",
|
|
223
|
+
"search.placeholder": "リクエストを絞り込み…",
|
|
224
|
+
"search.aria": "メソッド、URL、ステータスでリクエストを絞り込む",
|
|
225
|
+
"filter.all": "すべて",
|
|
226
|
+
"filter.issues": "要確認",
|
|
227
|
+
"filter.errors": "エラー",
|
|
228
|
+
"lang.label": "言語",
|
|
229
|
+
"theme.label": "テーマ",
|
|
230
|
+
"theme.auto": "システム設定",
|
|
231
|
+
"theme.light": "ライト",
|
|
232
|
+
"theme.dark": "ダーク",
|
|
233
|
+
"action.clear": "クリア",
|
|
234
|
+
"action.clear.title": "記録した履歴をクリア",
|
|
235
|
+
"follow.label": "最新を追従",
|
|
236
|
+
"requests.title": "リクエスト",
|
|
237
|
+
"requests.empty.title": "SQLはまだ記録されていません",
|
|
238
|
+
"requests.empty.body": "アプリのエンドポイントを呼び出してください。SQLを実行したリクエストがここにリアルタイムで表示されます。",
|
|
239
|
+
"requests.noMatch": "条件に一致するリクエストはありません",
|
|
240
|
+
"requests.background": "リクエスト外(起動時、cron、キュー)",
|
|
241
|
+
"detail.empty.title": "リクエストを選択してください",
|
|
242
|
+
"detail.empty.body": "左のリクエストを選ぶと、SQL、トランザクション、検出結果を確認できます。",
|
|
243
|
+
"detail.back": "リクエスト",
|
|
244
|
+
"kpi.queries": "クエリ数",
|
|
245
|
+
"kpi.sqlTime": "SQL時間",
|
|
246
|
+
"kpi.share": "リクエスト全体の{percent}%",
|
|
247
|
+
"kpi.depth": "サブクエリ最大の深さ",
|
|
248
|
+
"kpi.transactions": "トランザクション",
|
|
249
|
+
"kpi.total": "合計時間",
|
|
250
|
+
"kpi.status": "ステータス",
|
|
251
|
+
"insights.title": "検出結果",
|
|
252
|
+
"insights.none": "問題は見つかりませんでした。",
|
|
253
|
+
"tree.title": "クエリのタイムライン",
|
|
254
|
+
"tree.expandAll": "すべて展開",
|
|
255
|
+
"tree.collapseAll": "すべて折りたたむ",
|
|
256
|
+
"tree.dropped": "上限に達したため、古い{count}件を破棄しました。",
|
|
257
|
+
"tx.transaction": "トランザクション",
|
|
258
|
+
"tx.savepoint": "セーブポイント {name}",
|
|
259
|
+
"tx.statements": "{count}件",
|
|
260
|
+
"tx.statements.one": "{count}件",
|
|
261
|
+
"status.committed": "コミット済み",
|
|
262
|
+
"status.rolled-back": "ロールバック済み",
|
|
263
|
+
"status.open": "実行中",
|
|
264
|
+
"sql.formatted": "整形済み",
|
|
265
|
+
"sql.raw": "そのまま",
|
|
266
|
+
"sql.copy": "SQLをコピー",
|
|
267
|
+
"sql.copied": "コピーしました",
|
|
268
|
+
"sql.params": "パラメーター",
|
|
269
|
+
"sql.connection": "接続 {id}",
|
|
270
|
+
"sql.rows": "{count}行",
|
|
271
|
+
"sql.rows.one": "{count}行",
|
|
272
|
+
"sql.affected": "{count}行に影響",
|
|
273
|
+
"sql.subquery": "サブクエリ ×{count} ・ 深さ {depth}",
|
|
274
|
+
"sql.startedAt": "リクエスト開始から +{ms}",
|
|
275
|
+
"live.on": "ライブ",
|
|
276
|
+
"live.off": "再接続中…",
|
|
277
|
+
"shortcuts.title": "ショートカット",
|
|
278
|
+
"shortcuts.search": "検索にフォーカス",
|
|
279
|
+
"shortcuts.navigate": "前 / 次のリクエスト",
|
|
280
|
+
"shortcuts.expand": "すべて展開 / 折りたたみ",
|
|
281
|
+
"toast.copied": "SQLをクリップボードにコピーしました",
|
|
282
|
+
"toast.copyFailed": "クリップボードにコピーできませんでした",
|
|
283
|
+
"insight.title.n-plus-one": "N+1クエリ",
|
|
284
|
+
"insight.title.duplicate": "重複クエリ",
|
|
285
|
+
"insight.title.slow": "低速クエリ",
|
|
286
|
+
"insight.title.select-star": "SELECT *",
|
|
287
|
+
"insight.title.select-in-transaction": "トランザクション内の読み取り",
|
|
288
|
+
"insight.title.write-outside-transaction": "トランザクション外の書き込み",
|
|
289
|
+
"insight.title.deep-subquery": "深いサブクエリ",
|
|
290
|
+
"insight.title.error": "SQLエラー",
|
|
291
|
+
"insight.title.many-queries": "クエリ数が多い",
|
|
292
|
+
"insight.msg.n-plus-one": "類似クエリが{count}回、合計{total} — {sql}",
|
|
293
|
+
"insight.msg.duplicate": "同一のクエリが{count}回実行されました — {sql}",
|
|
294
|
+
"insight.msg.slow": "{ms}かかりました(しきい値 {threshold}) — {sql}",
|
|
295
|
+
"insight.msg.select-star": "{tables} に対する SELECT *",
|
|
296
|
+
"insight.msg.select-in-transaction": "トランザクション内でSELECTが{count}件実行されました(規約: トランザクションには書き込みのみ)",
|
|
297
|
+
"insight.msg.select-in-transaction.one": "トランザクション内でSELECTが{count}件実行されました(規約: トランザクションには書き込みのみ)",
|
|
298
|
+
"insight.msg.write-outside-transaction": "トランザクション外で書き込みが{count}件実行されました — 原子性が必要ならトランザクションにまとめてください",
|
|
299
|
+
"insight.msg.deep-subquery": "サブクエリが{depth}階層にネストしています",
|
|
300
|
+
"insight.msg.error": "SQLエラー: {error}",
|
|
301
|
+
"insight.msg.many-queries": "このリクエストは{count}件のSQLを実行しました",
|
|
302
|
+
"term.total": "合計 {ms}",
|
|
303
|
+
"term.queries": "{count}クエリ",
|
|
304
|
+
"term.queries.one": "{count}クエリ",
|
|
305
|
+
"term.inSql": "SQL {ms}",
|
|
306
|
+
"term.rows": "{count}行",
|
|
307
|
+
"term.rows.one": "{count}行",
|
|
308
|
+
"term.affected": "{count}行に影響",
|
|
309
|
+
"term.subquery": "サブクエリ×{count}(深さ {depth})",
|
|
310
|
+
"term.ready": "[sql-inspector] 有効 — {path} を開くとリクエストごとのSQLを確認できます",
|
|
311
|
+
"widget.title": "SQL Inspector",
|
|
312
|
+
"widget.open": "SQL Inspector を開く({keys})",
|
|
313
|
+
"widget.settings": "設定",
|
|
314
|
+
"widget.dock.bottom": "下に固定",
|
|
315
|
+
"widget.dock.right": "右に固定",
|
|
316
|
+
"widget.newTab": "新しいタブで開く",
|
|
317
|
+
"widget.close": "閉じる({key})",
|
|
318
|
+
"widget.autoOpen": "警告やエラー時に自動で開く",
|
|
319
|
+
"widget.showButton": "フローティングボタンを表示",
|
|
320
|
+
"widget.language": "言語",
|
|
321
|
+
"widget.lang.auto": "自動(ブラウザー設定)",
|
|
322
|
+
"widget.shortcuts": "ショートカット",
|
|
323
|
+
"widget.shortcut.toggle": "パネルを開く / 閉じる",
|
|
324
|
+
"widget.shortcut.button": "ボタンの表示 / 非表示",
|
|
325
|
+
"widget.shortcut.close": "パネルを閉じる",
|
|
326
|
+
"widget.clear": "履歴をクリア",
|
|
327
|
+
"widget.disable": "SQL Inspector をオフにする",
|
|
328
|
+
"widget.disable.hint": "{keys} でもう一度オンにできます。",
|
|
329
|
+
"widget.resize": "パネルのサイズを変更"
|
|
330
|
+
};
|
|
331
|
+
export const MESSAGES = { en, vi, ja };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createSqlInspector, type SqlInspector } from "./node.js";
|
|
2
|
+
export { buildReport, buildTree, DEFAULT_REPORT_OPTIONS, type ReportOptions } from "./report.js";
|
|
3
|
+
export { getInspector, type Inspector, isInspectorEnabled } from "./runtime.js";
|
|
4
|
+
export { startInspector } from "./setup.js";
|
|
5
|
+
export { analyzeSql, fingerprintOf, formatSql } from "./sql-analyzer.js";
|
|
6
|
+
export { TraceStore } from "./store.js";
|
|
7
|
+
export { installTracer, runInRequest } from "./tracer.js";
|
|
8
|
+
export type * from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createSqlInspector } from "./node.js";
|
|
2
|
+
export { buildReport, buildTree, DEFAULT_REPORT_OPTIONS } from "./report.js";
|
|
3
|
+
export { getInspector, isInspectorEnabled } from "./runtime.js";
|
|
4
|
+
export { startInspector } from "./setup.js";
|
|
5
|
+
export { analyzeSql, fingerprintOf, formatSql } from "./sql-analyzer.js";
|
|
6
|
+
export { TraceStore } from "./store.js";
|
|
7
|
+
export { installTracer, runInRequest } from "./tracer.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { Inspector } from "./runtime.js";
|
|
3
|
+
export declare const INSPECTOR_PATH = "__sql-inspector";
|
|
4
|
+
/** Matches `/__sql-inspector/...` under any base path; returns the part after it, or null. */
|
|
5
|
+
export declare function inspectorSubPath(url: string | undefined): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* The inspector is served straight from the Nitro handler chain's entry point, ahead of
|
|
8
|
+
* every Nuxt middleware: it must not need the app to authenticate it, and the queries those middlewares
|
|
9
|
+
* run to authenticate the inspector itself would otherwise pollute what it shows.
|
|
10
|
+
*/
|
|
11
|
+
export declare function handleInspectorRequest(req: IncomingMessage, res: ServerResponse, subPath: string, inspector: Inspector): void;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { buildReport } from "./report.js";
|
|
2
|
+
import { formatSql } from "./sql-analyzer.js";
|
|
3
|
+
import { renderPage } from "./ui/page.js";
|
|
4
|
+
const TRAILING_SLASHES = /\/+$/;
|
|
5
|
+
const REQUEST_DETAIL = /^\/api\/requests\/([\w-]+)$/;
|
|
6
|
+
export const INSPECTOR_PATH = "__sql-inspector";
|
|
7
|
+
const MATCH = new RegExp(`^((?:/[^/?#]+)*?)/${INSPECTOR_PATH}(/[^?#]*)?(?:[?#].*)?$`);
|
|
8
|
+
/** Matches `/__sql-inspector/...` under any base path; returns the part after it, or null. */
|
|
9
|
+
export function inspectorSubPath(url) {
|
|
10
|
+
const match = url ? MATCH.exec(url) : null;
|
|
11
|
+
return match ? (match[2] ?? "") : null;
|
|
12
|
+
}
|
|
13
|
+
function sendJson(res, body, status = 200) {
|
|
14
|
+
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
15
|
+
res.end(JSON.stringify(body));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The inspector is served straight from the Nitro handler chain's entry point, ahead of
|
|
19
|
+
* every Nuxt middleware: it must not need the app to authenticate it, and the queries those middlewares
|
|
20
|
+
* run to authenticate the inspector itself would otherwise pollute what it shows.
|
|
21
|
+
*/
|
|
22
|
+
export function handleInspectorRequest(req, res, subPath, inspector) {
|
|
23
|
+
const { store } = inspector;
|
|
24
|
+
const path = subPath.replace(TRAILING_SLASHES, "");
|
|
25
|
+
if (subPath === "") {
|
|
26
|
+
res.writeHead(308, { location: `${req.url.split("?")[0]}/` });
|
|
27
|
+
res.end();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (req.method === "GET" && path === "") {
|
|
31
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "x-content-type-options": "nosniff" });
|
|
32
|
+
res.end(renderPage(inspector.localeHint));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (req.method === "GET" && path === "/api/requests") {
|
|
36
|
+
sendJson(res, store.list().map((trace) => {
|
|
37
|
+
const { totals, insights } = buildReport(trace, inspector.report);
|
|
38
|
+
return {
|
|
39
|
+
id: trace.id,
|
|
40
|
+
method: trace.method,
|
|
41
|
+
url: trace.url,
|
|
42
|
+
status: trace.status,
|
|
43
|
+
startedAt: trace.startedAt,
|
|
44
|
+
durationMs: trace.durationMs,
|
|
45
|
+
queryCount: totals.queryCount,
|
|
46
|
+
sqlTimeMs: totals.sqlTimeMs,
|
|
47
|
+
warnings: insights.filter(i => i.severity !== "info").length,
|
|
48
|
+
errors: insights.filter(i => i.severity === "error").length
|
|
49
|
+
};
|
|
50
|
+
}));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const detail = REQUEST_DETAIL.exec(path);
|
|
54
|
+
if (req.method === "GET" && detail) {
|
|
55
|
+
const trace = store.get(detail[1]);
|
|
56
|
+
if (!trace) {
|
|
57
|
+
sendJson(res, { message: "Request not found (evicted or cleared)" }, 404);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const report = buildReport(trace, inspector.report);
|
|
61
|
+
sendJson(res, {
|
|
62
|
+
...report,
|
|
63
|
+
formattedSql: Object.fromEntries(trace.queries.map(q => [q.seq, formatSql(q.sql)]))
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (req.method === "GET" && path === "/api/stream") {
|
|
68
|
+
res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-store", "connection": "keep-alive" });
|
|
69
|
+
res.write(": connected\n\n");
|
|
70
|
+
let timer = null;
|
|
71
|
+
const pending = new Set();
|
|
72
|
+
const unsubscribe = store.subscribe((change) => {
|
|
73
|
+
pending.add(change.type === "clear" ? "clear" : change.requestId);
|
|
74
|
+
// A burst of statements becomes one event, so a 500-query request cannot flood the page.
|
|
75
|
+
timer ??= setTimeout(() => {
|
|
76
|
+
timer = null;
|
|
77
|
+
res.write(`data: ${JSON.stringify({ changed: [...pending] })}\n\n`);
|
|
78
|
+
pending.clear();
|
|
79
|
+
}, 80);
|
|
80
|
+
});
|
|
81
|
+
req.on("close", () => {
|
|
82
|
+
unsubscribe();
|
|
83
|
+
if (timer) {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (req.method === "POST" && path === "/api/clear") {
|
|
90
|
+
store.clear();
|
|
91
|
+
sendJson(res, { ok: true });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
sendJson(res, { message: "Not found" }, 404);
|
|
95
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
interface NitroEventLike {
|
|
3
|
+
node: {
|
|
4
|
+
req: IncomingMessage;
|
|
5
|
+
res: ServerResponse;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
interface NitroAppLike {
|
|
9
|
+
h3App: {
|
|
10
|
+
handler: (event: NitroEventLike) => unknown;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Nitro plugin (Nuxt, Nitro): patches mysql2 once, then wraps the top-level h3 handler (the
|
|
15
|
+
* same hook Nitro uses for its own async context) so every statement awaited anywhere beneath
|
|
16
|
+
* a request is attributed to it, middleware included.
|
|
17
|
+
*/
|
|
18
|
+
export default function sqlInspectorPlugin(nitroApp: NitroAppLike): void;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { translate } from "./i18n/index.js";
|
|
2
|
+
import { handleInspectorRequest, inspectorSubPath } from "./inspector-server.js";
|
|
3
|
+
import { startInspector } from "./setup.js";
|
|
4
|
+
import { runInRequest } from "./tracer.js";
|
|
5
|
+
/**
|
|
6
|
+
* Nitro plugin (Nuxt, Nitro): patches mysql2 once, then wraps the top-level h3 handler (the
|
|
7
|
+
* same hook Nitro uses for its own async context) so every statement awaited anywhere beneath
|
|
8
|
+
* a request is attributed to it, middleware included.
|
|
9
|
+
*/
|
|
10
|
+
export default function sqlInspectorPlugin(nitroApp) {
|
|
11
|
+
const inspector = startInspector();
|
|
12
|
+
if (!inspector) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const { store } = inspector;
|
|
16
|
+
const next = nitroApp.h3App.handler;
|
|
17
|
+
nitroApp.h3App.handler = (event) => {
|
|
18
|
+
const { req, res } = event.node;
|
|
19
|
+
const subPath = inspectorSubPath(req.url);
|
|
20
|
+
if (subPath !== null) {
|
|
21
|
+
handleInspectorRequest(req, res, subPath, inspector);
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const trace = store.startRequest(req.method ?? "GET", req.url ?? "/");
|
|
25
|
+
// `close` fires on a finished response and on an aborted one alike.
|
|
26
|
+
res.once("close", () => store.finishRequest(trace, res.statusCode));
|
|
27
|
+
return runInRequest(trace, () => next(event));
|
|
28
|
+
};
|
|
29
|
+
console.log(translate(inspector.locale, "term.ready", { path: "/__sql-inspector/" }));
|
|
30
|
+
}
|