@simplysm/sd-claude 14.0.98 โ†’ 14.0.99

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 (77) hide show
  1. package/claude/references/sd-simplysm14/README.md +16 -16
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +81 -153
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +179 -205
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +71 -57
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +49 -109
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +58 -86
  7. package/claude/references/sd-simplysm14/apis/angular/kanban.md +32 -40
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +38 -52
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +86 -110
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +54 -86
  11. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +82 -74
  12. package/claude/references/sd-simplysm14/apis/angular/sheet.md +56 -80
  13. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +15 -15
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +21 -21
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +79 -53
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +9 -11
  17. package/claude/references/sd-simplysm14/apis/core-browser/README.md +15 -15
  18. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +20 -20
  19. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +18 -18
  20. package/claude/references/sd-simplysm14/apis/core-common/README.md +20 -49
  21. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +66 -55
  22. package/claude/references/sd-simplysm14/apis/core-common/collection-ext.md +83 -56
  23. package/claude/references/sd-simplysm14/apis/core-common/errors.md +32 -21
  24. package/claude/references/sd-simplysm14/apis/core-common/obj.md +57 -39
  25. package/claude/references/sd-simplysm14/apis/core-common/serialization.md +36 -30
  26. package/claude/references/sd-simplysm14/apis/core-common/value-types.md +69 -41
  27. package/claude/references/sd-simplysm14/apis/core-node/README.md +4 -4
  28. package/claude/references/sd-simplysm14/apis/core-node/consola.md +15 -13
  29. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +11 -7
  30. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +8 -8
  31. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +29 -20
  32. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +14 -6
  33. package/claude/references/sd-simplysm14/apis/core-node/worker.md +3 -3
  34. package/claude/references/sd-simplysm14/apis/excel/README.md +3 -3
  35. package/claude/references/sd-simplysm14/apis/excel/cell.md +32 -32
  36. package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +23 -24
  37. package/claude/references/sd-simplysm14/apis/excel/style.md +24 -30
  38. package/claude/references/sd-simplysm14/apis/excel/utils.md +20 -23
  39. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +60 -71
  40. package/claude/references/sd-simplysm14/apis/excel/wrapper.md +36 -36
  41. package/claude/references/sd-simplysm14/apis/lint/README.md +7 -9
  42. package/claude/references/sd-simplysm14/apis/lint/recommended.md +59 -37
  43. package/claude/references/sd-simplysm14/apis/lint/rules.md +81 -74
  44. package/claude/references/sd-simplysm14/apis/orm-common/README.md +6 -6
  45. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +112 -78
  46. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +131 -75
  47. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +126 -82
  48. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +170 -113
  49. package/claude/references/sd-simplysm14/apis/orm-common/types.md +102 -48
  50. package/claude/references/sd-simplysm14/apis/orm-node/README.md +12 -13
  51. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +3 -3
  52. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +5 -5
  53. package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +67 -65
  54. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +130 -123
  55. package/claude/references/sd-simplysm14/apis/service-client/README.md +63 -63
  56. package/claude/references/sd-simplysm14/apis/service-client/orm.md +22 -22
  57. package/claude/references/sd-simplysm14/apis/service-client/transport.md +30 -26
  58. package/claude/references/sd-simplysm14/apis/service-common/README.md +8 -8
  59. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +13 -6
  60. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +1 -1
  61. package/claude/references/sd-simplysm14/apis/service-server/README.md +43 -47
  62. package/claude/references/sd-simplysm14/apis/service-server/built-in-services.md +35 -0
  63. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +20 -19
  64. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +23 -25
  65. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +9 -9
  66. package/claude/references/sd-simplysm14/apis/storage/README.md +26 -26
  67. package/claude/references/sd-simplysm14/manuals/client-component.md +9 -1
  68. package/claude/references/sd-simplysm14/manuals/client-crud.md +1 -1
  69. package/claude/references/sd-simplysm14/manuals/client-orm.md +1 -0
  70. package/claude/references/sd-simplysm14/manuals/client-service.md +1 -0
  71. package/claude/references/sd-simplysm14/manuals/client-shared-data.md +1 -0
  72. package/claude/references/sd-simplysm14/manuals/client-ssg.md +1 -0
  73. package/claude/sd-system-prompt.md +11 -26
  74. package/claude/skills/sd-docs/references/subagent-prompt.md +4 -3
  75. package/claude/skills/sd-spec/SKILL.md +87 -18
  76. package/claude/skills/sd-spec/references/format.md +2 -2
  77. package/package.json +1 -1
@@ -1,15 +1,15 @@
1
1
  # @simplysm/service-server
2
2
 
3
- Fastify ๊ธฐ๋ฐ˜ RPC ์„œ๋น„์Šค ์„œ๋ฒ„. WebSocket/HTTP ๋‘ ์ „์†ก์œผ๋กœ ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋ฅผ ๋…ธ์ถœํ•˜๊ณ , JWT ์ธ์ฆยท์ •์  ํŒŒ์ผยทํŒŒ์ผ ์—…๋กœ๋“œยท์„œ๋ฒ„์ธก ์ด๋ฒคํŠธ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธยท๋‚ด์žฅ ORM/์ž๋™์—…๋ฐ์ดํŠธ ์„œ๋น„์Šค๋ฅผ ํ•œ ํ”„๋กœ์„ธ์Šค์—์„œ ์ œ๊ณตํ•œ๋‹ค.
3
+ Fastify ์œ„์—์„œ ๋™์ž‘ํ•˜๋Š” RPC ์„œ๋ฒ„. `defineService` ๋กœ ์ •์˜ํ•œ ์„œ๋น„์Šค๋ฅผ WebSocketยทHTTP(`/api/:service/:method`) ๋‘ ์ „์†ก์œผ๋กœ ๋…ธ์ถœํ•˜๊ณ , JWT ์ธ์ฆยท์ •์  ํŒŒ์ผ ์„œ๋น™ยทํŒŒ์ผ ์—…๋กœ๋“œยท์„œ๋ฒ„์ธก ์ด๋ฒคํŠธ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธยท๋‚ด์žฅ ORM/์ž๋™์—…๋ฐ์ดํŠธ ์„œ๋น„์Šค๋ฅผ ํ•œ ํ”„๋กœ์„ธ์Šค์—์„œ ์ œ๊ณตํ•œ๋‹ค.
4
4
 
5
5
  ## ์‚ฌ์šฉ ํŠธ๋ฆฌ๊ฑฐ ์ธ๋ฑ์Šค
6
6
 
7
- - **์„œ๋ฒ„ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ** (`createServiceServer`, `ServiceServer`, `ServiceServerOptions`, `getEvent`/`emitEvent`) โ€” ์„œ๋ฒ„ ์•ฑ ์ง„์ž…์ ์—์„œ ์˜ต์…˜์„ ์ฃผ๊ณ  ์„œ๋ฒ„๋ฅผ ๋„์šฐ๊ฑฐ๋‚˜, ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ๋•Œ. ์•„๋ž˜ ์ธ๋ผ์ธ ์„น์…˜.
8
- - **์„œ๋น„์Šค ์ž‘์„ฑ** (`defineService`, `auth`, `ServiceContext`, `ServiceMethods`, `ServiceDefinition`, `getServiceAuthPermissions`) โ€” RPC ๋กœ ๋…ธ์ถœํ•  ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•˜๊ณ  ์ปจํ…์ŠคํŠธยท์ธ์ฆยท๊ถŒํ•œ์„ ๋ถ™์ผ ๋•Œ. ์ž์„ธํžˆ: [service-authoring.md](./service-authoring.md)
9
- - **JWT ์ธ์ฆ ํ† ํฐ** (`signJwt`, `verifyJwt`, `decodeJwt`, `AuthTokenPayload`) โ€” ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ์—์„œ ํ† ํฐ์„ ์„œ๋ช…ยท๊ฒ€์ฆํ•  ๋•Œ. ์•„๋ž˜ ์ธ๋ผ์ธ ์„น์…˜.
10
- - **๋‚ด์žฅ ์„œ๋น„์Šค** (`OrmService`, `AutoUpdateService` ๋ฐ `*Methods` ํƒ€์ž…) โ€” DB ์›๊ฒฉ ์‹คํ–‰ยท์•ฑ ์ž๋™์—…๋ฐ์ดํŠธ๋ฅผ ์„œ๋ฒ„ ์˜ต์…˜ `services` ์— ๋ผ์›Œ๋„ฃ์„ ๋•Œ. ์•„๋ž˜ ์ธ๋ผ์ธ ์„น์…˜.
11
- - **์ „์†ก ๊ณ„์ธต ๋‚ด๋ถ€** (`executeServiceMethod`, `createServiceContext`, `ServiceSocket`, `WebSocketHandler`, `ServerProtocolWrapper`, HTTP/์ •์ /์—…๋กœ๋“œ ํ•ธ๋“ค๋Ÿฌ, `getConfig`) โ€” ์ปค์Šคํ…€ ์ „์†กยทํ…Œ์ŠคํŠธยท๋””๋ฒ„๊น…์—์„œ ๋‚ด๋ถ€ ๊ตฌ์„ฑ์š”์†Œ๋ฅผ ์ง์ ‘ ๋‹ค๋ฃฐ ๋•Œ. ์ž์„ธํžˆ: [transport-internals.md](./transport-internals.md)
12
- - **V1 ๋ ˆ๊ฑฐ์‹œ ์ง€์›** (`handleV1Connection`, `V1ConnectionOptions`, `V1RequestHandler` ๋“ฑ) โ€” ๊ตฌ๋ฒ„์ „(verโ‰ 2) ํด๋ผ์ด์–ธํŠธ์˜ WebSocket ์—ฐ๊ฒฐ์„ ๋ฐ›์•„ ์ž๋™์—…๋ฐ์ดํŠธ๋งŒ ์‘๋Œ€ํ•  ๋•Œ. ์ž์„ธํžˆ: [v1-legacy.md](./v1-legacy.md)
7
+ - **์„œ๋ฒ„ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ** (์ด ๋ฌธ์„œ ์ธ๋ผ์ธ): `createServiceServer` / `ServiceServer` / `ServiceServerOptions` / `ServerEventProxy` โ€” ์„œ๋ฒ„ ์•ฑ ์ง„์ž…์ ์—์„œ ์˜ต์…˜์„ ์ฃผ๊ณ  ๊ธฐ๋™ยท์ข…๋ฃŒํ•˜๊ฑฐ๋‚˜, ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ๋•Œ.
8
+ - **JWT ์ธ์ฆ ํ† ํฐ** (์ด ๋ฌธ์„œ ์ธ๋ผ์ธ): `AuthTokenPayload` / `signJwt` / `verifyJwt` / `decodeJwt` โ€” ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ์—์„œ ํ† ํฐ์„ ์„œ๋ช…ยท๊ฒ€์ฆํ•  ๋•Œ.
9
+ - **์„œ๋น„์Šค ์ž‘์„ฑ** โ€” `defineService` / `auth` / `getServiceAuthPermissions` / `ServiceContext` / `createServiceContext` / `ServiceDefinition` / `ServiceMethods`. ์ž์„ธํžˆ: [service-authoring.md](./service-authoring.md)
10
+ - **๋‚ด์žฅ ์„œ๋น„์Šค** โ€” `OrmService` / `OrmServiceMethods` / `AutoUpdateService` / `AutoUpdateServiceMethods`. ์ž์„ธํžˆ: [built-in-services.md](./built-in-services.md)
11
+ - **์ „์†ก ๊ณ„์ธต ๋‚ด๋ถ€** โ€” `executeServiceMethod` / `createServiceContext` / `ServiceSocket` / `createServiceSocket` / `WebSocketHandler` / `createWebSocketHandler` / `ServerProtocolWrapper` / `createServerProtocolWrapper` / `handleHttpRequest` / `handleUpload` / `handleStaticFile` / `getConfig`. ์ž์„ธํžˆ: [transport-internals.md](./transport-internals.md)
12
+ - **V1 ๋ ˆ๊ฑฐ์‹œ** โ€” `handleV1Connection` / `V1ConnectionOptions` / `V1Request` / `V1Response` / `V1AutoUpdateMethods` / `V1RequestHandler` / `V1RequestHandlerResult` / `V1RequestHandlerContext`. ์ž์„ธํžˆ: [v1-legacy.md](./v1-legacy.md)
13
13
 
14
14
  ## ์„œ๋ฒ„ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ
15
15
 
@@ -17,24 +17,40 @@ Fastify ๊ธฐ๋ฐ˜ RPC ์„œ๋น„์Šค ์„œ๋ฒ„. WebSocket/HTTP ๋‘ ์ „์†ก์œผ๋กœ ์„œ๋น„์Šค
17
17
 
18
18
  ### createServiceServer / ServiceServer
19
19
 
20
- `createServiceServer<TAuthInfo = unknown>(options: ServiceServerOptions): ServiceServer<TAuthInfo>` โ€” ์˜ต์…˜์œผ๋กœ ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑ(์•„์ง ๋ฆฌ์Šจ ์•ˆ ํ•จ). `new ServiceServer<TAuthInfo>(options)` ์ง์ ‘ ์ƒ์„ฑ๊ณผ ๋™์ผ. `TAuthInfo` ๋Š” ์ธ์ฆ ํ† ํฐ `data` ํŽ˜์ด๋กœ๋“œ ํƒ€์ž…์œผ๋กœ `server.signAuthToken`ยท`server.verifyAuthToken`ยท`ctx.authInfo` ์— ๊ทธ๋Œ€๋กœ ํ๋ฅธ๋‹ค.
20
+ ```ts
21
+ function createServiceServer<TAuthInfo = unknown>(options: ServiceServerOptions): ServiceServer<TAuthInfo>;
22
+ ```
23
+
24
+ ์˜ต์…˜์œผ๋กœ ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•œ๋‹ค(์ด ์‹œ์ ์—” ์•„์ง ๋ฆฌ์Šจํ•˜์ง€ ์•Š์Œ). `new ServiceServer<TAuthInfo>(options)` ์ง์ ‘ ์ƒ์„ฑ๊ณผ ๋™์ผํ•˜๋‹ค.
25
+
26
+ - `TAuthInfo` โ€” ์ธ์ฆ ํ† ํฐ `data` ํŽ˜์ด๋กœ๋“œ ํƒ€์ž…. `signAuthToken`ยท`verifyAuthToken`ยท`ServiceContext.authInfo` ๊ฐ€ ๋ชจ๋‘ ์ด ํƒ€์ž…์œผ๋กœ ๋ฌถ์ธ๋‹ค.
21
27
 
22
28
  `ServiceServerOptions` ํ•„๋“œ:
23
29
 
24
30
  - `rootPath: string` โ€” ์„œ๋ฒ„ ์ž‘์—… ๋ฃจํŠธ. ์ •์  ํŒŒ์ผยท์—…๋กœ๋“œยท์ž๋™์—…๋ฐ์ดํŠธ๋Š” `rootPath/www` ํ•˜์œ„๋ฅผ, ์„ค์ •์€ `rootPath/.config.json` ์„ ๊ธฐ์ค€์œผ๋กœ ํ•œ๋‹ค. ์ ˆ๋Œ€๊ฒฝ๋กœ ๊ถŒ์žฅ.
