@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,44 @@
1
+ import { getSafeTimers } from "../../../../../@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/timers.js";
2
+ //#region ../../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
3
+ const NAME_WORKER_STATE = "__vitest_worker__";
4
+ function getWorkerState() {
5
+ const workerState = globalThis[NAME_WORKER_STATE];
6
+ if (!workerState) throw new Error("Vitest failed to access its internal state.\n\nOne of the following is possible:\n- \"vitest\" is imported directly without running \"vitest\" command\n- \"vitest\" is imported inside \"globalSetup\" (to fix this, use \"setupFiles\" instead, because \"globalSetup\" runs in a different context)\n- \"vitest\" is imported inside Vite / Vitest config file\n- Otherwise, it might be a Vitest bug. Please report it to https://github.com/vitest-dev/vitest/issues\n");
7
+ return workerState;
8
+ }
9
+ function isChildProcess() {
10
+ return typeof process !== "undefined" && !!process.send;
11
+ }
12
+ function resetModules(modules, resetMocks = false) {
13
+ const skipPaths = [
14
+ /\/vitest\/dist\//,
15
+ /vitest-virtual-\w+\/dist/,
16
+ /@vitest\/dist/,
17
+ ...!resetMocks ? [/^mock:/] : []
18
+ ];
19
+ modules.idToModuleMap.forEach((node, path) => {
20
+ if (skipPaths.some((re) => re.test(path))) return;
21
+ node.promise = void 0;
22
+ node.exports = void 0;
23
+ node.evaluated = false;
24
+ node.importers.clear();
25
+ });
26
+ }
27
+ function waitNextTick() {
28
+ const { setTimeout } = getSafeTimers();
29
+ return new Promise((resolve) => setTimeout(resolve, 0));
30
+ }
31
+ async function waitForImportsToResolve() {
32
+ await waitNextTick();
33
+ const state = getWorkerState();
34
+ const promises = [];
35
+ const resolvingCount = state.resolvingModules.size;
36
+ for (const [_, mod] of state.evaluatedModules.idToModuleMap) if (mod.promise && !mod.evaluated) promises.push(mod.promise);
37
+ if (!promises.length && !resolvingCount) return;
38
+ await Promise.allSettled(promises);
39
+ await waitForImportsToResolve();
40
+ }
41
+ //#endregion
42
+ export { getWorkerState, isChildProcess, resetModules, waitForImportsToResolve };
43
+
44
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuQlg1Rmc4QzQuanMiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vbm9kZV9tb2R1bGVzLy5wbnBtL3ZpdGVzdEA0LjEuMl9Ab3BlbnRlbGVtZXRyeSthcGlAMS45LjBfQHR5cGVzK25vZGVAMjUuMC43X3ZpdGVAOC4wLjNfQGVtbmFwaStjb3JlQDEuOS4yX19iYjMxMDg3Mjc4Zjk3YTM5YjMxYjkyMDEwYmVhYjk4MC9ub2RlX21vZHVsZXMvdml0ZXN0L2Rpc3QvY2h1bmtzL3V0aWxzLkJYNUZnOEM0LmpzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGdldFNhZmVUaW1lcnMgfSBmcm9tICdAdml0ZXN0L3V0aWxzL3RpbWVycyc7XG5cbmNvbnN0IE5BTUVfV09SS0VSX1NUQVRFID0gXCJfX3ZpdGVzdF93b3JrZXJfX1wiO1xuY2xhc3MgRW52aXJvbm1lbnRUZWFyZG93bkVycm9yIGV4dGVuZHMgRXJyb3Ige1xuXHRuYW1lID0gXCJFbnZpcm9ubWVudFRlYXJkb3duRXJyb3JcIjtcbn1cbmZ1bmN0aW9uIGdldFdvcmtlclN0YXRlKCkge1xuXHQvLyBAdHMtZXhwZWN0LWVycm9yIHVudHlwZWQgZ2xvYmFsXG5cdGNvbnN0IHdvcmtlclN0YXRlID0gZ2xvYmFsVGhpc1tOQU1FX1dPUktFUl9TVEFURV07XG5cdGlmICghd29ya2VyU3RhdGUpIHRocm93IG5ldyBFcnJvcihcIlZpdGVzdCBmYWlsZWQgdG8gYWNjZXNzIGl0cyBpbnRlcm5hbCBzdGF0ZS5cXG5cXG5PbmUgb2YgdGhlIGZvbGxvd2luZyBpcyBwb3NzaWJsZTpcXG4tIFxcXCJ2aXRlc3RcXFwiIGlzIGltcG9ydGVkIGRpcmVjdGx5IHdpdGhvdXQgcnVubmluZyBcXFwidml0ZXN0XFxcIiBjb21tYW5kXFxuLSBcXFwidml0ZXN0XFxcIiBpcyBpbXBvcnRlZCBpbnNpZGUgXFxcImdsb2JhbFNldHVwXFxcIiAodG8gZml4IHRoaXMsIHVzZSBcXFwic2V0dXBGaWxlc1xcXCIgaW5zdGVhZCwgYmVjYXVzZSBcXFwiZ2xvYmFsU2V0dXBcXFwiIHJ1bnMgaW4gYSBkaWZmZXJlbnQgY29udGV4dClcXG4tIFxcXCJ2aXRlc3RcXFwiIGlzIGltcG9ydGVkIGluc2lkZSBWaXRlIC8gVml0ZXN0IGNvbmZpZyBmaWxlXFxuLSBPdGhlcndpc2UsIGl0IG1pZ2h0IGJlIGEgVml0ZXN0IGJ1Zy4gUGxlYXNlIHJlcG9ydCBpdCB0byBodHRwczovL2dpdGh1Yi5jb20vdml0ZXN0LWRldi92aXRlc3QvaXNzdWVzXFxuXCIpO1xuXHRyZXR1cm4gd29ya2VyU3RhdGU7XG59XG5mdW5jdGlvbiBnZXRTYWZlV29ya2VyU3RhdGUoKSB7XG5cdC8vIEB0cy1leHBlY3QtZXJyb3IgdW50eXBlZCBnbG9iYWxcblx0cmV0dXJuIGdsb2JhbFRoaXNbTkFNRV9XT1JLRVJfU1RBVEVdO1xufVxuZnVuY3Rpb24gcHJvdmlkZVdvcmtlclN0YXRlKGNvbnRleHQsIHN0YXRlKSB7XG5cdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShjb250ZXh0LCBOQU1FX1dPUktFUl9TVEFURSwge1xuXHRcdHZhbHVlOiBzdGF0ZSxcblx0XHRjb25maWd1cmFibGU6IHRydWUsXG5cdFx0d3JpdGFibGU6IHRydWUsXG5cdFx0ZW51bWVyYWJsZTogZmFsc2Vcblx0fSk7XG5cdHJldHVybiBzdGF0ZTtcbn1cbmZ1bmN0aW9uIGlzQ2hpbGRQcm9jZXNzKCkge1xuXHRyZXR1cm4gdHlwZW9mIHByb2Nlc3MgIT09IFwidW5kZWZpbmVkXCIgJiYgISFwcm9jZXNzLnNlbmQ7XG59XG5mdW5jdGlvbiByZXNldE1vZHVsZXMobW9kdWxlcywgcmVzZXRNb2NrcyA9IGZhbHNlKSB7XG5cdGNvbnN0IHNraXBQYXRocyA9IFtcblx0XHQvXFwvdml0ZXN0XFwvZGlzdFxcLy8sXG5cdFx0L3ZpdGVzdC12aXJ0dWFsLVxcdytcXC9kaXN0Lyxcblx0XHQvQHZpdGVzdFxcL2Rpc3QvLFxuXHRcdC4uLiFyZXNldE1vY2tzID8gWy9ebW9jazovXSA6IFtdXG5cdF07XG5cdG1vZHVsZXMuaWRUb01vZHVsZU1hcC5mb3JFYWNoKChub2RlLCBwYXRoKSA9PiB7XG5cdFx0aWYgKHNraXBQYXRocy5zb21lKChyZSkgPT4gcmUudGVzdChwYXRoKSkpIHJldHVybjtcblx0XHRub2RlLnByb21pc2UgPSB2b2lkIDA7XG5cdFx0bm9kZS5leHBvcnRzID0gdm9pZCAwO1xuXHRcdG5vZGUuZXZhbHVhdGVkID0gZmFsc2U7XG5cdFx0bm9kZS5pbXBvcnRlcnMuY2xlYXIoKTtcblx0fSk7XG59XG5mdW5jdGlvbiB3YWl0TmV4dFRpY2soKSB7XG5cdGNvbnN0IHsgc2V0VGltZW91dCB9ID0gZ2V0U2FmZVRpbWVycygpO1xuXHRyZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgMCkpO1xufVxuYXN5bmMgZnVuY3Rpb24gd2FpdEZvckltcG9ydHNUb1Jlc29sdmUoKSB7XG5cdGF3YWl0IHdhaXROZXh0VGljaygpO1xuXHRjb25zdCBzdGF0ZSA9IGdldFdvcmtlclN0YXRlKCk7XG5cdGNvbnN0IHByb21pc2VzID0gW107XG5cdGNvbnN0IHJlc29sdmluZ0NvdW50ID0gc3RhdGUucmVzb2x2aW5nTW9kdWxlcy5zaXplO1xuXHRmb3IgKGNvbnN0IFtfLCBtb2RdIG9mIHN0YXRlLmV2YWx1YXRlZE1vZHVsZXMuaWRUb01vZHVsZU1hcCkgaWYgKG1vZC5wcm9taXNlICYmICFtb2QuZXZhbHVhdGVkKSBwcm9taXNlcy5wdXNoKG1vZC5wcm9taXNlKTtcblx0aWYgKCFwcm9taXNlcy5sZW5ndGggJiYgIXJlc29sdmluZ0NvdW50KSByZXR1cm47XG5cdGF3YWl0IFByb21pc2UuYWxsU2V0dGxlZChwcm9taXNlcyk7XG5cdGF3YWl0IHdhaXRGb3JJbXBvcnRzVG9SZXNvbHZlKCk7XG59XG5cbmV4cG9ydCB7IEVudmlyb25tZW50VGVhcmRvd25FcnJvciBhcyBFLCBnZXRTYWZlV29ya2VyU3RhdGUgYXMgYSwgZ2V0V29ya2VyU3RhdGUgYXMgZywgaXNDaGlsZFByb2Nlc3MgYXMgaSwgcHJvdmlkZVdvcmtlclN0YXRlIGFzIHAsIHJlc2V0TW9kdWxlcyBhcyByLCB3YWl0Rm9ySW1wb3J0c1RvUmVzb2x2ZSBhcyB3IH07XG4iXSwieF9nb29nbGVfaWdub3JlTGlzdCI6WzBdLCJtYXBwaW5ncyI6Ijs7QUFFQSxNQUFNLG9CQUFvQjtBQUkxQixTQUFTLGlCQUFpQjtDQUV6QixNQUFNLGNBQWMsV0FBVztBQUMvQixLQUFJLENBQUMsWUFBYSxPQUFNLElBQUksTUFBTSw4Y0FBOGM7QUFDaGYsUUFBTzs7QUFlUixTQUFTLGlCQUFpQjtBQUN6QixRQUFPLE9BQU8sWUFBWSxlQUFlLENBQUMsQ0FBQyxRQUFROztBQUVwRCxTQUFTLGFBQWEsU0FBUyxhQUFhLE9BQU87Q0FDbEQsTUFBTSxZQUFZO0VBQ2pCO0VBQ0E7RUFDQTtFQUNBLEdBQUcsQ0FBQyxhQUFhLENBQUMsU0FBUyxHQUFHLEVBQUU7RUFDaEM7QUFDRCxTQUFRLGNBQWMsU0FBUyxNQUFNLFNBQVM7QUFDN0MsTUFBSSxVQUFVLE1BQU0sT0FBTyxHQUFHLEtBQUssS0FBSyxDQUFDLENBQUU7QUFDM0MsT0FBSyxVQUFVLEtBQUs7QUFDcEIsT0FBSyxVQUFVLEtBQUs7QUFDcEIsT0FBSyxZQUFZO0FBQ2pCLE9BQUssVUFBVSxPQUFPO0dBQ3JCOztBQUVILFNBQVMsZUFBZTtDQUN2QixNQUFNLEVBQUUsZUFBZSxlQUFlO0FBQ3RDLFFBQU8sSUFBSSxTQUFTLFlBQVksV0FBVyxTQUFTLEVBQUUsQ0FBQzs7QUFFeEQsZUFBZSwwQkFBMEI7QUFDeEMsT0FBTSxjQUFjO0NBQ3BCLE1BQU0sUUFBUSxnQkFBZ0I7Q0FDOUIsTUFBTSxXQUFXLEVBQUU7Q0FDbkIsTUFBTSxpQkFBaUIsTUFBTSxpQkFBaUI7QUFDOUMsTUFBSyxNQUFNLENBQUMsR0FBRyxRQUFRLE1BQU0saUJBQWlCLGNBQWUsS0FBSSxJQUFJLFdBQVcsQ0FBQyxJQUFJLFVBQVcsVUFBUyxLQUFLLElBQUksUUFBUTtBQUMxSCxLQUFJLENBQUMsU0FBUyxVQUFVLENBQUMsZUFBZ0I7QUFDekMsT0FBTSxRQUFRLFdBQVcsU0FBUztBQUNsQyxPQUFNLHlCQUF5QiJ9
@@ -0,0 +1,28 @@
1
+ import "../../../../@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/helpers.js";
2
+ import "../../../../@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/error.js";
3
+ import "../../../../@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/timers.js";
4
+ import "../../../../@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/source-map.js";
5
+ import "../../../../pathe@2.0.3/node_modules/pathe/dist/index.js";
6
+ import "../../../../@vitest_runner@4.1.2/node_modules/@vitest/runner/dist/chunk-artifact.js";
7
+ import "../../../../@vitest_runner@4.1.2/node_modules/@vitest/runner/dist/index.js";
8
+ import "../../../../@vitest_runner@4.1.2/node_modules/@vitest/runner/dist/utils.js";
9
+ import "./chunks/utils.BX5Fg8C4.js";
10
+ import "./chunks/benchmark.CX_oY03V.js";
11
+ import "../../../../@vitest_spy@4.1.2/node_modules/@vitest/spy/dist/index.js";
12
+ import "../../../../chai@6.2.2/node_modules/chai/index.js";
13
+ import "../../../../@vitest_expect@4.1.2/node_modules/@vitest/expect/dist/index.js";
14
+ import "../../../../@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/offset.js";
15
+ import "../../../../@vitest_snapshot@4.1.2/node_modules/@vitest/snapshot/dist/index.js";
16
+ import "./chunks/_commonjsHelpers.D26ty3Ew.js";
17
+ import "./chunks/index.Chj8NDwU.js";
18
+ import "./chunks/rpc.MzXet3jl.js";
19
+ import "./chunks/test.p_J6dB8a.js";
20
+ import "../../../../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";
21
+ import "./chunks/evaluatedModules.Dg1zASAC.js";
22
+ import { require_dist } from "../../../../expect-type@1.3.0/node_modules/expect-type/dist/index.js";
23
+ import "./chunks/index.DOa3dzoN.js";
24
+ require_dist();
25
+ //#endregion
26
+ export {};
27
+
28
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vbm9kZV9tb2R1bGVzLy5wbnBtL3ZpdGVzdEA0LjEuMl9Ab3BlbnRlbGVtZXRyeSthcGlAMS45LjBfQHR5cGVzK25vZGVAMjUuMC43X3ZpdGVAOC4wLjNfQGVtbmFwaStjb3JlQDEuOS4yX19iYjMxMDg3Mjc4Zjk3YTM5YjMxYjkyMDEwYmVhYjk4MC9ub2RlX21vZHVsZXMvdml0ZXN0L2Rpc3QvaW5kZXguanMiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHsgTiBhcyBCZW5jaG1hcmtSdW5uZXIsIFQgYXMgVGVzdFJ1bm5lciwgYSBhcyBhc3NlcnQsIGMgYXMgY3JlYXRlRXhwZWN0LCBnIGFzIGV4cGVjdCwgaSBhcyBpbmplY3QsIHMgYXMgc2hvdWxkLCB2IGFzIHZpLCBiIGFzIHZpdGVzdCB9IGZyb20gJy4vY2h1bmtzL3Rlc3QucF9KNmRCOGEuanMnO1xuZXhwb3J0IHsgYiBhcyBiZW5jaCB9IGZyb20gJy4vY2h1bmtzL2JlbmNobWFyay5DWF9vWTAzVi5qcyc7XG5leHBvcnQgeyBWIGFzIEV2YWx1YXRlZE1vZHVsZXMgfSBmcm9tICcuL2NodW5rcy9ldmFsdWF0ZWRNb2R1bGVzLkRnMXpBU0FDLmpzJztcbmV4cG9ydCB7IGEgYXMgYXNzZXJ0VHlwZSB9IGZyb20gJy4vY2h1bmtzL2luZGV4LkRPYTNkem9OLmpzJztcbmV4cG9ydCB7IGV4cGVjdFR5cGVPZiB9IGZyb20gJ2V4cGVjdC10eXBlJztcbmV4cG9ydCB7IGFmdGVyQWxsLCBhZnRlckVhY2gsIGFyb3VuZEFsbCwgYXJvdW5kRWFjaCwgYmVmb3JlQWxsLCBiZWZvcmVFYWNoLCBkZXNjcmliZSwgaXQsIG9uVGVzdEZhaWxlZCwgb25UZXN0RmluaXNoZWQsIHJlY29yZEFydGlmYWN0LCBzdWl0ZSwgdGVzdCB9IGZyb20gJ0B2aXRlc3QvcnVubmVyJztcbmV4cG9ydCB7IGNoYWkgfSBmcm9tICdAdml0ZXN0L2V4cGVjdCc7XG5pbXBvcnQgJ0B2aXRlc3QvdXRpbHMvaGVscGVycyc7XG5pbXBvcnQgJ0B2aXRlc3QvdXRpbHMvdGltZXJzJztcbmltcG9ydCAnLi9jaHVua3MvdXRpbHMuQlg1Rmc4QzQuanMnO1xuaW1wb3J0ICdAdml0ZXN0L3J1bm5lci91dGlscyc7XG5pbXBvcnQgJ0B2aXRlc3QvdXRpbHMvZXJyb3InO1xuaW1wb3J0ICdwYXRoZSc7XG5pbXBvcnQgJ0B2aXRlc3Qvc25hcHNob3QnO1xuaW1wb3J0ICdAdml0ZXN0L3NweSc7XG5pbXBvcnQgJ0B2aXRlc3QvdXRpbHMvb2Zmc2V0JztcbmltcG9ydCAnQHZpdGVzdC91dGlscy9zb3VyY2UtbWFwJztcbmltcG9ydCAnLi9jaHVua3MvX2NvbW1vbmpzSGVscGVycy5EMjZ0eTNFdy5qcyc7XG5pbXBvcnQgJy4vY2h1bmtzL3JwYy5NelhldDNqbC5qcyc7XG5pbXBvcnQgJy4vY2h1bmtzL2luZGV4LkNoajhORHdVLmpzJztcbmltcG9ydCAndml0ZS9tb2R1bGUtcnVubmVyJztcbiJdLCJ4X2dvb2dsZV9pZ25vcmVMaXN0IjpbMF0sIm1hcHBpbmdzIjoiIn0=
@@ -0,0 +1,196 @@
1
+ import "./_virtual/_rolldown/runtime.js";
2
+ import { CachePresets, defineConfig } from "sonamu";
3
+ import path from "path";
4
+ import { getConsoleSink } from "@logtape/logtape";
5
+ import { getPrettyFormatter } from "@logtape/pretty";
6
+ import dotenv from "dotenv";
7
+ import { drivers, store } from "sonamu/cache";
8
+ import { drivers as drivers$1 } from "sonamu/storage";
9
+ //#region src/sonamu.config.ts
10
+ dotenv.config({ path: path.join(import.meta.dirname, "../.env") });
11
+ const host = process.env.HOST ?? "localhost";
12
+ const port = Number(process.env.PORT ?? 44900);
13
+ var sonamu_config_default = defineConfig({
14
+ projectName: process.env.PROJECT_NAME ?? "SonamuProject",
15
+ database: {
16
+ name: process.env.QGRID_DB_NAME ?? "qgrid",
17
+ defaultOptions: { connection: {
18
+ host: process.env.QGRID_DB_HOST ?? "localhost",
19
+ port: Number(process.env.QGRID_DB_PORT ?? 44901),
20
+ user: process.env.QGRID_DB_USER ?? "postgres",
21
+ password: process.env.QGRID_DB_PASSWORD ?? "postgres"
22
+ } },
23
+ environments: {
24
+ production: { connection: {
25
+ host: process.env.DEV0_DB_HOST,
26
+ port: Number(process.env.DEV0_DB_PORT),
27
+ database: process.env.DATABASE_NAME,
28
+ user: process.env.DEV0_DB_USER,
29
+ password: process.env.DEV0_DB_PASSWORD
30
+ } },
31
+ fixture: { connection: {
32
+ host: process.env.DEV0_DB_HOST,
33
+ port: Number(process.env.DEV0_DB_PORT),
34
+ database: process.env.DATABASE_NAME,
35
+ user: process.env.DEV0_DB_USER,
36
+ password: process.env.DEV0_DB_PASSWORD
37
+ } }
38
+ }
39
+ },
40
+ api: {
41
+ dir: "api",
42
+ timezone: "Asia/Seoul",
43
+ route: { prefix: "/api" }
44
+ },
45
+ i18n: {
46
+ defaultLocale: "ko",
47
+ supportedLocales: ["ko", "en"]
48
+ },
49
+ sync: { targets: ["web"] },
50
+ slackConfirm: process.env.SLACK_BOT_TOKEN && process.env.SLACK_CHANNEL_ID ? {
51
+ targets: ["development_master", "production_master"],
52
+ botToken: process.env.SLACK_BOT_TOKEN ?? "",
53
+ channelId: process.env.SLACK_CHANNEL_ID ?? ""
54
+ } : void 0,
55
+ test: {
56
+ parallel: true,
57
+ maxWorkers: 4,
58
+ devRunner: { enabled: true }
59
+ },
60
+ logging: {
61
+ sinks: { console: getConsoleSink({ formatter: getPrettyFormatter({
62
+ timestamp: "time",
63
+ categoryWidth: 20,
64
+ categoryTruncate: "middle"
65
+ }) }) },
66
+ loggers: [{
67
+ category: ["sonamu"],
68
+ sinks: ["console"],
69
+ lowestLevel: process.env.NODE_ENV === "test" ? "warning" : "debug"
70
+ }]
71
+ },
72
+ server: {
73
+ listen: {
74
+ port,
75
+ host
76
+ },
77
+ plugins: {
78
+ formbody: true,
79
+ qs: true,
80
+ multipart: { limits: { fileSize: 1024 * 1024 * 30 } },
81
+ static: {
82
+ root: path.join(import.meta.dirname, "/../", "public"),
83
+ prefix: "/api/public"
84
+ },
85
+ custom: (server) => {
86
+ server.get("/callback", async (request, reply) => {
87
+ const { code, state } = request.query;
88
+ if (!code || !state) return reply.redirect("/?oauth=error&reason=missing_params");
89
+ try {
90
+ const { QgridFrame } = await import("./application/qgrid/qgrid.frame.js");
91
+ await QgridFrame.handleOAuthCallback(code, state, reply);
92
+ } catch (e) {
93
+ return reply.redirect(`/?oauth=error&reason=${encodeURIComponent(e.message)}`);
94
+ }
95
+ });
96
+ }
97
+ },
98
+ apiConfig: {
99
+ contextProvider: (defaultContext, request) => {
100
+ return {
101
+ ...defaultContext,
102
+ ip: request.ip,
103
+ body: request.body
104
+ };
105
+ },
106
+ guardHandler: (_guard, _request, _api) => {
107
+ if (_guard === "user") console.log("user guard");
108
+ console.log("NOTHING YET");
109
+ },
110
+ cacheControlHandler: (req) => {
111
+ switch (req.type) {
112
+ case "assets":
113
+ if (req.path.match(/-[a-f0-9]+\./)) return CachePresets.immutable;
114
+ return CachePresets.longLived;
115
+ case "api":
116
+ if (req.method === "GET") {
117
+ if (req.path.startsWith("/api/static-data")) return CachePresets.shortLived;
118
+ if (req.path.startsWith("/api/terms")) return CachePresets.mediumLived;
119
+ }
120
+ return CachePresets.noCache;
121
+ case "ssr": return CachePresets.ssr;
122
+ case "csr": return CachePresets.shortLived;
123
+ }
124
+ }
125
+ },
126
+ storage: { drivers: {
127
+ fs: drivers$1.fs({
128
+ location: path.join(import.meta.dirname, "/../public/uploaded"),
129
+ visibility: "public",
130
+ urlBuilder: {
131
+ async generateURL(key) {
132
+ return `/api/public/uploaded/${key}`;
133
+ },
134
+ async generateSignedURL(key) {
135
+ return `/api/public/uploaded/${key}`;
136
+ }
137
+ }
138
+ }),
139
+ s3: drivers$1.s3({
140
+ credentials: {
141
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
142
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? ""
143
+ },
144
+ region: process.env.S3_REGION ?? "ap-northeast-2",
145
+ bucket: process.env.S3_BUCKET ?? "sonamu_default_bucket",
146
+ visibility: "private"
147
+ })
148
+ } },
149
+ cache: {
150
+ default: "main",
151
+ stores: { main: store().useL1Layer(drivers.memory({ maxSize: "50mb" })) },
152
+ ttl: "5m",
153
+ prefix: ""
154
+ },
155
+ lifecycle: {
156
+ onStart: async () => {
157
+ try {
158
+ const { TokenModel } = await import("./application/token/token.model.js");
159
+ const knex = TokenModel.getDB("w");
160
+ const migrationsDir = path.join(import.meta.dirname, "../src/migrations");
161
+ const [batch, log] = await knex.migrate.latest({ directory: migrationsDir });
162
+ if (log.length > 0) {
163
+ console.log(`✓ Migration: ${log.length} applied (batch ${batch})`);
164
+ log.forEach((name) => console.log(` ✓ ${name}`));
165
+ }
166
+ } catch (e) {
167
+ console.warn(`⚠ Migration skipped: ${e.message}`);
168
+ }
169
+ const { initPool } = await import("./application/qgrid/pool.js");
170
+ try {
171
+ const { TokenModel } = await import("./application/token/token.model.js");
172
+ const pool = initPool((await TokenModel.findActive("A")).map((e) => e.token));
173
+ console.log(`🌲 Server listening on http://${host}:${port} (${pool.workers.size} tokens loaded)`);
174
+ } catch (e) {
175
+ console.warn(`⚠ Pool init failed: ${e.message}`);
176
+ initPool([]);
177
+ console.log(`🌲 Server listening on http://${host}:${port} (no tokens)`);
178
+ }
179
+ },
180
+ onShutdown: () => {
181
+ console.log("graceful shutdown");
182
+ },
183
+ onError: (error, _request, reply) => {
184
+ console.error(error);
185
+ reply.status(500).send({
186
+ name: error.name,
187
+ message: error.message
188
+ });
189
+ }
190
+ }
191
+ }
192
+ });
193
+ //#endregion
194
+ export { sonamu_config_default as default };
195
+
196
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic29uYW11LmNvbmZpZy5qcyIsIm5hbWVzIjpbImRyaXZlcnMiLCJjYWNoZURyaXZlcnMiXSwic291cmNlcyI6WyIuLi9zcmMvc29uYW11LmNvbmZpZy50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgcGF0aCBmcm9tIFwicGF0aFwiO1xuXG5pbXBvcnQgeyBnZXRDb25zb2xlU2luayB9IGZyb20gXCJAbG9ndGFwZS9sb2d0YXBlXCI7XG5pbXBvcnQgeyBnZXRQcmV0dHlGb3JtYXR0ZXIgfSBmcm9tIFwiQGxvZ3RhcGUvcHJldHR5XCI7XG5pbXBvcnQgZG90ZW52IGZyb20gXCJkb3RlbnZcIjtcbmltcG9ydCB7IENhY2hlUHJlc2V0cywgZGVmaW5lQ29uZmlnIH0gZnJvbSBcInNvbmFtdVwiO1xuaW1wb3J0IHsgZHJpdmVycyBhcyBjYWNoZURyaXZlcnMsIHN0b3JlIH0gZnJvbSBcInNvbmFtdS9jYWNoZVwiO1xuaW1wb3J0IHsgZHJpdmVycyB9IGZyb20gXCJzb25hbXUvc3RvcmFnZVwiO1xuXG5kb3RlbnYuY29uZmlnKHsgcGF0aDogcGF0aC5qb2luKGltcG9ydC5tZXRhLmRpcm5hbWUsIFwiLi4vLmVudlwiKSB9KTtcblxuY29uc3QgaG9zdCA9IHByb2Nlc3MuZW52LkhPU1QgPz8gXCJsb2NhbGhvc3RcIjtcbmNvbnN0IHBvcnQgPSBOdW1iZXIocHJvY2Vzcy5lbnYuUE9SVCA/PyA0NDkwMCk7XG5cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHByb2plY3ROYW1lOiBwcm9jZXNzLmVudi5QUk9KRUNUX05BTUUgPz8gXCJTb25hbXVQcm9qZWN0XCIsXG4gIGRhdGFiYXNlOiB7XG4gICAgbmFtZTogcHJvY2Vzcy5lbnYuUUdSSURfREJfTkFNRSA/PyBcInFncmlkXCIsXG4gICAgZGVmYXVsdE9wdGlvbnM6IHtcbiAgICAgIGNvbm5lY3Rpb246IHtcbiAgICAgICAgaG9zdDogcHJvY2Vzcy5lbnYuUUdSSURfREJfSE9TVCA/PyBcImxvY2FsaG9zdFwiLFxuICAgICAgICBwb3J0OiBOdW1iZXIocHJvY2Vzcy5lbnYuUUdSSURfREJfUE9SVCA/PyA0NDkwMSksXG4gICAgICAgIHVzZXI6IHByb2Nlc3MuZW52LlFHUklEX0RCX1VTRVIgPz8gXCJwb3N0Z3Jlc1wiLFxuICAgICAgICBwYXNzd29yZDogcHJvY2Vzcy5lbnYuUUdSSURfREJfUEFTU1dPUkQgPz8gXCJwb3N0Z3Jlc1wiLFxuICAgICAgfSxcbiAgICB9LFxuICAgIGVudmlyb25tZW50czoge1xuICAgICAgcHJvZHVjdGlvbjoge1xuICAgICAgICBjb25uZWN0aW9uOiB7XG4gICAgICAgICAgaG9zdDogcHJvY2Vzcy5lbnYuREVWMF9EQl9IT1NULFxuICAgICAgICAgIHBvcnQ6IE51bWJlcihwcm9jZXNzLmVudi5ERVYwX0RCX1BPUlQpLFxuICAgICAgICAgIGRhdGFiYXNlOiBwcm9jZXNzLmVudi5EQVRBQkFTRV9OQU1FLFxuICAgICAgICAgIHVzZXI6IHByb2Nlc3MuZW52LkRFVjBfREJfVVNFUixcbiAgICAgICAgICBwYXNzd29yZDogcHJvY2Vzcy5lbnYuREVWMF9EQl9QQVNTV09SRCxcbiAgICAgICAgfSxcbiAgICAgIH0sXG4gICAgICBmaXh0dXJlOiB7XG4gICAgICAgIGNvbm5lY3Rpb246IHtcbiAgICAgICAgICBob3N0OiBwcm9jZXNzLmVudi5ERVYwX0RCX0hPU1QsXG4gICAgICAgICAgcG9ydDogTnVtYmVyKHByb2Nlc3MuZW52LkRFVjBfREJfUE9SVCksXG4gICAgICAgICAgZGF0YWJhc2U6IHByb2Nlc3MuZW52LkRBVEFCQVNFX05BTUUsXG4gICAgICAgICAgdXNlcjogcHJvY2Vzcy5lbnYuREVWMF9EQl9VU0VSLFxuICAgICAgICAgIHBhc3N3b3JkOiBwcm9jZXNzLmVudi5ERVYwX0RCX1BBU1NXT1JELFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICB9LFxuICB9LFxuICBhcGk6IHtcbiAgICBkaXI6IFwiYXBpXCIsXG4gICAgdGltZXpvbmU6IFwiQXNpYS9TZW91bFwiLFxuICAgIHJvdXRlOiB7XG4gICAgICBwcmVmaXg6IFwiL2FwaVwiLFxuICAgIH0sXG4gIH0sXG4gIGkxOG46IHtcbiAgICBkZWZhdWx0TG9jYWxlOiBcImtvXCIsXG4gICAgc3VwcG9ydGVkTG9jYWxlczogW1wia29cIiwgXCJlblwiXSxcbiAgfSxcbiAgc3luYzoge1xuICAgIHRhcmdldHM6IFtcIndlYlwiXSxcbiAgfSxcbiAgc2xhY2tDb25maXJtOlxuICAgIHByb2Nlc3MuZW52LlNMQUNLX0JPVF9UT0tFTiAmJiBwcm9jZXNzLmVudi5TTEFDS19DSEFOTkVMX0lEXG4gICAgICA/IHtcbiAgICAgICAgICB0YXJnZXRzOiBbXCJkZXZlbG9wbWVudF9tYXN0ZXJcIiwgXCJwcm9kdWN0aW9uX21hc3RlclwiXSxcbiAgICAgICAgICBib3RUb2tlbjogcHJvY2Vzcy5lbnYuU0xBQ0tfQk9UX1RPS0VOID8/IFwiXCIsXG4gICAgICAgICAgY2hhbm5lbElkOiBwcm9jZXNzLmVudi5TTEFDS19DSEFOTkVMX0lEID8/IFwiXCIsXG4gICAgICAgIH1cbiAgICAgIDogdW5kZWZpbmVkLFxuICB0ZXN0OiB7XG4gICAgcGFyYWxsZWw6IHRydWUsXG4gICAgbWF4V29ya2VyczogNCxcbiAgICBkZXZSdW5uZXI6IHsgZW5hYmxlZDogdHJ1ZSB9LFxuICB9LFxuICBsb2dnaW5nOiB7XG4gICAgc2lua3M6IHtcbiAgICAgIGNvbnNvbGU6IGdldENvbnNvbGVTaW5rKHtcbiAgICAgICAgZm9ybWF0dGVyOiBnZXRQcmV0dHlGb3JtYXR0ZXIoe1xuICAgICAgICAgIHRpbWVzdGFtcDogXCJ0aW1lXCIsXG4gICAgICAgICAgY2F0ZWdvcnlXaWR0aDogMjAsXG4gICAgICAgICAgY2F0ZWdvcnlUcnVuY2F0ZTogXCJtaWRkbGVcIixcbiAgICAgICAgfSksXG4gICAgICB9KSxcbiAgICB9LFxuICAgIGxvZ2dlcnM6IFtcbiAgICAgIHtcbiAgICAgICAgY2F0ZWdvcnk6IFtcInNvbmFtdVwiXSxcbiAgICAgICAgc2lua3M6IFtcImNvbnNvbGVcIl0sXG4gICAgICAgIGxvd2VzdExldmVsOiBwcm9jZXNzLmVudi5OT0RFX0VOViA9PT0gXCJ0ZXN0XCIgPyBcIndhcm5pbmdcIiA6IFwiZGVidWdcIixcbiAgICAgIH0sXG4gICAgXSxcbiAgfSxcbiAgc2VydmVyOiB7XG4gICAgbGlzdGVuOiB7IHBvcnQsIGhvc3QgfSxcbiAgICBwbHVnaW5zOiB7XG4gICAgICBmb3JtYm9keTogdHJ1ZSxcbiAgICAgIHFzOiB0cnVlLFxuICAgICAgbXVsdGlwYXJ0OiB7IGxpbWl0czogeyBmaWxlU2l6ZTogMTAyNCAqIDEwMjQgKiAzMCB9IH0sXG4gICAgICBzdGF0aWM6IHtcbiAgICAgICAgcm9vdDogcGF0aC5qb2luKGltcG9ydC5tZXRhLmRpcm5hbWUsIFwiLy4uL1wiLCBcInB1YmxpY1wiKSxcbiAgICAgICAgcHJlZml4OiBcIi9hcGkvcHVibGljXCIsXG4gICAgICB9LFxuICAgICAgY3VzdG9tOiAoc2VydmVyKSA9PiB7XG4gICAgICAgIC8vIE9BdXRoIOy9nOuwsSDigJQgQW50aHJvcGlj7J20IC9jYWxsYmFja+ycvOuhnCDrpqzri6TsnbTroIntirhcbiAgICAgICAgc2VydmVyLmdldChcIi9jYWxsYmFja1wiLCBhc3luYyAocmVxdWVzdCwgcmVwbHkpID0+IHtcbiAgICAgICAgICBjb25zdCB7IGNvZGUsIHN0YXRlIH0gPSByZXF1ZXN0LnF1ZXJ5IGFzIHsgY29kZT86IHN0cmluZzsgc3RhdGU/OiBzdHJpbmcgfTtcbiAgICAgICAgICBpZiAoIWNvZGUgfHwgIXN0YXRlKSB7XG4gICAgICAgICAgICByZXR1cm4gcmVwbHkucmVkaXJlY3QoXCIvP29hdXRoPWVycm9yJnJlYXNvbj1taXNzaW5nX3BhcmFtc1wiKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgLy8gRnJhbWXsnZggb2F1dGhDYWxsYmFja+ycvOuhnCDsnITsnoRcbiAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgeyBRZ3JpZEZyYW1lIH0gPSBhd2FpdCBpbXBvcnQoXCIuL2FwcGxpY2F0aW9uL3FncmlkL3FncmlkLmZyYW1lXCIpO1xuICAgICAgICAgICAgYXdhaXQgUWdyaWRGcmFtZS5oYW5kbGVPQXV0aENhbGxiYWNrKGNvZGUsIHN0YXRlLCByZXBseSk7XG4gICAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgcmV0dXJuIHJlcGx5LnJlZGlyZWN0KFxuICAgICAgICAgICAgICBgLz9vYXV0aD1lcnJvciZyZWFzb249JHtlbmNvZGVVUklDb21wb25lbnQoKGUgYXMgRXJyb3IpLm1lc3NhZ2UpfWAsXG4gICAgICAgICAgICApO1xuICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICB9LFxuICAgIH0sXG4gICAgYXBpQ29uZmlnOiB7XG4gICAgICBjb250ZXh0UHJvdmlkZXI6IChkZWZhdWx0Q29udGV4dCwgcmVxdWVzdCkgPT4ge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIC4uLmRlZmF1bHRDb250ZXh0LFxuICAgICAgICAgIGlwOiByZXF1ZXN0LmlwLFxuICAgICAgICAgIGJvZHk6IHJlcXVlc3QuYm9keSxcbiAgICAgICAgfTtcbiAgICAgIH0sXG4gICAgICBndWFyZEhhbmRsZXI6IChfZ3VhcmQsIF9yZXF1ZXN0LCBfYXBpKSA9PiB7XG4gICAgICAgIGlmIChfZ3VhcmQgPT09IFwidXNlclwiKSB7XG4gICAgICAgICAgY29uc29sZS5sb2coXCJ1c2VyIGd1YXJkXCIpO1xuICAgICAgICB9XG4gICAgICAgIGNvbnNvbGUubG9nKFwiTk9USElORyBZRVRcIik7XG4gICAgICB9LFxuICAgICAgY2FjaGVDb250cm9sSGFuZGxlcjogKHJlcSkgPT4ge1xuICAgICAgICBzd2l0Y2ggKHJlcS50eXBlKSB7XG4gICAgICAgICAgY2FzZSBcImFzc2V0c1wiOlxuICAgICAgICAgICAgLy8gSGFzaCDtj6ztlajrkJwg7YyM7J28OiDsmIHqtawg7LqQ7IucXG4gICAgICAgICAgICBpZiAocmVxLnBhdGgubWF0Y2goLy1bYS1mMC05XStcXC4vKSkge1xuICAgICAgICAgICAgICByZXR1cm4gQ2FjaGVQcmVzZXRzLmltbXV0YWJsZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiBDYWNoZVByZXNldHMubG9uZ0xpdmVkO1xuXG4gICAgICAgICAgY2FzZSBcImFwaVwiOlxuICAgICAgICAgICAgLy8gR0VUIOyalOyyreunjCDsupDsi7Eg6rOg66CkXG4gICAgICAgICAgICBpZiAocmVxLm1ldGhvZCA9PT0gXCJHRVRcIikge1xuICAgICAgICAgICAgICAvLyDtirnsoJUg6rK966Gc64qUIOynp+ydgCDsupDsi5xcbiAgICAgICAgICAgICAgaWYgKHJlcS5wYXRoLnN0YXJ0c1dpdGgoXCIvYXBpL3N0YXRpYy1kYXRhXCIpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIENhY2hlUHJlc2V0cy5zaG9ydExpdmVkO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGlmIChyZXEucGF0aC5zdGFydHNXaXRoKFwiL2FwaS90ZXJtc1wiKSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBDYWNoZVByZXNldHMubWVkaXVtTGl2ZWQ7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIOq4sOuzuDog7LqQ7IucIOyXhuydjFxuICAgICAgICAgICAgcmV0dXJuIENhY2hlUHJlc2V0cy5ub0NhY2hlO1xuXG4gICAgICAgICAgY2FzZSBcInNzclwiOlxuICAgICAgICAgICAgLy8gU1NSIO2OmOydtOyngDogMTDstIgg7LqQ7IucXG4gICAgICAgICAgICByZXR1cm4gQ2FjaGVQcmVzZXRzLnNzcjtcblxuICAgICAgICAgIGNhc2UgXCJjc3JcIjpcbiAgICAgICAgICAgIC8vIENTUiBmYWxsYmFjayAoaW5kZXguaHRtbCk6IDHrtoQg7LqQ7IucXG4gICAgICAgICAgICByZXR1cm4gQ2FjaGVQcmVzZXRzLnNob3J0TGl2ZWQ7XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfSxcbiAgICBzdG9yYWdlOiB7XG4gICAgICBkcml2ZXJzOiB7XG4gICAgICAgIGZzOiBkcml2ZXJzLmZzKHtcbiAgICAgICAgICBsb2NhdGlvbjogcGF0aC5qb2luKGltcG9ydC5tZXRhLmRpcm5hbWUsIFwiLy4uL3B1YmxpYy91cGxvYWRlZFwiKSxcbiAgICAgICAgICB2aXNpYmlsaXR5OiBcInB1YmxpY1wiLFxuICAgICAgICAgIHVybEJ1aWxkZXI6IHtcbiAgICAgICAgICAgIGFzeW5jIGdlbmVyYXRlVVJMKGtleSkge1xuICAgICAgICAgICAgICByZXR1cm4gYC9hcGkvcHVibGljL3VwbG9hZGVkLyR7a2V5fWA7XG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgYXN5bmMgZ2VuZXJhdGVTaWduZWRVUkwoa2V5KSB7XG4gICAgICAgICAgICAgIHJldHVybiBgL2FwaS9wdWJsaWMvdXBsb2FkZWQvJHtrZXl9YDtcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgfSxcbiAgICAgICAgfSksXG4gICAgICAgIHMzOiBkcml2ZXJzLnMzKHtcbiAgICAgICAgICBjcmVkZW50aWFsczoge1xuICAgICAgICAgICAgYWNjZXNzS2V5SWQ6IHByb2Nlc3MuZW52LkFXU19BQ0NFU1NfS0VZX0lEID8/IFwiXCIsXG4gICAgICAgICAgICBzZWNyZXRBY2Nlc3NLZXk6IHByb2Nlc3MuZW52LkFXU19TRUNSRVRfQUNDRVNTX0tFWSA/PyBcIlwiLFxuICAgICAgICAgIH0sXG4gICAgICAgICAgcmVnaW9uOiBwcm9jZXNzLmVudi5TM19SRUdJT04gPz8gXCJhcC1ub3J0aGVhc3QtMlwiLFxuICAgICAgICAgIGJ1Y2tldDogcHJvY2Vzcy5lbnYuUzNfQlVDS0VUID8/IFwic29uYW11X2RlZmF1bHRfYnVja2V0XCIsXG4gICAgICAgICAgdmlzaWJpbGl0eTogXCJwcml2YXRlXCIsXG4gICAgICAgIH0pLFxuICAgICAgfSxcbiAgICB9LFxuICAgIGNhY2hlOiB7XG4gICAgICBkZWZhdWx0OiBcIm1haW5cIixcbiAgICAgIHN0b3Jlczoge1xuICAgICAgICBtYWluOiBzdG9yZSgpLnVzZUwxTGF5ZXIoY2FjaGVEcml2ZXJzLm1lbW9yeSh7IG1heFNpemU6IFwiNTBtYlwiIH0pKSxcbiAgICAgIH0sXG4gICAgICB0dGw6IFwiNW1cIixcbiAgICAgIHByZWZpeDogXCJcIixcbiAgICB9LFxuICAgIGxpZmVjeWNsZToge1xuICAgICAgb25TdGFydDogYXN5bmMgKCkgPT4ge1xuICAgICAgICAvLyBEQiDrp4jsnbTqt7jroIjsnbTshZgg7J6Q64+ZIOyLpO2WiSAo7YWM7J2067iUIOyXhuycvOuptCDsg53shLEpXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgY29uc3QgeyBUb2tlbk1vZGVsIH0gPSBhd2FpdCBpbXBvcnQoXCIuL2FwcGxpY2F0aW9uL3Rva2VuL3Rva2VuLm1vZGVsXCIpO1xuICAgICAgICAgIGNvbnN0IGtuZXggPSBUb2tlbk1vZGVsLmdldERCKFwid1wiKTtcbiAgICAgICAgICBjb25zdCBtaWdyYXRpb25zRGlyID0gcGF0aC5qb2luKGltcG9ydC5tZXRhLmRpcm5hbWUsIFwiLi4vc3JjL21pZ3JhdGlvbnNcIik7XG4gICAgICAgICAgY29uc3QgW2JhdGNoLCBsb2ddID0gYXdhaXQga25leC5taWdyYXRlLmxhdGVzdCh7XG4gICAgICAgICAgICBkaXJlY3Rvcnk6IG1pZ3JhdGlvbnNEaXIsXG4gICAgICAgICAgfSk7XG4gICAgICAgICAgaWYgKGxvZy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZyhg4pyTIE1pZ3JhdGlvbjogJHtsb2cubGVuZ3RofSBhcHBsaWVkIChiYXRjaCAke2JhdGNofSlgKTtcbiAgICAgICAgICAgIGxvZy5mb3JFYWNoKChuYW1lOiBzdHJpbmcpID0+IGNvbnNvbGUubG9nKGAgIOKckyAke25hbWV9YCkpO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgIGNvbnNvbGUud2Fybihg4pqgIE1pZ3JhdGlvbiBza2lwcGVkOiAkeyhlIGFzIEVycm9yKS5tZXNzYWdlfWApO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gUG9vbCDstIjquLDtmZRcbiAgICAgICAgY29uc3QgeyBpbml0UG9vbCB9ID0gYXdhaXQgaW1wb3J0KFwiLi9hcHBsaWNhdGlvbi9xZ3JpZC9wb29sXCIpO1xuICAgICAgICB0cnkge1xuICAgICAgICAgIGNvbnN0IHsgVG9rZW5Nb2RlbCB9ID0gYXdhaXQgaW1wb3J0KFwiLi9hcHBsaWNhdGlvbi90b2tlbi90b2tlbi5tb2RlbFwiKTtcbiAgICAgICAgICBjb25zdCBlbnRyaWVzID0gYXdhaXQgVG9rZW5Nb2RlbC5maW5kQWN0aXZlKFwiQVwiKTtcbiAgICAgICAgICBjb25zdCB0b2tlbnMgPSBlbnRyaWVzLm1hcCgoZSkgPT4gZS50b2tlbik7XG4gICAgICAgICAgY29uc3QgcG9vbCA9IGluaXRQb29sKHRva2Vucyk7XG4gICAgICAgICAgY29uc29sZS5sb2coXG4gICAgICAgICAgICBg8J+MsiBTZXJ2ZXIgbGlzdGVuaW5nIG9uIGh0dHA6Ly8ke2hvc3R9OiR7cG9ydH0gKCR7cG9vbC53b3JrZXJzLnNpemV9IHRva2VucyBsb2FkZWQpYCxcbiAgICAgICAgICApO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgY29uc29sZS53YXJuKGDimqAgUG9vbCBpbml0IGZhaWxlZDogJHsoZSBhcyBFcnJvcikubWVzc2FnZX1gKTtcbiAgICAgICAgICBpbml0UG9vbChbXSk7XG4gICAgICAgICAgY29uc29sZS5sb2coYPCfjLIgU2VydmVyIGxpc3RlbmluZyBvbiBodHRwOi8vJHtob3N0fToke3BvcnR9IChubyB0b2tlbnMpYCk7XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICBvblNodXRkb3duOiAoKSA9PiB7XG4gICAgICAgIGNvbnNvbGUubG9nKFwiZ3JhY2VmdWwgc2h1dGRvd25cIik7XG4gICAgICB9LFxuICAgICAgb25FcnJvcjogKGVycm9yLCBfcmVxdWVzdCwgcmVwbHkpID0+IHtcbiAgICAgICAgY29uc29sZS5lcnJvcihlcnJvcik7XG4gICAgICAgIHJlcGx5LnN0YXR1cyg1MDApLnNlbmQoe1xuICAgICAgICAgIG5hbWU6IGVycm9yLm5hbWUsXG4gICAgICAgICAgbWVzc2FnZTogZXJyb3IubWVzc2FnZSxcbiAgICAgICAgfSk7XG4gICAgICB9LFxuICAgIH0sXG4gIH0sXG59KTtcbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O0FBU0EsT0FBTyxPQUFPLEVBQUUsTUFBTSxLQUFLLEtBQUssT0FBTyxLQUFLLFNBQVMsVUFBVSxFQUFFLENBQUM7QUFFbEUsTUFBTSxPQUFPLFFBQVEsSUFBSSxRQUFRO0FBQ2pDLE1BQU0sT0FBTyxPQUFPLFFBQVEsSUFBSSxRQUFRLE1BQU07QUFFOUMsSUFBQSx3QkFBZSxhQUFhO0NBQzFCLGFBQWEsUUFBUSxJQUFJLGdCQUFnQjtDQUN6QyxVQUFVO0VBQ1IsTUFBTSxRQUFRLElBQUksaUJBQWlCO0VBQ25DLGdCQUFnQixFQUNkLFlBQVk7R0FDVixNQUFNLFFBQVEsSUFBSSxpQkFBaUI7R0FDbkMsTUFBTSxPQUFPLFFBQVEsSUFBSSxpQkFBaUIsTUFBTTtHQUNoRCxNQUFNLFFBQVEsSUFBSSxpQkFBaUI7R0FDbkMsVUFBVSxRQUFRLElBQUkscUJBQXFCO0dBQzVDLEVBQ0Y7RUFDRCxjQUFjO0dBQ1osWUFBWSxFQUNWLFlBQVk7SUFDVixNQUFNLFFBQVEsSUFBSTtJQUNsQixNQUFNLE9BQU8sUUFBUSxJQUFJLGFBQWE7SUFDdEMsVUFBVSxRQUFRLElBQUk7SUFDdEIsTUFBTSxRQUFRLElBQUk7SUFDbEIsVUFBVSxRQUFRLElBQUk7SUFDdkIsRUFDRjtHQUNELFNBQVMsRUFDUCxZQUFZO0lBQ1YsTUFBTSxRQUFRLElBQUk7SUFDbEIsTUFBTSxPQUFPLFFBQVEsSUFBSSxhQUFhO0lBQ3RDLFVBQVUsUUFBUSxJQUFJO0lBQ3RCLE1BQU0sUUFBUSxJQUFJO0lBQ2xCLFVBQVUsUUFBUSxJQUFJO0lBQ3ZCLEVBQ0Y7R0FDRjtFQUNGO0NBQ0QsS0FBSztFQUNILEtBQUs7RUFDTCxVQUFV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
@@ -0,0 +1,8 @@
1
+ import "../_virtual/_rolldown/runtime.js";
2
+ import { createFixtureLoader } from "sonamu/test";
3
+ //#region src/testing/fixture.ts
4
+ const loadFixtures = createFixtureLoader({ fixture01: async () => {} });
5
+ //#endregion
6
+ export { loadFixtures };
7
+
8
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZml4dHVyZS5qcyIsIm5hbWVzIjpbXSwic291cmNlcyI6WyIuLi8uLi9zcmMvdGVzdGluZy9maXh0dXJlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNyZWF0ZUZpeHR1cmVMb2FkZXIgfSBmcm9tIFwic29uYW11L3Rlc3RcIjtcblxuZXhwb3J0IGNvbnN0IGxvYWRGaXh0dXJlcyA9IGNyZWF0ZUZpeHR1cmVMb2FkZXIoe1xuICAvLyBEZWZpbmUgeW91ciBmaXh0dXJlcy4uLlxuICBmaXh0dXJlMDE6IGFzeW5jICgpID0+IHt9LFxufSk7XG4iXSwibWFwcGluZ3MiOiI7OztBQUVBLE1BQWEsZUFBZSxvQkFBb0IsRUFFOUMsV0FBVyxZQUFZLElBQ3hCLENBQUMifQ==
@@ -0,0 +1,9 @@
1
+ import "../_virtual/_rolldown/runtime.js";
2
+ import dotenv from "dotenv";
3
+ import { setup } from "sonamu/test";
4
+ //#region src/testing/global.ts
5
+ dotenv.config();
6
+ //#endregion
7
+ export { setup };
8
+
9
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2xvYmFsLmpzIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0aW5nL2dsb2JhbC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgZG90ZW52IGZyb20gXCJkb3RlbnZcIjtcblxuZG90ZW52LmNvbmZpZygpO1xuXG4vLyBzb25hbXUuY29uZmlnLnRz7J2YIHRlc3Qg7ISk7KCV7J2EIOq4sOuwmOycvOuhnCDrs5HroKwg7YWM7Iqk7Yq4IO2ZmOqyveydhCDqtazshLHtlanri4jri6QuXG5leHBvcnQgeyBzZXR1cCB9IGZyb20gXCJzb25hbXUvdGVzdFwiO1xuIl0sIm1hcHBpbmdzIjoiOzs7O0FBRUEsT0FBTyxRQUFRIn0=
@@ -0,0 +1,17 @@
1
+ import "../_virtual/_rolldown/runtime.js";
2
+ import { vi } from "../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";
3
+ import "../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";
4
+ //#region src/testing/setup-mocks.ts
5
+ vi.mock("fs/promises", async (importOriginal) => {
6
+ const actual = await importOriginal();
7
+ return {
8
+ ...actual,
9
+ access: vi.fn((path, mode) => {
10
+ return actual.access(path, mode);
11
+ })
12
+ };
13
+ });
14
+ //#endregion
15
+ export {};
16
+
17
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2V0dXAtbW9ja3MuanMiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiLi4vLi4vc3JjL3Rlc3Rpbmcvc2V0dXAtbW9ja3MudHMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHR5cGUgeyBQYXRoTGlrZSB9IGZyb20gXCJmc1wiO1xuXG4vLyBpbXBvcnQgeyBOYWl0ZSB9IGZyb20gXCJzb25hbXVcIjtcbmltcG9ydCB7IHZpIH0gZnJvbSBcInZpdGVzdFwiO1xuXG4vLyBHbG9iYWxNb2NrOiBmcy9wcm9taXNlcyAo7IKs7JqpIOyYiOyLnCAtIO2VhOyalOyLnCDtmZzshLHtmZQpXG52aS5tb2NrKFwiZnMvcHJvbWlzZXNcIiwgYXN5bmMgKGltcG9ydE9yaWdpbmFsKSA9PiB7XG4gIGNvbnN0IGFjdHVhbCA9IChhd2FpdCBpbXBvcnRPcmlnaW5hbCgpKSBhcyB0eXBlb2YgaW1wb3J0KFwiZnMvcHJvbWlzZXNcIik7XG4gIHJldHVybiB7XG4gICAgLi4uYWN0dWFsLFxuICAgIGFjY2VzczogdmkuZm4oKHBhdGg6IFBhdGhMaWtlLCBtb2RlPzogbnVtYmVyKSA9PiB7XG4gICAgICAvLyBjb25zdCB2ZnMgPSBOYWl0ZS5nZXQoXCJtb2NrOmZzL3Byb21pc2VzOnZpcnR1YWxGaWxlU3lzdGVtXCIpLnJlc3VsdCgpO1xuICAgICAgLy8gaWYgKHZmcy5zb21lKCh2KSA9PiB2ID09PSBwYXRoKSkge1xuICAgICAgLy8gICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKCk7XG4gICAgICAvLyB9XG5cbiAgICAgIHJldHVybiBhY3R1YWwuYWNjZXNzKHBhdGgsIG1vZGUpO1xuICAgIH0pLFxuICAgIC8vIG1rZGlyOiB2aS5mbihcbiAgICAvLyAgIGFzeW5jIChcbiAgICAvLyAgICAgcGF0aDogUGF0aExpa2UsXG4gICAgLy8gICAgIG9wdGlvbnM/OiBNYWtlRGlyZWN0b3J5T3B0aW9ucyB8IE1vZGUgfCBudWxsLFxuICAgIC8vICAgKTogUHJvbWlzZTxzdHJpbmcgfCB1bmRlZmluZWQ+ID0+IHtcbiAgICAvLyAgICAgLy8gTmFpdGUudChcImZzOm1rZGlyXCIsIHsgcGF0aCwgb3B0aW9ucyB9KTtcbiAgICAvLyAgICAgaWYgKHR5cGVvZiBvcHRpb25zID09PSBcIm9iamVjdFwiICYmIG9wdGlvbnM/LnJlY3Vyc2l2ZSkge1xuICAgIC8vICAgICAgIHJldHVybiB0eXBlb2YgcGF0aCA9PT0gXCJzdHJpbmdcIiA/IHBhdGggOiBwYXRoLnRvU3RyaW5nKCk7XG4gICAgLy8gICAgIH1cbiAgICAvLyAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICAvLyAgIH0sXG4gICAgLy8gKSxcbiAgICAvLyB3cml0ZUZpbGU6IHZpLmZuKChwYXRoOiBQYXRoTGlrZSB8IEZpbGVIYW5kbGUsIGRhdGE6IHN0cmluZyB8IEJ1ZmZlciB8IFVpbnQ4QXJyYXkpID0+IHtcbiAgICAvLyAgIGNvbnN0IGZpbGVQYXRoID0gdHlwZW9mIHBhdGggPT09IFwic3RyaW5nXCIgPyBwYXRoIDogcGF0aC50b1N0cmluZygpO1xuXG4gICAgLy8gICAvLyBOYWl0ZS50KGBmcy9wcm9taXNlczp3cml0ZUZpbGVgLCB7IHBhdGg6IGZpbGVQYXRoLCBkYXRhIH0pO1xuICAgIC8vIH0pLFxuICAgIC8vIHJtOiB2aS5mbihhc3luYyAocGF0aDogUGF0aExpa2UsIG9wdGlvbnM/OiBSbU9wdGlvbnMpID0+IHtcbiAgICAvLyAgIGNvbnN0IGZpbGVQYXRoID0gdHlwZW9mIHBhdGggPT09IFwic3RyaW5nXCIgPyBwYXRoIDogcGF0aC50b1N0cmluZygpO1xuXG4gICAgLy8gICAvLyBOYWl0ZS50KGBmcy9wcm9taXNlczpybWAsIHsgcGF0aDogZmlsZVBhdGgsIG9wdGlvbnMgfSk7XG4gICAgLy8gICAvLyDsi6TsoJwg7IKt7KCc7ZWY7KeAIOyViuqzoCDquLDroZ3rp4wg7ZWoXG4gICAgLy8gICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKCk7XG4gICAgLy8gfSksXG4gIH07XG59KTtcbiJdLCJtYXBwaW5ncyI6Ijs7OztBQU1BLEdBQUcsS0FBSyxlQUFlLE9BQU8sbUJBQW1CO0NBQy9DLE1BQU0sU0FBVSxNQUFNLGdCQUFnQjtBQUN0QyxRQUFPO0VBQ0wsR0FBRztFQUNILFFBQVEsR0FBRyxJQUFJLE1BQWdCLFNBQWtCO0FBTS9DLFVBQU8sT0FBTyxPQUFPLE1BQU0sS0FBSztJQUNoQztFQXlCSDtFQUNEIn0=
@@ -0,0 +1,12 @@
1
+ import "../_virtual/_rolldown/runtime.js";
2
+ //#region src/utils/subset-loaders.ts
3
+ function getSubsetLoaders(subsets, subsetQueries) {
4
+ return subsets.reduce((acc, subset) => {
5
+ acc[subset] = subsetQueries[subset]?.loaders ?? [];
6
+ return acc;
7
+ }, {});
8
+ }
9
+ //#endregion
10
+ export { getSubsetLoaders };
11
+
12
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3Vic2V0LWxvYWRlcnMuanMiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL3N1YnNldC1sb2FkZXJzLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIHsgU3Vic2V0UXVlcnkgfSBmcm9tIFwic29uYW11XCI7XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRTdWJzZXRMb2FkZXJzKHN1YnNldHM6IHN0cmluZ1tdLCBzdWJzZXRRdWVyaWVzOiBSZWNvcmQ8c3RyaW5nLCBTdWJzZXRRdWVyeT4pIHtcbiAgcmV0dXJuIHN1YnNldHMucmVkdWNlKFxuICAgIChhY2MsIHN1YnNldCkgPT4ge1xuICAgICAgYWNjW3N1YnNldF0gPSBzdWJzZXRRdWVyaWVzW3N1YnNldF0/LmxvYWRlcnMgPz8gW107XG4gICAgICByZXR1cm4gYWNjO1xuICAgIH0sXG4gICAge30gYXMgUmVjb3JkPHN0cmluZywgU3Vic2V0UXVlcnlbXCJsb2FkZXJzXCJdPixcbiAgKTtcbn1cbiJdLCJtYXBwaW5ncyI6Ijs7QUFFQSxTQUFnQixpQkFBaUIsU0FBbUIsZUFBNEM7QUFDOUYsUUFBTyxRQUFRLFFBQ1osS0FBSyxXQUFXO0FBQ2YsTUFBSSxVQUFVLGNBQWMsU0FBUyxXQUFXLEVBQUU7QUFDbEQsU0FBTztJQUVULEVBQUUsQ0FDSCJ9
@@ -0,0 +1,181 @@
1
+ /**
2
+ * OAuth 유틸 — Claude Code 소스의 OAuth 플로우 그대로 재현
3
+ */
4
+ import { createHash, randomBytes } from "node:crypto";
5
+
6
+ import type { UsageResponse } from "./qgrid.types";
7
+
8
+ const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
9
+ const AUTHORIZE_URL = "https://claude.com/cai/oauth/authorize";
10
+ const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
11
+
12
+ // login 시 사용 (authorize URL)
13
+ const ALL_SCOPES = [
14
+ "org:create_api_key",
15
+ "user:profile",
16
+ "user:inference",
17
+ "user:sessions:claude_code",
18
+ "user:mcp_servers",
19
+ "user:file_upload",
20
+ ];
21
+
22
+ // refresh 시 사용 (org:create_api_key 제외)
23
+ const REFRESH_SCOPES = [
24
+ "user:profile",
25
+ "user:inference",
26
+ "user:sessions:claude_code",
27
+ "user:mcp_servers",
28
+ "user:file_upload",
29
+ ];
30
+
31
+ function base64URLEncode(buffer: Buffer): string {
32
+ return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
33
+ }
34
+
35
+ export function generatePKCE(): { codeVerifier: string; codeChallenge: string; state: string } {
36
+ const codeVerifier = base64URLEncode(randomBytes(32));
37
+ const codeChallenge = base64URLEncode(createHash("sha256").update(codeVerifier).digest());
38
+ const state = base64URLEncode(randomBytes(32));
39
+ return { codeVerifier, codeChallenge, state };
40
+ }
41
+
42
+ export function buildAuthUrl(codeChallenge: string, state: string, redirectUri: string): string {
43
+ const url = new URL(AUTHORIZE_URL);
44
+ url.searchParams.append("code", "true");
45
+ url.searchParams.append("client_id", CLIENT_ID);
46
+ url.searchParams.append("response_type", "code");
47
+ url.searchParams.append("redirect_uri", redirectUri);
48
+ url.searchParams.append("scope", ALL_SCOPES.join(" "));
49
+ url.searchParams.append("code_challenge", codeChallenge);
50
+ url.searchParams.append("code_challenge_method", "S256");
51
+ url.searchParams.append("state", state);
52
+ return url.toString();
53
+ }
54
+
55
+ export type OAuthTokens = {
56
+ accessToken: string;
57
+ refreshToken: string;
58
+ expiresAt: number;
59
+ scope: string;
60
+ accountUuid?: string;
61
+ emailAddress?: string;
62
+ };
63
+
64
+ export async function exchangeCodeForTokens(
65
+ code: string,
66
+ codeVerifier: string,
67
+ state: string,
68
+ redirectUri: string,
69
+ ): Promise<OAuthTokens> {
70
+ const res = await fetch(TOKEN_URL, {
71
+ method: "POST",
72
+ headers: { "Content-Type": "application/json" },
73
+ body: JSON.stringify({
74
+ grant_type: "authorization_code",
75
+ code,
76
+ redirect_uri: redirectUri,
77
+ client_id: CLIENT_ID,
78
+ code_verifier: codeVerifier,
79
+ state,
80
+ }),
81
+ });
82
+ if (!res.ok) {
83
+ const text = await res.text();
84
+ throw new Error(`Token exchange failed (${res.status}): ${text}`);
85
+ }
86
+
87
+ const data = (await res.json()) as {
88
+ access_token: string;
89
+ refresh_token: string;
90
+ expires_in: number;
91
+ scope: string;
92
+ account?: { uuid: string; email_address: string };
93
+ };
94
+
95
+ return {
96
+ accessToken: data.access_token,
97
+ refreshToken: data.refresh_token,
98
+ expiresAt: Date.now() + data.expires_in * 1000,
99
+ scope: data.scope,
100
+ accountUuid: data.account?.uuid,
101
+ emailAddress: data.account?.email_address,
102
+ };
103
+ }
104
+
105
+ export async function refreshAccessToken(refreshToken: string): Promise<OAuthTokens> {
106
+ const res = await fetch(TOKEN_URL, {
107
+ method: "POST",
108
+ headers: { "Content-Type": "application/json" },
109
+ body: JSON.stringify({
110
+ grant_type: "refresh_token",
111
+ refresh_token: refreshToken,
112
+ client_id: CLIENT_ID,
113
+ scope: REFRESH_SCOPES.join(" "),
114
+ }),
115
+ });
116
+ if (!res.ok) {
117
+ const text = await res.text();
118
+ throw new Error(`Token refresh failed (${res.status}): ${text}`);
119
+ }
120
+ const data = (await res.json()) as {
121
+ access_token: string;
122
+ refresh_token: string;
123
+ expires_in: number;
124
+ scope: string;
125
+ };
126
+
127
+ return {
128
+ accessToken: data.access_token,
129
+ refreshToken: data.refresh_token ?? refreshToken,
130
+ expiresAt: Date.now() + data.expires_in * 1000,
131
+ scope: data.scope,
132
+ };
133
+ }
134
+
135
+ const usageCache: Record<string, { data: UsageResponse; cachedAt: number }> = {};
136
+ const USAGE_API_CACHE_TTL = 60_000; // 1분
137
+
138
+ export async function fetchUsage(accessToken: string): Promise<UsageResponse> {
139
+ const cacheKey = accessToken.slice(-10);
140
+ const cached = usageCache[cacheKey];
141
+ if (cached && Date.now() - cached.cachedAt < USAGE_API_CACHE_TTL) {
142
+ console.log(`[usage] cache hit: ${cacheKey}`);
143
+ return cached.data;
144
+ }
145
+
146
+ const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
147
+ headers: {
148
+ Authorization: `Bearer ${accessToken}`,
149
+ "Content-Type": "application/json",
150
+ "anthropic-beta": "oauth-2025-04-20",
151
+ },
152
+ signal: AbortSignal.timeout(5000),
153
+ });
154
+ if (!res.ok) {
155
+ const text = await res.text();
156
+
157
+ // 파싱 시도해서 에러 메시지 추출
158
+ let errorMessage = `Error ${res.status}`;
159
+ try {
160
+ const parsed = JSON.parse(text);
161
+ errorMessage = parsed.error?.message ?? errorMessage;
162
+ } catch {
163
+ // 파싱 안 되면 기본 메시지
164
+ }
165
+
166
+ console.warn(`[usage] ${res.status}: ${errorMessage}`);
167
+
168
+ // 이전 성공 캐시 있으면 유지
169
+ if (cached?.data && !cached.data.error) return cached.data;
170
+
171
+ // 실패도 캐시 (반복 호출 방지)
172
+ const errorResult = { error: errorMessage };
173
+ usageCache[cacheKey] = { data: errorResult, cachedAt: Date.now() };
174
+ return errorResult;
175
+ }
176
+
177
+ const data = (await res.json()) as UsageResponse;
178
+ // cache invalidate
179
+ usageCache[cacheKey] = { data, cachedAt: Date.now() };
180
+ return data;
181
+ }