sonamu 0.9.20 → 0.10.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.
Files changed (299) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +36 -0
  2. package/dist/ai/agents/agent.js +5 -7
  3. package/dist/ai/agents/index.js +1 -2
  4. package/dist/ai/agents/types.js +1 -1
  5. package/dist/ai/index.js +1 -2
  6. package/dist/ai/providers/rtzr/api.js +2 -3
  7. package/dist/ai/providers/rtzr/error.js +14 -29
  8. package/dist/ai/providers/rtzr/index.js +1 -2
  9. package/dist/ai/providers/rtzr/model.js +13 -20
  10. package/dist/ai/providers/rtzr/options.js +2 -3
  11. package/dist/ai/providers/rtzr/provider.js +2 -3
  12. package/dist/ai/providers/rtzr/utils.js +12 -21
  13. package/dist/api/base-frame.js +4 -4
  14. package/dist/api/caster.js +21 -38
  15. package/dist/api/code-converters.js +41 -98
  16. package/dist/api/config.d.ts +1 -10
  17. package/dist/api/config.d.ts.map +1 -1
  18. package/dist/api/config.js +9 -8
  19. package/dist/api/context.js +2 -3
  20. package/dist/api/decorators.js +80 -116
  21. package/dist/api/index.js +2 -3
  22. package/dist/api/secret.js +6 -10
  23. package/dist/api/sonamu.d.ts.map +1 -1
  24. package/dist/api/sonamu.js +210 -406
  25. package/dist/api/validator.js +5 -8
  26. package/dist/api/websocket-helpers.js +22 -33
  27. package/dist/auth/audit-log/builders.d.ts.map +1 -1
  28. package/dist/auth/audit-log/builders.js +4 -3
  29. package/dist/auth/audit-log/client-ip.d.ts +3 -0
  30. package/dist/auth/audit-log/client-ip.d.ts.map +1 -0
  31. package/dist/auth/audit-log/client-ip.js +27 -0
  32. package/dist/auth/audit-log/events.d.ts +1 -0
  33. package/dist/auth/audit-log/events.d.ts.map +1 -1
  34. package/dist/auth/audit-log/events.js +2 -2
  35. package/dist/auth/audit-log/plugin.js +30 -61
  36. package/dist/auth/audit-log-ingestor.d.ts.map +1 -1
  37. package/dist/auth/audit-log-ingestor.js +26 -43
  38. package/dist/auth/auth-generator.js +16 -41
  39. package/dist/auth/better-auth-entities.js +3 -4
  40. package/dist/auth/better-auth-request.d.ts +3 -0
  41. package/dist/auth/better-auth-request.d.ts.map +1 -0
  42. package/dist/auth/better-auth-request.js +23 -0
  43. package/dist/auth/index.js +2 -3
  44. package/dist/auth/knex-adapter.js +18 -45
  45. package/dist/auth/plugins/entity-definitions/admin.js +2 -2
  46. package/dist/auth/plugins/entity-definitions/anonymous.js +2 -2
  47. package/dist/auth/plugins/entity-definitions/api-key.js +2 -2
  48. package/dist/auth/plugins/entity-definitions/audit-log.js +2 -2
  49. package/dist/auth/plugins/entity-definitions/index.js +2 -3
  50. package/dist/auth/plugins/entity-definitions/jwt.js +2 -2
  51. package/dist/auth/plugins/entity-definitions/organization.js +2 -2
  52. package/dist/auth/plugins/entity-definitions/passkey.js +2 -2
  53. package/dist/auth/plugins/entity-definitions/phone-number.js +2 -2
  54. package/dist/auth/plugins/entity-definitions/sso.js +2 -2
  55. package/dist/auth/plugins/entity-definitions/two-factor.js +2 -2
  56. package/dist/auth/plugins/entity-definitions/types.js +1 -1
  57. package/dist/auth/plugins/entity-definitions/username.js +2 -2
  58. package/dist/auth/plugins/index.js +1 -2
  59. package/dist/auth/plugins/wrappers/admin.js +2 -3
  60. package/dist/auth/plugins/wrappers/anonymous.js +2 -3
  61. package/dist/auth/plugins/wrappers/api-key.js +2 -3
  62. package/dist/auth/plugins/wrappers/index.js +1 -2
  63. package/dist/auth/plugins/wrappers/jwt.js +2 -3
  64. package/dist/auth/plugins/wrappers/organization.js +2 -3
  65. package/dist/auth/plugins/wrappers/passkey.js +2 -3
  66. package/dist/auth/plugins/wrappers/phone-number.js +2 -3
  67. package/dist/auth/plugins/wrappers/sso.js +2 -3
  68. package/dist/auth/plugins/wrappers/two-factor.js +2 -3
  69. package/dist/auth/plugins/wrappers/username.js +2 -3
  70. package/dist/bin/build-config.js +2 -2
  71. package/dist/bin/cli.js +152 -259
  72. package/dist/bin/fixture.d.ts.map +1 -1
  73. package/dist/bin/fixture.js +55 -97
  74. package/dist/bin/hmr-hook-register.js +3 -3
  75. package/dist/bin/migrate-targets.d.ts +3 -0
  76. package/dist/bin/migrate-targets.d.ts.map +1 -0
  77. package/dist/bin/migrate-targets.js +11 -0
  78. package/dist/bin/test-command.js +25 -55
  79. package/dist/bin/ts-loader-register.js +5 -6
  80. package/dist/bin/ts-loader-registration.js +6 -13
  81. package/dist/cache/cache-manager.js +3 -4
  82. package/dist/cache/decorator.js +11 -21
  83. package/dist/cache/drivers.js +2 -3
  84. package/dist/cache/index.js +2 -3
  85. package/dist/cache/types.js +1 -1
  86. package/dist/cache-control/cache-control.js +21 -34
  87. package/dist/cache-control/types.js +1 -1
  88. package/dist/compress/compress.js +10 -10
  89. package/dist/compress/index.js +1 -2
  90. package/dist/compress/types.js +1 -1
  91. package/dist/cone/cone-generator.js +25 -63
  92. package/dist/database/_batch_update.js +26 -46
  93. package/dist/database/base-model.js +44 -97
  94. package/dist/database/base-model.types.js +1 -1
  95. package/dist/database/db.d.ts +8 -14
  96. package/dist/database/db.d.ts.map +1 -1
  97. package/dist/database/db.js +127 -72
  98. package/dist/database/knex.js +5 -8
  99. package/dist/database/puri-subset.types.js +1 -1
  100. package/dist/database/puri-wrapper.js +11 -15
  101. package/dist/database/puri.js +117 -234
  102. package/dist/database/puri.types.js +3 -4
  103. package/dist/database/transaction-context.js +4 -5
  104. package/dist/database/upsert-builder.js +109 -176
  105. package/dist/dict/en.d.ts +1 -0
  106. package/dist/dict/en.d.ts.map +1 -1
  107. package/dist/dict/en.js +4 -4
  108. package/dist/dict/index.js +2 -3
  109. package/dist/dict/ko.d.ts +1 -0
  110. package/dist/dict/ko.d.ts.map +1 -1
  111. package/dist/dict/ko.js +4 -4
  112. package/dist/dict/rc-keys.js +3 -4
  113. package/dist/dict/sd.js +8 -19
  114. package/dist/dict/sonamu-dictionary.js +141 -284
  115. package/dist/dict/types.js +1 -1
  116. package/dist/dict/utils.js +4 -5
  117. package/dist/entity/entity-manager.d.ts +2 -2
  118. package/dist/entity/entity-manager.js +34 -82
  119. package/dist/entity/entity-template-cone.js +33 -66
  120. package/dist/entity/entity.js +157 -311
  121. package/dist/env.d.ts +14 -0
  122. package/dist/env.d.ts.map +1 -0
  123. package/dist/env.js +75 -0
  124. package/dist/exceptions/error-handler.js +2 -3
  125. package/dist/exceptions/so-exceptions.js +7 -5
  126. package/dist/filter/index.js +1 -2
  127. package/dist/filter/types.js +3 -4
  128. package/dist/filter/utils.js +21 -54
  129. package/dist/index.js +9 -8
  130. package/dist/logger/category.js +6 -12
  131. package/dist/logger/configure.js +23 -34
  132. package/dist/migration/code-generation.js +147 -315
  133. package/dist/migration/index-where-predicate.js +52 -144
  134. package/dist/migration/migration-set.js +19 -33
  135. package/dist/migration/migrator.d.ts +2 -0
  136. package/dist/migration/migrator.d.ts.map +1 -1
  137. package/dist/migration/migrator.js +69 -53
  138. package/dist/migration/postgresql-schema-reader.js +126 -225
  139. package/dist/migration/slack-confirm.d.ts +1 -0
  140. package/dist/migration/slack-confirm.d.ts.map +1 -1
  141. package/dist/migration/slack-confirm.js +28 -38
  142. package/dist/migration/types.js +1 -1
  143. package/dist/naite/messaging-types.js +1 -1
  144. package/dist/naite/naite-reporter.js +15 -32
  145. package/dist/naite/naite.js +43 -76
  146. package/dist/ssr/index.js +6 -9
  147. package/dist/ssr/registry.js +10 -18
  148. package/dist/ssr/renderer.js +10 -21
  149. package/dist/ssr/types.js +1 -1
  150. package/dist/storage/base-file.js +5 -10
  151. package/dist/storage/buffered-file.js +3 -4
  152. package/dist/storage/drivers.js +2 -3
  153. package/dist/storage/index.js +2 -3
  154. package/dist/storage/s3-driver.js +5 -9
  155. package/dist/storage/storage-manager.js +5 -5
  156. package/dist/storage/types.js +1 -1
  157. package/dist/storage/uploaded-file.js +4 -6
  158. package/dist/stream/index.js +1 -2
  159. package/dist/stream/sse.js +8 -13
  160. package/dist/stream/ws-audience-resolver.js +5 -5
  161. package/dist/stream/ws-audience.js +3 -4
  162. package/dist/stream/ws-cluster-bus.js +3 -4
  163. package/dist/stream/ws-core.js +1 -1
  164. package/dist/stream/ws-delivery.js +11 -25
  165. package/dist/stream/ws-local-connection-store.js +9 -18
  166. package/dist/stream/ws-presence-store.js +43 -97
  167. package/dist/stream/ws-registry.js +17 -22
  168. package/dist/stream/ws-telemetry-memory.js +38 -45
  169. package/dist/stream/ws-telemetry-trace.js +4 -6
  170. package/dist/stream/ws-telemetry.js +82 -135
  171. package/dist/stream/ws.js +47 -91
  172. package/dist/syncer/api-parser.js +81 -147
  173. package/dist/syncer/checksum.js +14 -32
  174. package/dist/syncer/code-generator.js +29 -47
  175. package/dist/syncer/entity-operations.js +17 -27
  176. package/dist/syncer/event-batcher.js +8 -15
  177. package/dist/syncer/file-patterns.js +3 -4
  178. package/dist/syncer/file-tracking.js +6 -10
  179. package/dist/syncer/index.js +1 -2
  180. package/dist/syncer/module-loader.js +10 -26
  181. package/dist/syncer/syncer-actions.js +19 -37
  182. package/dist/syncer/syncer.js +46 -98
  183. package/dist/syncer/watcher.js +12 -26
  184. package/dist/tasks/decorator.js +7 -11
  185. package/dist/tasks/step-wrapper.js +7 -8
  186. package/dist/tasks/workflow-manager.js +18 -25
  187. package/dist/template/entity-converter.js +40 -64
  188. package/dist/template/helpers.js +32 -63
  189. package/dist/template/implementations/entity.template.js +7 -11
  190. package/dist/template/implementations/entry-server.template.js +2 -3
  191. package/dist/template/implementations/generated.template.js +25 -51
  192. package/dist/template/implementations/generated_http.template.js +31 -58
  193. package/dist/template/implementations/generated_sso.template.js +45 -85
  194. package/dist/template/implementations/init_types.template.js +4 -7
  195. package/dist/template/implementations/model.template.js +5 -10
  196. package/dist/template/implementations/model_test.template.js +2 -3
  197. package/dist/template/implementations/queries.template.js +4 -7
  198. package/dist/template/implementations/sd.template.js +17 -35
  199. package/dist/template/implementations/services.template.js +18 -30
  200. package/dist/template/implementations/view_form.template.js +72 -125
  201. package/dist/template/implementations/view_id_all_select.template.js +2 -3
  202. package/dist/template/implementations/view_list.template.js +86 -143
  203. package/dist/template/implementations/view_search_input.template.js +2 -3
  204. package/dist/template/index.js +5 -8
  205. package/dist/template/template-manager.js +13 -26
  206. package/dist/template/template-types.js +2 -3
  207. package/dist/template/template.js +7 -11
  208. package/dist/template/zod-converter.js +173 -348
  209. package/dist/testing/_relation-graph.js +18 -37
  210. package/dist/testing/bootstrap.js +5 -8
  211. package/dist/testing/data-explorer.js +34 -78
  212. package/dist/testing/dev-test-routes.js +54 -60
  213. package/dist/testing/dev-vitest-manager.js +33 -84
  214. package/dist/testing/faker-mappings.js +3 -4
  215. package/dist/testing/fixture-generator.d.ts +2 -1
  216. package/dist/testing/fixture-generator.d.ts.map +1 -1
  217. package/dist/testing/fixture-generator.js +159 -321
  218. package/dist/testing/fixture-loader.js +2 -2
  219. package/dist/testing/fixture-manager.d.ts.map +1 -1
  220. package/dist/testing/fixture-manager.js +124 -227
  221. package/dist/testing/global-setup.d.ts.map +1 -1
  222. package/dist/testing/global-setup.js +29 -17
  223. package/dist/testing/index.d.ts +1 -0
  224. package/dist/testing/index.d.ts.map +1 -1
  225. package/dist/testing/index.js +5 -4
  226. package/dist/testing/naite-vitest-reporter.js +2 -3
  227. package/dist/testing/parallel-db-manager.js +5 -3
  228. package/dist/testing/vitest-helpers.d.ts.map +1 -1
  229. package/dist/testing/vitest-helpers.js +15 -12
  230. package/dist/types/types.d.ts +14 -14
  231. package/dist/types/types.js +27 -50
  232. package/dist/ui/ai-api.js +6 -11
  233. package/dist/ui/ai-client.js +86 -134
  234. package/dist/ui/api.js +100 -196
  235. package/dist/ui/cdd-service.js +78 -130
  236. package/dist/ui/cdd-types.js +1 -1
  237. package/dist/ui-web/assets/{index-Df8q-fhb.js → index-DFStGyd0.js} +49 -49
  238. package/dist/ui-web/assets/index-Dx4ap5i4.css +1 -0
  239. package/dist/ui-web/index.html +2 -2
  240. package/dist/utils/async-utils.js +13 -25
  241. package/dist/utils/class-name.js +3 -4
  242. package/dist/utils/console-util.js +11 -26
  243. package/dist/utils/controller.d.ts.map +1 -1
  244. package/dist/utils/controller.js +14 -12
  245. package/dist/utils/esm-utils.js +5 -8
  246. package/dist/utils/formatter.js +10 -22
  247. package/dist/utils/fs-utils.js +14 -25
  248. package/dist/utils/lodash-able.js +3 -4
  249. package/dist/utils/model.js +7 -14
  250. package/dist/utils/object-utils.js +41 -73
  251. package/dist/utils/path-utils.js +5 -9
  252. package/dist/utils/process-utils.js +4 -7
  253. package/dist/utils/sql-parser.js +6 -13
  254. package/dist/utils/type-utils.js +16 -26
  255. package/dist/utils/utils.js +18 -40
  256. package/dist/utils/zod-error.js +9 -16
  257. package/dist/vector/chunking.js +24 -37
  258. package/dist/vector/config.js +2 -2
  259. package/dist/vector/embedding.js +8 -19
  260. package/dist/vector/index.js +1 -2
  261. package/dist/vector/types.js +1 -1
  262. package/package.json +8 -8
  263. package/src/__tests__/env.test.ts +127 -0
  264. package/src/api/__tests__/config.test.ts +10 -1
  265. package/src/api/config.ts +4 -12
  266. package/src/api/sonamu.ts +28 -33
  267. package/src/auth/audit-log/builders.ts +3 -0
  268. package/src/auth/audit-log/client-ip.ts +38 -0
  269. package/src/auth/audit-log/events.ts +1 -0
  270. package/src/auth/audit-log-ingestor.ts +22 -15
  271. package/src/auth/better-auth-request.ts +20 -0
  272. package/src/bin/__tests__/migrate-targets.test.ts +28 -0
  273. package/src/bin/__tests__/test-command.test.ts +82 -1
  274. package/src/bin/cli.ts +9 -18
  275. package/src/bin/fixture.ts +5 -4
  276. package/src/bin/migrate-targets.ts +7 -0
  277. package/src/bin/test-command.ts +2 -2
  278. package/src/database/__tests__/db.test.ts +175 -0
  279. package/src/database/db.ts +193 -71
  280. package/src/dict/en.ts +2 -0
  281. package/src/dict/ko.ts +2 -0
  282. package/src/env.ts +123 -0
  283. package/src/migration/__tests__/migrator.test.ts +149 -0
  284. package/src/migration/migrator.ts +74 -17
  285. package/src/migration/slack-confirm.ts +21 -0
  286. package/src/skills/sonamu/database.md +1 -1
  287. package/src/skills/sonamu/puri.md +33 -0
  288. package/src/skills/sonamu/testing-devrunner.md +1 -1
  289. package/src/stream/ws-telemetry-memory.ts +2 -2
  290. package/src/syncer/checksum.ts +7 -12
  291. package/src/testing/fixture-generator.ts +2 -1
  292. package/src/testing/fixture-manager.ts +3 -4
  293. package/src/testing/global-setup.ts +42 -18
  294. package/src/testing/index.ts +1 -0
  295. package/src/testing/vitest-helpers.ts +14 -0
  296. package/src/utils/controller.ts +14 -7
  297. package/tsdown.api.config.ts +6 -0
  298. package/dist/_virtual/rolldown_runtime.js +0 -39
  299. package/dist/ui-web/assets/index-D4rYm-Xz.css +0 -1