25
- - `port: number` โ€” ๋ฆฌ์Šจ ํฌํŠธ(`host: "0.0.0.0"` ๊ณ ์ •). `0` ์„ ์ฃผ๋ฉด OS ๊ฐ€ ์ž„์˜ ํฌํŠธ๋ฅผ ํ• ๋‹นํ•˜๋ฏ€๋กœ ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ ์“ฐ๊ณ , ์‹ค์ œ ํฌํŠธ๋Š” `server.fastify.server.address()` ๋กœ ํ™•์ธ.
26
- - `ssl?: { pfxBytes: Uint8Array; passphrase: string }` โ€” HTTPS ์ธ์ฆ์„œ. `pfxBytes` = PFX ๋ฐ”์ดํŠธ(๋‚ด๋ถ€์—์„œ `Buffer` ๋กœ ๋ณ€ํ™˜), `passphrase` = PFX ๋น„๋ฐ€๋ฒˆํ˜ธ. ์ง€์ • ์‹œ HTTPS ๋กœ ๊ธฐ๋™ํ•˜๊ณ  HSTSยทcrossOriginOpenerPolicy ๋ณด์•ˆ ํ—ค๋”๊ฐ€ ์ผœ์ง„๋‹ค. ๋ฏธ์ง€์ • ์‹œ HTTP(ํ‰๋ฌธ)๋กœ ๋œจ๊ณ  `upgrade-insecure-requests` CSP ๊ฐ€ ํ•ด์ œ๋œ๋‹ค. ์‚ฌ๋‚ด๋ง ํ‰๋ฌธ์ด๋ฉด ์ƒ๋žต, ์™ธ๋ถ€ ๋…ธ์ถœ์ด๋ฉด ์ง€์ •.
27
- - `auth?: { jwtSecret: string } | false` โ€” JWT ์ธ์ฆ ์„ค์ •. ๊ฐ์ฒด๋ฉด `jwtSecret` ์œผ๋กœ ํ† ํฐ ์„œ๋ช…ยท๊ฒ€์ฆ; `false` ๋ฉด ์ธ์ฆ์„ ์˜๋„์ ์œผ๋กœ ๋น„ํ™œ์„ฑ(๊ถŒํ•œ ์š”๊ตฌ ๋ฉ”์„œ๋“œ๋„ ์ธ์ฆ ๊ฒ€์‚ฌ ์Šคํ‚ต); `undefined`(๋ฏธ์ง€์ •)์ด๋ฉด์„œ ๊ถŒํ•œ ์š”๊ตฌ(`auth(...)` ๋ž˜ํ•‘) ์„œ๋น„์Šค๊ฐ€ ํ•˜๋‚˜๋ผ๋„ ์žˆ์œผ๋ฉด `listen()` ์ด ์—๋Ÿฌ๋กœ ์ค‘๋‹จ. ์ธ์ฆ ์“ฐ๋Š” ์•ฑ์ด๋ฉด ๊ฐ์ฒด, ์ธ์ฆ์„ ๋ช…์‹œ์ ์œผ๋กœ ๋„๋ ค๋ฉด `false`.
28
- - `services: ServiceDefinition[]` โ€” `defineService` ๋กœ ๋งŒ๋“  ์„œ๋น„์Šค ์ •์˜ ๋ฐฐ์—ด. RPC ๋กœ ๋…ธ์ถœํ•  ์„œ๋น„์Šค ์ „๋ถ€๋ฅผ ์—ฌ๊ธฐ ๋“ฑ๋ก.
29
- - `legacyV1Handlers?: V1RequestHandler[]` โ€” V1(verโ‰ 2) ๋ ˆ๊ฑฐ์‹œ ํด๋ผ์ด์–ธํŠธ์˜ ์ปค์Šคํ…€ ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ. ์ž์„ธํžˆ: [v1-legacy.md](./v1-legacy.md).
31
+ - `port: number` โ€” ๋ฆฌ์Šจ ํฌํŠธ(๋ฐ”์ธ๋”ฉ ํ˜ธ์ŠคํŠธ๋Š” `"0.0.0.0"` ๊ณ ์ •). `0` ์„ ์ฃผ๋ฉด OS ๊ฐ€ ์ž„์˜ ํฌํŠธ๋ฅผ ํ• ๋‹นํ•˜๋ฏ€๋กœ ํ…Œ์ŠคํŠธ์— ์“ฐ๊ณ , ์‹ค์ œ ํฌํŠธ๋Š” `server.fastify.server.address()` ๋กœ ํ™•์ธํ•œ๋‹ค.
32
+ - `ssl?: { pfxBytes: Uint8Array; passphrase?: string } | { pemKeyBytes: Uint8Array; certBytes: Uint8Array; caBytes?: Uint8Array; passphrase?: string } | { letsencrypt: { domains: string[]; email: string; staging?: boolean } }` โ€” HTTPS ์ธ์ฆ์„œ. ํ˜•์‹์€ ๋“ค์–ด์˜จ ํ•„๋“œ๋กœ ๊ตฌ๋ถ„ํ•œ๋‹ค.
33
+ - `pfxBytes` ๊ฐ€ ์žˆ์œผ๋ฉด PFX ๋ฐฉ์‹(์ธ์ฆ์„œ+ํ‚ค ๋ฒˆ๋“ค, `passphrase` ๋Š” PFX ๋น„๋ฐ€๋ฒˆํ˜ธ).
34
+ - `pemKeyBytes`+`certBytes` ๊ฐ€ ์žˆ์œผ๋ฉด PEM ๋ฐฉ์‹(`pemKeyBytes` ๊ฐœ์ธํ‚คยท`certBytes` ์ธ์ฆ์„œ, ์„ ํƒ์ ์œผ๋กœ `caBytes` ์ค‘๊ฐ„ CA ์ฒด์ธยท`passphrase` ์•”ํ˜ธํ™”๋œ ํ‚ค ๋น„๋ฐ€๋ฒˆํ˜ธ). ๋ฐ”์ดํŠธ๋Š” ๋‚ด๋ถ€์—์„œ `Buffer` ๋กœ ๋ณ€ํ™˜.
35
+ - `letsencrypt` ๊ฐ€ ์žˆ์œผ๋ฉด Let's Encrypt ์ž๋™ ๋ฐœ๊ธ‰/๊ฐฑ์‹ . `domains` ์ธ์ฆ์„œ๋ฅผ TLS-ALPN-01 ์ฑŒ๋ฆฐ์ง€๋กœ ๋ฐœ๊ธ‰ํ•ด `rootPath/.acme/`(๊ณ„์ •ํ‚คยท์ธ์ฆ์„œยทํ‚ค)์— ์ €์žฅํ•˜๊ณ , ๋งŒ๋ฃŒ 30์ผ ์ „ ์ž๋™ ๊ฐฑ์‹  ํ›„ ๋ฌด์ค‘๋‹จ ๊ต์ฒดํ•œ๋‹ค. `email` ์€ LE ๊ณ„์ • ์—ฐ๋ฝ์ฒ˜, `staging: true` ๋ฉด LE ์Šคํ…Œ์ด์ง•(๋ ˆ์ดํŠธ๋ฆฌ๋ฐ‹ ํšŒํ”ผ, ํ…Œ์ŠคํŠธ์šฉ)์„ ์“ด๋‹ค. ์บ์‹œ๋œ ์œ ํšจ ์ธ์ฆ์„œ๊ฐ€ ์žˆ์œผ๋ฉด ์ฆ‰์‹œ ์ ์šฉํ•˜๊ณ , ์—†์œผ๋ฉด ์ตœ์ดˆ ๋ฐœ๊ธ‰ ์™„๋ฃŒ๊นŒ์ง€ `listen()` ์ด ๋Œ€๊ธฐํ•˜๋ฉฐ ๋ฐœ๊ธ‰ ์‹คํŒจ ์‹œ throw ํ•œ๋‹ค(`SD_ACME_DIRECTORY_URL` ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ACME ๋””๋ ‰ํ† ๋ฆฌ URL ์žฌ์ •์˜ ๊ฐ€๋Šฅ โ€” ์‚ฌ์„ค CAยทํ…Œ์ŠคํŠธ์šฉ).
36
+
37
+ ์ง€์ • ์‹œ(์–ด๋А ๋ฐฉ์‹์ด๋“ ) HTTPS ๋กœ ๊ธฐ๋™ํ•˜๊ณ  HSTSยท`crossOriginOpenerPolicy` ๋ณด์•ˆ ํ—ค๋”๊ฐ€ ์ผœ์ง„๋‹ค. ๋ฏธ์ง€์ • ์‹œ HTTP(ํ‰๋ฌธ)๋กœ ๋œจ๊ณ  `upgrade-insecure-requests` CSP ๊ฐ€ ํ•ด์ œ๋œ๋‹ค. ์‚ฌ๋‚ด๋ง ํ‰๋ฌธ์ด๋ฉด ์ƒ๋žต, ์™ธ๋ถ€ ๋…ธ์ถœ์ด๋ฉด ์ง€์ •.
38
+
39
+ `letsencrypt` ์ „์ œ(์ฝ”๋“œ ๋ฐ–, ์šด์˜์ž ์ฑ…์ž„):
40
+ - **Node 20.18.0+ ๋˜๋Š” 22.9.0+** โ€” ํ•ธ๋“œ์…ฐ์ดํฌ ์ค‘ ์ธ์ฆ์„œ๋ฅผ ์ฃผ์ž…ํ•˜๋Š” `TLSSocket.setKeyCert` ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๋ฏธ๋งŒ์ด๋ฉด ๊ธฐ๋™ ์‹œ throw.
41
+ - TLS-ALPN-01 ์€ **์™€์ผ๋“œ์นด๋“œ ๋ถˆ๊ฐ€** โ€” `domains` ๋Š” ์ •ํ™•ํ•œ FQDN.
42
+ - ๊ฒ€์ฆ connection ์ด ํฌํŠธ 443 ์œผ๋กœ ์ธ์ž…๋˜์–ด ์ด ์„œ๋ฒ„์— ๋„๋‹ฌํ•ด์•ผ ํ•œ๋‹ค: ๊ณต๊ฐœ DNS A ๋ ˆ์ฝ”๋“œ โ†’ ์„œ๋ฒ„, ์•ž๋‹จ์— L4 ํ”„๋ก์‹œ๊ฐ€ ์žˆ์œผ๋ฉด SNI ๊ธฐ์ค€์œผ๋กœ ์ด ์„œ๋ฒ„์— ํŒจ์Šค์Šค๋ฃจ(์˜ˆ: nginx `stream` + `ssl_preread`). ๋„๋ฉ”์ธ CAA ๊ฐ€ `letsencrypt.org` ๋ฅผ ํ—ˆ์šฉํ•˜๊ณ , ์„œ๋ฒ„์—์„œ LE API ๋กœ ์•„์›ƒ๋ฐ”์šด๋“œ๊ฐ€ ๊ฐ€๋Šฅํ•ด์•ผ ํ•œ๋‹ค.
43
+ - `auth?: { jwtSecret: string } | false` โ€” JWT ์ธ์ฆ ์„ค์ •. ๊ฐ์ฒด๋ฉด `jwtSecret` ์œผ๋กœ ํ† ํฐ์„ ์„œ๋ช…ยท๊ฒ€์ฆํ•œ๋‹ค. `false` ๋ฉด ์ธ์ฆ์„ **์˜๋„์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”**(`auth(...)` ๋ž˜ํ•‘ ๋ฉ”์„œ๋“œ๋„ ์ธ์ฆ ๊ฒ€์‚ฌ ์Šคํ‚ต). `undefined`(๋ฏธ์ง€์ •)์ธ๋ฐ ๊ถŒํ•œ ์š”๊ตฌ(`auth(...)` ๋ž˜ํ•‘) ์„œ๋น„์Šค๊ฐ€ ํ•˜๋‚˜๋ผ๋„ ๋“ฑ๋ก๋ผ ์žˆ์œผ๋ฉด `listen()` ์ด throw โ€” ์„ค์ • ๋ˆ„๋ฝ๊ณผ ์˜๋„์  ๋น„ํ™œ์„ฑํ™”๋ฅผ ๊ตฌ๋ถ„ํ•œ๋‹ค.
44
+ - `services: ServiceDefinition[]` โ€” `defineService` ๋กœ ๋งŒ๋“  ์„œ๋น„์Šค ์ •์˜ ๋ฐฐ์—ด. RPC ๋กœ ๋…ธ์ถœํ•  ์„œ๋น„์Šค ์ „๋ถ€๋ฅผ ์—ฌ๊ธฐ ๋“ฑ๋กํ•œ๋‹ค. ๋ผ์šฐํŒ…์€ ์ •์˜์˜ `names` ๋งค์นญ์œผ๋กœ ์ด๋ค„์ง„๋‹ค.
45
+ - `legacyV1Handlers?: V1RequestHandler[]` โ€” V1(verโ‰ 2) ๋ ˆ๊ฑฐ์‹œ ํด๋ผ์ด์–ธํŠธ์šฉ ์ปค์Šคํ…€ ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ(์„ ํƒ). ์ž์„ธํžˆ: [v1-legacy.md](./v1-legacy.md).
30
46
 
31
47
  `ServiceServer<TAuthInfo>` ๋ฉค๋ฒ„(`EventEmitter<{ ready: void; close: void }>` ์ƒ์†):
32
48
 
33
49
  - `readonly options: ServiceServerOptions` โ€” ์ƒ์„ฑ ์‹œ ๋ฐ›์€ ์˜ต์…˜ ์›๋ณธ.
34
- - `readonly fastify: FastifyInstance` โ€” ๋‚ด๋ถ€ Fastify ์ธ์Šคํ„ด์Šค. ํฌํŠธ ์กฐํšŒยท์ถ”๊ฐ€ ๋ผ์šฐํŠธ ๋“ฑ๋ก ๋“ฑ์— ์ง์ ‘ ์ ‘๊ทผ.
35
- - `isOpen: boolean` โ€” ๋ฆฌ์Šจ ์„ฑ๊ณต ํ›„ `true`, `close()` ํ›„ `false`.
36
- - `listen(): Promise<void>` โ€” ํ”Œ๋Ÿฌ๊ทธ์ธ ๋“ฑ๋ก(websocket/helmet/multipart/static/cors) ํ›„ ๋ฆฌ์Šจ. ์™„๋ฃŒ ์‹œ `"ready"` ์ด๋ฒคํŠธ ๋ฐœ์ƒ, `SIGINT`/`SIGTERM` ์ •์ƒ ์ข…๋ฃŒ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก(10์ดˆ ๋‚ด ๋ฏธ์ข…๋ฃŒ ์‹œ ๊ฐ•์ œ `process.exit(1)`). `auth` ๋ฏธ์„ค์ •์ธ๋ฐ ๊ถŒํ•œ ์š”๊ตฌ ์„œ๋น„์Šค๊ฐ€ ์žˆ์œผ๋ฉด ์‹œ์ž‘ ์ „ throw.
37
- - `close(): Promise<void>` โ€” ๋ชจ๋“  WebSocket ์ข…๋ฃŒ ํ›„ Fastify ์ข…๋ฃŒ, `"close"` ์ด๋ฒคํŠธ ๋ฐœ์ƒ.
50
+ - `readonly fastify: FastifyInstance` โ€” ๋‚ด๋ถ€ Fastify ์ธ์Šคํ„ด์Šค. ์‹ค์ œ ํฌํŠธ ์กฐํšŒยท์ถ”๊ฐ€ ๋ผ์šฐํŠธ ๋“ฑ๋ก ๋“ฑ์— ์ง์ ‘ ์ ‘๊ทผํ•œ๋‹ค.
51
+ - `isOpen: boolean` โ€” `listen()` ์„ฑ๊ณต ํ›„ `true`, `close()` ํ›„ `false`. ์ •์ƒ ์ข…๋ฃŒ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ค‘๋ณต close ๋ฅผ ๋ง‰๋Š” ๋ฐ ์“ด๋‹ค.
52
+ - `listen(): Promise<void>` โ€” ํ”Œ๋Ÿฌ๊ทธ์ธ ๋“ฑ๋ก(websocketยทhelmetยทmultipartยทstaticยทcors) โ†’ ๋ผ์šฐํŠธ ๋ฐ”์ธ๋”ฉ โ†’ ๋ฆฌ์Šจ โ†’ ์ •์ƒ ์ข…๋ฃŒ ํ•ธ๋“ค๋Ÿฌ(`SIGINT`/`SIGTERM`, 10์ดˆ ๋‚ด ๋ฏธ์ข…๋ฃŒ ์‹œ ๊ฐ•์ œ `process.exit(1)`) ๋“ฑ๋ก ํ›„ `"ready"` ์ด๋ฒคํŠธ ๋ฐœ์ƒ. `auth` ๋ฏธ์„ค์ •์ธ๋ฐ ๊ถŒํ•œ ์š”๊ตฌ ์„œ๋น„์Šค๊ฐ€ ์žˆ์œผ๋ฉด ์‹œ์ž‘ ์ „ throw.
53
+ - `close(): Promise<void>` โ€” ๋ชจ๋“  WebSocket ์—ฐ๊ฒฐ ์ข…๋ฃŒ ํ›„ Fastify ์ข…๋ฃŒ, `"close"` ์ด๋ฒคํŠธ ๋ฐœ์ƒ.
38
54
  - `signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>` โ€” `auth.jwtSecret` ์œผ๋กœ ํ† ํฐ ์„œ๋ช…. ์‹œํฌ๋ฆฟ ๋ฏธ์„ค์ • ์‹œ throw.
39
55
  - `verifyAuthToken(token: string): Promise<AuthTokenPayload<TAuthInfo>>` โ€” ํ† ํฐ ๊ฒ€์ฆยท๋””์ฝ”๋“œ. ์‹œํฌ๋ฆฟ ๋ฏธ์„ค์ • ์‹œ throw.
40
56
  - `getEvent`/`emitEvent` โ€” ์•„๋ž˜ "์„œ๋ฒ„์ธก ์ด๋ฒคํŠธ ๋ฐœ์ƒ" ์ฐธ์กฐ.
