@cartanova/qgrid-cli 1.0.2 → 1.0.3

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 (114) hide show
  1. package/bundle/dist/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/decorate.js +9 -0
  2. package/bundle/dist/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/decorateMetadata.js +6 -0
  3. package/bundle/dist/dist/_virtual/_rolldown/runtime.js +15 -0
  4. package/bundle/dist/dist/application/qgrid/oauth.js +140 -0
  5. package/bundle/dist/dist/application/qgrid/pool.js +100 -0
  6. package/bundle/dist/dist/application/qgrid/qgrid.frame.js +280 -0
  7. package/bundle/dist/dist/application/qgrid/qgrid.types.js +79 -0
  8. package/bundle/dist/dist/application/qgrid/worker.js +183 -0
  9. package/bundle/dist/dist/application/queries.generated.js +31 -0
  10. package/bundle/dist/dist/application/request-log/request-log.model.js +113 -0
  11. package/bundle/dist/dist/application/request-log/request-log.types.js +13 -0
  12. package/bundle/dist/dist/application/sonamu.generated.js +99 -0
  13. package/bundle/dist/dist/application/sonamu.generated.sso.js +34 -0
  14. package/bundle/dist/dist/application/token/token.model.js +157 -0
  15. package/bundle/dist/dist/application/token/token.types.js +22 -0
  16. package/bundle/dist/dist/i18n/en.js +63 -0
  17. package/bundle/dist/dist/i18n/ko.js +62 -0
  18. package/bundle/dist/dist/i18n/sd.generated.js +272 -0
  19. package/bundle/dist/dist/index.js +11 -0
  20. package/bundle/dist/dist/migrations/20260410171405_create__request_logs.js +26 -0
  21. package/bundle/dist/dist/migrations/20260410171406_create__tokens.js +24 -0
  22. package/bundle/dist/dist/migrations/20260411120624_alter_tokens_alter2.js +16 -0
  23. package/bundle/dist/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +155 -0
  24. package/bundle/dist/dist/node_modules/.pnpm/@vitest_expect@4.1.2/node_modules/@vitest/expect/dist/index.js +1484 -0
  25. package/bundle/dist/dist/node_modules/.pnpm/@vitest_pretty-format@4.1.2/node_modules/@vitest/pretty-format/dist/index.js +887 -0
  26. package/bundle/dist/dist/node_modules/.pnpm/@vitest_runner@4.1.2/node_modules/@vitest/runner/dist/chunk-artifact.js +2746 -0
  27. package/bundle/dist/dist/node_modules/.pnpm/@vitest_runner@4.1.2/node_modules/@vitest/runner/dist/index.js +8 -0
  28. package/bundle/dist/dist/node_modules/.pnpm/@vitest_runner@4.1.2/node_modules/@vitest/runner/dist/utils.js +8 -0
  29. package/bundle/dist/dist/node_modules/.pnpm/@vitest_snapshot@4.1.2/node_modules/@vitest/snapshot/dist/index.js +662 -0
  30. package/bundle/dist/dist/node_modules/.pnpm/@vitest_spy@4.1.2/node_modules/@vitest/spy/dist/index.js +386 -0
  31. package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.js +82 -0
  32. package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/constants.js +48 -0
  33. package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/diff.js +1307 -0
  34. package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/display.js +558 -0
  35. package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/error.js +33 -0
  36. package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/helpers.js +226 -0
  37. package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/offset.js +27 -0
  38. package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/serialize.js +77 -0
  39. package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/source-map.js +374 -0
  40. package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/timers.js +37 -0
  41. package/bundle/dist/dist/node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.js +2886 -0
  42. package/bundle/dist/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/branding.js +10 -0
  43. package/bundle/dist/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/index.js +109 -0
  44. package/bundle/dist/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/messages.js +10 -0
  45. package/bundle/dist/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/overloads.js +10 -0
  46. package/bundle/dist/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/utils.js +10 -0
  47. package/bundle/dist/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js +1127 -0
  48. package/bundle/dist/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/index.js +21 -0
  49. package/bundle/dist/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.js +432 -0
  50. package/bundle/dist/dist/node_modules/.pnpm/tinybench@2.9.0/node_modules/tinybench/dist/index.js +506 -0
  51. package/bundle/dist/dist/node_modules/.pnpm/tinyrainbow@3.1.0/node_modules/tinyrainbow/dist/index.js +86 -0
  52. package/bundle/dist/dist/node_modules/.pnpm/vite@8.0.3_@emnapi_core@1.9.2_@emnapi_runtime@1.9.2_@types_node@25.0.7_esbuild@0.27.4_jiti@2.6.1_sass@1.98.0_tsx@4.21.0/node_modules/vite/dist/node/module-runner.js +426 -0
  53. package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.js +6 -0
  54. package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/benchmark.CX_oY03V.js +41 -0
  55. package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/evaluatedModules.Dg1zASAC.js +19 -0
  56. package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/index.Chj8NDwU.js +202 -0
  57. package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/index.DOa3dzoN.js +16 -0
  58. package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/rpc.MzXet3jl.js +59 -0
  59. package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/test.p_J6dB8a.js +2910 -0
  60. package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.js +44 -0
  61. package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/index.js +28 -0
  62. package/bundle/dist/dist/sonamu.config.js +196 -0
  63. package/bundle/dist/dist/testing/fixture.js +8 -0
  64. package/bundle/dist/dist/testing/global.js +9 -0
  65. package/bundle/dist/dist/testing/setup-mocks.js +17 -0
  66. package/bundle/dist/dist/utils/subset-loaders.js +12 -0
  67. package/bundle/src/src/application/qgrid/oauth.ts +181 -0
  68. package/bundle/src/src/application/qgrid/pool.ts +132 -0
  69. package/bundle/src/src/application/qgrid/qgrid.frame.ts +275 -0
  70. package/bundle/src/src/application/qgrid/qgrid.types.ts +117 -0
  71. package/bundle/src/src/application/qgrid/worker.ts +234 -0
  72. package/bundle/src/src/application/queries.generated.ts +59 -0
  73. package/bundle/src/src/application/request-log/request-log.entity.json +75 -0
  74. package/bundle/src/src/application/request-log/request-log.model.ts +155 -0
  75. package/bundle/src/src/application/request-log/request-log.types.ts +15 -0
  76. package/bundle/src/src/application/sonamu.generated.http +187 -0
  77. package/bundle/src/src/application/sonamu.generated.sso.ts +66 -0
  78. package/bundle/src/src/application/sonamu.generated.ts +135 -0
  79. package/bundle/src/src/application/token/token.entity.json +37 -0
  80. package/bundle/src/src/application/token/token.model.ts +160 -0
  81. package/bundle/src/src/application/token/token.types.ts +26 -0
  82. package/bundle/src/src/i18n/en.ts +59 -0
  83. package/bundle/src/src/i18n/ko.ts +59 -0
  84. package/bundle/src/src/i18n/sd.generated.ts +384 -0
  85. package/bundle/src/src/index.ts +6 -0
  86. package/bundle/src/src/migrations/20260410171405_create__request_logs.ts +23 -0
  87. package/bundle/src/src/migrations/20260410171406_create__tokens.ts +21 -0
  88. package/bundle/src/src/migrations/20260411120624_alter_tokens_alter2.ts +15 -0
  89. package/bundle/src/src/sonamu.config.ts +248 -0
  90. package/bundle/src/src/testing/fixture.ts +6 -0
  91. package/bundle/src/src/testing/global.ts +6 -0
  92. package/bundle/src/src/testing/setup-mocks.ts +44 -0
  93. package/bundle/src/src/typings/fastify.d.ts +8 -0
  94. package/bundle/src/src/typings/sonamu.d.ts +17 -0
  95. package/bundle/src/src/utils/subset-loaders.ts +11 -0
  96. package/bundle/web-dist/web-dist/client/assets/index-DlPchHBY.js +116 -0
  97. package/bundle/web-dist/web-dist/client/assets/index-vZsvyuFn.css +1 -0
  98. package/bundle/web-dist/web-dist/client/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
  99. package/bundle/web-dist/web-dist/client/assets/routes-CagDz23B.js +1 -0
  100. package/bundle/web-dist/web-dist/client/assets/sd.generated-BrQoQXvv.js +47 -0
  101. package/bundle/web-dist/web-dist/client/assets/services.generated-BspO2L0j.js +1 -0
  102. package/bundle/web-dist/web-dist/client/assets/show-aXUGa0gm.js +29 -0
  103. package/bundle/web-dist/web-dist/client/assets/tokens-DRkXtZ3Q.js +1 -0
  104. package/bundle/web-dist/web-dist/client/assets/vendor-react-DH4kUfK2.js +9 -0
  105. package/bundle/web-dist/web-dist/client/assets/vendor-tanstack-CUxlo6YN.js +1 -0
  106. package/bundle/web-dist/web-dist/client/index.html +18 -0
  107. package/bundle/web-dist/web-dist/server/assets/react-dom-CGIzOSuj.js +335 -0
  108. package/bundle/web-dist/web-dist/server/assets/routes-ClzReRep.js +490 -0
  109. package/bundle/web-dist/web-dist/server/assets/sd.generated-BnonqqKr.js +21694 -0
  110. package/bundle/web-dist/web-dist/server/assets/services.generated-D5ABD5fr.js +836 -0
  111. package/bundle/web-dist/web-dist/server/assets/show-C9pcdSxc.js +23364 -0
  112. package/bundle/web-dist/web-dist/server/assets/tokens-JH6FsnWZ.js +860 -0
  113. package/bundle/web-dist/web-dist/server/entry-server.generated.js +44646 -0
  114. package/package.json +11 -10
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Worker — Claude CLI stream-json 프로세스 하나를 관리.
3
+ *
4
+ * 기존 SocratsAI ClaudeCliService에서 추출 + 리팩터:
5
+ * - 토큰/모델을 생성자에서 주입
6
+ * - env allowlist (PATH, HOME, TMPDIR, CLAUDE_CODE_OAUTH_TOKEN만)
7
+ * - callCount 추적 + maxCalls 도달 시 프로세스 재활용
8
+ * - QuotaError/TimeoutError/ProcessError 구분
9
+ */
10
+ import { type ChildProcess, spawn } from "node:child_process";
11
+ import { mkdirSync } from "node:fs";
12
+
13
+ import type { CliResult, QueryInput } from "./qgrid.types";
14
+ import { maskToken, ProcessError, QuotaError, TimeoutError } from "./qgrid.types";
15
+
16
+ type PendingRequest = {
17
+ resolve: (value: CliResult) => void;
18
+ reject: (reason: Error) => void;
19
+ timer: ReturnType<typeof setTimeout>;
20
+ };
21
+
22
+ export type WorkerConfig = {
23
+ token: string;
24
+ model: string;
25
+ timeout: number;
26
+ cwd: string;
27
+ maxCalls: number;
28
+ };
29
+
30
+ export class Worker {
31
+ tokenId: string;
32
+ config: WorkerConfig;
33
+ process: ChildProcess | null = null;
34
+ buffer = "";
35
+ pending: PendingRequest | null = null;
36
+ sessionId: string | null = null;
37
+ queue: Array<() => void> = [];
38
+ cwdCreated = false;
39
+ callCount = 0;
40
+ needsRecycle = false;
41
+
42
+ constructor(config: WorkerConfig) {
43
+ this.config = config;
44
+ this.tokenId = config.token;
45
+ }
46
+
47
+ getQueueDepth(): number {
48
+ return this.queue.length + (this.pending ? 1 : 0);
49
+ }
50
+
51
+ rejectPending(err: Error): void {
52
+ if (!this.pending) return;
53
+ this.pending.reject(err);
54
+ clearTimeout(this.pending.timer);
55
+ this.pending = null;
56
+ }
57
+
58
+ ensureProcess(): ChildProcess {
59
+ if (this.process && this.process.exitCode === null && !this.needsRecycle) {
60
+ return this.process;
61
+ }
62
+
63
+ if (this.process && this.process.exitCode === null) {
64
+ this.process.kill();
65
+ this.process = null;
66
+ }
67
+
68
+ const env: NodeJS.ProcessEnv = {
69
+ PATH: process.env.PATH,
70
+ HOME: process.env.HOME,
71
+ TMPDIR: process.env.TMPDIR,
72
+ CLAUDE_CODE_OAUTH_TOKEN: this.config.token,
73
+ };
74
+
75
+ const { cwd } = this.config;
76
+ if (!this.cwdCreated) {
77
+ mkdirSync(cwd, { recursive: true });
78
+ this.cwdCreated = true;
79
+ }
80
+
81
+ const child = spawn(
82
+ "claude",
83
+ [
84
+ "-p",
85
+ "--input-format",
86
+ "stream-json",
87
+ "--output-format",
88
+ "stream-json",
89
+ "--verbose",
90
+ "--max-turns",
91
+ "1",
92
+ "--model",
93
+ this.config.model,
94
+ ],
95
+ { stdio: ["pipe", "pipe", "ignore"], env, cwd },
96
+ );
97
+
98
+ child.stdout?.on("data", (d: Buffer) => {
99
+ this.buffer += d.toString();
100
+ this.processBuffer();
101
+ });
102
+
103
+ child.on("close", () => {
104
+ this.process = null;
105
+ this.rejectPending(
106
+ new ProcessError(`CLI process closed (token: ${maskToken(this.tokenId)})`),
107
+ );
108
+ this.drainQueue();
109
+ });
110
+
111
+ child.on("error", (err) => {
112
+ this.process = null;
113
+ this.rejectPending(
114
+ new ProcessError(`CLI process error: ${err.message} (token: ${maskToken(this.tokenId)})`),
115
+ );
116
+ this.drainQueue();
117
+ });
118
+
119
+ this.process = child;
120
+ this.buffer = "";
121
+ this.sessionId = null;
122
+ this.callCount = 0;
123
+ this.needsRecycle = false;
124
+ return child;
125
+ }
126
+
127
+ processBuffer(): void {
128
+ const lines = this.buffer.split("\n");
129
+ this.buffer = lines.pop() ?? "";
130
+
131
+ lines
132
+ .filter((line) => line.trim() !== "")
133
+ .forEach((line) => {
134
+ try {
135
+ const j = JSON.parse(line);
136
+
137
+ if (j.type === "system" && j.subtype === "init" && j.session_id) {
138
+ this.sessionId = j.session_id;
139
+ }
140
+
141
+ if (j.type === "result" && this.pending) {
142
+ let text: string = j.result ?? "";
143
+ text = text.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "");
144
+
145
+ if (text.startsWith("You've hit")) {
146
+ this.pending.reject(
147
+ new QuotaError(`Quota exhausted (token: ${maskToken(this.tokenId)})`),
148
+ );
149
+ clearTimeout(this.pending.timer);
150
+ this.pending = null;
151
+ this.drainQueue();
152
+ return;
153
+ }
154
+
155
+ const u = j.usage ?? {};
156
+ this.pending.resolve({
157
+ text,
158
+ usage: {
159
+ input_tokens: u.input_tokens ?? 0,
160
+ output_tokens: u.output_tokens ?? 0,
161
+ cache_creation_input_tokens: u.cache_creation_input_tokens ?? 0,
162
+ cache_read_input_tokens: u.cache_read_input_tokens ?? 0,
163
+ },
164
+ durationMs: j.duration_ms ?? 0,
165
+ costUsd: j.total_cost_usd ?? 0,
166
+ });
167
+ clearTimeout(this.pending.timer);
168
+ this.pending = null;
169
+ this.drainQueue();
170
+ }
171
+ } catch {
172
+ // skip non-json lines
173
+ }
174
+ });
175
+ }
176
+
177
+ drainQueue(): void {
178
+ if (this.pending) return;
179
+ const next = this.queue.shift();
180
+ if (next) next();
181
+ }
182
+
183
+ async query(input: QueryInput, timeoutMs?: number): Promise<CliResult> {
184
+ if (this.pending) {
185
+ await new Promise<void>((resolve) => {
186
+ this.queue.push(resolve);
187
+ });
188
+ }
189
+
190
+ const child = this.ensureProcess();
191
+ const timeout = timeoutMs ?? this.config.timeout;
192
+
193
+ this.callCount++;
194
+ if (this.callCount >= this.config.maxCalls) {
195
+ this.needsRecycle = true;
196
+ }
197
+
198
+ const prompt = input.system ? `${input.system}\n\n${input.prompt}` : input.prompt;
199
+
200
+ return new Promise<CliResult>((resolve, reject) => {
201
+ const timer = setTimeout(() => {
202
+ this.pending = null;
203
+ reject(
204
+ new TimeoutError(`Timeout after ${timeout / 1000}s (token: ${maskToken(this.tokenId)})`),
205
+ );
206
+ this.kill();
207
+ }, timeout);
208
+
209
+ this.pending = { resolve, reject, timer };
210
+
211
+ const msg = JSON.stringify({
212
+ type: "user",
213
+ message: { role: "user", content: prompt },
214
+ session_id: this.sessionId ?? "default",
215
+ parent_tool_use_id: null,
216
+ });
217
+
218
+ child.stdin?.write(`${msg}\n`);
219
+ });
220
+ }
221
+
222
+ kill(): void {
223
+ if (this.process && this.process.exitCode === null) {
224
+ this.process.kill();
225
+ }
226
+ this.process = null;
227
+ this.buffer = "";
228
+ this.sessionId = null;
229
+ this.callCount = 0;
230
+ this.needsRecycle = false;
231
+ this.rejectPending(new ProcessError(`Worker killed (token: ${maskToken(this.tokenId)})`));
232
+ this.drainQueue();
233
+ }
234
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @generated
3
+ * 직접 수정하지 마세요.
4
+ */
5
+ /* oxlint-disable */
6
+
7
+ import type { SSRQuery } from "sonamu/ssr";
8
+
9
+ // SSRQuery 헬퍼 함수
10
+ function createSSRQuery(
11
+ modelName: string,
12
+ methodName: string,
13
+ params: any[],
14
+ serviceKey: [string, string],
15
+ ): SSRQuery {
16
+ return { modelName, methodName, params, serviceKey, __brand: "SSRQuery" } as SSRQuery;
17
+ }
18
+
19
+ import { RequestLogListParams } from "./request-log/request-log.types";
20
+ import { TokenSubsetKey, RequestLogSubsetKey } from "./sonamu.generated";
21
+ import { TokenListParams } from "./token/token.types";
22
+
23
+ export namespace TokenService {
24
+ export const getToken = <T extends TokenSubsetKey>(subset: T, id: number): SSRQuery =>
25
+ createSSRQuery("TokenModel", "findById", [subset, id], ["Token", "getToken"]);
26
+
27
+ export const getTokens = <T extends TokenSubsetKey, LP extends TokenListParams>(
28
+ subset: T,
29
+ rawParams?: LP,
30
+ ): SSRQuery =>
31
+ createSSRQuery("TokenModel", "findMany", [subset, rawParams], ["Token", "getTokens"]);
32
+ }
33
+
34
+ export namespace RequestLogService {
35
+ export const getRequestLog = <T extends RequestLogSubsetKey>(subset: T, id: number): SSRQuery =>
36
+ createSSRQuery("RequestLogModel", "findById", [subset, id], ["RequestLog", "getRequestLog"]);
37
+
38
+ export const getRequestLogs = <T extends RequestLogSubsetKey, LP extends RequestLogListParams>(
39
+ subset: T,
40
+ rawParams?: LP,
41
+ ): SSRQuery =>
42
+ createSSRQuery(
43
+ "RequestLogModel",
44
+ "findMany",
45
+ [subset, rawParams],
46
+ ["RequestLog", "getRequestLogs"],
47
+ );
48
+ }
49
+
50
+ export namespace QgridService {
51
+ export const stats = (): SSRQuery =>
52
+ createSSRQuery("QgridFrame", "stats", [], ["Qgrid", "stats"]);
53
+
54
+ export const usage = (tokenName?: string): SSRQuery =>
55
+ createSSRQuery("QgridFrame", "usage", [tokenName], ["Qgrid", "usage"]);
56
+
57
+ export const health = (): SSRQuery =>
58
+ createSSRQuery("QgridFrame", "health", [], ["Qgrid", "health"]);
59
+ }
@@ -0,0 +1,75 @@
1
+ {
2
+ "id": "RequestLog",
3
+ "table": "request_logs",
4
+ "title": "RequestLog",
5
+ "cone": {
6
+ "note": "RequestLog 엔티티. 테스트 데이터 생성 시 각 필드는 실제 데이터와 유사한 형식으로 생성됩니다."
7
+ },
8
+ "props": [
9
+ {
10
+ "name": "id",
11
+ "type": "integer",
12
+ "desc": "ID",
13
+ "cone": {
14
+ "note": "정수 형식으로 생성됩니다.",
15
+ "fixtureGenerator": "faker.number.int({ \"min\": 1, \"max\": 1000 })"
16
+ }
17
+ },
18
+ {
19
+ "name": "created_at",
20
+ "type": "date",
21
+ "desc": "등록일시",
22
+ "dbDefault": "CURRENT_TIMESTAMP",
23
+ "cone": { "note": "날짜 형식으로 생성됩니다.", "fixtureGenerator": "faker.date.past()" }
24
+ },
25
+ {
26
+ "name": "token_name",
27
+ "toFilter": true,
28
+ "desc": "토큰 이름",
29
+ "type": "string",
30
+ "length": 100
31
+ },
32
+ { "name": "query", "type": "string", "desc": "prompt 전문" },
33
+ { "name": "response", "desc": "llm 응답 전문", "type": "string" },
34
+ { "name": "input_tokens", "type": "integer", "desc": "입력 토큰" },
35
+ { "name": "output_tokens", "type": "integer", "desc": "출력 토큰" },
36
+ { "name": "cache_read_tokens", "type": "integer", "desc": "캐시 읽기 토큰" },
37
+ { "name": "cache_creation_tokens", "type": "integer", "desc": "캐시 생성 토큰" },
38
+ { "name": "duration_ms", "type": "integer", "desc": "응답 소요시간(ms)" }
39
+ ],
40
+ "indexes": [],
41
+ "subsets": {
42
+ "A": {
43
+ "fields": [
44
+ "id",
45
+ "created_at",
46
+ "token_name",
47
+ "query",
48
+ "response",
49
+ "input_tokens",
50
+ "output_tokens",
51
+ "cache_read_tokens",
52
+ "cache_creation_tokens",
53
+ "duration_ms"
54
+ ],
55
+ "cone": { "note": "전체 필드 서브셋" }
56
+ }
57
+ },
58
+ "enums": {
59
+ "RequestLogOrderBy": {
60
+ "values": { "id-desc": "ID최신순" },
61
+ "cone": { "note": "가능한 값: id-desc", "values": { "id-desc": { "note": "ID최신순" } } }
62
+ },
63
+ "RequestLogSearchField": {
64
+ "values": { "id": "ID", "token_name": "토큰이름", "query": "쿼리" },
65
+ "cone": {
66
+ "note": "가능한 값: id, token_name, query",
67
+ "values": {
68
+ "id": { "note": "ID" },
69
+ "token_name": { "note": "토큰이름" },
70
+ "query": { "note": "쿼리" }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,155 @@
1
+ import {
2
+ api,
3
+ asArray,
4
+ BadRequestException,
5
+ BaseModelClass,
6
+ exhaustive,
7
+ type ListResult,
8
+ NotFoundException,
9
+ } from "sonamu";
10
+
11
+ import { SD } from "../../i18n/sd.generated";
12
+ import type { RequestLogSubsetKey, RequestLogSubsetMapping } from "../sonamu.generated";
13
+ import { requestLogLoaderQueries, requestLogSubsetQueries } from "../sonamu.generated.sso";
14
+ import type { RequestLogListParams, RequestLogSaveParams } from "./request-log.types";
15
+
16
+ /*
17
+ RequestLog Model
18
+ */
19
+ class RequestLogModelClass extends BaseModelClass<
20
+ RequestLogSubsetKey,
21
+ RequestLogSubsetMapping,
22
+ typeof requestLogSubsetQueries,
23
+ typeof requestLogLoaderQueries
24
+ > {
25
+ constructor() {
26
+ super("RequestLog", requestLogSubsetQueries, requestLogLoaderQueries);
27
+ }
28
+
29
+ @api({ httpMethod: "GET", clients: ["axios", "tanstack-query"], resourceName: "RequestLog" })
30
+ async findById<T extends RequestLogSubsetKey>(
31
+ subset: T,
32
+ id: number,
33
+ ): Promise<RequestLogSubsetMapping[T]> {
34
+ const { rows } = await this.findMany(subset, {
35
+ id,
36
+ num: 1,
37
+ page: 1,
38
+ });
39
+ if (!rows[0]) {
40
+ throw new NotFoundException(SD("error.entityNotFound")("RequestLog", id));
41
+ }
42
+
43
+ return rows[0];
44
+ }
45
+
46
+ async findOne<T extends RequestLogSubsetKey>(
47
+ subset: T,
48
+ listParams: RequestLogListParams,
49
+ ): Promise<RequestLogSubsetMapping[T] | null> {
50
+ const { rows } = await this.findMany(subset, {
51
+ ...listParams,
52
+ num: 1,
53
+ page: 1,
54
+ });
55
+
56
+ return rows[0] ?? null;
57
+ }
58
+
59
+ @api({ httpMethod: "GET", clients: ["axios", "tanstack-query"], resourceName: "RequestLogs" })
60
+ async findMany<T extends RequestLogSubsetKey, LP extends RequestLogListParams>(
61
+ subset: T,
62
+ rawParams?: LP,
63
+ ): Promise<ListResult<LP, RequestLogSubsetMapping[T]>> {
64
+ // params with defaults
65
+ const params = {
66
+ num: 24,
67
+ page: 1,
68
+ search: "id" as const,
69
+ orderBy: "id-desc" as const,
70
+ ...rawParams,
71
+ } satisfies RequestLogListParams;
72
+
73
+ // build queries
74
+ const { qb, onSubset: _ } = this.getSubsetQueries(subset);
75
+
76
+ // id
77
+ if (params.id) {
78
+ qb.whereIn("request_logs.id", asArray(params.id));
79
+ }
80
+
81
+ // token_name filter (toFilter)
82
+ if (params.token_name) {
83
+ qb.where("request_logs.token_name", params.token_name);
84
+ }
85
+
86
+ // search-keyword
87
+ if (params.search && params.keyword && params.keyword.length > 0) {
88
+ if (params.search === "id") {
89
+ qb.where("request_logs.id", Number(params.keyword));
90
+ } else if (params.search === "token_name") {
91
+ qb.where("request_logs.token_name", "like", `%${params.keyword}%`);
92
+ } else if (params.search === "query") {
93
+ qb.where("request_logs.query", "like", `%${params.keyword}%`);
94
+ } else {
95
+ throw new BadRequestException(SD("error.unknownSearchField")(params.search));
96
+ }
97
+ }
98
+
99
+ // orderBy
100
+ if (params.orderBy) {
101
+ // default orderBy
102
+ if (params.orderBy === "id-desc") {
103
+ qb.orderBy("request_logs.id", "desc");
104
+ } else {
105
+ exhaustive(params.orderBy);
106
+ }
107
+ }
108
+
109
+ const enhancers = this.createEnhancers({
110
+ A: (row) => ({
111
+ ...row,
112
+ // 서브셋별로 virtual 필드 계산로직 추가
113
+ }),
114
+ });
115
+
116
+ return this.executeSubsetQuery({
117
+ subset,
118
+ qb,
119
+ params,
120
+ enhancers,
121
+ debug: false,
122
+ });
123
+ }
124
+
125
+ @api({ httpMethod: "POST", clients: ["axios", "tanstack-mutation"] })
126
+ async save(spa: RequestLogSaveParams[]): Promise<number[]> {
127
+ const wdb = this.getPuri("w");
128
+
129
+ // register
130
+ spa.forEach((sp) => {
131
+ wdb.ubRegister("request_logs", sp);
132
+ });
133
+
134
+ // transaction
135
+ return wdb.transaction(async (trx) => {
136
+ const ids = await trx.ubUpsert("request_logs");
137
+
138
+ return ids;
139
+ });
140
+ }
141
+
142
+ @api({ httpMethod: "POST", clients: ["axios", "tanstack-mutation"] })
143
+ async del(ids: number[]): Promise<number> {
144
+ const wdb = this.getPuri("w");
145
+
146
+ // transaction
147
+ await wdb.transaction(async (trx) => {
148
+ return trx.table("request_logs").whereIn("request_logs.id", ids).delete();
149
+ });
150
+
151
+ return ids.length;
152
+ }
153
+ }
154
+
155
+ export const RequestLogModel = new RequestLogModelClass();
@@ -0,0 +1,15 @@
1
+ import type { z } from "zod";
2
+
3
+ import { RequestLogBaseListParams, RequestLogBaseSchema } from "../sonamu.generated";
4
+
5
+ // RequestLog - ListParams
6
+ export const RequestLogListParams = RequestLogBaseListParams;
7
+ export type RequestLogListParams = z.infer<typeof RequestLogListParams>;
8
+
9
+ // RequestLog - SaveParams
10
+ export const RequestLogSaveParams = RequestLogBaseSchema.partial({
11
+ id: true,
12
+ created_at: true,
13
+ token_name: true,
14
+ });
15
+ export type RequestLogSaveParams = z.infer<typeof RequestLogSaveParams>;