@@ -1,7 +1,6 @@
1
1
  import { Sonamu, init_sonamu } from "../api/sonamu.js";
2
2
  import { DEFAULT_VECTOR_CONFIG } from "./config.js";
3
3
  import { embedMany } from "ai";
4
-
5
4
  //#region src/vector/embedding.ts
6
5
  init_sonamu();
7
6
  /**
@@ -40,9 +39,7 @@ var EmbeddingClass = class {
40
39
  async getVoyageClient() {
41
40
  const { VoyageAIClient } = await import("voyageai");
42
41
  const apiKey = Sonamu.secrets?.voyage_api_key ?? process.env.VOYAGE_API_KEY;
43
- if (!apiKey) {
44
- throw new Error("VOYAGE_API_KEY가 설정되지 않았습니다. 환경변수를 확인하세요.");
45
- }
42
+ if (!apiKey) throw new Error("VOYAGE_API_KEY가 설정되지 않았습니다. 환경변수를 확인하세요.");
46
43
  return new VoyageAIClient({ apiKey });
47
44
  }
48
45
  /**
@@ -51,9 +48,7 @@ var EmbeddingClass = class {
51
48
  async getOpenAIProvider() {
52
49
  const { createOpenAI } = await import("@ai-sdk/openai");
53
50
  const apiKey = Sonamu.secrets?.openai_api_key ?? process.env.OPENAI_API_KEY;
54
- if (!apiKey) {
55
- throw new Error("OPENAI_API_KEY가 설정되지 않았습니다. 환경변수를 확인하세요.");
56
- }
51
+ if (!apiKey) throw new Error("OPENAI_API_KEY가 설정되지 않았습니다. 환경변수를 확인하세요.");
57
52
  return createOpenAI({ apiKey });
58
53
  }
59
54
  /**
@@ -65,9 +60,7 @@ var EmbeddingClass = class {
65
60
  */