@@ -50,14 +66,14 @@ const server = createServiceServer<AuthInfo>({
50
66
  await server.listen();
51
67
  ```
52
68
 
53
- ์ฃผ์˜: `auth` ๋ฅผ ๋ฏธ์ง€์ •ํ•œ ์ฑ„ ๊ถŒํ•œ ์š”๊ตฌ ์„œ๋น„์Šค๋ฅผ ๋“ฑ๋กํ•˜๋ฉด `listen()` ์ด ์ฆ‰์‹œ throw ํ•œ๋‹ค. ์ธ์ฆ์„ ๋„๋ ค๋ฉด `auth: false` ๋ฅผ ๋ช…์‹œํ•  ๊ฒƒ.
69
+ ์ฃผ์˜: `auth` ๋ฅผ ๋ฏธ์ง€์ •ํ•œ ์ฑ„ ๊ถŒํ•œ ์š”๊ตฌ ์„œ๋น„์Šค๋ฅผ ๋“ฑ๋กํ•˜๋ฉด `listen()` ์ด ์ฆ‰์‹œ throw ํ•œ๋‹ค. ์ธ์ฆ์„ ๋„๋ ค๋ฉด `auth: false` ๋ฅผ ๋ช…์‹œํ•œ๋‹ค.
54
70
 
55
71
  ### ์„œ๋ฒ„์ธก ์ด๋ฒคํŠธ ๋ฐœ์ƒ
56
72
 
57
73
  ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๊ตฌ๋… ์ค‘์ธ ํด๋ผ์ด์–ธํŠธ์— ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธํ•  ๋•Œ. ์ด๋ฒคํŠธ ์ •์˜ ๊ฐ์ฒด(`@simplysm/service-common` ์˜ `defineEvent`)๋Š” ํด๋ผ์ด์–ธํŠธยท์„œ๋ฒ„๊ฐ€ ๊ณต์œ ํ•œ๋‹ค.
58
74
 
59
- - `emitEvent<TEventDef>(eventDef: TEventDef, infoSelector: (info: TEventDef["$info"]) => boolean, data: TEventDef["$data"]): Promise<void>` โ€” `eventDef.eventName` ์„ ๊ตฌ๋…ํ•œ ์ „ ํด๋ผ์ด์–ธํŠธ ๋ฆฌ์Šค๋„ˆ ์ค‘ `infoSelector(info)` ๊ฐ€ `true` ์ธ ๋Œ€์ƒ์—๊ฒŒ๋งŒ `data` ์ „์†ก. ์ „์ฒด ์ „์†ก์€ `() => true`, ์–ด๋А ๊ตฌ๋…์—๋„ ์•ˆ ๊ฑธ๋ฆฌ๋ฉด ์ „์†ก ์ž์ฒด๊ฐ€ ์ƒ๋žต๋จ.
60
- - `getEvent<TEventDef>(eventDef): ServerEventProxy<TEventDef>` โ€” ๊ฐ™์€ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ˜๋ณต ๋ฐœ์ƒ์‹œํ‚ฌ ๋•Œ ์“ฐ๋Š” ํ”„๋ก์‹œ. `proxy.emit(infoSelector, data)` ๋Š” `emitEvent` ์™€ ๋™์ผ.
75
+ - `emitEvent<TEventDef>(eventDef: TEventDef, infoSelector: (info: TEventDef["$info"]) => boolean, data: TEventDef["$data"]): Promise<void>` โ€” `eventDef.eventName` ์„ ๊ตฌ๋…ํ•œ ์ „ ํด๋ผ์ด์–ธํŠธ ๋ฆฌ์Šค๋„ˆ ์ค‘ `infoSelector(info)` ๊ฐ€ `true` ์ธ ๋Œ€์ƒ์—๊ฒŒ๋งŒ `data` ์ „์†ก. ์ „์ฒด ์ „์†ก์€ `() => true`, ์–ด๋А ๊ตฌ๋…์—๋„ ์•ˆ ๊ฑธ๋ฆฌ๋ฉด ์ „์†ก ์ž์ฒด๊ฐ€ ์ƒ๋žต๋œ๋‹ค.
76
+ - `getEvent<TEventDef>(eventDef): ServerEventProxy<TEventDef>` โ€” ๊ฐ™์€ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ˜๋ณต ๋ฐœ์ƒ์‹œํ‚ฌ ๋•Œ ์“ฐ๋Š” ํ”„๋ก์‹œ. `proxy.emit(infoSelector, data)` ๋Š” `emitEvent` ์™€ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.
61
77
  - `ServerEventProxy<TEventDef>` โ€” `{ emit(infoSelector, data): Promise<void> }` ํ˜•ํƒœ. ๊ตฌ๋…(๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก)์€ ํด๋ผ์ด์–ธํŠธ ์ „์šฉ์ด๋ผ ์„œ๋ฒ„์—๋Š” ๋ฐœ์ƒ ๋ฉ”์„œ๋“œ๋งŒ ์žˆ๋‹ค.
62
78
 
63
79
  ```ts
@@ -75,14 +91,14 @@ export const OrderService = defineService("Order", (ctx) => ({
75
91
 
76
92
  ## JWT ์ธ์ฆ ํ† ํฐ
77
93
 
78
- ๋กœ๊ทธ์ธ ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ์—์„œ ์ž๊ฒฉ ํ™•์ธ ํ›„ ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๊ณ , ๋‹ค๋ฅธ ๋ฉ”์„œ๋“œ์—์„œ ํ† ํฐ์„ ๊ฒ€์ฆํ•  ๋•Œ. ๋ณดํ†ต์€ `server.signAuthToken`/`server.verifyAuthToken`(์‹œํฌ๋ฆฟ ์ž๋™ ์‚ฌ์šฉ)์„ ์“ฐ๊ณ , ์‹œํฌ๋ฆฟ์„ ์ง์ ‘ ๋‹ค๋ฃฐ ๋•Œ๋งŒ ์•„๋ž˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ.
94
+ ๋กœ๊ทธ์ธ ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ์—์„œ ์ž๊ฒฉ ํ™•์ธ ํ›„ ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๊ณ , ๋‹ค๋ฅธ ๋ฉ”์„œ๋“œ์—์„œ ํ† ํฐ์„ ๊ฒ€์ฆํ•  ๋•Œ. ๋ณดํ†ต์€ `server.signAuthToken`/`server.verifyAuthToken`(์‹œํฌ๋ฆฟ ์ž๋™ ์‚ฌ์šฉ)์„ ์“ฐ๊ณ , ์‹œํฌ๋ฆฟ์„ ์ง์ ‘ ๋‹ค๋ฃฐ ๋•Œ๋งŒ ์•„๋ž˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
79
95
 
80
- - `AuthTokenPayload<TAuthInfo>` โ€” JWT ํŽ˜์ด๋กœ๋“œ. `jose` ์˜ `JWTPayload`(`exp`/`iat` ๋“ฑ)๋ฅผ ํ™•์žฅํ•˜๋ฉฐ ๋‹ค์Œ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.
81
- - `roles: string[]` โ€” ๊ถŒํ•œ ์—ญํ•  ๋ชฉ๋ก. `auth(["admin"], ...)` ์˜ ๊ถŒํ•œ ๋งค์นญ ๋Œ€์ƒ.
82
- - `data: TAuthInfo` โ€” ์•ฑ ์ •์˜ ์‚ฌ์šฉ์ž ์ •๋ณด. `ctx.authInfo` ๋กœ ๋…ธ์ถœ.
83
- - `signJwt<TAuthInfo>(jwtSecret: string, payload: AuthTokenPayload<TAuthInfo>): Promise<string>` โ€” HS256 ์œผ๋กœ ์„œ๋ช…. `iat` ์ž๋™ ์„ค์ •, ๋งŒ๋ฃŒ 12์‹œ๊ฐ„ ๊ณ ์ •.
84
- - `verifyJwt<TAuthInfo>(jwtSecret: string, token: string): Promise<AuthTokenPayload<TAuthInfo>>` โ€” ์„œ๋ช…ยท๋งŒ๋ฃŒ ๊ฒ€์ฆ ํ›„ ํŽ˜์ด๋กœ๋“œ ๋ฐ˜ํ™˜. ๋งŒ๋ฃŒ ์‹œ "ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", ๊ทธ ์™ธ ๊ฒ€์ฆ ์‹คํŒจ ์‹œ "์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค." ๋กœ throw.
85
- - `decodeJwt<TAuthInfo>(token: string): AuthTokenPayload<TAuthInfo>` โ€” ์„œ๋ช… ๊ฒ€์ฆ ์—†์ด ํŽ˜์ด๋กœ๋“œ๋งŒ ๋””์ฝ”๋“œ(๋™๊ธฐ). ๊ฒ€์ฆ์ด ๋๋‚œ ํ† ํฐ์˜ ๋‚ด์šฉ๋งŒ ๋‹ค์‹œ ์ฝ์„ ๋•Œ ์‚ฌ์šฉํ•˜๊ณ , ๋งŒ๋ฃŒยท์œ„๋ณ€์กฐ ํŒ์ •์—๋Š” ์“ฐ์ง€ ๋ง ๊ฒƒ.
96
+ - `AuthTokenPayload<TAuthInfo>` โ€” JWT ํŽ˜์ด๋กœ๋“œ. `jose` ์˜ `JWTPayload`(`exp`ยท`iat` ๋“ฑ)๋ฅผ ํ™•์žฅํ•˜๋ฉฐ ๋‹ค์Œ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.
97
+ - `roles: string[]` โ€” ๊ถŒํ•œ ์—ญํ•  ๋ชฉ๋ก. `auth(["admin"], ...)` ์˜ ๊ถŒํ•œ ๋งค์นญ ๋Œ€์ƒ์œผ๋กœ, ์š”๊ตฌ ์—ญํ•  ์ค‘ ํ•˜๋‚˜๋ผ๋„ ์ด ๋ฐฐ์—ด์— ์žˆ์œผ๋ฉด ํ†ต๊ณผ.
98
+ - `data: TAuthInfo` โ€” ์•ฑ์ด ์ •์˜ํ•˜๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด. `ctx.authInfo` ๋กœ ๋…ธ์ถœ๋œ๋‹ค.
99
+ - `signJwt<TAuthInfo>(jwtSecret: string, payload: AuthTokenPayload<TAuthInfo>): Promise<string>` โ€” HS256 ์œผ๋กœ ์„œ๋ช…. `iat` ์ž๋™ ์„ค์ •, ๋งŒ๋ฃŒ๋Š” 12์‹œ๊ฐ„ ๊ณ ์ •.
100
+ - `verifyJwt<TAuthInfo>(jwtSecret: string, token: string): Promise<AuthTokenPayload<TAuthInfo>>` โ€” ์„œ๋ช…ยท๋งŒ๋ฃŒ ๊ฒ€์ฆ ํ›„ ํŽ˜์ด๋กœ๋“œ ๋ฐ˜ํ™˜. ๋งŒ๋ฃŒ(`ERR_JWT_EXPIRED`)๋ฉด `"ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."`, ๊ทธ ์™ธ ๊ฒ€์ฆ ์‹คํŒจ๋ฉด `"์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค."` ๋กœ throw.
101
+ - `decodeJwt<TAuthInfo>(token: string): AuthTokenPayload<TAuthInfo>` โ€” ์„œ๋ช… ๊ฒ€์ฆ ์—†์ด ํŽ˜์ด๋กœ๋“œ๋งŒ ๋””์ฝ”๋“œ(๋™๊ธฐ). ์ด๋ฏธ ๊ฒ€์ฆ๋œ ํ† ํฐ์˜ ๋‚ด์šฉ๋งŒ ๋‹ค์‹œ ์ฝ์„ ๋•Œ ์“ฐ๊ณ , ๋งŒ๋ฃŒยท์œ„๋ณ€์กฐ ํŒ์ •์—๋Š” ์“ฐ์ง€ ๋ง ๊ฒƒ.
86
102
 
87
103
  ```ts
88
104
  const AuthService = defineService("Auth", (ctx) => ({
@@ -92,23 +108,3 @@ const AuthService = defineService("Auth", (ctx) => ({
92
108
  },
93
109
  }));
94
110
  ```
95
-
96
- ## ๋‚ด์žฅ ์„œ๋น„์Šค
97
-
98
- ์„œ๋ฒ„ ์˜ต์…˜ `services` ๋ฐฐ์—ด์— ๊ทธ๋Œ€๋กœ ์ถ”๊ฐ€ํ•ด ์“ฐ๋Š” ๋ฏธ๋ฆฌ ์ •์˜๋œ ์„œ๋น„์Šค. ๋‘ ์ด๋ฆ„(๋ณ„์นญ)์œผ๋กœ ๋…ธ์ถœ๋˜๋ฉฐ, ํด๋ผ์ด์–ธํŠธ ํƒ€์ž… ๊ณต์œ ์šฉ `*Methods` ํƒ€์ž…๋„ ํ•จ๊ป˜ export ๋œ๋‹ค.
99
-
100
- - `OrmService` / `OrmServiceMethods` โ€” `["Orm", "SdOrmService"]` ๋‘ ์ด๋ฆ„์œผ๋กœ ๋…ธ์ถœ. ์ „์ฒด๊ฐ€ `auth(...)` ๋ž˜ํ•‘์ด๋ผ ๋กœ๊ทธ์ธ ํ•„์š”ํ•˜๊ณ , **WebSocket ์ „์†ก ์ „์šฉ**(์†Œ์ผ“ ๋‹จ์œ„๋กœ DB ์ปค๋„ฅ์…˜์„ ํ’€๋งํ•˜๋ฏ€๋กœ HTTP ํ˜ธ์ถœ ์‹œ throw). DB ์ ‘์† ์ •๋ณด๋Š” `ctx.getConfig("orm")[configName]` ์œผ๋กœ `rootPath/.config.json` ์˜ `orm` ์„น์…˜์—์„œ ์ฝ๋Š”๋‹ค. ์†Œ์ผ“์ด ๋‹ซํžˆ๋ฉด ๊ทธ ์†Œ์ผ“์˜ ๋ชจ๋“  ์ปค๋„ฅ์…˜์„ ์ž๋™ ์ •๋ฆฌ. ๋ฉ”์„œ๋“œ:
101
- - `getInfo(opt)` โ€” dialectยทdatabaseยทschema ์กฐํšŒ(`mssql-azure` ๋Š” `mssql` ๋กœ ์ •๊ทœํ™”).
102
- - `connect(opt)` โ€” ์ƒˆ DB ์—ฐ๊ฒฐ์„ ํ’€์— ์ถ”๊ฐ€ํ•˜๊ณ  ์ •์ˆ˜ `connId` ๋ฐ˜ํ™˜.
103
- - `close(connId)` โ€” ํ•ด๋‹น ์—ฐ๊ฒฐ ์ข…๋ฃŒ.
104
- - `beginTransaction(connId, isolationLevel?)` / `commitTransaction(connId)` / `rollbackTransaction(connId)` โ€” connId ๋Œ€์ƒ ํŠธ๋žœ์žญ์…˜ ์ œ์–ด.
105
- - `executeParametrized(connId, query, params?)` โ€” ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ SQL ์‹คํ–‰.
106
- - `executeDefs(connId, defs, options?)` โ€” `QueryDef[]` ๋ฅผ dialect ์— ๋งž์ถฐ ๋นŒ๋“œยท์‹คํ–‰ํ•˜๊ณ , `options[i]` ๊ฐ€ ์žˆ์œผ๋ฉด ๊ฒฐ๊ณผ๋ฅผ ํŒŒ์‹ฑยท๋ฐ˜ํ™˜(์ „๋ถ€ `null` ์ด๋ฉด ์ผ๊ด„ ์‹คํ–‰๋งŒ).
107
- - `bulkInsert(connId, tableName, columnDefs, records)` โ€” ๋Œ€๋Ÿ‰ ์‚ฝ์ž….
108
- - `AutoUpdateService` / `AutoUpdateServiceMethods` โ€” `["AutoUpdate", "SdAutoUpdateService"]` ๋‘ ์ด๋ฆ„์œผ๋กœ ๋…ธ์ถœ. ์ธ์ฆ ๋ถˆํ•„์š”. ๋ฉ”์„œ๋“œ `getLastVersion(platform: string)` ์€ `rootPath/www/<clientName>/<platform>/updates` ์—์„œ `android` ๋ฉด `.apk`, ๊ทธ ์™ธ๋ฉด `.exe` ํŒŒ์ผ ์ค‘ semver ์ตœ๋Œ€ ๋ฒ„์ „์„ ์ฐพ์•„ `{ version, downloadPath }` ๋ฐ˜ํ™˜(๋Œ€์ƒ ์—†์œผ๋ฉด `undefined`).
109
-
110
- ```ts
111
- services: [OrmService, AutoUpdateService, ...์•ฑ์„œ๋น„์Šค๋“ค]
112
- ```
113
-
114
- ์ฃผ์˜: ๋‘ ๋‚ด์žฅ ์„œ๋น„์Šค๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์งง์€ ์ด๋ฆ„(`getService("Orm")`/`getService("AutoUpdate")`) ๋˜๋Š” ๋ ˆ๊ฑฐ์‹œ ์ด๋ฆ„(`SdOrmService`/`SdAutoUpdateService`) ์–ด๋А ์ชฝ์œผ๋กœ๋„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.
@@ -0,0 +1,35 @@
1
+ # @simplysm/service-server โ€” ๋‚ด์žฅ ์„œ๋น„์Šค
2
+
3
+ ์„œ๋ฒ„ ์˜ต์…˜ `services` ๋ฐฐ์—ด์— ๊ทธ๋Œ€๋กœ ์ถ”๊ฐ€ํ•ด ์“ฐ๋Š” ๋ฏธ๋ฆฌ ์ •์˜๋œ `ServiceDefinition` ๋‘ ๊ฐœ. ๊ฐ๊ฐ ๋‘ ์ด๋ฆ„(๋ณ„์นญ)์œผ๋กœ ๋…ธ์ถœ๋˜๋ฉฐ, ํด๋ผ์ด์–ธํŠธ ํƒ€์ž… ๊ณต์œ ์šฉ `*Methods` ํƒ€์ž…๋„ ํ•จ๊ป˜ export ๋œ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋Š” ์งง์€ ์ด๋ฆ„ ๋˜๋Š” ๋ ˆ๊ฑฐ์‹œ ์ด๋ฆ„ ์–ด๋А ์ชฝ์œผ๋กœ๋„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.
4
+
5
+ ```ts
6
+ services: [OrmService, AutoUpdateService, ...์•ฑ์„œ๋น„์Šค๋“ค]
7
+ ```
8
+
9
+ ## OrmService / OrmServiceMethods
10
+
11
+ `["Orm", "SdOrmService"]` ๋‘ ์ด๋ฆ„์œผ๋กœ ๋…ธ์ถœ. DB ์ ‘์†์„ ์›๊ฒฉ ์‹คํ–‰ RPC ๋กœ ์ œ๊ณตํ•˜๋Š” ์„œ๋น„์Šค๋กœ, ํด๋ผ์ด์–ธํŠธ์˜ ORM ์ปค๋„ฅํ„ฐ๊ฐ€ ํ˜ธ์ถœํ•œ๋‹ค.
12
+
13
+ - ์ „์ฒด๊ฐ€ `auth(...)` ๋ž˜ํ•‘์ด๋ผ **๋กœ๊ทธ์ธ ํ•„์š”**.
14
+ - **WebSocket ์ „์†ก ์ „์šฉ** โ€” ์†Œ์ผ“ ๋‹จ์œ„๋กœ DB ์ปค๋„ฅ์…˜์„ ํ’€๋ง(`WeakMap<ServiceSocket, Map<connId, DbConn>>`)ํ•˜๋ฏ€๋กœ, `ctx.socket` ์ด ์—†๋Š” HTTP ํ˜ธ์ถœ ์‹œ `"WebSocket ์—ฐ๊ฒฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค..."` throw.
15
+ - DB ์ ‘์† ์ •๋ณด๋Š” `ctx.getConfig<...>("orm")[configName]` ์œผ๋กœ `rootPath/.config.json` ์˜ `orm` ์„น์…˜์—์„œ ์ฝ๊ณ , ํ˜ธ์ถœ `opt.config` ๋กœ ๋ฎ์–ด์“ด๋‹ค. ์„ค์ •์ด ์—†์œผ๋ฉด throw.
16
+ - ์†Œ์ผ“์ด ๋‹ซํžˆ๋ฉด ๊ทธ ์†Œ์ผ“์˜ ์—ด๋ฆฐ ๋ชจ๋“  ์ปค๋„ฅ์…˜์„ ์ž๋™ ์ •๋ฆฌํ•œ๋‹ค.
17
+
18
+ ๋ฉ”์„œ๋“œ(`OrmServiceMethods`):
19
+
20
+ - `getInfo(opt: DbConnOptions & { configName }): Promise<{ dialect; database?; schema? }>` โ€” ์ ‘์† ์„ค์ •์˜ dialectยทdatabaseยทschema ์กฐํšŒ. `dialect` ๊ฐ€ `"mssql-azure"` ๋ฉด `"mssql"` ๋กœ ์ •๊ทœํ™”ํ•ด ๋Œ๋ ค์ค€๋‹ค.
21
+ - `connect(opt: DbConnOptions & { configName }): Promise<number>` โ€” ์ƒˆ DB ์—ฐ๊ฒฐ์„ ํ’€์— ์ถ”๊ฐ€ํ•˜๊ณ  ์ •์ˆ˜ `connId` ๋ฐ˜ํ™˜. ๊ฐ™์€ ์†Œ์ผ“์˜ ์ฒซ ์—ฐ๊ฒฐ ์‹œ ์†Œ์ผ“ `close` ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๊ฑธ์–ด ์ข…๋ฃŒ ์‹œ ์ •๋ฆฌํ•˜๋„๋ก ๋“ฑ๋กํ•œ๋‹ค.
22
+ - `close(connId: number): Promise<void>` โ€” ํ•ด๋‹น ์—ฐ๊ฒฐ ์ข…๋ฃŒ. ์ข…๋ฃŒ ์ค‘ ์—๋Ÿฌ๋Š” ๋ฌด์‹œ(warn ๋กœ๊ทธ)๋œ๋‹ค.
23
+ - `beginTransaction(connId: number, isolationLevel?: IsolationLevel): Promise<void>` โ€” ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘. `isolationLevel` ๋ฏธ์ง€์ • ์‹œ ๋“œ๋ผ์ด๋ฒ„ ๊ธฐ๋ณธ๊ฐ’.
24
+ - `commitTransaction(connId: number): Promise<void>` / `rollbackTransaction(connId: number): Promise<void>` โ€” `connId` ๋Œ€์ƒ ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ยท๋กค๋ฐฑ.
25
+ - `executeParametrized(connId: number, query: string, params?: unknown[]): Promise<unknown[][]>` โ€” ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ SQL ์‹คํ–‰.
26
+ - `executeDefs(connId: number, defs: QueryDef[], options?: (ResultMeta | undefined)[]): Promise<unknown[][]>` โ€” `QueryDef[]` ๋ฅผ dialect ์— ๋งž์ถฐ ๋นŒ๋“œยท์‹คํ–‰. `options` ๊ฐ€ ์ „๋ถ€ `null`/`undefined` ๋ฉด ์ „์ฒด๋ฅผ ํ•œ ๋ฒˆ์— ์ผ๊ด„ ์‹คํ–‰๋งŒ ํ•˜๊ณ  ๋นˆ ๊ฒฐ๊ณผ๋ฅผ ๋Œ๋ ค์ฃผ๋ฉฐ, ๊ฐ `options[i]` ๊ฐ€ ์žˆ์œผ๋ฉด ํ•ด๋‹น ๊ฒฐ๊ณผ์…‹์„ ํŒŒ์‹ฑํ•ด ๋ฐ˜ํ™˜ํ•œ๋‹ค.
27
+ - `bulkInsert(connId: number, tableName: string, columnDefs: Record<string, ColumnMeta>, records: Record<string, unknown>[]): Promise<void>` โ€” ๋Œ€๋Ÿ‰ ์‚ฝ์ž….
28
+
29
+ ## AutoUpdateService / AutoUpdateServiceMethods
30
+
31
+ `["AutoUpdate", "SdAutoUpdateService"]` ๋‘ ์ด๋ฆ„์œผ๋กœ ๋…ธ์ถœ. ์ธ์ฆ ๋ถˆํ•„์š”. ๋ฐฐํฌ๋œ ์•ฑ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ตœ์‹  ์„ค์น˜๋ณธ์„ ์กฐํšŒํ•˜๋Š” ๋ฐ ์“ด๋‹ค.
32
+
33
+ - `getLastVersion(platform: string): Promise<{ version: string; downloadPath: string } | undefined>` โ€” `rootPath/www/<clientName>/<platform>/updates` ๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ํ›„๋ณด ํŒŒ์ผ์„ ๊ณจ๋ผ semver ์ตœ๋Œ€ ๋ฒ„์ „์„ ์ฐพ๋Š”๋‹ค. `platform === "android"` ๋ฉด `.apk`, ๊ทธ ์™ธ๋ฉด `.exe` ํ™•์žฅ์ž์— ํŒŒ์ผ๋ช…์ด `^[0-9.]*$` ์ธ ๊ฒƒ๋งŒ ํ›„๋ณด. ๋Œ€์ƒ ๋ฒ„์ „์ด ์žˆ์œผ๋ฉด `{ version, downloadPath }`(๋‹ค์šด๋กœ๋“œ ๊ฒฝ๋กœ๋Š” `/<clientName>/<platform>/updates/<ํŒŒ์ผ>` posix ๊ฒฝ๋กœ), ์—†์œผ๋ฉด `undefined`. `ctx.clientPath` ๊ฐ€ ์—†์œผ๋ฉด throw.
34
+
35
+ ์ฃผ์˜: `getLastVersion` ์€ verโ‰ 2 ๋ ˆ๊ฑฐ์‹œ ํด๋ผ์ด์–ธํŠธ์˜ `SdAutoUpdateService.getLastVersion` fallback ์œผ๋กœ๋„ ์ž๋™ ์—ฐ๊ฒฐ๋œ๋‹ค(๋ ˆ๊ฑฐ์‹œ ํ๋ฆ„์€ [v1-legacy.md](./v1-legacy.md) ์ฐธ์กฐ).
@@ -1,6 +1,6 @@
1
1
  # @simplysm/service-server โ€” ์„œ๋น„์Šค ์ž‘์„ฑ
2
2
 
3
- RPC ๋กœ ๋…ธ์ถœํ•  ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•˜๊ณ , ์ปจํ…์ŠคํŠธ๋กœ ์ธ์ฆ ์ •๋ณดยทํด๋ผ์ด์–ธํŠธ ์ •๋ณดยท์„ค์ •์— ์ ‘๊ทผํ•˜๋ฉฐ, ์ธ์ฆยท๊ถŒํ•œ์„ ๋ถ™์ผ ๋•Œ ๊ฐ™์ด ์ฝ๋Š” ๋ฌถ์Œ. ์„œ๋ฒ„ ์˜ต์…˜ `services` ์— ๋“ฑ๋กํ•  `ServiceDefinition` ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ.
3
+ RPC ๋กœ ๋…ธ์ถœํ•  ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•˜๊ณ , ์š”์ฒญ ์ปจํ…์ŠคํŠธ๋กœ ์ธ์ฆ ์ •๋ณดยทํด๋ผ์ด์–ธํŠธ ์ •๋ณดยท์„ค์ •์— ์ ‘๊ทผํ•˜๋ฉฐ, ์ธ์ฆยท๊ถŒํ•œ์„ ๋ถ™์ผ ๋•Œ ์ฝ๋Š” ๋ฌถ์Œ. ์„œ๋ฒ„ ์˜ต์…˜ `services` ์— ๋“ฑ๋กํ•  `ServiceDefinition` ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ๋‹ค.
4
4
 
5
5
  ## defineService
6
6
 
@@ -8,12 +8,13 @@ RPC ๋กœ ๋…ธ์ถœํ•  ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•˜๊ณ , ์ปจํ…์ŠคํŠธ๋กœ ์ธ์ฆ
8
8
  function defineService<TMethods extends Record<string, (...args: any[]) => any>>(
9
9
  name: string | string[],
10
10
  factory: (ctx: ServiceContext) => TMethods,
11
- ): ServiceDefinition<TMethods>
11
+ ): ServiceDefinition<TMethods>;
12
12
  ```
13
13
 
14
- - `name: string | string[]` โ€” ์„œ๋น„์Šค ์ด๋ฆ„. ๋ฐฐ์—ด์ด๋ฉด ์—ฌ๋Ÿฌ ์ด๋ฆ„(๋ณ„์นญ)์œผ๋กœ ๋™์‹œ ๋…ธ์ถœํ•˜๋ฉฐ ์ฒซ ์š”์†Œ๊ฐ€ ๋Œ€ํ‘œ ์ด๋ฆ„(`definition.name`). ๋นˆ ๋ฐฐ์—ด์ด๋ฉด throw. ํด๋ผ์ด์–ธํŠธ๋Š” ์ด ์ด๋ฆ„์œผ๋กœ `getService("<name>")` ํ˜ธ์ถœ. ์‹ /๊ตฌ ์ด๋ฆ„์„ ๊ฐ™์ด ๋ฐ›์œผ๋ ค๋ฉด `["New", "Old"]`.
15
- - `factory: (ctx) => TMethods` โ€” **์š”์ฒญ๋งˆ๋‹ค ํ˜ธ์ถœ**๋˜์–ด ๋ฉ”์„œ๋“œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํŒฉํ† ๋ฆฌ. `ctx` ๋กœ ๊ทธ ์š”์ฒญ์˜ ์ธ์ฆยทํด๋ผ์ด์–ธํŠธ ์ •๋ณด๊ฐ€ ๋“ค์–ด์˜ค๋ฏ€๋กœ ๋ฉ”์„œ๋“œ ์•ˆ์—์„œ `ctx.*` ๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์ฐธ์กฐ. ์š”์ฒญ ๊ฐ„ ๊ณต์œ  ์ƒํƒœ(์ปค๋„ฅ์…˜ ํ’€ ๋“ฑ)๋Š” ํŒฉํ† ๋ฆฌ ๋ฐ”๊นฅ ๋ชจ๋“ˆ ์Šค์ฝ”ํ”„์— ๋‘˜ ๊ฒƒ.
16
- - ๋ฐ˜ํ™˜ `TMethods` ์˜ ๊ฐ ๊ฐ’์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ˜ธ์ถœํ•  ๋ฉ”์„œ๋“œ. ๋™๊ธฐยท๋น„๋™๊ธฐ ๋ชจ๋‘ ๊ฐ€๋Šฅํ•˜๋ฉฐ ๋ฐ˜ํ™˜๊ฐ’์ด ๊ทธ๋Œ€๋กœ ์‘๋‹ต์œผ๋กœ ์ง๋ ฌํ™”๋œ๋‹ค.
14
+ - `name: string | string[]` โ€” ์„œ๋น„์Šค ์ด๋ฆ„. ๋ฐฐ์—ด์ด๋ฉด ์—ฌ๋Ÿฌ ์ด๋ฆ„(๋ณ„์นญ)์œผ๋กœ ๋™์‹œ ๋…ธ์ถœํ•˜๋ฉฐ ์ฒซ ์š”์†Œ๊ฐ€ ๋Œ€ํ‘œ ์ด๋ฆ„(`definition.name`)์ด ๋œ๋‹ค. ๋นˆ ๋ฐฐ์—ด์ด๋ฉด throw. ํด๋ผ์ด์–ธํŠธ๋Š” ์ด ์ด๋ฆ„์œผ๋กœ `getService("<name>")` ํ˜ธ์ถœํ•œ๋‹ค. ์‹ ยท๊ตฌ ์ด๋ฆ„์„ ๊ฐ™์ด ๋ฐ›์œผ๋ ค๋ฉด `["New", "Old"]`.
15
+ - `factory: (ctx) => TMethods` โ€” **์š”์ฒญ๋งˆ๋‹ค ํ˜ธ์ถœ**๋˜์–ด ๋ฉ”์„œ๋“œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํŒฉํ† ๋ฆฌ. `ctx` ๋กœ ๊ทธ ์š”์ฒญ์˜ ์ธ์ฆยทํด๋ผ์ด์–ธํŠธ ์ •๋ณด๊ฐ€ ๋“ค์–ด์˜ค๋ฏ€๋กœ ๋ฉ”์„œ๋“œ ์•ˆ์—์„œ `ctx.*` ๋ฅผ ์ฐธ์กฐํ•œ๋‹ค. ์š”์ฒญ ๊ฐ„ ๊ณต์œ  ์ƒํƒœ(์ปค๋„ฅ์…˜ ํ’€ ๋“ฑ)๋Š” ํŒฉํ† ๋ฆฌ ๋ฐ”๊นฅ ๋ชจ๋“ˆ ์Šค์ฝ”ํ”„์— ๋‘”๋‹ค.
16
+ - ๋ฐ˜ํ™˜ `TMethods` ์˜ ๊ฐ ๊ฐ’์ด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ˜ธ์ถœํ•  ๋ฉ”์„œ๋“œ. ๋™๊ธฐยท๋น„๋™๊ธฐ ๋ชจ๋‘ ๊ฐ€๋Šฅํ•˜๋ฉฐ ๋ฐ˜ํ™˜๊ฐ’์ด ๊ทธ๋Œ€๋กœ ์‘๋‹ต์œผ๋กœ ์ง๋ ฌํ™”๋œ๋‹ค.
17
+ - ๋ฐ˜ํ™˜ `ServiceDefinition` ์˜ `authPermissions` ๋Š” `factory` ๊ฐ€ `auth(...)` ๋ž˜ํ•‘์ด๋ฉด ๊ทธ ๊ถŒํ•œ ๋ฐฐ์—ด, ์•„๋‹ˆ๋ฉด `undefined` ๋กœ ์ž๋™ ์ฑ„์›Œ์ง„๋‹ค.
17
18
 
18
19
  ```ts
19
20
  export const UserService = defineService("User", (ctx) => ({
@@ -32,11 +33,11 @@ function auth<TFn extends (...args: any[]) => any>(fn: TFn): TFn;
32
33
  function auth<TFn extends (...args: any[]) => any>(permissions: string[], fn: TFn): TFn;
33
34
  ```
34
35
 
35
- - `auth(fn)` โ€” ๋กœ๊ทธ์ธ๋งŒ ์š”๊ตฌ(๊ถŒํ•œ ์—ญํ•  ๋ฌด๊ด€). ํ† ํฐ์ด ์—†์œผ๋ฉด "๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค." throw.
36
- - `auth(permissions, fn)` โ€” `permissions: string[]` ์˜ ์—ญํ•  ์ค‘ ํ•˜๋‚˜๋ผ๋„ ํ† ํฐ `roles` ์— ์žˆ์–ด์•ผ ํ†ต๊ณผ. ์—†์œผ๋ฉด "๊ถŒํ•œ์ด ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค." throw. ๋นˆ ๋ฐฐ์—ด์€ `auth(fn)` ๊ณผ ๋™์ผ(๋กœ๊ทธ์ธ๋งŒ).
37
- - **์„œ๋น„์Šค ์ˆ˜์ค€**: `defineService("User", auth((ctx) => ({ ... })))` โ€” ๊ทธ ์„œ๋น„์Šค์˜ ๋ชจ๋“  ๋ฉ”์„œ๋“œ์— ์ ์šฉ. `defineService` ๊ฐ€ `authPermissions` ๋กœ ์ถ”์ถœ.
38
- - **๋ฉ”์„œ๋“œ ์ˆ˜์ค€**: ๋ฐ˜ํ™˜ ๊ฐ์ฒด ์•ˆ ๊ฐœ๋ณ„ ๋ฉ”์„œ๋“œ๋ฅผ `auth(...)` ๋กœ ๊ฐ์Œˆ. ๋ฉ”์„œ๋“œ ์ˆ˜์ค€ ๊ถŒํ•œ์ด ์„œ๋น„์Šค ์ˆ˜์ค€๋ณด๋‹ค ์šฐ์„ .
39
- - ์ ์šฉ ์šฐ์„ ์ˆœ์œ„: ๋ฉ”์„œ๋“œ ๋ž˜ํ•‘ ๊ถŒํ•œ โ†’ ์—†์œผ๋ฉด ์„œ๋น„์Šค ๊ถŒํ•œ. ์„œ๋ฒ„ ์˜ต์…˜ `auth` ๊ฐ€ `undefined` ๋ฉด ๊ถŒํ•œ ์š”๊ตฌ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์‹œ ์„ค์ • ์˜ค๋ฅ˜ throw, `false` ๋ฉด ์ธ์ฆ ๊ฒ€์‚ฌ ์ž์ฒด๋ฅผ ์Šคํ‚ต.
36
+ - `auth(fn)` โ€” ๋กœ๊ทธ์ธ๋งŒ ์š”๊ตฌ(๊ถŒํ•œ ์—ญํ•  ๋ฌด๊ด€). ํ† ํฐ์ด ์—†์œผ๋ฉด `"๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."` throw.
37
+ - `auth(permissions, fn)` โ€” `permissions: string[]` ์˜ ์—ญํ•  ์ค‘ ํ•˜๋‚˜๋ผ๋„ ํ† ํฐ `roles` ์— ์žˆ์–ด์•ผ ํ†ต๊ณผ. ์—†์œผ๋ฉด `"๊ถŒํ•œ์ด ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค."` throw. ๋นˆ ๋ฐฐ์—ด์€ `auth(fn)` ๊ณผ ๋™์ผ(๋กœ๊ทธ์ธ๋งŒ).
38
+ - **์„œ๋น„์Šค ์ˆ˜์ค€ ์ ์šฉ**: `defineService("User", auth((ctx) => ({ ... })))` โ€” ๊ทธ ์„œ๋น„์Šค์˜ ๋ชจ๋“  ๋ฉ”์„œ๋“œ์— ์ ์šฉ. `defineService` ๊ฐ€ ๊ถŒํ•œ์„ `authPermissions` ๋กœ ์ถ”์ถœํ•œ๋‹ค.
39
+ - **๋ฉ”์„œ๋“œ ์ˆ˜์ค€ ์ ์šฉ**: ๋ฐ˜ํ™˜ ๊ฐ์ฒด ์•ˆ ๊ฐœ๋ณ„ ๋ฉ”์„œ๋“œ๋ฅผ `auth(...)` ๋กœ ๊ฐ์‹ผ๋‹ค. ๋ฉ”์„œ๋“œ ์ˆ˜์ค€ ๊ถŒํ•œ์ด ์„œ๋น„์Šค ์ˆ˜์ค€๋ณด๋‹ค ์šฐ์„ ํ•œ๋‹ค.
40
+ - ์ ์šฉ ์šฐ์„ ์ˆœ์œ„: ๋ฉ”์„œ๋“œ ๋ž˜ํ•‘ ๊ถŒํ•œ โ†’ ์—†์œผ๋ฉด ์„œ๋น„์Šค ๊ถŒํ•œ. ์„œ๋ฒ„ ์˜ต์…˜ `auth` ๊ฐ€ `undefined` ๋ฉด ๊ถŒํ•œ ์š”๊ตฌ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์‹œ ์„ค์ • ์˜ค๋ฅ˜ throw, `false` ๋ฉด ์ธ์ฆ ๊ฒ€์‚ฌ ์ž์ฒด๋ฅผ ์Šคํ‚ตํ•œ๋‹ค.
40
41
 
41
42
  ```ts
42
43
  export const AdminService = defineService("Admin", auth((ctx) => ({
@@ -45,18 +46,18 @@ export const AdminService = defineService("Admin", auth((ctx) => ({
45
46
  })));
46
47
  ```
47
48
 
48
- ## ServiceContext
49
+ ## ServiceContext / createServiceContext
49
50
 
50
- `factory` ๊ฐ€ ์š”์ฒญ๋งˆ๋‹ค ๋ฐ›๋Š” ์ปจํ…์ŠคํŠธ. ๋ฉ”์„œ๋“œ ์•ˆ์—์„œ ์ธ์ฆยทํด๋ผ์ด์–ธํŠธยท์„ค์ •ยท์„œ๋ฒ„์— ์ ‘๊ทผํ•˜๋Š” ํ†ต๋กœ.
51
+ `factory` ๊ฐ€ ์š”์ฒญ๋งˆ๋‹ค ๋ฐ›๋Š” ์ปจํ…์ŠคํŠธ. ๋ฉ”์„œ๋“œ ์•ˆ์—์„œ ์ธ์ฆยทํด๋ผ์ด์–ธํŠธยท์„ค์ •ยท์„œ๋ฒ„์— ์ ‘๊ทผํ•˜๋Š” ํ†ต๋กœ๋‹ค. `createServiceContext(server, socket?, http?, legacy?)` ๊ฐ€ ์ƒ์„ฑํ•˜๋‚˜, ์ผ๋ฐ˜ ์ž‘์„ฑ์—์„œ๋Š” `factory` ์˜ ์ธ์ž๋กœ ์ž๋™ ์ฃผ์ž…๋˜๋ฏ€๋กœ ์ง์ ‘ ๋งŒ๋“ค ์ผ์€ ํ…Œ์ŠคํŠธ๋ฟ์ด๋‹ค.
51
52
 
52
53
  - `server: ServiceServer<TAuthInfo>` โ€” ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค. `ctx.server.emitEvent(...)` ๋กœ ์ด๋ฒคํŠธ ๋ฐœ์ƒ, `ctx.server.signAuthToken(...)` ๋กœ ํ† ํฐ ๋ฐœ๊ธ‰.
53
- - `socket?: ServiceSocket` โ€” WebSocket ์š”์ฒญ์ผ ๋•Œ๋งŒ ์กด์žฌํ•˜๋Š” ์†Œ์ผ“. HTTP ์š”์ฒญ์ด๋ฉด `undefined`(์†Œ์ผ“ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์€ ์กด์žฌ ๊ฒ€์‚ฌ ํ•„์ˆ˜).
54
+ - `socket?: ServiceSocket` โ€” WebSocket ์š”์ฒญ์ผ ๋•Œ๋งŒ ์กด์žฌํ•˜๋Š” ์†Œ์ผ“. HTTP ์š”์ฒญ์ด๋ฉด `undefined`(์†Œ์ผ“์ด ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์€ ์กด์žฌ ๊ฒ€์‚ฌ ํ•„์ˆ˜).
54
55
  - `http?: { clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> }` โ€” HTTP ์š”์ฒญ์ผ ๋•Œ๋งŒ ์กด์žฌ.
55
56
  - `legacy?: { clientName?: string }` โ€” V1 ๋ ˆ๊ฑฐ์‹œ ์—ฐ๊ฒฐ ์ปจํ…์ŠคํŠธ(์ž๋™์—…๋ฐ์ดํŠธ ์ „์šฉ).
56
- - `get authInfo(): TAuthInfo | undefined` โ€” ๊ฒ€์ฆ๋œ ํ† ํฐ์˜ `data` ํŽ˜์ด๋กœ๋“œ(์†Œ์ผ“โ†’HTTP ์ˆœ). ๋น„๋กœ๊ทธ์ธ ์š”์ฒญ์ด๋ฉด `undefined`(๊ฒฐ์ธก์„ ๊ทธ๋Œ€๋กœ ๋…ธ์ถœํ•˜๋ฏ€๋กœ ๋ฐ›๋Š” ์ชฝ๋„ ์˜ต์…”๋„๋กœ ๋‹ค๋ฃฐ ๊ฒƒ).
57
- - `get clientName(): string | undefined` โ€” ์š”์ฒญ ํด๋ผ์ด์–ธํŠธ ์ด๋ฆ„(์†Œ์ผ“โ†’HTTPโ†’๋ ˆ๊ฑฐ์‹œ ์ˆœ ์šฐ์„ ). ๋นˆ ๋ฌธ์ž์—ดยท`..`ยท์Šฌ๋ž˜์‹œ(`/`,`\`) ํฌํ•จ ๋“ฑ ๊ฒฝ๋กœ ํƒˆ์ถœ ์œ„ํ—˜ ๊ฐ’์ด๋ฉด throw.
58
- - `get clientPath(): string | undefined` โ€” `rootPath/www/<clientName>` ์ ˆ๋Œ€๊ฒฝ๋กœ. clientName ์—†์œผ๋ฉด `undefined`.
59
- - `getConfig<T>(section: string): Promise<T>` โ€” `rootPath/.config.json` ๋ฃจํŠธ ์„ค์ •์— ํด๋ผ์ด์–ธํŠธ๋ณ„ `www/<clientName>/.config.json` ์„ ๋จธ์ง€ํ•œ ๋’ค `section` ํ‚ค ๊ฐ’์„ ๋ฐ˜ํ™˜. ์„น์…˜์ด ์—†์œผ๋ฉด throw. ์„ค์ • ํŒŒ์ผ์€ ๋ณ€๊ฒฝ ์‹œ ์ž๋™ ๋ฆฌ๋กœ๋“œ(ํŒŒ์ผ ์›Œ์ฒ˜ + ์บ์‹œ).
57
+ - `get authInfo(): TAuthInfo | undefined` โ€” ๊ฒ€์ฆ๋œ ํ† ํฐ์˜ `data` ํŽ˜์ด๋กœ๋“œ(์†Œ์ผ“โ†’HTTP ์ˆœ์œผ๋กœ ์กฐํšŒ). ๋น„๋กœ๊ทธ์ธ ์š”์ฒญ์ด๋ฉด `undefined` โ€” ๊ฒฐ์ธก์„ ๊ทธ๋Œ€๋กœ ๋…ธ์ถœํ•˜๋ฏ€๋กœ ๋ฐ›๋Š” ์ชฝ๋„ ์˜ต์…”๋„๋กœ ๋‹ค๋ฃฌ๋‹ค.
58
+ - `get clientName(): string | undefined` โ€” ์š”์ฒญ ํด๋ผ์ด์–ธํŠธ ์ด๋ฆ„(์†Œ์ผ“โ†’HTTPโ†’๋ ˆ๊ฑฐ์‹œ ์ˆœ ์šฐ์„ ). ๋นˆ ๋ฌธ์ž์—ดยท`..`ยท์Šฌ๋ž˜์‹œ(`/`ยท`\`) ํฌํ•จ ๋“ฑ ๊ฒฝ๋กœ ํƒˆ์ถœ ์œ„ํ—˜ ๊ฐ’์ด๋ฉด throw.
59
+ - `get clientPath(): string | undefined` โ€” `rootPath/www/<clientName>` ์ ˆ๋Œ€๊ฒฝ๋กœ. `clientName` ์ด ์—†์œผ๋ฉด `undefined`.
60
+ - `getConfig<T>(section: string): Promise<T>` โ€” `rootPath/.config.json` ๋ฃจํŠธ ์„ค์ •์— ํด๋ผ์ด์–ธํŠธ๋ณ„ `www/<clientName>/.config.json` ์„ ๋จธ์ง€ํ•œ ๋’ค `section` ํ‚ค ๊ฐ’์„ ๋ฐ˜ํ™˜. ์„น์…˜์ด ์—†์œผ๋ฉด throw. ์„ค์ • ํŒŒ์ผ์€ ๋ณ€๊ฒฝ ์‹œ ์ž๋™ ๋ฆฌ๋กœ๋“œ๋œ๋‹ค(ํŒŒ์ผ ์›Œ์ฒ˜ + ์บ์‹œ).
60
61
 
61
62
  ```ts
62
63
  export const ReportService = defineService("Report", auth((ctx) => ({
@@ -68,7 +69,7 @@ export const ReportService = defineService("Report", auth((ctx) => ({
68
69
  ## ServiceDefinition / ServiceMethods / getServiceAuthPermissions
69
70
 
70
71
  - `ServiceDefinition<TMethods>` โ€” `defineService` ๋ฐ˜ํ™˜ ํƒ€์ž…. `{ name: string; names: string[]; factory: (ctx) => TMethods; authPermissions?: string[] }`. `name` ์€ ๋Œ€ํ‘œ ์ด๋ฆ„, `names` ๋Š” ๋ณ„์นญ ์ „์ฒด, `authPermissions` ๋Š” ์„œ๋น„์Šค ์ˆ˜์ค€ `auth` ๊ถŒํ•œ(์—†์œผ๋ฉด `undefined`).
71
- - `type ServiceMethods<TDefinition>` โ€” `ServiceDefinition<M>` ์—์„œ ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜ `M` ๋งŒ ์ถ”์ถœํ•˜๋Š” ํƒ€์ž… ์œ ํ‹ธ. ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋น„์Šค ํƒ€์ž…์„ ๊ณต์œ ํ•˜๋ ค๊ณ  common ํŒจํ‚ค์ง€์— `export type XxxServiceMethods = ServiceMethods<typeof XxxService>` ๋กœ ์žฌ๋…ธ์ถœํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ๋Š” `client.getService<XxxServiceMethods>("Xxx")` ๋กœ ์‚ฌ์šฉ.
72
- - `getServiceAuthPermissions(fn: Function): string[] | undefined` โ€” `auth(...)` ๋กœ ๋ž˜ํ•‘๋œ ํ•จ์ˆ˜์—์„œ ๊ถŒํ•œ ๋ฐฐ์—ด์„ ์ฝ์Œ. ๋ž˜ํ•‘ ์•ˆ ๋์œผ๋ฉด `undefined`. ๋‚ด๋ถ€ ์‹คํ–‰๊ธฐยท์ปค์Šคํ…€ ์ „์†ก์—์„œ๋งŒ ํ•„์š”(์ผ๋ฐ˜ ์ž‘์„ฑ์—์„œ๋Š” ๋ถˆํ•„์š”).
72
+ - `type ServiceMethods<TDefinition>` โ€” `ServiceDefinition<M>` ์—์„œ ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜ `M` ๋งŒ ์ถ”์ถœํ•˜๋Š” ํƒ€์ž… ์œ ํ‹ธ. ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋น„์Šค ํƒ€์ž…์„ ๊ณต์œ ํ•˜๋ ค๊ณ  common ํŒจํ‚ค์ง€์— `export type XxxServiceMethods = ServiceMethods<typeof XxxService>` ๋กœ ์žฌ๋…ธ์ถœํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ๋Š” `client.getService<XxxServiceMethods>("Xxx")` ๋กœ ์“ด๋‹ค.
73
+ - `getServiceAuthPermissions(fn: Function): string[] | undefined` โ€” `auth(...)` ๋กœ ๋ž˜ํ•‘๋œ ํ•จ์ˆ˜์—์„œ ๊ถŒํ•œ ๋ฐฐ์—ด์„ ์ฝ๋Š”๋‹ค. ๋ž˜ํ•‘ ์•ˆ ๋์œผ๋ฉด `undefined`. ๋‚ด๋ถ€ ์‹คํ–‰๊ธฐยท์ปค์Šคํ…€ ์ „์†ก์—์„œ๋งŒ ํ•„์š”ํ•˜๊ณ  ์ผ๋ฐ˜ ์ž‘์„ฑ์—์„œ๋Š” ์“ฐ์ง€ ์•Š๋Š”๋‹ค.
73
74
 
74
75
  ์ฃผ์˜: ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์“ฐ๋Š” ์„œ๋น„์Šค ์ด๋ฆ„ ๋ฌธ์ž์—ด๊ณผ `ServiceMethods` ํƒ€์ž…์€ ๋‹จ์ผ ์†Œ์Šค(`defineService` ์ด๋ฆ„ / `typeof XxxService`)๋ฅผ ๋”ฐ๋ฅธ๋‹ค. ํ˜ธ์ถœ๋ถ€์—์„œ ์ด๋ฆ„ยท์ œ๋„ค๋ฆญ์„ ์ค‘๋ณต ์ •์˜ํ•˜์ง€ ๋ง ๊ฒƒ.
@@ -14,22 +14,20 @@ function executeServiceMethod(
14
14
  socket?: ServiceSocket;
15
15
  http?: { clientName: string; authTokenPayload?: AuthTokenPayload };
16
16
  },
17
- ): Promise<unknown>
17
+ ): Promise<unknown>;
18
18
  ```
19
19
 
20
- ์š”์ฒญ 1๊ฑด์„ ์‹ค์ œ ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋กœ ๋ผ์šฐํŒ…ยท์‹คํ–‰ํ•˜๋Š” ํ•ต์‹ฌ ๊ฒŒ์ดํŠธํ‚คํผ. WebSocket/HTTP ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋ชจ๋‘ ์ด ํ•จ์ˆ˜๋กœ ์ˆ˜๋ ดํ•œ๋‹ค.
20
+ ์š”์ฒญ 1๊ฑด์„ ์‹ค์ œ ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋กœ ๋ผ์šฐํŒ…ยท์‹คํ–‰ํ•˜๋Š” ํ•ต์‹ฌ ๊ฒŒ์ดํŠธํ‚คํผ. WebSocketยทHTTP ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋ชจ๋‘ ์ด ํ•จ์ˆ˜๋กœ ์ˆ˜๋ ดํ•œ๋‹ค.
21
21
 
22
- - `serviceName` / `methodName` โ€” `server.options.services` ์˜ `names` ๋งค์นญ์œผ๋กœ ์„œ๋น„์Šค๋ฅผ, ๊ทธ ํŒฉํ† ๋ฆฌ ์‚ฐ์ถœ ๊ฐ์ฒด์—์„œ ๋ฉ”์„œ๋“œ๋ฅผ ์ฐพ์Œ. ์—†์œผ๋ฉด throw.
23
- - `params: unknown[]` โ€” ๋ฉ”์„œ๋“œ ์ธ์ž ๋ฐฐ์—ด. ์Šคํ”„๋ ˆ๋“œ๋˜์–ด ๋ฉ”์„œ๋“œ์— ์ „๋‹ฌ.
24
- - `socket?` / `http?` โ€” ๋‘˜ ์ค‘ ํ•˜๋‚˜๋กœ ์š”์ฒญ ์ถœ์ฒ˜ ์ „๋‹ฌ. `clientName` ์— `..`ยท์Šฌ๋ž˜์‹œ(`/`,`\`)๊ฐ€ ์žˆ์œผ๋ฉด ๋ณด์•ˆ ์ฐจ๋‹จ throw.
25
- - ๋™์ž‘: ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ โ†’ ํŒฉํ† ๋ฆฌ ํ˜ธ์ถœ โ†’ ๋ฉ”์„œ๋“œ ์กฐํšŒ โ†’ ์ธ์ฆ/๊ถŒํ•œ ๊ฒ€์‚ฌ(`auth` ๋ž˜ํ•‘ ๊ถŒํ•œ ๊ธฐ์ค€) โ†’ ์‹คํ–‰ ํ›„ ๋ฐ˜ํ™˜๊ฐ’ ๋ฐ˜ํ™˜. `auth: false` ๋ฉด ์ธ์ฆ ์Šคํ‚ต, `auth` ๋ฏธ์„ค์ •์ธ๋ฐ ๊ถŒํ•œ ์š”๊ตฌ ๋ฉ”์„œ๋“œ๋ฉด ์„ค์ • ์˜ค๋ฅ˜ throw.
22
+ - `serviceName` / `methodName` โ€” `server.options.services` ์˜ `names` ๋งค์นญ์œผ๋กœ ์„œ๋น„์Šค๋ฅผ, ๊ทธ ํŒฉํ† ๋ฆฌ ์‚ฐ์ถœ ๊ฐ์ฒด์—์„œ ๋ฉ”์„œ๋“œ๋ฅผ ์ฐพ๋Š”๋‹ค. ์—†์œผ๋ฉด throw.
23
+ - `params: unknown[]` โ€” ๋ฉ”์„œ๋“œ ์ธ์ž ๋ฐฐ์—ด. ์Šคํ”„๋ ˆ๋“œ๋˜์–ด ๋ฉ”์„œ๋“œ์— ์ „๋‹ฌ๋œ๋‹ค.
24
+ - `socket?` / `http?` โ€” ๋‘˜ ์ค‘ ํ•˜๋‚˜๋กœ ์š”์ฒญ ์ถœ์ฒ˜๋ฅผ ์ „๋‹ฌ. `clientName` ์— `..`ยท์Šฌ๋ž˜์‹œ(`/`ยท`\`)๊ฐ€ ์žˆ์œผ๋ฉด ๋ณด์•ˆ ์ฐจ๋‹จ throw.
25
+ - ๋™์ž‘ ์ˆœ์„œ: ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ โ†’ ํŒฉํ† ๋ฆฌ ํ˜ธ์ถœ โ†’ ๋ฉ”์„œ๋“œ ์กฐํšŒ โ†’ ์ธ์ฆยท๊ถŒํ•œ ๊ฒ€์‚ฌ(`auth` ๋ž˜ํ•‘ ๊ถŒํ•œ ๊ธฐ์ค€) โ†’ ์‹คํ–‰ ํ›„ ๋ฐ˜ํ™˜๊ฐ’ ๋ฐ˜ํ™˜. `auth: false` ๋ฉด ์ธ์ฆ ์Šคํ‚ต, `auth` ๋ฏธ์„ค์ •์ธ๋ฐ ๊ถŒํ•œ ์š”๊ตฌ ๋ฉ”์„œ๋“œ๋ฉด ์„ค์ • ์˜ค๋ฅ˜ throw.
26
26
 
27
27
  ## createServiceContext
28
28
 
29
29
  ```ts
30
- function createServiceContext<TAuthInfo>(
31
- server, socket?, http?, legacy?,
32
- ): ServiceContext<TAuthInfo>
30
+ function createServiceContext<TAuthInfo>(server, socket?, http?, legacy?): ServiceContext<TAuthInfo>;
33
31
  ```
34
32
 
35
33
  `ServiceContext`(์ธ์ฆยทํด๋ผ์ด์–ธํŠธยท์„ค์ • ์ ‘๊ทผ์ž) ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ ๋‹ค.
@@ -39,23 +37,23 @@ function createServiceContext<TAuthInfo>(
39
37
  - `http?: { clientName; authTokenPayload? }` โ€” HTTP ์š”์ฒญ ์ถœ์ฒ˜.
40
38
  - `legacy?: { clientName? }` โ€” V1 ๋ ˆ๊ฑฐ์‹œ ์ถœ์ฒ˜(`clientName` ๋งŒ).
41
39
 
42
- ์ปจํ…์ŠคํŠธ ํ•„๋“œ ์˜๋ฏธ๋Š” [service-authoring.md](./service-authoring.md) ์˜ ServiceContext ์ ˆ ์ฐธ์กฐ. ํ…Œ์ŠคํŠธ์—์„œ ์ปจํ…์ŠคํŠธ๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋ฅผ ๋‹จ์œ„ ํ˜ธ์ถœํ•  ๋•Œ ์œ ์šฉ.
40
+ ์ปจํ…์ŠคํŠธ ํ•„๋“œ ์˜๋ฏธ๋Š” [service-authoring.md](./service-authoring.md) ์˜ ServiceContext ์ ˆ ์ฐธ์กฐ. ํ…Œ์ŠคํŠธ์—์„œ ์ปจํ…์ŠคํŠธ๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋ฅผ ๋‹จ์œ„ ํ˜ธ์ถœํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ค.
43
41
 
44
42
  ## ServiceSocket / createServiceSocket
45
43
 
46
- ๋‹จ์ผ WebSocket ์—ฐ๊ฒฐ์„ ๊ฐ์‹ธ ํ”„๋กœํ† ์ฝœ ์ธ์ฝ”๋”ฉยทping/pong ์—ฐ๊ฒฐ ์œ ์ง€ยท์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ถ”์ ์„ ๋‹ด๋‹นํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค. `createServiceSocket(socket, clientId, clientName, connReq)` ๋กœ ์ƒ์„ฑ.
44
+ ๋‹จ์ผ WebSocket ์—ฐ๊ฒฐ์„ ๊ฐ์‹ธ ํ”„๋กœํ† ์ฝœ ์ธ์ฝ”๋”ฉยทping/pong ์—ฐ๊ฒฐ ์œ ์ง€ยท์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ถ”์ ์„ ๋‹ด๋‹นํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค. `createServiceSocket(socket, clientId, clientName, connReq)` ๋กœ ์ƒ์„ฑํ•œ๋‹ค.
47
45
 
48
46
  - `readonly connectedAtDateTime: DateTime` โ€” ์—ฐ๊ฒฐ ์„ฑ๋ฆฝ ์‹œ๊ฐ.
49
47
  - `readonly clientName: string` โ€” ์—ฐ๊ฒฐ ์‹œ ๋ฐ›์€ ํด๋ผ์ด์–ธํŠธ ์ด๋ฆ„.
50
48
  - `readonly connReq: FastifyRequest` โ€” ์›๋ณธ ์—ฐ๊ฒฐ ์š”์ฒญ(์›๊ฒฉ ์ฃผ์†Œ ๋“ฑ).
51
49
  - `authTokenPayload?: AuthTokenPayload` โ€” ์†Œ์ผ“ `auth` ๋ฉ”์‹œ์ง€๋กœ ๊ฒ€์ฆ๋œ ํ† ํฐ. ์ดํ›„ ๊ทธ ์†Œ์ผ“ ์š”์ฒญ์˜ `ctx.authInfo` ์ถœ์ฒ˜(set ์œผ๋กœ ๊ฐฑ์‹ ).
52
50
  - `close(): void` โ€” ์†Œ์ผ“ ์ฆ‰์‹œ ์ข…๋ฃŒ(terminate).
53
- - `send(uuid, msg): Promise<number>` โ€” ์„œ๋ฒ„ ๋ฉ”์‹œ์ง€๋ฅผ ํ”„๋กœํ† ์ฝœ๋กœ ์ธ์ฝ”๋”ฉํ•ด ์ „์†ก, ๋ณด๋‚ธ ๋ฐ”์ดํŠธ ์ˆ˜ ๋ฐ˜ํ™˜(์†Œ์ผ“ ๋ฏธ๊ฐœ๋ฐฉ์ด๋ฉด `0`).
54
- - `addListener(key, eventName, info): void` โ€” ์ด๋ฒคํŠธ ๊ตฌ๋… ๋“ฑ๋ก. `key` = ๊ตฌ๋… ์‹๋ณ„์ž, `info` = selector ๋งค์นญ์šฉ ๋ฉ”ํƒ€.
51
+ - `send(uuid, msg): Promise<number>` โ€” ์„œ๋ฒ„ ๋ฉ”์‹œ์ง€๋ฅผ ํ”„๋กœํ† ์ฝœ๋กœ ์ธ์ฝ”๋”ฉํ•ด ์ „์†กํ•˜๊ณ  ๋ณด๋‚ธ ๋ฐ”์ดํŠธ ์ˆ˜ ๋ฐ˜ํ™˜(์†Œ์ผ“ ๋ฏธ๊ฐœ๋ฐฉ์ด๋ฉด `0`).
52
+ - `addListener(key, eventName, info): void` โ€” ์ด๋ฒคํŠธ ๊ตฌ๋… ๋“ฑ๋ก. `key` ๋Š” ๊ตฌ๋… ์‹๋ณ„์ž, `info` ๋Š” selector ๋งค์นญ์šฉ ๋ฉ”ํƒ€.
55
53
  - `removeListener(key): void` โ€” `key` ๋กœ ๊ตฌ๋… 1๊ฑด ํ•ด์ œ.
56
- - `getEventListeners(eventName): { key, info }[]` โ€” ํ•ด๋‹น ์ด๋ฒคํŠธ์˜ ๊ตฌ๋… ๋ชฉ๋ก ์กฐํšŒ.
54
+ - `getEventListeners(eventName): { key; info }[]` โ€” ํ•ด๋‹น ์ด๋ฒคํŠธ์˜ ๊ตฌ๋… ๋ชฉ๋ก ์กฐํšŒ.
57
55
  - `filterEventTargetKeys(targetKeys): string[]` โ€” ์ฃผ์–ด์ง„ ํ‚ค ์ค‘ ์ด ์†Œ์ผ“์— ์กด์žฌํ•˜๋Š” ๊ฒƒ๋งŒ ๋ฐ˜ํ™˜.
58
- - `on("error" | "close" | "message", handler): void` โ€” ์†Œ์ผ“ ์ด๋ฒคํŠธ ํ›„ํ‚น. 5์ดˆ ์ฃผ๊ธฐ ping, pong ๋ฏธ์ˆ˜์‹  ์‹œ ์ž๋™ terminate.
56
+ - `on("error" | "close" | "message", handler): void` โ€” ์†Œ์ผ“ ์ด๋ฒคํŠธ ํ›„ํ‚น. 5์ดˆ ์ฃผ๊ธฐ๋กœ ping ํ•˜๊ณ  pong ๋ฏธ์ˆ˜์‹  ์‹œ ์ž๋™ terminate ํ•œ๋‹ค.
59
57
 
60
58
  ## WebSocketHandler / createWebSocketHandler
61
59
 
@@ -63,36 +61,36 @@ function createServiceContext<TAuthInfo>(
63
61
  function createWebSocketHandler(
64
62
  runMethod: (def) => Promise<unknown>,
65
63
  jwtSecret: string | undefined,
66
- ): WebSocketHandler
64
+ ): WebSocketHandler;
67
65
  ```
68
66
 
69
67
  ์—ฌ๋Ÿฌ `ServiceSocket` ์„ `clientId` ๋กœ ๊ด€๋ฆฌํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ ๋ฉ”์‹œ์ง€๋ฅผ `runMethod`(๋ณดํ†ต `executeServiceMethod` ๋ฐ”์ธ๋”ฉ)๋กœ ๋ผ์šฐํŒ…ํ•˜๋ฉฐ, ์ด๋ฒคํŠธ๋ฅผ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธํ•œ๋‹ค.
70
68
 
71
- - `addSocket(socket, clientId, clientName, connReq): void` โ€” ์—ฐ๊ฒฐ ๋“ฑ๋ก. ๊ฐ™์€ `clientId` ์˜ ๊ธฐ์กด ์—ฐ๊ฒฐ์€ ๋‹ซ๊ณ  ๊ต์ฒด. ์ƒ์„ฑ ์ค‘ ์˜ˆ์™ธ ์‹œ ์†Œ์ผ“ terminate.
69
+ - `addSocket(socket, clientId, clientName, connReq): void` โ€” ์—ฐ๊ฒฐ ๋“ฑ๋ก. ๊ฐ™์€ `clientId` ์˜ ๊ธฐ์กด ์—ฐ๊ฒฐ์€ ๋‹ซ๊ณ  ๊ต์ฒดํ•œ๋‹ค. ์ƒ์„ฑ ์ค‘ ์˜ˆ์™ธ ์‹œ ์†Œ์ผ“ terminate.
72
70
  - `closeAll(): void` โ€” ๋ชจ๋“  ์—ฐ๊ฒฐ ์ข…๋ฃŒ(์„œ๋ฒ„ `close()` ์‹œ ํ˜ธ์ถœ).
73
71
  - `emit<TEventDef>(eventName, infoSelector, data): Promise<void>` โ€” ์ „ ์†Œ์ผ“์˜ ํ•ด๋‹น ์ด๋ฒคํŠธ ๊ตฌ๋… ์ค‘ `infoSelector(info)` ๊ฐ€ `true` ์ธ ํ‚ค์—๊ฒŒ๋งŒ `evt:on` ๋ฉ”์‹œ์ง€ ์ „์†ก. `ServiceServer.emitEvent` ์˜ ์‹ค์ œ ๊ตฌํ˜„.
74
- - ์ฒ˜๋ฆฌํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ ๋ฉ”์‹œ์ง€ ์ข…๋ฅ˜: `"<service>.<method>"`(RPC ํ˜ธ์ถœ), `evt:add`/`evt:remove`/`evt:gets`/`evt:emit`(์ด๋ฒคํŠธ ๊ตฌ๋…ยทํ•ด์ œยท์กฐํšŒยท๋ฐœ์ƒ), `auth`(์†Œ์ผ“ ํ† ํฐ ๊ฒ€์ฆ). ๊ทธ ์™ธ๋Š” `BAD_MESSAGE` ์—๋Ÿฌ ์‘๋‹ต. ์ฒ˜๋ฆฌ ์ค‘ ์˜ˆ์™ธ๋Š” `INTERNAL_ERROR` ์‘๋‹ต์ด๋ฉฐ, `DEV` ํ™˜๊ฒฝ์—์„œ๋งŒ ์—๋Ÿฌ ์Šคํƒ ํฌํ•จ.
72
+ - ์ฒ˜๋ฆฌํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ ๋ฉ”์‹œ์ง€ ์ข…๋ฅ˜: `"<service>.<method>"`(RPC ํ˜ธ์ถœ), `evt:add`ยท`evt:remove`ยท`evt:gets`ยท`evt:emit`(์ด๋ฒคํŠธ ๊ตฌ๋…ยทํ•ด์ œยท์กฐํšŒยท๋ฐœ์ƒ), `auth`(์†Œ์ผ“ ํ† ํฐ ๊ฒ€์ฆ). ๊ทธ ์™ธ๋Š” `BAD_MESSAGE` ์—๋Ÿฌ ์‘๋‹ต. ์ฒ˜๋ฆฌ ์ค‘ ์˜ˆ์™ธ๋Š” `INTERNAL_ERROR` ์‘๋‹ต์ด๋ฉฐ, `DEV` ํ™˜๊ฒฝ์—์„œ๋งŒ ์—๋Ÿฌ ์Šคํƒ์„ ํฌํ•จํ•œ๋‹ค.
75
73
 
76
74
  ## HTTP / ์ •์  / ์—…๋กœ๋“œ ํ•ธ๋“ค๋Ÿฌ
77
75
 
78
- `fastify` ๋ผ์šฐํŠธ์— ์ง์ ‘ ๋ฌผ๋ฆฌ๋Š” ์ €์ˆ˜์ค€ ํ•จ์ˆ˜๋“ค. ์ปค์Šคํ…€ ๋ผ์šฐํŠธ๋ฅผ ์งค ๋•Œ๋งŒ ์ง์ ‘ ์‚ฌ์šฉ.
76
+ `fastify` ๋ผ์šฐํŠธ์— ์ง์ ‘ ๋ฌผ๋ฆฌ๋Š” ์ €์ˆ˜์ค€ ํ•จ์ˆ˜๋“ค. ์ปค์Šคํ…€ ๋ผ์šฐํŠธ๋ฅผ ์งค ๋•Œ๋งŒ ์ง์ ‘ ์‚ฌ์šฉํ•œ๋‹ค.
79
77
 
80
- - `handleHttpRequest(req, reply, jwtSecret, runMethod)` โ€” `/api/:service/:method` ์ฒ˜๋ฆฌ. `x-sd-client-name` ํ—ค๋” ํ•„์ˆ˜, `Authorization: Bearer <token>` ๊ฒ€์ฆ(์‹คํŒจ ์‹œ 401). GET ์€ `?json=` ์ฟผ๋ฆฌ, POST ๋Š” ๋ฐฐ์—ด ๋ณธ๋ฌธ์—์„œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„ `runMethod` ์‹คํ–‰. ๋ณธ๋ฌธ์ด ๋ฐฐ์—ด์ด ์•„๋‹ˆ๋ฉด 400, ๊ทธ ์™ธ HTTP ๋ฉ”์„œ๋“œ๋Š” 405.
81
- - `handleUpload(req, reply, rootPath, jwtSecret)` โ€” `/upload` multipart ์ฒ˜๋ฆฌ. multipart ์•„๋‹ˆ๋ฉด 400, ์ธ์ฆ ํ† ํฐ ํ•„์ˆ˜(์—†๊ฑฐ๋‚˜ ๋ฌดํšจ๋ฉด 401). ํŒŒ์ผ์„ `rootPath/www/uploads/<uuid><ext>` ๋กœ ์ €์žฅํ•˜๊ณ  `ServiceUploadResult[]`(`{ path, filename, size }`) ๋ฐ˜ํ™˜. ๋„์ค‘ ์‹คํŒจ ์‹œ ๊ทธ ์š”์ฒญ์—์„œ ์ €์žฅํ•œ ํŒŒ์ผ์„ ๋ชจ๋‘ ๋กค๋ฐฑ ์‚ญ์ œ ํ›„ 500.
78
+ - `handleHttpRequest(req, reply, jwtSecret, runMethod)` โ€” `/api/:service/:method` ์ฒ˜๋ฆฌ. `x-sd-client-name` ํ—ค๋” ํ•„์ˆ˜, `Authorization: Bearer <token>` ๊ฒ€์ฆ(์‹คํŒจ ์‹œ 401). GET ์€ `?json=` ์ฟผ๋ฆฌ, POST ๋Š” ๋ฐฐ์—ด ๋ณธ๋ฌธ์—์„œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„ `runMethod` ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. ๋ณธ๋ฌธ์ด ๋ฐฐ์—ด์ด ์•„๋‹ˆ๋ฉด 400, ๊ทธ ์™ธ HTTP ๋ฉ”์„œ๋“œ๋Š” 405.
79
+ - `handleUpload(req, reply, rootPath, jwtSecret)` โ€” `/upload` multipart ์ฒ˜๋ฆฌ. multipart ๊ฐ€ ์•„๋‹ˆ๋ฉด 400, ์ธ์ฆ ํ† ํฐ ํ•„์ˆ˜(์—†๊ฑฐ๋‚˜ ๋ฌดํšจ๋ฉด 401). ํŒŒ์ผ์„ `rootPath/www/uploads/<uuid><ext>` ๋กœ ์ €์žฅํ•˜๊ณ  `ServiceUploadResult[]`(`{ path, filename, size }`) ๋ฐ˜ํ™˜. ๋„์ค‘ ์‹คํŒจํ•˜๋ฉด ๊ทธ ์š”์ฒญ์—์„œ ์ €์žฅํ•œ ํŒŒ์ผ์„ ๋ชจ๋‘ ๋กค๋ฐฑ ์‚ญ์ œ ํ›„ 500.
82
80
  - `handleStaticFile(req, reply, rootPath, urlPath)` โ€” `rootPath/www` ํ•˜์œ„ ์ •์  ํŒŒ์ผ ์ „์†ก. `www` ๋ฐ– ๊ฒฝ๋กœ๋Š” ์ฐจ๋‹จ(throw), ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฉด ์Šฌ๋ž˜์‹œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ํ›„ `index.html`, `.` ์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ์ˆจ๊น€ ํŒŒ์ผ์€ 403, ๋ฏธ์กด์žฌ๋Š” 404, ๊ทธ ์™ธ ์ „์†ก ์‹คํŒจ๋Š” 500 HTML ์‘๋‹ต. SPA ํด๋ฐฑ: ๋ฏธ์กด์žฌ + ํ™•์žฅ์ž ์—†๋Š” ํŽ˜์ด์ง€ ์š”์ฒญ์ด๋ฉด `www` ๋ฃจํŠธ ๋ฐฉํ–ฅ์œผ๋กœ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด `index.csr.html`(SSG ํด๋ผ์ด์–ธํŠธ์˜ SPA ์…ธ)์„ ์ฐพ์•„ ๋ฐ˜ํ™˜ โ€” ์…ธ ํŒŒ์ผ์ด ์—†๋Š” ๊ธฐ์กด ํด๋ผ์ด์–ธํŠธ๋Š” ๊ทธ๋Œ€๋กœ 404.
83
81
 
84
82
  ## ServerProtocolWrapper / createServerProtocolWrapper
85
83
 
86
- ๋ฉ”์‹œ์ง€ ์ธ์ฝ”๋”ฉ/๋””์ฝ”๋”ฉ์„ ํฌ๊ธฐยท๋‚ด์šฉ์— ๋”ฐ๋ผ worker ์Šค๋ ˆ๋“œ์™€ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋กœ ์ž๋™ ๋ถ„๋ฐฐํ•˜๋Š” ๋ž˜ํผ. `createServerProtocolWrapper()` ๋กœ ์ƒ์„ฑ(worker ๋Š” ์ง€์—ฐ ์‹ฑ๊ธ€ํ„ด, ์†Œ์ผ“ ๊ฐ„ ๊ณต์œ ).
84
+ ๋ฉ”์‹œ์ง€ ์ธ์ฝ”๋”ฉยท๋””์ฝ”๋”ฉ์„ ํฌ๊ธฐยท๋‚ด์šฉ์— ๋”ฐ๋ผ worker ์Šค๋ ˆ๋“œ์™€ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋กœ ์ž๋™ ๋ถ„๋ฐฐํ•˜๋Š” ๋ž˜ํผ. `createServerProtocolWrapper()` ๋กœ ์ƒ์„ฑํ•œ๋‹ค(worker ๋Š” ์ง€์—ฐ ์‹ฑ๊ธ€ํ„ด์ด๋ผ ์†Œ์ผ“ ๊ฐ„ ๊ณต์œ ).
87
85
 
88
- - `encode(uuid, message): Promise<{ chunks: Bytes[]; totalSize: number }>` โ€” `body` ์— `Uint8Array` ๊ฐ€ ์žˆ์œผ๋ฉด(๋‹จ์ผ ๋˜๋Š” ๋ฐฐ์—ด ์š”์†Œ ์ค‘ ํ•˜๋‚˜๋ผ๋„) worker, ์•„๋‹ˆ๋ฉด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ธ์ฝ”๋”ฉ.
89
- - `decode(bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>>` โ€” ์ฒญํฌ ์žฌ์กฐ๋ฆฝ(stateful)์€ ํ•ญ์ƒ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋‹จ์ผ ๋ˆ„์ ๊ธฐ์—์„œ, ์žฌ์กฐ๋ฆฝ ์™„๋ฃŒ ํ›„ 30KB ์ดˆ๊ณผ JSON ํŒŒ์‹ฑ(stateless)๋งŒ worker ์œ„์ž„. ์ง„ํ–‰ ์ค‘์ด๋ฉด `{ type: "progress" }`, ์™„๋ฃŒ๋ฉด `{ type: "complete", uuid, message }`.
86
+ - `encode(uuid, message): Promise<{ chunks: Bytes[]; totalSize: number }>` โ€” `body` ์— `Uint8Array` ๊ฐ€ ์žˆ์œผ๋ฉด(๋‹จ์ผ์ด๊ฑฐ๋‚˜ ๋ฐฐ์—ด ์š”์†Œ ์ค‘ ํ•˜๋‚˜๋ผ๋„) worker, ์•„๋‹ˆ๋ฉด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ธ์ฝ”๋”ฉ.
87
+ - `decode(bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>>` โ€” ์ฒญํฌ ์žฌ์กฐ๋ฆฝ(stateful)์€ ํ•ญ์ƒ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋‹จ์ผ ๋ˆ„์ ๊ธฐ์—์„œ ์ˆ˜ํ–‰ํ•˜๊ณ , ์žฌ์กฐ๋ฆฝ ์™„๋ฃŒ ํ›„ 30KB ์ดˆ๊ณผ JSON ํŒŒ์‹ฑ(stateless)๋งŒ worker ์— ์œ„์ž„. ์ง„ํ–‰ ์ค‘์ด๋ฉด `{ type: "progress" }`, ์™„๋ฃŒ๋ฉด `{ type: "complete", uuid, message }`.
90
88
  - `dispose(): void` โ€” ํ”„๋กœํ† ์ฝœ ๋ฆฌ์†Œ์Šค ํ•ด์ œ.
91
89
 
92
90
  ## getConfig
93
91
 
94
92
  ```ts
95
- function getConfig<TConfig>(filePath: string): Promise<TConfig | undefined>
93
+ function getConfig<TConfig>(filePath: string): Promise<TConfig | undefined>;
96
94
  ```
97
95
 
98
96
  `filePath` JSON ์„ค์ •์„ ์ฝ์–ด ์บ์‹œยทํŒŒ์ผ์›Œ์น˜ํ•œ๋‹ค. `ServiceContext.getConfig` ์˜ ๋‚ด๋ถ€ ๊ตฌํ˜„. ์บ์‹œ ํžˆํŠธ ์‹œ ์ฆ‰์‹œ ๋ฐ˜ํ™˜(์ ‘๊ทผ ์‹œ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ๊ฐฑ์‹ ), ํŒŒ์ผ ๋ณ€๊ฒฝ ์‹œ ์ž๋™ ๋ฆฌ๋กœ๋“œ, 1์‹œ๊ฐ„ ๋ฌด์ ‘๊ทผ ์‹œ ์บ์‹œยท์›Œ์ฒ˜ GC. ํŒŒ์ผ์ด ์—†์œผ๋ฉด `undefined`.
@@ -1,6 +1,6 @@
1
1
  # @simplysm/service-server โ€” V1 ๋ ˆ๊ฑฐ์‹œ ์ง€์›
2
2
 
3
- `ver !== "2"`(๊ตฌ๋ฒ„์ „) WebSocket ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•œ ๋ ˆ๊ฑฐ์‹œ ํ•ธ๋“ค๋Ÿฌ. ์ฃผ๋กœ ๊ตฌ๋ฒ„์ „ ์•ฑ์˜ ์ž๋™์—…๋ฐ์ดํŠธ(`SdAutoUpdateService.getLastVersion`) ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. `ServiceServer` ๋Š” ver=2 ๊ฐ€ ์•„๋‹Œ ์—ฐ๊ฒฐ์„ ์ž๋™์œผ๋กœ ์ด ํ•ธ๋“ค๋Ÿฌ๋กœ ๋„˜๊ธด๋‹ค(`AutoUpdate` ์„œ๋น„์Šค๋‚˜ `legacyV1Handlers` ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ, ๋‘˜ ๋‹ค ์—†์œผ๋ฉด ์ฝ”๋“œ 1008 ๋กœ ์—ฐ๊ฒฐ ๊ฑฐ๋ถ€). `ServiceServerOptions.legacyV1Handlers` ๋กœ ์ปค์Šคํ…€ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ผ์šธ ๋•Œ๋งŒ ์ง์ ‘ ๋‹ค๋ฃฌ๋‹ค.
3
+ `ver !== "2"`(๊ตฌ๋ฒ„์ „) WebSocket ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•œ ๋ ˆ๊ฑฐ์‹œ ํ•ธ๋“ค๋Ÿฌ. ์ฃผ๋กœ ๊ตฌ๋ฒ„์ „ ์•ฑ์˜ ์ž๋™์—…๋ฐ์ดํŠธ(`SdAutoUpdateService.getLastVersion`) ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. `ServiceServer` ๋Š” ver=2 ๊ฐ€ ์•„๋‹Œ ์—ฐ๊ฒฐ์„ ์ž๋™์œผ๋กœ ์ด ํ•ธ๋“ค๋Ÿฌ๋กœ ๋„˜๊ธฐ๋ฉฐ(`AutoUpdate` ์„œ๋น„์Šค๋‚˜ `legacyV1Handlers` ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ, ๋‘˜ ๋‹ค ์—†์œผ๋ฉด ์ฝ”๋“œ 1008 ๋กœ ์—ฐ๊ฒฐ ๊ฑฐ๋ถ€), `ServiceServerOptions.legacyV1Handlers` ๋กœ ์ปค์Šคํ…€ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ผ์šธ ๋•Œ๋งŒ ์ง์ ‘ ๋‹ค๋ฃฌ๋‹ค.
4
4
 
5
5
  ## handleV1Connection
6
6
 
@@ -9,27 +9,27 @@ function handleV1Connection(socket, autoUpdateMethods: V1AutoUpdateMethods, clie
9
9
  function handleV1Connection(socket, options: V1ConnectionOptions): void;
10
10
  ```
11
11
 
12
- V1 WebSocket ์—ฐ๊ฒฐ 1๊ฑด์„ ๋ฐ›์•„ ์—ฐ๊ฒฐ ์•Œ๋ฆผ(`{ name: "connected" }`) ์ „์†ก ํ›„ ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์ฒ˜๋ฆฌ ์ˆœ์„œ: ์ปค์Šคํ…€ ํ•ธ๋“ค๋Ÿฌ๋“ค โ†’ (๋ฏธ์ฒ˜๋ฆฌ ์‹œ) `SdAutoUpdateService.getLastVersion` fallback โ†’ ๊ทธ๋ž˜๋„ ๋ฏธ์ฒ˜๋ฆฌ๋ฉด `UPGRADE_REQUIRED` ์—๋Ÿฌ ์‘๋‹ต. ๋ฉ”์‹œ์ง€ ํŒŒ์‹ฑยท์ฒ˜๋ฆฌ ์ค‘ ์˜ˆ์™ธ๋Š” ์žก์•„ warn ๋กœ๊ทธ๋งŒ ๋‚จ๊ธฐ๊ณ  ์‘๋‹ตํ•˜์ง€ ์•Š๋Š”๋‹ค.
12
+ V1 WebSocket ์—ฐ๊ฒฐ 1๊ฑด์„ ๋ฐ›์•„ ์—ฐ๊ฒฐ ์•Œ๋ฆผ(`{ name: "connected" }`)์„ ๋ณด๋‚ธ ๋’ค ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์ฒ˜๋ฆฌ ์ˆœ์„œ: ์ปค์Šคํ…€ ํ•ธ๋“ค๋Ÿฌ๋“ค โ†’ (๋ฏธ์ฒ˜๋ฆฌ ์‹œ) `SdAutoUpdateService.getLastVersion` fallback โ†’ ๊ทธ๋ž˜๋„ ๋ฏธ์ฒ˜๋ฆฌ๋ฉด `UPGRADE_REQUIRED` ์—๋Ÿฌ ์‘๋‹ต. ๋ฉ”์‹œ์ง€ ํŒŒ์‹ฑยท์ฒ˜๋ฆฌ ์ค‘ ์˜ˆ์™ธ๋Š” ์žก์•„ warn ๋กœ๊ทธ๋งŒ ๋‚จ๊ธฐ๊ณ  ์‘๋‹ตํ•˜์ง€ ์•Š๋Š”๋‹ค.
13
13
 
14
14
  - `socket: WebSocket` โ€” `ws` ์˜ ์›์‹œ ์†Œ์ผ“.
15
- - 2๋ฒˆ์งธ ์ธ์ž โ€” `V1AutoUpdateMethods` ๊ฐ์ฒด(์ž๋™์—…๋ฐ์ดํŠธ๋งŒ ์‘๋Œ€)์ด๊ฑฐ๋‚˜ `V1ConnectionOptions`(ํ•ธ๋“ค๋ŸฌยทํŒฉํ† ๋ฆฌ ํฌํ•จ). `"getLastVersion" in arg` ๋กœ ๋ถ„๊ธฐ.
16
- - `clientNameSetter?` โ€” ์ฒซ ์‹œ๊ทธ๋‹ˆ์ฒ˜์—์„œ๋งŒ. ์š”์ฒญ์˜ `clientName` ์„ ์™ธ๋ถ€์— ํ†ต์ง€ํ•˜๋Š” ์ฝœ๋ฐฑ.
15
+ - 2๋ฒˆ์งธ ์ธ์ž โ€” `V1AutoUpdateMethods` ๊ฐ์ฒด(์ž๋™์—…๋ฐ์ดํŠธ๋งŒ ์‘๋Œ€)์ด๊ฑฐ๋‚˜ `V1ConnectionOptions`(ํ•ธ๋“ค๋ŸฌยทํŒฉํ† ๋ฆฌ ํฌํ•จ). `"getLastVersion" in arg` ๋กœ ๋ถ„๊ธฐํ•œ๋‹ค.
16
+ - `clientNameSetter?` โ€” ์ฒซ ์‹œ๊ทธ๋‹ˆ์ฒ˜์—์„œ๋งŒ ๋ฐ›๋Š”๋‹ค. ์š”์ฒญ์˜ `clientName` ์„ ์™ธ๋ถ€์— ํ†ต์ง€ํ•˜๋Š” ์ฝœ๋ฐฑ.
17
17
 
18
18
  ## V1ConnectionOptions
19
19
 
20
20
  - `serviceContext?: ServiceContext` โ€” ๋ชจ๋“  ์š”์ฒญ์—์„œ ๊ณต์œ ํ•  ๊ณ ์ • ์ปจํ…์ŠคํŠธ.
21
- - `serviceContextFactory?: (request: V1Request) => ServiceContext` โ€” ์š”์ฒญ๋งˆ๋‹ค ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค ๋•Œ. `serviceContext` ๋ณด๋‹ค ์šฐ์„ .
22
- - `handlers?: V1RequestHandler[]` โ€” ์ปค์Šคํ…€ ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ ๋ชฉ๋ก. ์•ž์—์„œ๋ถ€ํ„ฐ ํ˜ธ์ถœ๋˜๋ฉฐ ์ฒซ `handled: true` ์—์„œ ๋ฉˆ์ถค. ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์žˆ๋Š”๋ฐ ์ปจํ…์ŠคํŠธ๊ฐ€ ์—†์œผ๋ฉด throw.
21
+ - `serviceContextFactory?: (request: V1Request) => ServiceContext` โ€” ์š”์ฒญ๋งˆ๋‹ค ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค ๋•Œ. `serviceContext` ๋ณด๋‹ค ์šฐ์„ ํ•œ๋‹ค.
22
+ - `handlers?: V1RequestHandler[]` โ€” ์ปค์Šคํ…€ ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ ๋ชฉ๋ก. ์•ž์—์„œ๋ถ€ํ„ฐ ํ˜ธ์ถœ๋˜๋ฉฐ ์ฒซ `handled: true` ์—์„œ ๋ฉˆ์ถ˜๋‹ค. ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์žˆ๋Š”๋ฐ ์ปจํ…์ŠคํŠธ๊ฐ€ ์—†์œผ๋ฉด throw.
23
23
  - `autoUpdateMethods?: V1AutoUpdateMethods` โ€” ์ž๋™์—…๋ฐ์ดํŠธ fallback ๊ตฌํ˜„(๊ณ ์ •).
24
- - `autoUpdateMethodsFactory?: (ctx: V1RequestHandlerContext) => V1AutoUpdateMethods` โ€” ์š”์ฒญ๋งˆ๋‹ค fallback ๊ตฌํ˜„ ์ƒ์„ฑ. ์ง€์ • ์‹œ `autoUpdateMethods` ๋Œ€์‹  ์‚ฌ์šฉ(์ปจํ…์ŠคํŠธ ์—†์œผ๋ฉด throw).
24
+ - `autoUpdateMethodsFactory?: (ctx: V1RequestHandlerContext) => V1AutoUpdateMethods` โ€” ์š”์ฒญ๋งˆ๋‹ค fallback ๊ตฌํ˜„์„ ์ƒ์„ฑ. ์ง€์ • ์‹œ `autoUpdateMethods` ๋Œ€์‹  ์‚ฌ์šฉํ•˜๋ฉฐ ์ปจํ…์ŠคํŠธ๊ฐ€ ์—†์œผ๋ฉด throw.
25
25
  - `clientNameSetter?: (clientName: string | undefined) => void` โ€” ๋งค ์š”์ฒญ `clientName` ํ†ต์ง€ ์ฝœ๋ฐฑ.
26
26
 
27
27
  ## V1RequestHandler ์™€ ๊ด€๋ จ ํƒ€์ž…
28
28
 
29
29
  - `V1Request` โ€” `{ uuid: string; command: string; params: unknown[]; clientName?: string }`. ๊ตฌ๋ฒ„์ „ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ด๋Š” ์š”์ฒญ ํ˜•ํƒœ. `command` ๋Š” `"<service>.<method>"` ํ˜•ํƒœ์˜ ๋ช…๋ น ํ‚ค.
30
- - `V1Response` โ€” `{ name: "response"; reqUuid: string; state: "success" | "error"; body: unknown }`. ์„œ๋ฒ„๊ฐ€ ๋Œ๋ ค๋ณด๋‚ด๋Š” ์‘๋‹ต ํ˜•ํƒœ. `state` = ์‘๋‹ต ์ƒํƒœ๋กœ `"success"`(์ •์ƒ)ยท`"error"`(์˜ค๋ฅ˜) ๊ตฌ๋ถ„.
30
+ - `V1Response` โ€” `{ name: "response"; reqUuid: string; state: "success" | "error"; body: unknown }`. ์„œ๋ฒ„๊ฐ€ ๋Œ๋ ค๋ณด๋‚ด๋Š” ์‘๋‹ต ํ˜•ํƒœ. `state` ๋Š” ์‘๋‹ต ์ƒํƒœ๋กœ `"success"`(์ •์ƒ)ยท`"error"`(์˜ค๋ฅ˜)๋ฅผ ๊ตฌ๋ถ„.
31
31
  - `V1RequestHandlerContext` โ€” `{ request: V1Request; serviceContext: ServiceContext }`. ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋ฐ›๋Š” ์ธ์ž.
32
- - `V1RequestHandlerResult` โ€” `{ handled: true; state?: "success" | "error"; body: unknown } | { handled: false }`. `handled` = ์ด ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ–ˆ๋Š”์ง€. `false` ๋ฉด ๋‹ค์Œ ํ•ธ๋“ค๋Ÿฌยทfallback ์œผ๋กœ ๋„˜์–ด๊ฐ€๊ณ , `true` ๋ฉด ๊ทธ `state`(๊ธฐ๋ณธ `"success"`)ยท`body` ๋กœ ์ฆ‰์‹œ ์‘๋‹ต.
32
+ - `V1RequestHandlerResult` โ€” `{ handled: true; state?: "success" | "error"; body: unknown } | { handled: false }`. `handled` ๋Š” ์ด ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ–ˆ๋Š”์ง€ ์—ฌ๋ถ€. `false` ๋ฉด ๋‹ค์Œ ํ•ธ๋“ค๋Ÿฌยทfallback ์œผ๋กœ ๋„˜์–ด๊ฐ€๊ณ , `true` ๋ฉด ๊ทธ `state`(๊ธฐ๋ณธ `"success"`)ยท`body` ๋กœ ์ฆ‰์‹œ ์‘๋‹ตํ•œ๋‹ค.
33
33
  - `V1RequestHandler` โ€” `(ctx: V1RequestHandlerContext) => V1RequestHandlerResult | Promise<V1RequestHandlerResult>`. ๋™๊ธฐยท๋น„๋™๊ธฐ ๋ชจ๋‘ ๊ฐ€๋Šฅ.
34
34
  - `V1AutoUpdateMethods` โ€” `{ getLastVersion: (platform: string) => Promise<unknown> | unknown }`. `SdAutoUpdateService.getLastVersion` ๋ช…๋ น์˜ fallback ์ธํ„ฐํŽ˜์ด์Šค.
35
35
 
@@ -1,17 +1,17 @@
1
1
  # @simplysm/storage
2
2
 
3
- Node.js ํ™˜๊ฒฝ์—์„œ FTP/FTPS/SFTP ์›๊ฒฉ ์Šคํ† ๋ฆฌ์ง€์— ์ ‘์†ํ•ด ํŒŒ์ผยท๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์—…๋กœ๋“œยท๋‹ค์šด๋กœ๋“œยท์กฐํšŒยท์‚ญ์ œํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ. ํ”„๋กœํ† ์ฝœ๋ณ„ ๊ตฌํ˜„์„ ๋™์ผ ์ธํ„ฐํŽ˜์ด์Šค(`StorageClient`)๋กœ ํ†ต์ผํ•˜๊ณ , `StorageFactory.connect` ์ฝœ๋ฐฑ ํŒจํ„ด์œผ๋กœ ์—ฐ๊ฒฐ/์ข…๋ฃŒ๋ฅผ ์ž๋™ ๊ด€๋ฆฌํ•œ๋‹ค. ์ „ ๊ตฌํ˜„์ด Node ์ „์šฉ(`basic-ftp`ยท`ssh2-sftp-client`ยท`fs`/`os`/`path`/`stream` ์˜์กด)์ด๋ผ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‚ฌ์šฉ ๋ถˆ๊ฐ€.
3
+ Node ์ „์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ. FTP/FTPS/SFTP ์›๊ฒฉ ์Šคํ† ๋ฆฌ์ง€์— ์ ‘์†ํ•ด ํŒŒ์ผยท๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์—…๋กœ๋“œยท๋‹ค์šด๋กœ๋“œยท์กฐํšŒยท์‚ญ์ œํ•œ๋‹ค. ํ”„๋กœํ† ์ฝœ๋ณ„ ๊ตฌํ˜„(`basic-ftp`ยท`ssh2-sftp-client`)์„ ๋‹จ์ผ ์ธํ„ฐํŽ˜์ด์Šค `StorageClient` ๋กœ ํ†ต์ผํ•˜๊ณ , `StorageFactory.connect` ์ฝœ๋ฐฑ ํŒจํ„ด์œผ๋กœ ์—ฐ๊ฒฐ/์ข…๋ฃŒ๋ฅผ ์ž๋™ ๊ด€๋ฆฌํ•œ๋‹ค. `fs`/`os`/`path`/`stream` ์— ์˜์กดํ•˜๋ฏ€๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์‚ฌ์šฉ ๋ถˆ๊ฐ€.
4
4
 
5
5
  ## ์‚ฌ์šฉ ํŠธ๋ฆฌ๊ฑฐ ์ธ๋ฑ์Šค
6
6
 
7
- - **StorageFactory.connect** โ€” ํ”„๋กœํ† ์ฝœยท์ ‘์†์ •๋ณด๋กœ ์—ฐ๊ฒฐ์„ ์—ด๊ณ  ์ฝœ๋ฐฑ ์•ˆ์—์„œ ํŒŒ์ผ ์ž‘์—… ํ›„ ์ž๋™ ์ข…๋ฃŒํ•  ๋•Œ. ์ง์ ‘ ํด๋ผ์ด์–ธํŠธ ์ธ์Šคํ„ด์Šคํ™”๋ณด๋‹ค ๊ถŒ์žฅ๋˜๋Š” ์ง„์ž…์ .
8
- - **StorageClient** โ€” connect ์ฝœ๋ฐฑ์ด ๋ฐ›๋Š” ํŒŒ์ผ ์ž‘์—… ์ธํ„ฐํŽ˜์ด์Šค(mkdir/list/put/readFile/exists/remove/rename/uploadDir ๋“ฑ). ์–ด๋–ค ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•  ๋•Œ.
7
+ - **StorageFactory** โ€” ํ”„๋กœํ† ์ฝœยท์ ‘์†์ •๋ณด๋กœ ์—ฐ๊ฒฐ์„ ์—ด๊ณ  ์ฝœ๋ฐฑ ์•ˆ์—์„œ ํŒŒ์ผ ์ž‘์—… ํ›„ ์ž๋™ ์ข…๋ฃŒํ•  ๋•Œ. ๊ถŒ์žฅ ์ง„์ž…์ .
8
+ - **StorageClient** โ€” connect ์ฝœ๋ฐฑ์ด ๋ฐ›๋Š” ํŒŒ์ผ ์ž‘์—… ์ธํ„ฐํŽ˜์ด์Šค(connect/mkdir/rename/list/readFile/exists/put/uploadDir/remove/close). ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ํ™•์ธํ•  ๋•Œ.
9
9
  - **StorageConnConfig / StorageProtocol / FileInfo** โ€” connect ์ธ์ž ํ˜•ํƒœ, ์ง€์› ํ”„๋กœํ† ์ฝœ ๋ฆฌํ„ฐ๋Ÿด, list ๋ฐ˜ํ™˜ ํ•ญ๋ชฉ ํ˜•ํƒœ๋ฅผ ํ™•์ธํ•  ๋•Œ.
10
- - **FtpStorageClient / SftpStorageClient** โ€” ํŒฉํ† ๋ฆฌ ์—†์ด ์—ฐ๊ฒฐ์„ ์ง์ ‘ ์žก๊ฑฐ๋‚˜(์ˆ˜๋ช…ยท์žฌ์—ฐ๊ฒฐ ์ง์ ‘ ์ œ์–ด), ํ”„๋กœํ† ์ฝœ๋ณ„ ์ธ์ฆยท๋™์ž‘ ์ฐจ์ด๋ฅผ ํ™•์ธํ•  ๋•Œ. ๋น„๊ถŒ์žฅ.
10
+ - **FtpStorageClient / SftpStorageClient** โ€” ํŒฉํ† ๋ฆฌ ์—†์ด ์—ฐ๊ฒฐ ์ˆ˜๋ช…์„ ์ง์ ‘ ์ œ์–ดํ•˜๊ฑฐ๋‚˜ ํ”„๋กœํ† ์ฝœ๋ณ„ ์ธ์ฆยท๋™์ž‘ ์ฐจ์ด๋ฅผ ํ™•์ธํ•  ๋•Œ. ๋น„๊ถŒ์žฅ.
11
11
 
12
12
  ## StorageFactory (์ง„์ž…์ )
13
13
 
14
- ์Šคํ† ๋ฆฌ์ง€ ์ ‘์† ์ง„์ž…์ . ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค ํ•„์š” ์—†์ด ์ •์  `connect` ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค. ์—ฐ๊ฒฐ ์ƒ์„ฑ โ†’ ์ฝœ๋ฐฑ ์‹คํ–‰ โ†’ ์ž๋™ ์ข…๋ฃŒ๋ฅผ ํ•œ ๋ฒˆ์— ๋ฌถ์–ด ์ฒ˜๋ฆฌํ•œ๋‹ค.
14
+ ์Šคํ† ๋ฆฌ์ง€ ์ ‘์† ์ง„์ž…์ . ์ธ์Šคํ„ด์Šคํ™” ์—†์ด ์ •์  `connect` ๋งŒ ํ˜ธ์ถœํ•œ๋‹ค. ์—ฐ๊ฒฐ ์ƒ์„ฑ โ†’ ์ฝœ๋ฐฑ ์‹คํ–‰ โ†’ ์ž๋™ ์ข…๋ฃŒ๋ฅผ ํ•œ ๋ฒˆ์— ๋ฌถ์–ด ์ฒ˜๋ฆฌํ•œ๋‹ค.
15
15
 
16
16
  ```ts
17
17
  class StorageFactory {
@@ -23,10 +23,10 @@ class StorageFactory {
23
23
  }
24
24
  ```
25
25
 
26
- - `type: StorageProtocol` โ€” ์‚ฌ์šฉํ•  ํ”„๋กœํ† ์ฝœ. ๋‚ด๋ถ€์—์„œ `"sftp"` โ†’ `SftpStorageClient`, `"ftps"` โ†’ `FtpStorageClient(secure=true)`, `"ftp"` โ†’ `FtpStorageClient(secure=false)` ๋ฅผ ์ƒ์„ฑ. ๋ณด์•ˆ ์ „์†ก์ด ํ•„์š”ํ•˜๋ฉด `"ftps"`/`"sftp"`.
26
+ - `type: StorageProtocol` โ€” ์‚ฌ์šฉํ•  ํ”„๋กœํ† ์ฝœ. ๋‚ด๋ถ€์—์„œ `"sftp"` โ†’ `new SftpStorageClient()`, `"ftps"` โ†’ `new FtpStorageClient(true)`, `"ftp"` โ†’ `new FtpStorageClient(false)` ๋กœ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ƒ์„ฑ. ๋ณด์•ˆ ์ „์†ก์ด ํ•„์š”ํ•˜๋ฉด `"ftps"`/`"sftp"`.
27
27
  - `config: StorageConnConfig` โ€” ์ ‘์† ์„ค์ •(host/port/user/password). ์•„๋ž˜ StorageConnConfig ์ฐธ์กฐ.
28
- - `fn: (storage: StorageClient) => R | Promise<R>` โ€” ์—ฐ๊ฒฐ๋œ `StorageClient` ๋ฅผ ๋ฐ›์•„ ํŒŒ์ผ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ฝœ๋ฐฑ. ๋ฐ˜ํ™˜๊ฐ’์ด ๊ทธ๋Œ€๋กœ `connect` ์˜ ๊ฒฐ๊ณผ(`Promise<R>`) ๊ฐ€ ๋จ. ๋™๊ธฐยท๋น„๋™๊ธฐ ๋ชจ๋‘ ํ—ˆ์šฉ.
29
- - ๋™์ž‘: `client.connect(config)` ํ›„ `fn` ์‹คํ–‰, `finally` ์—์„œ `client.close()` ํ˜ธ์ถœํ•˜๋ฉฐ ์ข…๋ฃŒ ์ค‘ ์˜ค๋ฅ˜๋Š” ๋ฌด์‹œ(์ด๋ฏธ ์ข…๋ฃŒ๋œ ๊ฒฝ์šฐ ๋Œ€๋น„). ์ฝœ๋ฐฑ์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋‚˜๋„ ์—ฐ๊ฒฐ์€ ๋ฐ˜๋“œ์‹œ ๋‹ซํžˆ๊ณ  ์˜ˆ์™ธ๋Š” ๊ทธ๋Œ€๋กœ ์ „ํŒŒ๋จ.
28
+ - `fn: (storage: StorageClient) => R | Promise<R>` โ€” ์—ฐ๊ฒฐ๋œ `StorageClient` ๋ฅผ ๋ฐ›์•„ ํŒŒ์ผ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ฝœ๋ฐฑ. ๋ฐ˜ํ™˜๊ฐ’์ด ๊ทธ๋Œ€๋กœ `connect` ์˜ ๊ฒฐ๊ณผ(`Promise<R>`)๊ฐ€ ๋จ. ๋™๊ธฐยท๋น„๋™๊ธฐ ๋ชจ๋‘ ํ—ˆ์šฉ.
29
+ - ๋™์ž‘: `client.connect(config)` ์„ฑ๊ณต ํ›„ `fn` ์‹คํ–‰, `finally` ์—์„œ `client.close()` ํ˜ธ์ถœํ•˜๋ฉฐ ์ข…๋ฃŒ ์ค‘ ์˜ค๋ฅ˜๋Š” `.catch(() => {})` ๋กœ ๋ฌด์‹œ(์ด๋ฏธ ์ข…๋ฃŒ๋œ ๊ฒฝ์šฐ ๋Œ€๋น„). ์ฝœ๋ฐฑ์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋‚˜๋„ ์—ฐ๊ฒฐ์€ ๋ฐ˜๋“œ์‹œ ๋‹ซํžˆ๊ณ  ์˜ˆ์™ธ๋Š” ๊ทธ๋Œ€๋กœ ์ „ํŒŒ๋จ.
30
30
 
31
31
  ```ts
32
32
  const names = await StorageFactory.connect("sftp", { host: "10.0.0.1", user: "u", password: "p" }, async (s) => {
@@ -49,9 +49,9 @@ interface StorageConnConfig { host: string; port?: number; user?: string; passwo
49
49
  ```
50
50
 
51
51
  - `host: string` โ€” ์ ‘์† ๋Œ€์ƒ ์„œ๋ฒ„ ํ˜ธ์ŠคํŠธ๋ช… ๋˜๋Š” IP. ํ•„์ˆ˜.
52
- - `port?: number` โ€” ์ ‘์† ํฌํŠธ. ๋ฏธ์ง€์ • ์‹œ ๊ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ธฐ๋ณธ๊ฐ’(FTP 21, SFTP 22) ์‚ฌ์šฉ. ๋น„ํ‘œ์ค€ ํฌํŠธ๋ฉด ๋ช…์‹œ.
53
- - `user?: string` โ€” ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž๋ช…. ๋ฏธ์ง€์ • ์‹œ ์ต๋ช…/๊ธฐ๋ณธ ์‚ฌ์šฉ์ž.
54
- - `password?: string` โ€” ๋กœ๊ทธ์ธ ๋น„๋ฐ€๋ฒˆํ˜ธ. **SFTP ์—์„œ ์ด ๊ฐ’์ด ๋ฏธ์ง€์ •์ด๋ฉด** password ์ธ์ฆ ๋Œ€์‹  `~/.ssh/id_ed25519` ๊ฐœ์ธํ‚ค + SSH agent(`SSH_AUTH_SOCK` ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์žˆ์œผ๋ฉด `agent` ์˜ต์…˜)๋กœ ์ธ์ฆ์„ ์‹œ๋„ํ•˜๊ณ , ํ‚ค ํŒŒ์‹ฑ ์‹คํŒจ(์•”ํ˜ธํ™” ํ‚ค ๋“ฑ) ์‹œ agent ๋‹จ๋… ์žฌ์‹œ๋„. FTP/FTPS ์—์„œ๋Š” ๋ฏธ์ง€์ • ์‹œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ธฐ๋ณธ(์ต๋ช…) ์ฒ˜๋ฆฌ. SFTP ํ‚ค ์ธ์ฆ์„ ์“ฐ๋ ค๋ฉด password ๋ฅผ ๋„˜๊ธฐ์ง€ ๋ง ๊ฒƒ.
52
+ - `port?: number` โ€” ์ ‘์† ํฌํŠธ. ๋ฏธ์ง€์ • ์‹œ ๊ทธ๋Œ€๋กœ `undefined` ๋กœ ์ „๋‹ฌ๋˜์–ด ๊ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ธฐ๋ณธ ํฌํŠธ(`basic-ftp`/`ssh2-sftp-client` ๊ธฐ๋ณธ๊ฐ’)๋ฅผ ์‚ฌ์šฉ. ๋น„ํ‘œ์ค€ ํฌํŠธ๋ฉด ๋ช…์‹œ.
53
+ - `user?: string` โ€” ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž๋ช…(FTP `user`, SFTP `username` ์œผ๋กœ ๋งคํ•‘). ๋ฏธ์ง€์ • ์‹œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ธฐ๋ณธ ์‚ฌ์šฉ์ž.
54
+ - `password?: string` โ€” ๋กœ๊ทธ์ธ ๋น„๋ฐ€๋ฒˆํ˜ธ. **SFTP ์—์„œ ์ด ๊ฐ’์ด `null`(๋ฏธ์ง€์ •)์ด๋ฉด** password ์ธ์ฆ ๋Œ€์‹  `~/.ssh/id_ed25519` ๊ฐœ์ธํ‚ค๋กœ ์ธ์ฆ์„ ์‹œ๋„ํ•˜๊ณ (`SSH_AUTH_SOCK` ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์žˆ์œผ๋ฉด `agent` ์˜ต์…˜๋„ ํ•จ๊ป˜ ์‚ฌ์šฉ), ํ‚ค ํŒŒ์‹ฑ์ด ์‹คํŒจํ•˜๋ฉด(์•”ํ˜ธํ™”๋œ ํ‚ค ๋“ฑ) agent ๋‹จ๋…์œผ๋กœ ์žฌ์‹œ๋„ํ•œ๋‹ค. FTP/FTPS ๋Š” ๋ฏธ์ง€์ • ์‹œ ๊ทธ๋Œ€๋กœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์œ„์ž„. SFTP ํ‚ค ์ธ์ฆ์„ ์“ฐ๋ ค๋ฉด password ๋ฅผ ๋„˜๊ธฐ์ง€ ๋ง ๊ฒƒ.
55
55
 
56
56
  ### StorageProtocol
57
57
 
@@ -59,9 +59,9 @@ interface StorageConnConfig { host: string; port?: number; user?: string; passwo
59
59
  type StorageProtocol = "ftp" | "ftps" | "sftp";
60
60
  ```
61
61
 
62
- - `"ftp"` โ€” ํ‰๋ฌธ FTP. ๋ณด์•ˆ ์ฑ„๋„ ์—†์Œ(`FtpStorageClient` ๋ฅผ `secure=false` ๋กœ ์ƒ์„ฑ). ๋‚ด๋ถ€๋งยทํ…Œ์ŠคํŠธ์—์„œ๋งŒ ๊ถŒ์žฅ.
63
- - `"ftps"` โ€” TLS ๋กœ ์•”ํ˜ธํ™”๋œ FTP(`FtpStorageClient` ๋ฅผ `secure=true` ๋กœ ์ƒ์„ฑ). ์™ธ๋ถ€๋ง FTP ์ ‘์† ์‹œ.
64
- - `"sftp"` โ€” SSH ๊ธฐ๋ฐ˜ SFTP(`SftpStorageClient`). ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ๋ณด์•ˆ ์ „์†ก.
62
+ - `"ftp"` โ€” ํ‰๋ฌธ FTP. `FtpStorageClient(false)` ์ƒ์„ฑ(`secure=false`, TLS ์—†์Œ). ๋‚ด๋ถ€๋งยทํ…Œ์ŠคํŠธ์—์„œ๋งŒ ๊ถŒ์žฅ.
63
+ - `"ftps"` โ€” TLS ๋กœ ์•”ํ˜ธํ™”๋œ FTP. `FtpStorageClient(true)` ์ƒ์„ฑ(`secure=true`). ์™ธ๋ถ€๋ง FTP ์ ‘์† ์‹œ.
64
+ - `"sftp"` โ€” SSH ๊ธฐ๋ฐ˜ SFTP. `SftpStorageClient` ์ƒ์„ฑ. ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ๋ณด์•ˆ ์ „์†ก.
65
65
 
66
66
  ### FileInfo
67
67
 
@@ -72,11 +72,11 @@ interface FileInfo { name: string; isFile: boolean; }
72
72
  ```
73
73
 
74
74
  - `name: string` โ€” ํ•ญ๋ชฉ ์ด๋ฆ„(ํŒŒ์ผ๋ช… ๋˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ๋ช…, ๊ฒฝ๋กœ ์•„๋‹˜).
75
- - `isFile: boolean` โ€” ํŒŒ์ผ์ด๋ฉด `true`, ๋””๋ ‰ํ† ๋ฆฌ๋ฉด `false`. ๋””๋ ‰ํ† ๋ฆฌ ํƒ์ƒ‰ ์‹œ ํŒŒ์ผ๋งŒ ๊ณจ๋ผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ถ„๊ธฐ ๊ธฐ์ค€์œผ๋กœ ์‚ฌ์šฉ. FTP ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ `isFile`, SFTP ๋Š” ํ•ญ๋ชฉ type ์ด `"-"`(์ผ๋ฐ˜ ํŒŒ์ผ)์ธ ๊ฒฝ์šฐ๋งŒ `true`(๋””๋ ‰ํ† ๋ฆฌยท์‹ฌ๋ณผ๋ฆญ ๋งํฌ๋Š” `false`).
75
+ - `isFile: boolean` โ€” ํŒŒ์ผ์ด๋ฉด `true`, ๋””๋ ‰ํ† ๋ฆฌ๋ฉด `false`. ๋””๋ ‰ํ† ๋ฆฌ ํƒ์ƒ‰ ์‹œ ํŒŒ์ผ๋งŒ ๊ณจ๋ผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ถ„๊ธฐ ๊ธฐ์ค€์œผ๋กœ ์‚ฌ์šฉ. FTP ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ `isFile` ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ, SFTP ๋Š” ํ•ญ๋ชฉ `type` ์ด `"-"`(์ผ๋ฐ˜ ํŒŒ์ผ)์ผ ๋•Œ๋งŒ `true`(๋””๋ ‰ํ† ๋ฆฌยท์‹ฌ๋ณผ๋ฆญ ๋งํฌ๋Š” `false`).
76
76
 
77
77
  ### StorageClient
78
78
 
79
- `connect` ์ฝœ๋ฐฑ ์•ˆ์—์„œ ๋ฐ›๋Š” ํŒŒ์ผ ์ž‘์—… ์ธํ„ฐํŽ˜์ด์Šค. `FtpStorageClient`ยท`SftpStorageClient` ๊ฐ€ ๊ตฌํ˜„. ๋ชจ๋“  ๋ฉ”์„œ๋“œ๋Š” `Promise` ๋ฐ˜ํ™˜. ๊ฒฝ๋กœ๋Š” ์›๊ฒฉ ๊ธฐ์ค€ ๊ฒฝ๋กœ ๋ฌธ์ž์—ด.
79
+ `connect` ์ฝœ๋ฐฑ ์•ˆ์—์„œ ๋ฐ›๋Š” ํŒŒ์ผ ์ž‘์—… ์ธํ„ฐํŽ˜์ด์Šค. `FtpStorageClient`ยท`SftpStorageClient` ๊ฐ€ ๊ตฌํ˜„. ๋ชจ๋“  ๋ฉ”์„œ๋“œ๋Š” `Promise` ๋ฐ˜ํ™˜. ๊ฒฝ๋กœ ์ธ์ž๋Š” ์›๊ฒฉ ๊ธฐ์ค€ ๊ฒฝ๋กœ ๋ฌธ์ž์—ด.
80
80
 
81
81
  ```ts
82
82
  interface StorageClient {
@@ -93,15 +93,15 @@ interface StorageClient {
93
93
  }
94
94
  ```
95
95
 
96
- - `connect(config)` โ€” ์„œ๋ฒ„์— ์—ฐ๊ฒฐ. ์ด๋ฏธ ์—ฐ๊ฒฐ๋œ ์ธ์Šคํ„ด์Šค์—์„œ ์žฌํ˜ธ์ถœํ•˜๋ฉด `SdError` throw(๋จผ์ € `close()` ํ•„์š”). `StorageFactory.connect` ์‚ฌ์šฉ ์‹œ ์ง์ ‘ ํ˜ธ์ถœ ๋ถˆํ•„์š”. ์—ฐ๊ฒฐ ๋„์ค‘ ์‹คํŒจํ•˜๋ฉด ๋‚ด๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—ฐ๊ฒฐ์„ ๋‹ซ๊ณ  ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ throw.
97
- - `mkdir(dirPath)` โ€” ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ. ๋ถ€๋ชจ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์—†์œผ๋ฉด ํ•จ๊ป˜ ์ƒ์„ฑ(FTP `ensureDir`, SFTP ์žฌ๊ท€ `mkdir`).
98
- - `rename(fromPath, toPath)` โ€” ํŒŒ์ผ/๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ ์ด๋™ยท์ด๋ฆ„ ๋ณ€๊ฒฝ. `toPath` ๋กœ ์˜ฎ๊ธฐ๊ฑฐ๋‚˜ ์ƒˆ ์ด๋ฆ„์„ ๋ถ€์—ฌํ•  ๋•Œ.
96
+ - `connect(config)` โ€” ์„œ๋ฒ„์— ์—ฐ๊ฒฐ. ์ด๋ฏธ ์—ฐ๊ฒฐ๋œ ์ธ์Šคํ„ด์Šค์—์„œ ์žฌํ˜ธ์ถœํ•˜๋ฉด `SdError`("์ด๋ฏธ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค") throw(๋จผ์ € `close()` ํ•„์š”). ์—ฐ๊ฒฐ ๋„์ค‘ ์‹คํŒจํ•˜๋ฉด ๋‚ด๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—ฐ๊ฒฐ์„ ๋‹ซ๊ณ (FTP `close()`, SFTP `end()`) ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ throw. `StorageFactory.connect` ์‚ฌ์šฉ ์‹œ ์ง์ ‘ ํ˜ธ์ถœ ๋ถˆํ•„์š”.
97
+ - `mkdir(dirPath)` โ€” ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ. ๋ถ€๋ชจ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์—†์œผ๋ฉด ํ•จ๊ป˜ ์ƒ์„ฑ(FTP `ensureDir`, SFTP ์žฌ๊ท€ `mkdir(path, true)`).
98
+ - `rename(fromPath, toPath)` โ€” ํŒŒ์ผ/๋””๋ ‰ํ† ๋ฆฌ๋ฅผ `fromPath` ์—์„œ `toPath` ๋กœ ์ด๋™ยท์ด๋ฆ„ ๋ณ€๊ฒฝ.
99
99
  - `list(dirPath)` โ€” ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด ํ•ญ๋ชฉ์„ `FileInfo[]` ๋กœ ๋ฐ˜ํ™˜. ๋ชฉ๋ก์„ ํŒŒ์ผ/ํด๋”๋กœ ๊ฐˆ๋ผ ์ฒ˜๋ฆฌํ•  ๋•Œ.
100
- - `readFile(filePath)` โ€” ์›๊ฒฉ ํŒŒ์ผ ์ „์ฒด๋ฅผ `Bytes`(Uint8Array) ๋กœ ๋ฉ”๋ชจ๋ฆฌ์— ๋‹ค์šด๋กœ๋“œ(์ŠคํŠธ๋ฆฌ๋ฐ ์•„๋‹˜ โ€” ํฐ ํŒŒ์ผ์€ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€๋‹ด). ํ…์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•˜๋ฉด ํ˜ธ์ถœ ์ธก์—์„œ ๋””์ฝ”๋”ฉ. SFTP ๋Š” ์‘๋‹ต์ด ์˜ˆ์ƒ ํƒ€์ž…(Buffer/string) ์ด ์•„๋‹ˆ๋ฉด `SdError` throw.
101
- - `exists(filePath)` โ€” ํŒŒ์ผ/๋””๋ ‰ํ† ๋ฆฌ ์กด์žฌ ์—ฌ๋ถ€. **๋ชจ๋“  ์˜ˆ์™ธ(๋ถ€๋ชจ ์—†์Œยท๊ถŒํ•œยท๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜ ํฌํ•จ) ์— ๋Œ€ํ•ด `false` ๋ฐ˜ํ™˜** โ€” throw ์—†์Œ์ด๋ฏ€๋กœ `true` ๋งŒ "ํ™•์‹คํžˆ ์กด์žฌ"๋กœ ์‹ ๋ขฐํ•œ๋‹ค. FTP ๋Š” `size()` ๋กœ ํŒŒ์ผ์„ O(1) ํ™•์ธ ํ›„ ์‹คํŒจ ์‹œ ๋ถ€๋ชจ ๋””๋ ‰ํ† ๋ฆฌ ๋ชฉ๋ก์œผ๋กœ ๋””๋ ‰ํ† ๋ฆฌ ํ™•์ธ(์Šฌ๋ž˜์‹œ ์—†๋Š” ๊ฒฝ๋กœ๋Š” ๋ฃจํŠธ `/` ๊ธฐ์ค€์ด๋ผ ํ•ญ๋ชฉ ๋งŽ์€ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ๋Š” ๋А๋ ค์งˆ ์ˆ˜ ์žˆ์Œ). SFTP ๋Š” `exists()` ๊ฒฐ๊ณผ๊ฐ€ ๋ฌธ์ž์—ด(`'d'`/`'-'`/`'l'`)์ด๋ฉด ์กด์žฌ๋กœ ํŒ์ •. ์ž‘์—… ์ „ ๋ถ„๊ธฐ ํ™•์ธ์— ์‚ฌ์šฉ.
102
- - `put(localPathOrBuffer, storageFilePath)` โ€” ๋‹จ์ผ ํŒŒ์ผ ์—…๋กœ๋“œ. ์ฒซ ์ธ์ž๊ฐ€ `string` ์ด๋ฉด ๋กœ์ปฌ ํŒŒ์ผ ๊ฒฝ๋กœ์—์„œ, `Bytes` ๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ฐ”์ดํŠธ์—์„œ ์—…๋กœ๋“œ. ์ƒ์„ฑํ•œ ์ฝ˜ํ…์ธ ๋ฅผ ๋ฐ”๋กœ ์˜ฌ๋ฆด ๋• `Bytes` ๋กœ ์ „๋‹ฌ.
103
- - `uploadDir(fromPath, toPath)` โ€” ๋กœ์ปฌ ๋””๋ ‰ํ† ๋ฆฌ ์ „์ฒด๋ฅผ ์›๊ฒฉ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์žฌ๊ท€ ์—…๋กœ๋“œ. ๋นŒ๋“œ ์‚ฐ์ถœ๋ฌผ ํด๋”๋ฅผ ํ†ต์งธ๋กœ ๋ฐฐํฌํ•  ๋•Œ.
104
- - `remove(filePath)` โ€” ์›๊ฒฉ ํŒŒ์ผ ์‚ญ์ œ.
100
+ - `readFile(filePath)` โ€” ์›๊ฒฉ ํŒŒ์ผ ์ „์ฒด๋ฅผ `Bytes`(Uint8Array)๋กœ ๋ฉ”๋ชจ๋ฆฌ์— ๋‹ค์šด๋กœ๋“œ(์ŠคํŠธ๋ฆฌ๋ฐ ์•„๋‹˜ โ€” ํฐ ํŒŒ์ผ์€ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€๋‹ด). ํ…์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•˜๋ฉด ํ˜ธ์ถœ ์ธก์—์„œ ๋””์ฝ”๋”ฉ(์˜ˆ: `new TextDecoder().decode(...)`). SFTP ๋Š” ์‘๋‹ต์ด ์˜ˆ์ƒ ํƒ€์ž…(Uint8Array/string)์ด ์•„๋‹ˆ๋ฉด `SdError`("์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ์‘๋‹ต ํƒ€์ž…์ž…๋‹ˆ๋‹ค") throw.
101
+ - `exists(filePath)` โ€” ํŒŒ์ผ/๋””๋ ‰ํ† ๋ฆฌ ์กด์žฌ ์—ฌ๋ถ€. **๋ชจ๋“  ์˜ˆ์™ธ(๋ถ€๋ชจ ๋””๋ ‰ํ† ๋ฆฌ ์—†์Œยท๊ถŒํ•œยท๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜ ํฌํ•จ)์— ๋Œ€ํ•ด `false` ๋ฐ˜ํ™˜** โ€” throw ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ `true` ๋งŒ "ํ™•์‹คํžˆ ์กด์žฌ"๋กœ ์‹ ๋ขฐํ•œ๋‹ค. FTP ๋Š” `size()` ๋กœ ํŒŒ์ผ์„ O(1) ํ™•์ธ ํ›„ ์‹คํŒจ ์‹œ ๋ถ€๋ชจ ๋””๋ ‰ํ† ๋ฆฌ ๋ชฉ๋ก(`list`)์œผ๋กœ ๋””๋ ‰ํ† ๋ฆฌ/ํŒŒ์ผ์„ ์žฌํ™•์ธ(์Šฌ๋ž˜์‹œ ์—†๋Š” ๊ฒฝ๋กœ๋Š” ๋ฃจํŠธ `/` ๊ธฐ์ค€ โ€” ํ•ญ๋ชฉ์ด ๋งŽ์€ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ๋Š” ๋А๋ ค์งˆ ์ˆ˜ ์žˆ์Œ). SFTP ๋Š” `exists()` ๊ฒฐ๊ณผ๊ฐ€ ๋ฌธ์ž์—ด(`'d'` ๋””๋ ‰ํ† ๋ฆฌ/`'-'` ํŒŒ์ผ/`'l'` ์‹ฌ๋ณผ๋ฆญ ๋งํฌ)์ด๋ฉด ์กด์žฌ๋กœ ํŒ์ •. ์ž‘์—… ์ „ ๋ถ„๊ธฐ ํ™•์ธ์— ์‚ฌ์šฉ.
102
+ - `put(localPathOrBuffer, storageFilePath)` โ€” ๋‹จ์ผ ํŒŒ์ผ ์—…๋กœ๋“œ. ์ฒซ ์ธ์ž๊ฐ€ `string` ์ด๋ฉด ๋กœ์ปฌ ํŒŒ์ผ ๊ฒฝ๋กœ์—์„œ(FTP `uploadFrom`, SFTP `fastPut`), `Bytes` ๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ฐ”์ดํŠธ์—์„œ ์—…๋กœ๋“œ(FTP ๋Š” `Readable.from` ์ŠคํŠธ๋ฆผ, SFTP ๋Š” `Buffer.from` ๋ณ€ํ™˜). ์ƒ์„ฑํ•œ ์ฝ˜ํ…์ธ ๋ฅผ ๋ฐ”๋กœ ์˜ฌ๋ฆด ๋• `Bytes` ๋กœ ์ „๋‹ฌ.
103
+ - `uploadDir(fromPath, toPath)` โ€” ๋กœ์ปฌ ๋””๋ ‰ํ† ๋ฆฌ `fromPath` ์ „์ฒด๋ฅผ ์›๊ฒฉ ๋””๋ ‰ํ† ๋ฆฌ `toPath` ๋กœ ์žฌ๊ท€ ์—…๋กœ๋“œ. ๋นŒ๋“œ ์‚ฐ์ถœ๋ฌผ ํด๋”๋ฅผ ํ†ต์งธ๋กœ ๋ฐฐํฌํ•  ๋•Œ.
104
+ - `remove(filePath)` โ€” ์›๊ฒฉ ํŒŒ์ผ ์‚ญ์ œ(FTP `remove`, SFTP `delete`).
105
105
  - `close()` โ€” ์—ฐ๊ฒฐ ์ข…๋ฃŒ. ์ด๋ฏธ ์ข…๋ฃŒ/๋ฏธ์—ฐ๊ฒฐ ์ƒํƒœ์—์„œ ํ˜ธ์ถœํ•ด๋„ ์˜ค๋ฅ˜ ์—†์Œ. ์ข…๋ฃŒ ํ›„ ๊ฐ™์€ ์ธ์Šคํ„ด์Šค์—์„œ `connect()` ๋กœ ์žฌ์—ฐ๊ฒฐ ๊ฐ€๋Šฅ. `StorageFactory.connect` ์‚ฌ์šฉ ์‹œ ์ง์ ‘ ํ˜ธ์ถœ ๋ถˆํ•„์š”.
106
106
 
107
107
  ๋ฏธ์—ฐ๊ฒฐ ์ƒํƒœ์—์„œ ์ž‘์—… ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ชจ๋“  ๊ตฌํ˜„์ฒด๊ฐ€ `SdError`("์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค") throw.
@@ -115,9 +115,9 @@ new FtpStorageClient(secure?: boolean) // secure=true โ†’ FTPS, ์ƒ๋žต/false
115
115
  new SftpStorageClient()
116
116
  ```
117
117
 
118
- - `FtpStorageClient` ์˜ `secure` ์ƒ์„ฑ์ž ์ธ์ž โ€” `true` ๋ฉด TLS(FTPS), ์ƒ๋žต/`false`(๊ธฐ๋ณธ) ๋ฉด ํ‰๋ฌธ FTP. ํŒฉํ† ๋ฆฌ๋Š” `"ftps"`โ†’`true`, `"ftp"`โ†’`false` ๋กœ ๋งคํ•‘.
118
+ - `FtpStorageClient` ์˜ `secure` ์ƒ์„ฑ์ž ์ธ์ž โ€” `true` ๋ฉด TLS(FTPS), ์ƒ๋žต/`false`(๊ธฐ๋ณธ)๋ฉด ํ‰๋ฌธ FTP. ํŒฉํ† ๋ฆฌ๋Š” `"ftps"`โ†’`true`, `"ftp"`โ†’`false` ๋กœ ๋งคํ•‘.
119
119
  - `SftpStorageClient` ๋Š” ์ƒ์„ฑ์ž ์ธ์ž ์—†์Œ. password ๋ฏธ์ง€์ • ์‹œ ํ‚ค/agent ์ธ์ฆ ๊ฒฝ๋กœ๋ฅผ ํƒ„๋‹ค(StorageConnConfig ์˜ `password` ํ’€์ด ์ฐธ์กฐ).
120
- - ์ง์ ‘ ์‚ฌ์šฉ ์‹œ `connect()` โ†’ ์ž‘์—… โ†’ `close()` ์ˆœ์œผ๋กœ ํ˜ธ์ถœํ•˜๊ณ , ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ์—๋„ `close()` ๊ฐ€ ํ˜ธ์ถœ๋˜๋„๋ก `try/finally` ๋กœ ๊ฐ์Œ€ ๊ฒƒ. ๋™์ผ ์ธ์Šคํ„ด์Šค์—์„œ `close()` ์—†์ด `connect()` ๋ฅผ ์žฌํ˜ธ์ถœํ•˜๋ฉด ์—ฐ๊ฒฐ ๋ˆ„์ˆ˜๋กœ throw.
120
+ - ์ง์ ‘ ์‚ฌ์šฉ ์‹œ `connect()` โ†’ ์ž‘์—… โ†’ `close()` ์ˆœ์œผ๋กœ ํ˜ธ์ถœํ•˜๊ณ , ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ์—๋„ `close()` ๊ฐ€ ํ˜ธ์ถœ๋˜๋„๋ก `try/finally` ๋กœ ๊ฐ์Œ€ ๊ฒƒ. ๋™์ผ ์ธ์Šคํ„ด์Šค์—์„œ `close()` ์—†์ด `connect()` ๋ฅผ ์žฌํ˜ธ์ถœํ•˜๋ฉด `SdError` throw(์—ฐ๊ฒฐ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€).
121
121
 
122
122
  ```ts
123
123
  const client = new SftpStorageClient();
@@ -1,5 +1,7 @@
1
1
  # ํด๋ผ์ด์–ธํŠธ ํ™”๋ฉด ์ž‘์„ฑ ๋งค๋‰ด์–ผ
2
2
 
3
+ ํ”„๋ฆฌ๋ Œ๋”(SSG) ๋Œ€์ƒ ํ™”๋ฉด์„ ์ž‘์„ฑํ•  ๋•Œ๋Š” [client-ssg.md](./client-ssg.md) ์˜ SSR-safeยท์„œ๋ฒ„ ์—ฐ๊ฒฐ ์ œ์•ฝ์„ ํ•จ๊ป˜ ์ ์šฉ.
4
+
3
5
  ## ํŒŒ์ผ๋ช…ยท์—ญํ• ยท์œ„์น˜
4
6
 
5
7
  ํ™”๋ฉด ํŒŒ์ผ๋ช…์€ `<domain>.<์—ญํ• >.ts` ํ˜•์‹. ์—ญํ•  ์ ‘๋ฏธ์‚ฌ๋กœ ์ฑ…์ž„์„ ํ‘œ์‹œ.
@@ -643,7 +645,13 @@ async onSubmit(): Promise<void> {
643
645
 
644
646
  ```html
645
647
  <sd-crud-list ...>
646
- <ng-template #filterTpl>...</ng-template>
648
+ <ng-template #filterTpl>
649
+ <!-- sd-crud-list ๊ฐ€ ์ด๋ฏธ form-box-inline ์œผ๋กœ ๊ฐ์‹ธ๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ ๋‹ค์‹œ ๋ฌถ์ง€ ์•Š์Œ -->
650
+ <div>
651
+ <label>๊ฒ€์ƒ‰์–ด</label>
652
+ <sd-textfield [type]="'text'" [(value)]="filter().searchText" (valueChange)="mark(filter)" />
653
+ </div>
654
+ </ng-template>
647
655
 
648
656
  <sd-sheet-column [key]="..." [header]="...">
649
657
  <ng-template [cell]="items()" let-item="item">...</ng-template>