66
61
  async embed(texts, provider, inputType = "document", onProgress) {
67
62
  const maxBatchSize = provider === "voyage" ? this.config.voyage.batchSize : this.config.openai.batchSize;
68
- if (texts.length <= maxBatchSize) {
69
- return provider === "voyage" ? await this.embedVoyage(texts, inputType) : await this.embedOpenAI(texts);
70
- }
63
+ if (texts.length <= maxBatchSize) return provider === "voyage" ? await this.embedVoyage(texts, inputType) : await this.embedOpenAI(texts);
71
64
  const batches = Array.from({ length: Math.ceil(texts.length / maxBatchSize) }, (_, i) => texts.slice(i * maxBatchSize, (i + 1) * maxBatchSize));
72
65
  const results = await Promise.all(batches.map((batch) => provider === "voyage" ? this.embedVoyage(batch, inputType) : this.embedOpenAI(batch)));
73
66
  onProgress?.(texts.length, texts.length);
@@ -77,8 +70,7 @@ var EmbeddingClass = class {
77
70
  * 단일 텍스트 임베딩 (편의 메서드)
78
71
  */
79
72
  async embedOne(text, provider, inputType = "document") {
80
- const results = await this.embed([text], provider, inputType);
81
- return results[0];
73
+ return (await this.embed([text], provider, inputType))[0];
82
74
  }
83
75
  /**
84
76
  * Voyage AI 임베딩
@@ -91,9 +83,7 @@ var EmbeddingClass = class {
91
83
  model: voyageConfig.model,
92
84
  inputType
93
85
  });
94
- if (!response.data) {
95
- throw new Error("Voyage API: 응답 데이터가 없습니다.");
96
- }
86
+ if (!response.data) throw new Error("Voyage API: 응답 데이터가 없습니다.");
97
87
  return response.data.map((item) => ({
98
88
  embedding: item.embedding ?? [],
99
89
  model: voyageConfig.model,
@@ -106,9 +96,8 @@ var EmbeddingClass = class {
106
96
  async embedOpenAI(texts) {
107
97
  const openai = await this.getOpenAIProvider();
108
98
  const openaiConfig = this.config.openai;
109
- const model = openai.embeddingModel(openaiConfig.model);
110
99
  const { embeddings, usage } = await embedMany({
111
- model,
100
+ model: openai.embeddingModel(openaiConfig.model),
112
101
  values: texts
113
102
  });
114
103
  return embeddings.map((embedding) => ({
@@ -125,7 +114,7 @@ var EmbeddingClass = class {
125
114
  }
126
115
  };
127
116
  const Embedding = new EmbeddingClass();
128
-
129
117
  //#endregion
130
118
  export { Embedding };
131
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW1iZWRkaW5nLmpzIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy92ZWN0b3IvZW1iZWRkaW5nLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHR5cGUgT3BlbkFJUHJvdmlkZXIgfSBmcm9tIFwiQGFpLXNkay9vcGVuYWlcIjtcbmltcG9ydCB7IGVtYmVkTWFueSB9IGZyb20gXCJhaVwiO1xuaW1wb3J0IHsgdHlwZSBFbWJlZGRpbmdNb2RlbCB9IGZyb20gXCJhaVwiO1xuaW1wb3J0IHsgdHlwZSBWb3lhZ2VBSUNsaWVudCB9IGZyb20gXCJ2b3lhZ2VhaVwiO1xuXG5pbXBvcnQgeyBTb25hbXUgfSBmcm9tIFwiLi4vYXBpL3NvbmFtdVwiO1xuaW1wb3J0IHsgREVGQVVMVF9WRUNUT1JfQ09ORklHIH0gZnJvbSBcIi4vY29uZmlnXCI7XG5pbXBvcnQge1xuICB0eXBlIEVtYmVkZGluZ1Byb3ZpZGVyLFxuICB0eXBlIEVtYmVkZGluZ1Jlc3VsdCxcbiAgdHlwZSBQcm9ncmVzc0NhbGxiYWNrLFxuICB0eXBlIFZlY3RvckNvbmZpZyxcbiAgdHlwZSBWZWN0b3JJbnB1dFR5cGUsXG59IGZyb20gXCIuL3R5cGVzXCI7XG5cbi8qKlxuICog7J6E67Kg65SpIO2BtOudvOydtOyWuO2KuFxuICogVm95YWdlIEFJ7JmAIE9wZW5BSSDsnoTrsqDrlKnsnYQgU0RLIOuwqeyLneycvOuhnCDthrXtlakg7KeA7JuQXG4gKi9cbmNsYXNzIEVtYmVkZGluZ0NsYXNzIHtcbiAgcHJpdmF0ZSBjb25maWc6IFZlY3RvckNvbmZpZztcblxuICBjb25zdHJ1Y3Rvcihjb25maWc6IFBhcnRpYWw8VmVjdG9yQ29uZmlnPiA9IHt9KSB7XG4gICAgdGhpcy5jb25maWcgPSB7XG4gICAgICB2b3lhZ2U6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnZveWFnZSwgLi4uY29uZmlnLnZveWFnZSB9LFxuICAgICAgb3BlbmFpOiB7IC4uLkRFRkFVTFRfVkVDVE9SX0NPTkZJRy5vcGVuYWksIC4uLmNvbmZpZy5vcGVuYWkgfSxcbiAgICAgIGNodW5raW5nOiB7IC4uLkRFRkFVTFRfVkVDVE9SX0NPTkZJRy5jaHVua2luZywgLi4uY29uZmlnLmNodW5raW5nIH0sXG4gICAgICBzZWFyY2g6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnNlYXJjaCwgLi4uY29uZmlnLnNlYXJjaCB9LFxuICAgICAgcGd2ZWN0b3I6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnBndmVjdG9yLCAuLi5jb25maWcucGd2ZWN0b3IgfSxcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFZveWFnZSBBSSDtgbTrnbzsnbTslrjtirgg7LSI6riw7ZmUXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGdldFZveWFnZUNsaWVudCgpOiBQcm9taXNlPFZveWFnZUFJQ2xpZW50PiB7XG4gICAgY29uc3QgeyBWb3lhZ2VBSUNsaWVudCB9ID0gYXdhaXQgaW1wb3J0KFwidm95YWdlYWlcIik7XG4gICAgY29uc3QgYXBpS2V5ID0gU29uYW11LnNlY3JldHM/LnZveWFnZV9hcGlfa2V5ID8/IHByb2Nlc3MuZW52LlZPWUFHRV9BUElfS0VZO1xuICAgIGlmICghYXBpS2V5KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJWT1lBR0VfQVBJX0tFWeqwgCDshKTsoJXrkJjsp4Ag7JWK7JWY7Iq164uI64ukLiDtmZjqsr3rs4DsiJjrpbwg7ZmV7J247ZWY7IS47JqULlwiKTtcbiAgICB9XG4gICAgcmV0dXJuIG5ldyBWb3lhZ2VBSUNsaWVudCh7IGFwaUtleSB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBPcGVuQUkgcHJvdmlkZXIg7IOd7ISxXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGdldE9wZW5BSVByb3ZpZGVyKCk6IFByb21pc2U8T3BlbkFJUHJvdmlkZXI+IHtcbiAgICBjb25zdCB7IGNyZWF0ZU9wZW5BSSB9ID0gYXdhaXQgaW1wb3J0KFwiQGFpLXNkay9vcGVuYWlcIik7XG4gICAgY29uc3QgYXBpS2V5ID0gU29uYW11LnNlY3JldHM/Lm9wZW5haV9hcGlfa2V5ID8/IHByb2Nlc3MuZW52Lk9QRU5BSV9BUElfS0VZO1xuICAgIGlmICghYXBpS2V5KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJPUEVOQUlfQVBJX0tFWeqwgCDshKTsoJXrkJjsp4Ag7JWK7JWY7Iq164uI64ukLiDtmZjqsr3rs4DsiJjrpbwg7ZmV7J247ZWY7IS47JqULlwiKTtcbiAgICB9XG4gICAgcmV0dXJuIGNyZWF0ZU9wZW5BSSh7IGFwaUtleSB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiDthY3siqTtirgg7J6E67Kg65SpIOyDneyEsVxuICAgKiBAcGFyYW0gdGV4dHMgLSDsnoTrsqDrlKntlaAg7YWN7Iqk7Yq4IOuwsOyXtCAoYmF0Y2hTaXpl7J207IOBIOyLnCDsnpDrj5kg67aE7ZWgKVxuICAgKiBAcGFyYW0gcHJvdmlkZXIgLSAndm95YWdlJyB8ICdvcGVuYWknXG4gICAqIEBwYXJhbSBpbnB1dFR5cGUgLSAnZG9jdW1lbnQnIHwgJ3F1ZXJ5JyAoVm95YWdlIEFJ66eMIO2VtOuLuSlcbiAgICogQHBhcmFtIG9uUHJvZ3Jlc3MgLSDsp4TtlonrpaAg7L2c67CxXG4gICAqL1xuICBhc3luYyBlbWJlZChcbiAgICB0ZXh0czogc3RyaW5nW10sXG4gICAgcHJvdmlkZXI6IEVtYmVkZGluZ1Byb3ZpZGVyLFxuICAgIGlucHV0VHlwZTogVmVjdG9ySW5wdXRUeXBlID0gXCJkb2N1bWVudFwiLFxuICAgIG9uUHJvZ3Jlc3M/OiBQcm9ncmVzc0NhbGxiYWNrLFxuICApOiBQcm9taXNlPEVtYmVkZGluZ1Jlc3VsdFtdPiB7XG4gICAgY29uc3QgbWF4QmF0Y2hTaXplID1cbiAgICAgIHByb3ZpZGVyID09PSBcInZveWFnZVwiID8gdGhpcy5jb25maWcudm95YWdlLmJhdGNoU2l6ZSA6IHRoaXMuY29uZmlnLm9wZW5haS5iYXRjaFNpemU7XG5cbiAgICAvLyBiYXRjaFNpemXsnbTtlZjrqbQg67CU66GcIO2YuOy2nFxuICAgIGlmICh0ZXh0cy5sZW5ndGggPD0gbWF4QmF0Y2hTaXplKSB7XG4gICAgICByZXR1cm4gcHJvdmlkZXIgPT09IFwidm95YWdlXCJcbiAgICAgICAgPyBhd2FpdCB0aGlzLmVtYmVkVm95YWdlKHRleHRzLCBpbnB1dFR5cGUpXG4gICAgICAgIDogYXdhaXQgdGhpcy5lbWJlZE9wZW5BSSh0ZXh0cyk7XG4gICAgfVxuXG4gICAgLy8gYmF0Y2hTaXpl7J207IOB7J2066m0IOyekOuPmeycvOuhnCDrgpjriKDshJwg7LKY66asXG4gICAgY29uc3QgYmF0Y2hlcyA9IEFycmF5LmZyb20oeyBsZW5ndGg6IE1hdGguY2VpbCh0ZXh0cy5sZW5ndGggLyBtYXhCYXRjaFNpemUpIH0sIChfLCBpKSA9PlxuICAgICAgdGV4dHMuc2xpY2UoaSAqIG1heEJhdGNoU2l6ZSwgKGkgKyAxKSAqIG1heEJhdGNoU2l6ZSksXG4gICAgKTtcblxuICAgIGNvbnN0IHJlc3VsdHMgPSBhd2FpdCBQcm9taXNlLmFsbChcbiAgICAgIGJhdGNoZXMubWFwKChiYXRjaCkgPT5cbiAgICAgICAgcHJvdmlkZXIgPT09IFwidm95YWdlXCIgPyB0aGlzLmVtYmVkVm95YWdlKGJhdGNoLCBpbnB1dFR5cGUpIDogdGhpcy5lbWJlZE9wZW5BSShiYXRjaCksXG4gICAgICApLFxuICAgICk7XG5cbiAgICBvblByb2dyZXNzPy4odGV4dHMubGVuZ3RoLCB0ZXh0cy5sZW5ndGgpO1xuICAgIHJldHVybiByZXN1bHRzLmZsYXQoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiDri6jsnbwg7YWN7Iqk7Yq4IOyehOuyoOuUqSAo7Y647J2YIOuplOyEnOuTnClcbiAgICovXG4gIGFzeW5jIGVtYmVkT25lKFxuICAgIHRleHQ6IHN0cmluZyxcbiAgICBwcm92aWRlcjogRW1iZWRkaW5nUHJvdmlkZXIsXG4gICAgaW5wdXRUeXBlOiBWZWN0b3JJbnB1dFR5cGUgPSBcImRvY3VtZW50XCIsXG4gICk6IFByb21pc2U8RW1iZWRkaW5nUmVzdWx0PiB7XG4gICAgY29uc3QgcmVzdWx0cyA9IGF3YWl0IHRoaXMuZW1iZWQoW3RleHRdLCBwcm92aWRlciwgaW5wdXRUeXBlKTtcbiAgICByZXR1cm4gcmVzdWx0c1swXTtcbiAgfVxuXG4gIC8qKlxuICAgKiBWb3lhZ2UgQUkg7J6E67Kg65SpXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGVtYmVkVm95YWdlKFxuICAgIHRleHRzOiBzdHJpbmdbXSxcbiAgICBpbnB1dFR5cGU6IFZlY3RvcklucHV0VHlwZSxcbiAgKTogUHJvbWlzZTxFbWJlZGRpbmdSZXN1bHRbXT4ge1xuICAgIGNvbnN0IGNsaWVudCA9IGF3YWl0IHRoaXMuZ2V0Vm95YWdlQ2xpZW50KCk7XG4gICAgY29uc3Qgdm95YWdlQ29uZmlnID0gdGhpcy5jb25maWcudm95YWdlO1xuXG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBjbGllbnQuZW1iZWQoe1xuICAgICAgaW5wdXQ6IHRleHRzLFxuICAgICAgbW9kZWw6IHZveWFnZUNvbmZpZy5tb2RlbCxcbiAgICAgIGlucHV0VHlwZTogaW5wdXRUeXBlLFxuICAgIH0pO1xuICAgIGlmICghcmVzcG9uc2UuZGF0YSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiVm95YWdlIEFQSTog7J2R64u1IOuNsOydtO2EsOqwgCDsl4bsirXri4jri6QuXCIpO1xuICAgIH1cblxuICAgIHJldHVybiByZXNwb25zZS5kYXRhLm1hcCgoaXRlbSkgPT4gKHtcbiAgICAgIGVtYmVkZGluZzogaXRlbS5lbWJlZGRpbmcgPz8gW10sXG4gICAgICBtb2RlbDogdm95YWdlQ29uZmlnLm1vZGVsLFxuICAgICAgdG9rZW5Db3VudDogcmVzcG9uc2UudXNhZ2U/LnRvdGFsVG9rZW5zID8/IDAsXG4gICAgfSkpO1xuICB9XG5cbiAgLyoqXG4gICAqIE9wZW5BSSDsnoTrsqDrlKlcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZW1iZWRPcGVuQUkodGV4dHM6IHN0cmluZ1tdKTogUHJvbWlzZTxFbWJlZGRpbmdSZXN1bHRbXT4ge1xuICAgIGNvbnN0IG9wZW5haSA9IGF3YWl0IHRoaXMuZ2V0T3BlbkFJUHJvdmlkZXIoKTtcbiAgICBjb25zdCBvcGVuYWlDb25maWcgPSB0aGlzLmNvbmZpZy5vcGVuYWk7XG4gICAgY29uc3QgbW9kZWwgPSBvcGVuYWkuZW1iZWRkaW5nTW9kZWwob3BlbmFpQ29uZmlnLm1vZGVsKTtcblxuICAgIGNvbnN0IHsgZW1iZWRkaW5ncywgdXNhZ2UgfSA9IGF3YWl0IGVtYmVkTWFueSh7XG4gICAgICBtb2RlbDogbW9kZWwgYXMgRW1iZWRkaW5nTW9kZWwsXG4gICAgICB2YWx1ZXM6IHRleHRzLFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIGVtYmVkZGluZ3MubWFwKChlbWJlZGRpbmcpID0+ICh7XG4gICAgICBlbWJlZGRpbmcsXG4gICAgICBtb2RlbDogb3BlbmFpQ29uZmlnLm1vZGVsLFxuICAgICAgdG9rZW5Db3VudDogdXNhZ2U/LnRva2VucyA/PyAwLFxuICAgIH0pKTtcbiAgfVxuXG4gIC8qKlxuICAgKiDsnoTrsqDrlKkgcHJvdmlkZXLsnZgg7LCo7JuQIOyImCDrsJjtmZhcbiAgICovXG4gIGdldERpbWVuc2lvbnMocHJvdmlkZXI6IEVtYmVkZGluZ1Byb3ZpZGVyKTogbnVtYmVyIHtcbiAgICByZXR1cm4gcHJvdmlkZXIgPT09IFwidm95YWdlXCIgPyB0aGlzLmNvbmZpZy52b3lhZ2UuZGltZW5zaW9ucyA6IHRoaXMuY29uZmlnLm9wZW5haS5kaW1lbnNpb25zO1xuICB9XG59XG5leHBvcnQgY29uc3QgRW1iZWRkaW5nID0gbmV3IEVtYmVkZGluZ0NsYXNzKCk7XG4iXSwibWFwcGluZ3MiOiI7Ozs7O2FBS3VDOzs7OztBQWN2QyxJQUFNLGlCQUFOLE1BQXFCO0NBQ25CLEFBQVE7Q0FFUixZQUFZLFNBQWdDLEVBQUUsRUFBRTtBQUM5QyxPQUFLLFNBQVM7R0FDWixRQUFRO0lBQUUsR0FBRyxzQkFBc0I7SUFBUSxHQUFHLE9BQU87SUFBUTtHQUM3RCxRQUFRO0lBQUUsR0FBRyxzQkFBc0I7SUFBUSxHQUFH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
119
+
120
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW1iZWRkaW5nLmpzIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy92ZWN0b3IvZW1iZWRkaW5nLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHR5cGUgT3BlbkFJUHJvdmlkZXIgfSBmcm9tIFwiQGFpLXNkay9vcGVuYWlcIjtcbmltcG9ydCB7IGVtYmVkTWFueSB9IGZyb20gXCJhaVwiO1xuaW1wb3J0IHsgdHlwZSBFbWJlZGRpbmdNb2RlbCB9IGZyb20gXCJhaVwiO1xuaW1wb3J0IHsgdHlwZSBWb3lhZ2VBSUNsaWVudCB9IGZyb20gXCJ2b3lhZ2VhaVwiO1xuXG5pbXBvcnQgeyBTb25hbXUgfSBmcm9tIFwiLi4vYXBpL3NvbmFtdVwiO1xuaW1wb3J0IHsgREVGQVVMVF9WRUNUT1JfQ09ORklHIH0gZnJvbSBcIi4vY29uZmlnXCI7XG5pbXBvcnQge1xuICB0eXBlIEVtYmVkZGluZ1Byb3ZpZGVyLFxuICB0eXBlIEVtYmVkZGluZ1Jlc3VsdCxcbiAgdHlwZSBQcm9ncmVzc0NhbGxiYWNrLFxuICB0eXBlIFZlY3RvckNvbmZpZyxcbiAgdHlwZSBWZWN0b3JJbnB1dFR5cGUsXG59IGZyb20gXCIuL3R5cGVzXCI7XG5cbi8qKlxuICog7J6E67Kg65SpIO2BtOudvOydtOyWuO2KuFxuICogVm95YWdlIEFJ7JmAIE9wZW5BSSDsnoTrsqDrlKnsnYQgU0RLIOuwqeyLneycvOuhnCDthrXtlakg7KeA7JuQXG4gKi9cbmNsYXNzIEVtYmVkZGluZ0NsYXNzIHtcbiAgcHJpdmF0ZSBjb25maWc6IFZlY3RvckNvbmZpZztcblxuICBjb25zdHJ1Y3Rvcihjb25maWc6IFBhcnRpYWw8VmVjdG9yQ29uZmlnPiA9IHt9KSB7XG4gICAgdGhpcy5jb25maWcgPSB7XG4gICAgICB2b3lhZ2U6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnZveWFnZSwgLi4uY29uZmlnLnZveWFnZSB9LFxuICAgICAgb3BlbmFpOiB7IC4uLkRFRkFVTFRfVkVDVE9SX0NPTkZJRy5vcGVuYWksIC4uLmNvbmZpZy5vcGVuYWkgfSxcbiAgICAgIGNodW5raW5nOiB7IC4uLkRFRkFVTFRfVkVDVE9SX0NPTkZJRy5jaHVua2luZywgLi4uY29uZmlnLmNodW5raW5nIH0sXG4gICAgICBzZWFyY2g6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnNlYXJjaCwgLi4uY29uZmlnLnNlYXJjaCB9LFxuICAgICAgcGd2ZWN0b3I6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnBndmVjdG9yLCAuLi5jb25maWcucGd2ZWN0b3IgfSxcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFZveWFnZSBBSSDtgbTrnbzsnbTslrjtirgg7LSI6riw7ZmUXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGdldFZveWFnZUNsaWVudCgpOiBQcm9taXNlPFZveWFnZUFJQ2xpZW50PiB7XG4gICAgY29uc3QgeyBWb3lhZ2VBSUNsaWVudCB9ID0gYXdhaXQgaW1wb3J0KFwidm95YWdlYWlcIik7XG4gICAgY29uc3QgYXBpS2V5ID0gU29uYW11LnNlY3JldHM/LnZveWFnZV9hcGlfa2V5ID8/IHByb2Nlc3MuZW52LlZPWUFHRV9BUElfS0VZO1xuICAgIGlmICghYXBpS2V5KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJWT1lBR0VfQVBJX0tFWeqwgCDshKTsoJXrkJjsp4Ag7JWK7JWY7Iq164uI64ukLiDtmZjqsr3rs4DsiJjrpbwg7ZmV7J247ZWY7IS47JqULlwiKTtcbiAgICB9XG4gICAgcmV0dXJuIG5ldyBWb3lhZ2VBSUNsaWVudCh7IGFwaUtleSB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBPcGVuQUkgcHJvdmlkZXIg7IOd7ISxXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGdldE9wZW5BSVByb3ZpZGVyKCk6IFByb21pc2U8T3BlbkFJUHJvdmlkZXI+IHtcbiAgICBjb25zdCB7IGNyZWF0ZU9wZW5BSSB9ID0gYXdhaXQgaW1wb3J0KFwiQGFpLXNkay9vcGVuYWlcIik7XG4gICAgY29uc3QgYXBpS2V5ID0gU29uYW11LnNlY3JldHM/Lm9wZW5haV9hcGlfa2V5ID8/IHByb2Nlc3MuZW52Lk9QRU5BSV9BUElfS0VZO1xuICAgIGlmICghYXBpS2V5KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJPUEVOQUlfQVBJX0tFWeqwgCDshKTsoJXrkJjsp4Ag7JWK7JWY7Iq164uI64ukLiDtmZjqsr3rs4DsiJjrpbwg7ZmV7J247ZWY7IS47JqULlwiKTtcbiAgICB9XG4gICAgcmV0dXJuIGNyZWF0ZU9wZW5BSSh7IGFwaUtleSB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiDthY3siqTtirgg7J6E67Kg65SpIOyDneyEsVxuICAgKiBAcGFyYW0gdGV4dHMgLSDsnoTrsqDrlKntlaAg7YWN7Iqk7Yq4IOuwsOyXtCAoYmF0Y2hTaXpl7J207IOBIOyLnCDsnpDrj5kg67aE7ZWgKVxuICAgKiBAcGFyYW0gcHJvdmlkZXIgLSAndm95YWdlJyB8ICdvcGVuYWknXG4gICAqIEBwYXJhbSBpbnB1dFR5cGUgLSAnZG9jdW1lbnQnIHwgJ3F1ZXJ5JyAoVm95YWdlIEFJ66eMIO2VtOuLuSlcbiAgICogQHBhcmFtIG9uUHJvZ3Jlc3MgLSDsp4TtlonrpaAg7L2c67CxXG4gICAqL1xuICBhc3luYyBlbWJlZChcbiAgICB0ZXh0czogc3RyaW5nW10sXG4gICAgcHJvdmlkZXI6IEVtYmVkZGluZ1Byb3ZpZGVyLFxuICAgIGlucHV0VHlwZTogVmVjdG9ySW5wdXRUeXBlID0gXCJkb2N1bWVudFwiLFxuICAgIG9uUHJvZ3Jlc3M/OiBQcm9ncmVzc0NhbGxiYWNrLFxuICApOiBQcm9taXNlPEVtYmVkZGluZ1Jlc3VsdFtdPiB7XG4gICAgY29uc3QgbWF4QmF0Y2hTaXplID1cbiAgICAgIHByb3ZpZGVyID09PSBcInZveWFnZVwiID8gdGhpcy5jb25maWcudm95YWdlLmJhdGNoU2l6ZSA6IHRoaXMuY29uZmlnLm9wZW5haS5iYXRjaFNpemU7XG5cbiAgICAvLyBiYXRjaFNpemXsnbTtlZjrqbQg67CU66GcIO2YuOy2nFxuICAgIGlmICh0ZXh0cy5sZW5ndGggPD0gbWF4QmF0Y2hTaXplKSB7XG4gICAgICByZXR1cm4gcHJvdmlkZXIgPT09IFwidm95YWdlXCJcbiAgICAgICAgPyBhd2FpdCB0aGlzLmVtYmVkVm95YWdlKHRleHRzLCBpbnB1dFR5cGUpXG4gICAgICAgIDogYXdhaXQgdGhpcy5lbWJlZE9wZW5BSSh0ZXh0cyk7XG4gICAgfVxuXG4gICAgLy8gYmF0Y2hTaXpl7J207IOB7J2066m0IOyekOuPmeycvOuhnCDrgpjriKDshJwg7LKY66asXG4gICAgY29uc3QgYmF0Y2hlcyA9IEFycmF5LmZyb20oeyBsZW5ndGg6IE1hdGguY2VpbCh0ZXh0cy5sZW5ndGggLyBtYXhCYXRjaFNpemUpIH0sIChfLCBpKSA9PlxuICAgICAgdGV4dHMuc2xpY2UoaSAqIG1heEJhdGNoU2l6ZSwgKGkgKyAxKSAqIG1heEJhdGNoU2l6ZSksXG4gICAgKTtcblxuICAgIGNvbnN0IHJlc3VsdHMgPSBhd2FpdCBQcm9taXNlLmFsbChcbiAgICAgIGJhdGNoZXMubWFwKChiYXRjaCkgPT5cbiAgICAgICAgcHJvdmlkZXIgPT09IFwidm95YWdlXCIgPyB0aGlzLmVtYmVkVm95YWdlKGJhdGNoLCBpbnB1dFR5cGUpIDogdGhpcy5lbWJlZE9wZW5BSShiYXRjaCksXG4gICAgICApLFxuICAgICk7XG5cbiAgICBvblByb2dyZXNzPy4odGV4dHMubGVuZ3RoLCB0ZXh0cy5sZW5ndGgpO1xuICAgIHJldHVybiByZXN1bHRzLmZsYXQoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiDri6jsnbwg7YWN7Iqk7Yq4IOyehOuyoOuUqSAo7Y647J2YIOuplOyEnOuTnClcbiAgICovXG4gIGFzeW5jIGVtYmVkT25lKFxuICAgIHRleHQ6IHN0cmluZyxcbiAgICBwcm92aWRlcjogRW1iZWRkaW5nUHJvdmlkZXIsXG4gICAgaW5wdXRUeXBlOiBWZWN0b3JJbnB1dFR5cGUgPSBcImRvY3VtZW50XCIsXG4gICk6IFByb21pc2U8RW1iZWRkaW5nUmVzdWx0PiB7XG4gICAgY29uc3QgcmVzdWx0cyA9IGF3YWl0IHRoaXMuZW1iZWQoW3RleHRdLCBwcm92aWRlciwgaW5wdXRUeXBlKTtcbiAgICByZXR1cm4gcmVzdWx0c1swXTtcbiAgfVxuXG4gIC8qKlxuICAgKiBWb3lhZ2UgQUkg7J6E67Kg65SpXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGVtYmVkVm95YWdlKFxuICAgIHRleHRzOiBzdHJpbmdbXSxcbiAgICBpbnB1dFR5cGU6IFZlY3RvcklucHV0VHlwZSxcbiAgKTogUHJvbWlzZTxFbWJlZGRpbmdSZXN1bHRbXT4ge1xuICAgIGNvbnN0IGNsaWVudCA9IGF3YWl0IHRoaXMuZ2V0Vm95YWdlQ2xpZW50KCk7XG4gICAgY29uc3Qgdm95YWdlQ29uZmlnID0gdGhpcy5jb25maWcudm95YWdlO1xuXG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBjbGllbnQuZW1iZWQoe1xuICAgICAgaW5wdXQ6IHRleHRzLFxuICAgICAgbW9kZWw6IHZveWFnZUNvbmZpZy5tb2RlbCxcbiAgICAgIGlucHV0VHlwZTogaW5wdXRUeXBlLFxuICAgIH0pO1xuICAgIGlmICghcmVzcG9uc2UuZGF0YSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiVm95YWdlIEFQSTog7J2R64u1IOuNsOydtO2EsOqwgCDsl4bsirXri4jri6QuXCIpO1xuICAgIH1cblxuICAgIHJldHVybiByZXNwb25zZS5kYXRhLm1hcCgoaXRlbSkgPT4gKHtcbiAgICAgIGVtYmVkZGluZzogaXRlbS5lbWJlZGRpbmcgPz8gW10sXG4gICAgICBtb2RlbDogdm95YWdlQ29uZmlnLm1vZGVsLFxuICAgICAgdG9rZW5Db3VudDogcmVzcG9uc2UudXNhZ2U/LnRvdGFsVG9rZW5zID8/IDAsXG4gICAgfSkpO1xuICB9XG5cbiAgLyoqXG4gICAqIE9wZW5BSSDsnoTrsqDrlKlcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZW1iZWRPcGVuQUkodGV4dHM6IHN0cmluZ1tdKTogUHJvbWlzZTxFbWJlZGRpbmdSZXN1bHRbXT4ge1xuICAgIGNvbnN0IG9wZW5haSA9IGF3YWl0IHRoaXMuZ2V0T3BlbkFJUHJvdmlkZXIoKTtcbiAgICBjb25zdCBvcGVuYWlDb25maWcgPSB0aGlzLmNvbmZpZy5vcGVuYWk7XG4gICAgY29uc3QgbW9kZWwgPSBvcGVuYWkuZW1iZWRkaW5nTW9kZWwob3BlbmFpQ29uZmlnLm1vZGVsKTtcblxuICAgIGNvbnN0IHsgZW1iZWRkaW5ncywgdXNhZ2UgfSA9IGF3YWl0IGVtYmVkTWFueSh7XG4gICAgICBtb2RlbDogbW9kZWwgYXMgRW1iZWRkaW5nTW9kZWwsXG4gICAgICB2YWx1ZXM6IHRleHRzLFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIGVtYmVkZGluZ3MubWFwKChlbWJlZGRpbmcpID0+ICh7XG4gICAgICBlbWJlZGRpbmcsXG4gICAgICBtb2RlbDogb3BlbmFpQ29uZmlnLm1vZGVsLFxuICAgICAgdG9rZW5Db3VudDogdXNhZ2U/LnRva2VucyA/PyAwLFxuICAgIH0pKTtcbiAgfVxuXG4gIC8qKlxuICAgKiDsnoTrsqDrlKkgcHJvdmlkZXLsnZgg7LCo7JuQIOyImCDrsJjtmZhcbiAgICovXG4gIGdldERpbWVuc2lvbnMocHJvdmlkZXI6IEVtYmVkZGluZ1Byb3ZpZGVyKTogbnVtYmVyIHtcbiAgICByZXR1cm4gcHJvdmlkZXIgPT09IFwidm95YWdlXCIgPyB0aGlzLmNvbmZpZy52b3lhZ2UuZGltZW5zaW9ucyA6IHRoaXMuY29uZmlnLm9wZW5haS5kaW1lbnNpb25zO1xuICB9XG59XG5leHBvcnQgY29uc3QgRW1iZWRkaW5nID0gbmV3IEVtYmVkZGluZ0NsYXNzKCk7XG4iXSwibWFwcGluZ3MiOiI7Ozs7WUFLc0M7Ozs7O0FBY3RDLElBQU0saUJBQU4sTUFBcUI7Q0FDbkI7Q0FFQSxZQUFZLFNBQWdDLENBQUMsR0FBRztFQUM5QyxLQUFLLFNBQVM7R0FDWixRQUFRO0lBQUUsR0FBRyxzQkFBc0I7SUFBUSxHQUFHLE9BQU87R0FBTztHQUM1RCxRQUFRO0lBQUUsR0FBRyxzQkFBc0I7SUFBUSxHQUFHLE9BQU87R0FBTztHQUM1RCxVQUFVO0lBQUUsR0FBRyxzQkFBc0I7SUFBVSxHQUFHLE9BQU87R0FBUztHQUNsRSxRQUFRO0lBQUUsR0FBRyxzQkFBc0I7SUFBUSxHQUFHLE9BQU87R0FBTztHQUM1RCxVQUFVO0lBQUUsR0FBRyxzQkFBc0I7SUFBVSxHQUFHLE9BQU87R0FBUztFQUNwRTtDQUNGOzs7O0NBS0EsTUFBYyxrQkFBMkM7RUFDdkQsTUFBTSxFQUFFLG1CQUFtQixNQUFNLE9BQU87RUFDeEMsTUFBTSxTQUFTLE9BQU8sU0FBUyxrQkFBa0IsUUFBUSxJQUFJO0VBQzdELElBQUksQ0FBQyxRQUNILE1BQU0sSUFBSSxNQUFNLDBDQUEwQztFQUU1RCxPQUFPLElBQUksZUFBZSxFQUFFLE9BQU8sQ0FBQztDQUN0Qzs7OztDQUtBLE1BQWMsb0JBQTZDO0VBQ3pELE1BQU0sRUFBRSxpQkFBaUIsTUFBTSxPQUFPO0VBQ3RDLE1BQU0sU0FBUyxPQUFPLFNBQVMsa0JBQWtCLFFBQVEsSUFBSTtFQUM3RCxJQUFJLENBQUMsUUFDSCxNQUFNLElBQUksTUFBTSwwQ0FBMEM7RUFFNUQsT0FBTyxhQUFhLEVBQUUsT0FBTyxDQUFDO0NBQ2hDOzs7Ozs7OztDQVNBLE1BQU0sTUFDSixPQUNBLFVBQ0EsWUFBNkIsWUFDN0IsWUFDNEI7RUFDNUIsTUFBTSxlQUNKLGFBQWEsV0FBVyxLQUFLLE9BQU8sT0FBTyxZQUFZLEtBQUssT0FBTyxPQUFPO0VBRzVFLElBQUksTUFBTSxVQUFVLGNBQ2xCLE9BQU8sYUFBYSxXQUNoQixNQUFNLEtBQUssWUFBWSxPQUFPLFNBQVMsSUFDdkMsTUFBTSxLQUFLLFlBQVksS0FBSztFQUlsQyxNQUFNLFVBQVUsTUFBTSxLQUFLLEVBQUUsUUFBUSxLQUFLLEtBQUssTUFBTSxTQUFTLFlBQVksRUFBRSxJQUFJLEdBQUcsTUFDakYsTUFBTSxNQUFNLElBQUksZUFBZSxJQUFJLEtBQUssWUFBWSxDQUN0RDtFQUVBLE1BQU0sVUFBVSxNQUFNLFFBQVEsSUFDNUIsUUFBUSxLQUFLLFVBQ1gsYUFBYSxXQUFXLEtBQUssWUFBWSxPQUFPLFNBQVMsSUFBSSxLQUFLLFlBQVksS0FBSyxDQUNyRixDQUNGO0VBRUEsYUFBYSxNQUFNLFFBQVEsTUFBTSxNQUFNO0VBQ3ZDLE9BQU8sUUFBUSxLQUFLO0NBQ3RCOzs7O0NBS0EsTUFBTSxTQUNKLE1BQ0EsVUFDQSxZQUE2QixZQUNIO0VBRTFCLFFBQU8sTUFEZSxLQUFLLE1BQU0sQ0FBQyxJQUFJLEdBQUcsVUFBVSxTQUFTLEVBQ3JELENBQVE7Q0FDakI7Ozs7Q0FLQSxNQUFjLFlBQ1osT0FDQSxXQUM0QjtFQUM1QixNQUFNLFNBQVMsTUFBTSxLQUFLLGdCQUFnQjtFQUMxQyxNQUFNLGVBQWUsS0FBSyxPQUFPO0VBRWpDLE1BQU0sV0FBVyxNQUFNLE9BQU8sTUFBTTtHQUNsQyxPQUFPO0dBQ1AsT0FBTyxhQUFhO0dBQ1Q7RUFDYixDQUFDO0VBQ0QsSUFBSSxDQUFDLFNBQVMsTUFDWixNQUFNLElBQUksTUFBTSwyQkFBMkI7RUFHN0MsT0FBTyxTQUFTLEtBQUssS0FBSyxVQUFVO0dBQ2xDLFdBQVcsS0FBSyxhQUFhLENBQUM7R0FDOUIsT0FBTyxhQUFhO0dBQ3BCLFlBQVksU0FBUyxPQUFPLGVBQWU7RUFDN0MsRUFBRTtDQUNKOzs7O0NBS0EsTUFBYyxZQUFZLE9BQTZDO0VBQ3JFLE1BQU0sU0FBUyxNQUFNLEtBQUssa0JBQWtCO0VBQzVDLE1BQU0sZUFBZSxLQUFLLE9BQU87RUFHakMsTUFBTSxFQUFFLFlBQVksVUFBVSxNQUFNLFVBQVU7R0FDckMsT0FISyxPQUFPLGVBQWUsYUFBYSxLQUd4QztHQUNQLFFBQVE7RUFDVixDQUFDO0VBRUQsT0FBTyxXQUFXLEtBQUssZUFBZTtHQUNwQztHQUNBLE9BQU8sYUFBYTtHQUNwQixZQUFZLE9BQU8sVUFBVTtFQUMvQixFQUFFO0NBQ0o7Ozs7Q0FLQSxjQUFjLFVBQXFDO0VBQ2pELE9BQU8sYUFBYSxXQUFXLEtBQUssT0FBTyxPQUFPLGFBQWEsS0FBSyxPQUFPLE9BQU87Q0FDcEY7QUFDRjtBQUNBLE1BQWEsWUFBWSxJQUFJLGVBQWUifQ==
@@ -2,5 +2,4 @@ import { DEFAULT_VECTOR_CONFIG, createVectorConfig } from "./config.js";
2
2
  import { Chunking } from "./chunking.js";
3
3
  import { Embedding } from "./embedding.js";
4
4
  import "./types.js";
5
-
6
- export { Chunking, DEFAULT_VECTOR_CONFIG, Embedding, createVectorConfig };
5
+ export { Chunking, DEFAULT_VECTOR_CONFIG, Embedding, createVectorConfig };
@@ -1 +1 @@
1
- export { };
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonamu",
3
- "version": "0.9.20",
3
+ "version": "0.10.1",
4
4
  "description": "Sonamu — TypeScript Fullstack API Framework",
5
5
  "keywords": [
6
6
  "framework",
@@ -120,19 +120,19 @@
120
120
  "minimatch": "^10.0.3",
121
121
  "node-cron": "^4.2.1",
122
122
  "node-sql-parser": "^5.2.0",
123
- "oxfmt": "^0.43.0",
124
- "oxlint": "^1.61.0",
123
+ "oxfmt": "^0.55.0",
124
+ "oxlint": "^1.70.0",
125
125
  "pg": "^8.16.3",
126
126
  "prompts": "^2.4.2",
127
127
  "qs": "^6.14.1",
128
128
  "radashi": "^12.2.0",
129
129
  "tsicli": "^1.0.5",
130
- "vite": "8.0.5",
131
- "vitest": "^4.1.2",
130
+ "vite": "8.0.16",
131
+ "vitest": "^4.1.9",
132
+ "@sonamu-kit/hmr-hook": "^0.5.1",
132
133
  "@sonamu-kit/hmr-runner": "^0.2.0",
133
134
  "@sonamu-kit/tasks": "^0.3.1",
134
- "@sonamu-kit/ts-loader": "^2.2.0",
135
- "@sonamu-kit/hmr-hook": "^0.5.1"
135
+ "@sonamu-kit/ts-loader": "^2.2.0"
136
136
  },
137
137
  "devDependencies": {
138
138
  "@types/bcrypt": "^6",
@@ -143,7 +143,7 @@
143
143
  "@types/prompts": "^2.0.14",
144
144
  "@types/qs": "^6.14.0",
145
145
  "nodemon": "^3.1.10",
146
- "tsdown": "^0.12.5",
146
+ "tsdown": "^0.22.3",
147
147
  "typescript": "^6.0.3"
148
148
  },
149
149
  "peerDependencies": {
@@ -0,0 +1,127 @@
1
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
5
+ import { afterEach, describe, expect, it } from "vitest";
6
+
7
+ import { readAllEnvironmentSnapshots, readEnvironmentSnapshot } from "../env";
8
+
9
+ describe("readAllEnvironmentSnapshots", () => {
10
+ const tempRoots: string[] = [];
11
+
12
+ afterEach(async () => {
13
+ await Promise.all(
14
+ tempRoots.splice(0).map((rootPath) => rm(rootPath, { recursive: true, force: true })),
15
+ );
16
+ });
17
+
18
+ it("does not reuse the current environment dotenv values as other environments' base values", async () => {
19
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
20
+ tempRoots.push(rootPath);
21
+
22
+ await mkdir(rootPath, { recursive: true });
23
+ await writeFile(path.join(rootPath, ".env"), "SONAMU_DB_USER=base_user\n");
24
+ await writeFile(
25
+ path.join(rootPath, ".env.development"),
26
+ "SONAMU_DB_HOST=development.example.com\nSONAMU_DB_PASSWORD=development_password\n",
27
+ );
28
+ await writeFile(path.join(rootPath, ".env.staging"), "SONAMU_DB_HOST=staging.example.com\n");
29
+
30
+ const snapshots = readAllEnvironmentSnapshots(rootPath, {
31
+ NODE_ENV: "development",
32
+ SONAMU_DB_PASSWORD: "shell_password",
33
+ });
34
+
35
+ expect(snapshots.development.SONAMU_DB_HOST).toBe("development.example.com");
36
+ expect(snapshots.development.SONAMU_DB_PASSWORD).toBe("shell_password");
37
+ expect(snapshots.staging.SONAMU_DB_HOST).toBe("staging.example.com");
38
+ expect(snapshots.staging.SONAMU_DB_PASSWORD).toBe("shell_password");
39
+ expect(snapshots.staging.SONAMU_DB_USER).toBe("base_user");
40
+ });
41
+
42
+ it("throws when both common and environment dotenv files are missing", async () => {
43
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
44
+ tempRoots.push(rootPath);
45
+
46
+ await writeFile(path.join(rootPath, ".env.local"), "SONAMU_DB_HOST=local.example.com\n");
47
+
48
+ expect(() => readAllEnvironmentSnapshots(rootPath)).toThrow(
49
+ /Missing Sonamu dotenv file.*\.env.*\.env\.test/,
50
+ );
51
+ });
52
+
53
+ it("allows environment snapshots when only the common dotenv file exists", async () => {
54
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
55
+ tempRoots.push(rootPath);
56
+
57
+ await writeFile(path.join(rootPath, ".env"), "SONAMU_DB_HOST=common.example.com\n");
58
+
59
+ const snapshots = readAllEnvironmentSnapshots(rootPath);
60
+
61
+ expect(snapshots.development.SONAMU_DB_HOST).toBe("common.example.com");
62
+ expect(snapshots.production.SONAMU_DB_HOST).toBe("common.example.com");
63
+ });
64
+
65
+ it("allows an environment snapshot when only the environment dotenv file exists", async () => {
66
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
67
+ tempRoots.push(rootPath);
68
+
69
+ await writeFile(path.join(rootPath, ".env.development"), "SONAMU_DB_HOST=dev.example.com\n");
70
+
71
+ const snapshot = readEnvironmentSnapshot(rootPath, "development");
72
+
73
+ expect(snapshot.SONAMU_DB_HOST).toBe("dev.example.com");
74
+ });
75
+
76
+ it("keeps exported environment variables over dotenv file values", async () => {
77
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
78
+ tempRoots.push(rootPath);
79
+
80
+ await writeFile(
81
+ path.join(rootPath, ".env.development"),
82
+ "SONAMU_DB_HOST=file-host\nSONAMU_DB_PASSWORD=file-password\n",
83
+ );
84
+
85
+ const snapshot = readEnvironmentSnapshot(rootPath, "development", {
86
+ SONAMU_DB_HOST: "runtime-host",
87
+ SONAMU_DB_PASSWORD: "runtime-password",
88
+ });
89
+
90
+ expect(snapshot.SONAMU_DB_HOST).toBe("runtime-host");
91
+ expect(snapshot.SONAMU_DB_PASSWORD).toBe("runtime-password");
92
+ expect(snapshot.NODE_ENV).toBe("development");
93
+ });
94
+
95
+ it("lets environment dotenv values override common dotenv values", async () => {
96
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
97
+ tempRoots.push(rootPath);
98
+
99
+ await writeFile(path.join(rootPath, ".env"), "SONAMU_DB_HOST=common.example.com\n");
100
+ await writeFile(path.join(rootPath, ".env.production"), "SONAMU_DB_HOST=prod.example.com\n");
101
+
102
+ const snapshot = readEnvironmentSnapshot(rootPath, "production");
103
+
104
+ expect(snapshot.SONAMU_DB_HOST).toBe("prod.example.com");
105
+ expect(snapshot.NODE_ENV).toBe("production");
106
+ });
107
+
108
+ it("does not let preloaded common dotenv values override environment dotenv values", async () => {
109
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
110
+ tempRoots.push(rootPath);
111
+
112
+ await writeFile(
113
+ path.join(rootPath, ".env"),
114
+ "SONAMU_DB_HOST=common.example.com\nSONAMU_DB_USER=common_user\n",
115
+ );
116
+ await writeFile(path.join(rootPath, ".env.production"), "SONAMU_DB_HOST=prod.example.com\n");
117
+
118
+ const snapshot = readEnvironmentSnapshot(rootPath, "production", {
119
+ SONAMU_DB_HOST: "common.example.com",
120
+ SONAMU_DB_USER: "shell_user",
121
+ });
122
+
123
+ expect(snapshot.SONAMU_DB_HOST).toBe("prod.example.com");
124
+ expect(snapshot.SONAMU_DB_USER).toBe("shell_user");
125
+ expect(snapshot.NODE_ENV).toBe("production");
126
+ });
127
+ });
@@ -21,7 +21,9 @@ function resetRegisterState() {
21
21
  }
22
22
 
23
23
  async function createTempRoot(): Promise<string> {
24
- return mkdtemp(path.join(os.tmpdir(), "sonamu-config-test-"));
24
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-config-test-"));
25
+ await writeFile(path.join(rootPath, ".env"), "SONAMU_DB_HOST=localhost\n");
26
+ return rootPath;
25
27
  }
26
28
 
27
29
  async function writeSourceFixture(rootPath: string): Promise<void> {
@@ -136,6 +138,7 @@ describe("loadConfig", () => {
136
138
  const tempRoots: string[] = [];
137
139
  const originalHot = process.env.HOT;
138
140
  const originalVitest = process.env.VITEST;
141
+ const originalNodeEnv = process.env.NODE_ENV;
139
142
 
140
143
  beforeEach(() => {
141
144
  vi.resetModules();
@@ -162,6 +165,12 @@ describe("loadConfig", () => {
162
165
  process.env.VITEST = originalVitest;
163
166
  }
164
167
 
168
+ if (originalNodeEnv === undefined) {
169
+ delete process.env.NODE_ENV;
170
+ } else {
171
+ process.env.NODE_ENV = originalNodeEnv;
172
+ }
173
+
165
174
  await Promise.all(
166
175
  tempRoots.splice(0).map((rootPath) => rm(rootPath, { recursive: true, force: true })),
167
176
  );
package/src/api/config.ts CHANGED
@@ -17,6 +17,7 @@ import { type Knex } from "knex";
17
17
 
18
18
  import { type CacheConfig } from "../cache/types";
19
19
  import { type SonamuDBConfig } from "../database/db";
20
+ import { applyCurrentSnapshotToProcessEnv } from "../env";
20
21
  import { type SonamuLoggingOptions } from "../logger/configure";
21
22
  import { type StorageConfig } from "../storage/types";
22
23
  import { type WebSocketRuntimeOptions } from "../stream/ws";
@@ -94,19 +95,8 @@ export type SonamuConfig<TSinkId extends string = string, TFilterId extends stri
94
95
  database: {
95
96
  // 데이터베이스(pg는 pg 모듈, pgnative는 pg-native 모듈의 설치가 필요합니다.)
96
97
  database?: "pg" | "pgnative";
97
- // 기본 데이터베이스 이름
98
- name: string;
99
98
  // 모든 환경에 적용될 기본 Knex 옵션
100
- defaultOptions: DatabaseConfig;
101
- // 환경별 설정
102
- environments?: {
103
- development?: DatabaseConfig;
104
- development_slave?: DatabaseConfig;
105
- production?: DatabaseConfig;
106
- production_slave?: DatabaseConfig;
107
- fixture?: DatabaseConfig;
108
- test?: DatabaseConfig;
109
- };
99
+ defaultOptions?: DatabaseConfig;
110
100
  };
111
101
 
112
102
  logging?: false | SonamuLoggingOptions<TSinkId, TFilterId>;
@@ -319,6 +309,8 @@ export function defineConfig(config: Executable<SonamuConfig>): Promise<SonamuCo
319
309
  * @returns
320
310
  */
321
311
  export async function loadConfig(rootPath: string): Promise<SonamuConfig> {
312
+ applyCurrentSnapshotToProcessEnv(rootPath);
313
+
322
314
  const shouldLoadSourceConfig = process.env.HOT === "yes" || process.env.VITEST === "true";
323
315
  const configPath = shouldLoadSourceConfig
324
316
  ? `${rootPath}/src/sonamu.config.ts`
package/src/api/sonamu.ts CHANGED
@@ -15,6 +15,7 @@ import { type WebSocket } from "ws";
15
15
  import { type ZodObject } from "zod";
16
16
 
17
17
  import { BASE_FIELD_MAPPINGS } from "../auth/better-auth-entities";
18
+ import { createBetterAuthRequest } from "../auth/better-auth-request";
18
19
  import { applyCacheHeaders, CachePresets } from "../cache-control/cache-control";
19
20
  import { type CacheControlConfig, type CacheControlRequest } from "../cache-control/types";
20
21
  import { type CacheConfig, type CacheManager } from "../cache/types";
@@ -24,6 +25,7 @@ import { DB } from "../database/db";
24
25
  import { type SonamuDBConfig } from "../database/db";
25
26
  import { SD, setSDConfig } from "../dict/sd";
26
27
  import { type LocalizedString } from "../dict/types";
28
+ import { getSonamuEnvironment, readAllEnvironmentSnapshots } from "../env";
27
29
  import { NotFoundException } from "../exceptions/so-exceptions";
28
30
  import { BufferedFile } from "../storage/buffered-file";
29
31
  import { type StorageManager } from "../storage/storage-manager";
@@ -231,6 +233,7 @@ class SonamuClass {
231
233
  // API 루트 패스
232
234
  const { findApiRootPath } = await import("../utils/utils");
233
235
  this.apiRootPath = apiRootPath ?? findApiRootPath();
236
+ const baseEnvBeforeConfigLoad = { ...process.env };
234
237
 
235
238
  // 설정을 로딩하는 것부터 시작
236
239
  const configStart = performance.now();
@@ -240,6 +243,7 @@ class SonamuClass {
240
243
  setSDConfig(this.config.i18n);
241
244
  // sonamu.config.ts 기본값 설정
242
245
  this.config.database.database = this.config.database.database ?? "pg";
246
+ this.config.database.defaultOptions = this.config.database.defaultOptions ?? {};
243
247
  this.config.database.defaultOptions.client = this.config.database.database ?? "pg";
244
248
 
245
249
  // 로깅 설정
@@ -252,7 +256,15 @@ class SonamuClass {
252
256
 
253
257
  // DB 로드
254
258
  const { DB } = await import("../database/db");
255
- this.dbConfig = DB.generateDBConfig(this.config.database);
259
+ const { isLocal: isLocalEnvironment } = await import("../utils/controller");
260
+ const environmentSnapshots = isLocalEnvironment()
261
+ ? readAllEnvironmentSnapshots(this.apiRootPath, baseEnvBeforeConfigLoad)
262
+ : undefined;
263
+ this.dbConfig = DB.generateDBConfig(
264
+ this.config.database,
265
+ this.config.projectName,
266
+ environmentSnapshots,
267
+ );
256
268
  DB.setConfig(this.dbConfig);
257
269
 
258
270
  // Entity 로드
@@ -1104,12 +1116,6 @@ class SonamuClass {
1104
1116
  traceContext = this.websocketRuntime.telemetryController.createConnectionContext({
1105
1117
  headers: request.headers,
1106
1118
  });
1107
- runGuards({
1108
- guards: api.options.guards,
1109
- config,
1110
- request,
1111
- api,
1112
- });
1113
1119
 
1114
1120
  preRegisterPhase = "query";
1115
1121
  const reqBody = await this.parseWebSocketRequestParams(api, request);
@@ -1128,6 +1134,16 @@ class SonamuClass {
1128
1134
 
1129
1135
  const scopedWs = this.createScopedWebSocketConnection(rawWs, () => wsContext);
1130
1136
  wsContext = await this.createWebSocketContext(config, request, scopedWs);
1137
+
1138
+ await this.asyncLocalStorage.run({ context: wsContext }, async () => {
1139
+ runGuards({
1140
+ guards: api.options.guards,
1141
+ config,
1142
+ request,
1143
+ api,
1144
+ });
1145
+ });
1146
+
1131
1147
  this.websocketRuntime.activateConnection(rawWs.id);
1132
1148
  preRegisterPhase = "handler";
1133
1149
 
@@ -1790,29 +1806,9 @@ class SonamuClass {
1790
1806
  method: ["GET", "POST"],
1791
1807
  url: `${basePath}/*`,
1792
1808
  handler: async (request, reply) => {
1793
- const url = new URL(request.url, `http://${request.headers.host}`);
1794
- const headers = convertFastifyHeadersToStandard(request.headers);
1795
-
1796
- // IP 헤더 fallback: 프록시가 표준 IP 헤더를 주입하지 않는 환경에서도
1797
- // better-auth/infra의 getClientIpFromRequest()가 IP를 인식할 수 있도록
1798
- // Fastify가 resolve한 request.ip를 x-real-ip로 주입한다.
1799
- const IP_HEADERS = [
1800
- "cf-connecting-ip",
1801
- "x-forwarded-for",
1802
- "x-real-ip",
1803
- "x-vercel-forwarded-for",
1804
- ];
1805
- if (request.ip && !IP_HEADERS.some((h) => headers.has(h))) {
1806
- headers.set("x-real-ip", request.ip);
1807
- }
1808
-
1809
- const req = new Request(url.toString(), {
1810
- method: request.method,
1811
- headers,
1812
- ...(request.body ? { body: JSON.stringify(request.body) } : {}),
1813
- });
1814
-
1815
- const response = await this.auth.handler(req);
1809
+ const response = await this.auth.handler(
1810
+ createBetterAuthRequest(request, options.advanced?.ipAddress?.ipAddressHeaders),
1811
+ );
1816
1812
 
1817
1813
  reply.status(response.status);
1818
1814
  response.headers.forEach((value: string, key: string) => {
@@ -1825,8 +1821,7 @@ class SonamuClass {
1825
1821
 
1826
1822
  private async printStartupSummary() {
1827
1823
  const chalk = (await import("chalk")).default;
1828
- const env = process.env.NODE_ENV ?? "development";
1829
- const activePreset = env === "production" ? "production_master" : "development_master";
1824
+ const activePreset = getSonamuEnvironment();
1830
1825
 
1831
1826
  const dim = (msg: string) => console.log(chalk.dim(`✓ ${msg}`));
1832
1827
  const green = (msg: string) => console.log(chalk.green(`✓ ${msg}`));
@@ -1843,7 +1838,7 @@ class SonamuClass {
1843
1838
  | { host?: string; port?: number; database?: string }
1844
1839
  | undefined;
1845
1840
  const host = conn?.host ?? "localhost";
1846
- const addr = `@ ${host}:${conn?.port ?? 5432}/${conn?.database ?? this.config.database.name}`;
1841
+ const addr = `@ ${host}:${conn?.port ?? 5432}/${conn?.database ?? "(unknown)"}`;
1847
1842
  const padded = name.padEnd(maxLen);
1848
1843
  const remoteTag = isLocal() && !isLocalHost(host) ? chalk.yellow(` \u26a0 remote`) : "";
1849
1844
 
@@ -1,3 +1,5 @@
1
+ import { randomUUID } from "node:crypto";
2
+
1
3
  import {
2
4
  type AccountSnapshot,
3
5
  type AuditLogEvent,
@@ -24,6 +26,7 @@ const createEvent = (
24
26
  location?: BuilderLocation,
25
27
  ): AuditLogEvent => ({
26
28
  ...base,
29
+ eventId: randomUUID(),
27
30
  eventData: {
28
31
  ...data,
29
32
  triggeredBy: trigger.triggeredBy,
@@ -0,0 +1,38 @@
1
+ import { isIP } from "node:net";
2
+
3
+ const CLIENT_IP_HEADERS = [
4
+ "cf-connecting-ip",
5
+ "x-forwarded-for",
6
+ "x-real-ip",
7
+ "x-vercel-forwarded-for",
8
+ ] as const;
9
+
10
+ const HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
11
+
12
+ export function replaceClientIpHeaders(
13
+ headers: Headers,
14
+ clientIp: string,
15
+ configuredHeaders: readonly string[] = [],
16
+ ): void {
17
+ const candidates = new Set(
18
+ [...CLIENT_IP_HEADERS, ...configuredHeaders]
19
+ .map((header) => header.trim().toLowerCase())
20
+ .filter((header) => HEADER_NAME_PATTERN.test(header)),
21
+ );
22
+
23
+ for (const header of candidates) {
24
+ headers.delete(header);
25
+ }
26
+
27
+ for (const header of candidates) {
28
+ headers.set(header, clientIp);
29
+ }
30
+ }
31
+
32
+ export function getValidClientIp(value: unknown): string | null {
33
+ if (typeof value !== "string" || value.length > 45 || isIP(value) === 0) {
34
+ return null;
35
+ }
36
+
37
+ return value;
38
+ }
@@ -5,6 +5,7 @@ export type AuditLogEvent = {
5
5
  eventType: string;
6
6
  eventData: Record<string, unknown>;
7
7
  eventKey: string;
8
+ eventId?: string;
8
9
  eventDisplayName?: string;
9
10
  ipAddress?: string;
10
11
  city?: string;
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
 
3
3
  import { type Knex } from "knex";
4
4
 
5
+ import { getValidClientIp } from "./audit-log/client-ip";
5
6
  import { type AuditLogEvent } from "./audit-log/events";
6
7
 
7
8
  const AUDIT_EVENT_SOURCE = "better_auth";
@@ -137,6 +138,10 @@ function computeDedupeKey(parts: {
137
138
  return createHash("sha256").update(raw).digest("hex");
138
139
  }
139
140
 
141
+ function computeEventIdDedupeKey(source: string, eventId: string): string {
142
+ return createHash("sha256").update(`${source}|${eventId}`).digest("hex");
143
+ }
144
+
140
145
  /**
141
146
  * sonamuAuditLog 플러그인이 구성한 AuditLogEvent를 audit_events 테이블에 적재합니다.
142
147
  * ON CONFLICT (dedupe_key) DO NOTHING으로 중복을 silent 무시합니다.
@@ -172,26 +177,28 @@ export async function ingestAuditEvent(db: Knex, event: AuditLogEvent): Promise<
172
177
  const trigger_context = pickString(eventData, "triggerContext");
173
178
  const user_agent = pickString(eventData, "userAgent");
174
179
 
175
- const ip_address = event.ipAddress ?? null;
180
+ const ip_address = getValidClientIp(event.ipAddress);
176
181
  const city = event.city ?? null;
177
182
  const country = event.country ?? null;
178
183
  const country_code = event.countryCode ?? null;
179
184
 
180
185
  const category = classifyCategory(event.eventType);
181
- const dedupe_key = computeDedupeKey({
182
- source: AUDIT_EVENT_SOURCE,
183
- event_type: event.eventType,
184
- event_key: event.eventKey,
185
- actor_user_id,
186
- subject_user_id,
187
- organization_id,
188
- team_id,
189
- session_id,
190
- identifier,
191
- reason,
192
- action,
193
- occurred_at,
194
- });
186
+ const dedupe_key = event.eventId
187
+ ? computeEventIdDedupeKey(AUDIT_EVENT_SOURCE, event.eventId)
188
+ : computeDedupeKey({
189
+ source: AUDIT_EVENT_SOURCE,
190
+ event_type: event.eventType,
191
+ event_key: event.eventKey,
192
+ actor_user_id,
193
+ subject_user_id,
194
+ organization_id,
195
+ team_id,
196
+ session_id,
197
+ identifier,
198
+ reason,
199
+ action,
200
+ occurred_at,
201
+ });
195
202
 
196
203
  // ON CONFLICT DO NOTHING: dedupe_key UNIQUE 위반을 silent 무시.
197
204
  // try/catch 방식은 PG 커넥션을 aborted 상태로 만드므로 사용하지 않는다.
@@ -0,0 +1,20 @@
1
+ import { type FastifyRequest } from "fastify";
2
+
3
+ import { convertFastifyHeadersToStandard } from "../utils/utils";
4
+ import { replaceClientIpHeaders } from "./audit-log/client-ip";
5
+
6
+ export function createBetterAuthRequest(
7
+ request: FastifyRequest,
8
+ ipAddressHeaders?: readonly string[],
9
+ ): Request {
10
+ const url = new URL(request.url, `http://${request.headers.host}`);
11
+ const headers = convertFastifyHeadersToStandard(request.headers);
12
+
13
+ replaceClientIpHeaders(headers, request.ip, ipAddressHeaders);
14
+
15
+ return new Request(url.toString(), {
16
+ method: request.method,
17
+ headers,
18
+ ...(request.body ? { body: JSON.stringify(request.body) } : {}),
19
+ });
20
+ }