@simplysm/core-node 13.0.0-beta.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 (111) hide show
  1. package/.cache/typecheck-node.tsbuildinfo +1 -0
  2. package/.cache/typecheck-tests-node.tsbuildinfo +1 -0
  3. package/README.md +375 -0
  4. package/dist/core-common/src/common.types.d.ts +74 -0
  5. package/dist/core-common/src/common.types.d.ts.map +1 -0
  6. package/dist/core-common/src/env.d.ts +6 -0
  7. package/dist/core-common/src/env.d.ts.map +1 -0
  8. package/dist/core-common/src/errors/argument-error.d.ts +25 -0
  9. package/dist/core-common/src/errors/argument-error.d.ts.map +1 -0
  10. package/dist/core-common/src/errors/not-implemented-error.d.ts +29 -0
  11. package/dist/core-common/src/errors/not-implemented-error.d.ts.map +1 -0
  12. package/dist/core-common/src/errors/sd-error.d.ts +27 -0
  13. package/dist/core-common/src/errors/sd-error.d.ts.map +1 -0
  14. package/dist/core-common/src/errors/timeout-error.d.ts +31 -0
  15. package/dist/core-common/src/errors/timeout-error.d.ts.map +1 -0
  16. package/dist/core-common/src/extensions/arr-ext.d.ts +15 -0
  17. package/dist/core-common/src/extensions/arr-ext.d.ts.map +1 -0
  18. package/dist/core-common/src/extensions/arr-ext.helpers.d.ts +19 -0
  19. package/dist/core-common/src/extensions/arr-ext.helpers.d.ts.map +1 -0
  20. package/dist/core-common/src/extensions/arr-ext.types.d.ts +215 -0
  21. package/dist/core-common/src/extensions/arr-ext.types.d.ts.map +1 -0
  22. package/dist/core-common/src/extensions/map-ext.d.ts +57 -0
  23. package/dist/core-common/src/extensions/map-ext.d.ts.map +1 -0
  24. package/dist/core-common/src/extensions/set-ext.d.ts +36 -0
  25. package/dist/core-common/src/extensions/set-ext.d.ts.map +1 -0
  26. package/dist/core-common/src/features/debounce-queue.d.ts +53 -0
  27. package/dist/core-common/src/features/debounce-queue.d.ts.map +1 -0
  28. package/dist/core-common/src/features/event-emitter.d.ts +66 -0
  29. package/dist/core-common/src/features/event-emitter.d.ts.map +1 -0
  30. package/dist/core-common/src/features/serial-queue.d.ts +47 -0
  31. package/dist/core-common/src/features/serial-queue.d.ts.map +1 -0
  32. package/dist/core-common/src/index.d.ts +32 -0
  33. package/dist/core-common/src/index.d.ts.map +1 -0
  34. package/dist/core-common/src/types/date-only.d.ts +152 -0
  35. package/dist/core-common/src/types/date-only.d.ts.map +1 -0
  36. package/dist/core-common/src/types/date-time.d.ts +96 -0
  37. package/dist/core-common/src/types/date-time.d.ts.map +1 -0
  38. package/dist/core-common/src/types/lazy-gc-map.d.ts +80 -0
  39. package/dist/core-common/src/types/lazy-gc-map.d.ts.map +1 -0
  40. package/dist/core-common/src/types/time.d.ts +68 -0
  41. package/dist/core-common/src/types/time.d.ts.map +1 -0
  42. package/dist/core-common/src/types/uuid.d.ts +35 -0
  43. package/dist/core-common/src/types/uuid.d.ts.map +1 -0
  44. package/dist/core-common/src/utils/bytes.d.ts +51 -0
  45. package/dist/core-common/src/utils/bytes.d.ts.map +1 -0
  46. package/dist/core-common/src/utils/date-format.d.ts +90 -0
  47. package/dist/core-common/src/utils/date-format.d.ts.map +1 -0
  48. package/dist/core-common/src/utils/json.d.ts +34 -0
  49. package/dist/core-common/src/utils/json.d.ts.map +1 -0
  50. package/dist/core-common/src/utils/num.d.ts +60 -0
  51. package/dist/core-common/src/utils/num.d.ts.map +1 -0
  52. package/dist/core-common/src/utils/obj.d.ts +258 -0
  53. package/dist/core-common/src/utils/obj.d.ts.map +1 -0
  54. package/dist/core-common/src/utils/path.d.ts +23 -0
  55. package/dist/core-common/src/utils/path.d.ts.map +1 -0
  56. package/dist/core-common/src/utils/primitive.d.ts +18 -0
  57. package/dist/core-common/src/utils/primitive.d.ts.map +1 -0
  58. package/dist/core-common/src/utils/str.d.ts +103 -0
  59. package/dist/core-common/src/utils/str.d.ts.map +1 -0
  60. package/dist/core-common/src/utils/template-strings.d.ts +84 -0
  61. package/dist/core-common/src/utils/template-strings.d.ts.map +1 -0
  62. package/dist/core-common/src/utils/transferable.d.ts +47 -0
  63. package/dist/core-common/src/utils/transferable.d.ts.map +1 -0
  64. package/dist/core-common/src/utils/wait.d.ts +19 -0
  65. package/dist/core-common/src/utils/wait.d.ts.map +1 -0
  66. package/dist/core-common/src/utils/xml.d.ts +36 -0
  67. package/dist/core-common/src/utils/xml.d.ts.map +1 -0
  68. package/dist/core-common/src/zip/sd-zip.d.ts +80 -0
  69. package/dist/core-common/src/zip/sd-zip.d.ts.map +1 -0
  70. package/dist/core-node/src/features/fs-watcher.d.ts +70 -0
  71. package/dist/core-node/src/features/fs-watcher.d.ts.map +1 -0
  72. package/dist/core-node/src/index.d.ts +7 -0
  73. package/dist/core-node/src/index.d.ts.map +1 -0
  74. package/dist/core-node/src/utils/fs.d.ts +197 -0
  75. package/dist/core-node/src/utils/fs.d.ts.map +1 -0
  76. package/dist/core-node/src/utils/path.d.ts +75 -0
  77. package/dist/core-node/src/utils/path.d.ts.map +1 -0
  78. package/dist/core-node/src/worker/create-worker.d.ts +23 -0
  79. package/dist/core-node/src/worker/create-worker.d.ts.map +1 -0
  80. package/dist/core-node/src/worker/types.d.ts +67 -0
  81. package/dist/core-node/src/worker/types.d.ts.map +1 -0
  82. package/dist/core-node/src/worker/worker.d.ts +27 -0
  83. package/dist/core-node/src/worker/worker.d.ts.map +1 -0
  84. package/dist/features/fs-watcher.js +100 -0
  85. package/dist/features/fs-watcher.js.map +7 -0
  86. package/dist/index.js +7 -0
  87. package/dist/index.js.map +7 -0
  88. package/dist/utils/fs.js +305 -0
  89. package/dist/utils/fs.js.map +7 -0
  90. package/dist/utils/path.js +48 -0
  91. package/dist/utils/path.js.map +7 -0
  92. package/dist/worker/create-worker.js +85 -0
  93. package/dist/worker/create-worker.js.map +7 -0
  94. package/dist/worker/types.js +1 -0
  95. package/dist/worker/types.js.map +7 -0
  96. package/dist/worker/worker.js +142 -0
  97. package/dist/worker/worker.js.map +7 -0
  98. package/lib/worker-dev-proxy.js +12 -0
  99. package/package.json +23 -0
  100. package/src/features/fs-watcher.ts +176 -0
  101. package/src/index.ts +11 -0
  102. package/src/utils/fs.ts +550 -0
  103. package/src/utils/path.ts +128 -0
  104. package/src/worker/create-worker.ts +141 -0
  105. package/src/worker/types.ts +86 -0
  106. package/src/worker/worker.ts +207 -0
  107. package/tests/utils/fs-watcher.spec.ts +295 -0
  108. package/tests/utils/fs.spec.ts +754 -0
  109. package/tests/utils/path.spec.ts +192 -0
  110. package/tests/worker/fixtures/test-worker.ts +35 -0
  111. package/tests/worker/sd-worker.spec.ts +183 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/worker/create-worker.ts"],
4
+ "sourcesContent": ["import { parentPort } from \"worker_threads\";\nimport { SdError, transferableDecode, transferableEncode } from \"@simplysm/core-common\";\nimport type { WorkerRequest, WorkerResponse } from \"./types\";\n\n//#region createSdWorker\n\n/**\n * \uC6CC\uCEE4 \uC2A4\uB808\uB4DC\uC5D0\uC11C \uC0AC\uC6A9\uD560 \uC6CC\uCEE4 \uD329\uD1A0\uB9AC.\n *\n * @example\n * // \uC774\uBCA4\uD2B8 \uC5C6\uB294 \uC6CC\uCEE4\n * export default createWorker({\n * add: (a: number, b: number) => a + b,\n * });\n *\n * // \uC774\uBCA4\uD2B8 \uC788\uB294 \uC6CC\uCEE4\n * interface MyEvents { progress: number; }\n * const methods = {\n * calc: (x: number) => { sender.send(\"progress\", 50); return x * 2; },\n * };\n * const sender = createWorker<typeof methods, MyEvents>(methods);\n * export default sender;\n */\nexport function createWorker<\n TMethods extends Record<string, (...args: any[]) => unknown>,\n TEvents extends Record<string, unknown> = Record<string, never>,\n>(\n methods: TMethods,\n): {\n send<K extends keyof TEvents & string>(event: K, data?: TEvents[K]): void;\n __methods: TMethods;\n __events: TEvents;\n} {\n if (parentPort === null) {\n throw new SdError(\"\uC774 \uC2A4\uD06C\uB9BD\uD2B8\uB294 \uC6CC\uCEE4 \uC2A4\uB808\uB4DC\uC5D0\uC11C \uC2E4\uD589\uB418\uC5B4\uC57C \uD569\uB2C8\uB2E4 (parentPort \uD544\uC694).\");\n }\n\n const port = parentPort;\n\n // Worker \uC2A4\uB808\uB4DC\uC758 stdout\uC740 \uBA54\uC778 \uC2A4\uB808\uB4DC\uB85C \uC790\uB3D9 \uC804\uB2EC\uB418\uC9C0 \uC54A\uC74C\n // stdout.write\uB97C \uAC00\uB85C\uCC44\uC11C \uBA54\uC2DC\uC9C0 \uD504\uB85C\uD1A0\uCF5C\uC744 \uD1B5\uD574 \uBA54\uC778 \uC2A4\uB808\uB4DC\uB85C \uC804\uB2EC\n process.stdout.write = (\n chunk: string | Uint8Array,\n encodingOrCallback?: BufferEncoding | ((err?: Error) => void),\n callback?: (err?: Error) => void,\n ): boolean => {\n const body = typeof chunk === \"string\" ? chunk : new TextDecoder().decode(chunk);\n const response: WorkerResponse = { type: \"log\", body };\n const serialized = transferableEncode(response);\n port.postMessage(serialized.result, serialized.transferList);\n\n const cb = typeof encodingOrCallback === \"function\" ? encodingOrCallback : callback;\n if (cb) {\n queueMicrotask(() => cb());\n }\n\n return true;\n };\n\n port.on(\"message\", async (serializedRequest: unknown) => {\n const decoded = transferableDecode(serializedRequest);\n\n // \uC694\uCCAD \uAD6C\uC870 \uAC80\uC99D\n if (\n decoded == null ||\n typeof decoded !== \"object\" ||\n !(\"id\" in decoded) ||\n !(\"method\" in decoded) ||\n !(\"params\" in decoded)\n ) {\n let decodedStr: string;\n try {\n decodedStr = JSON.stringify(decoded);\n } catch {\n decodedStr = String(decoded);\n }\n const errorResponse: WorkerResponse = {\n type: \"error\",\n request: { id: \"unknown\", method: \"unknown\", params: [] },\n body: new SdError(`\uD615\uC2DD\uC774 \uC798\uBABB\uB41C \uC6CC\uCEE4 \uC694\uCCAD: ${decodedStr}`),\n };\n const serialized = transferableEncode(errorResponse);\n port.postMessage(serialized.result, serialized.transferList);\n return;\n }\n const request = decoded as WorkerRequest;\n\n const methodFn = methods[request.method] as ((...args: unknown[]) => unknown) | undefined;\n\n if (methodFn == null) {\n const response: WorkerResponse = {\n request,\n type: \"error\",\n body: new SdError(`\uC54C \uC218 \uC5C6\uB294 \uBA54\uC11C\uB4DC: ${request.method}`),\n };\n\n const serialized = transferableEncode(response);\n port.postMessage(serialized.result, serialized.transferList);\n return;\n }\n\n try {\n const result = await methodFn(...request.params);\n\n const response: WorkerResponse = {\n request,\n type: \"return\",\n body: result,\n };\n\n const serialized = transferableEncode(response);\n port.postMessage(serialized.result, serialized.transferList);\n } catch (err) {\n const response: WorkerResponse = {\n request,\n type: \"error\",\n body: err instanceof Error ? err : new Error(String(err)),\n };\n\n const serialized = transferableEncode(response);\n port.postMessage(serialized.result, serialized.transferList);\n }\n });\n\n return {\n __methods: methods,\n __events: {} as TEvents,\n send<K extends keyof TEvents & string>(event: K, data?: TEvents[K]) {\n const response: WorkerResponse = {\n type: \"event\",\n event,\n body: data,\n };\n\n const serialized = transferableEncode(response);\n port.postMessage(serialized.result, serialized.transferList);\n },\n };\n}\n\n//#endregion\n"],
5
+ "mappings": "AAAA,SAAS,kBAAkB;AAC3B,SAAS,SAAS,oBAAoB,0BAA0B;AAsBzD,SAAS,aAId,SAKA;AACA,MAAI,eAAe,MAAM;AACvB,UAAM,IAAI,QAAQ,gKAA6C;AAAA,EACjE;AAEA,QAAM,OAAO;AAIb,UAAQ,OAAO,QAAQ,CACrB,OACA,oBACA,aACY;AACZ,UAAM,OAAO,OAAO,UAAU,WAAW,QAAQ,IAAI,YAAY,EAAE,OAAO,KAAK;AAC/E,UAAM,WAA2B,EAAE,MAAM,OAAO,KAAK;AACrD,UAAM,aAAa,mBAAmB,QAAQ;AAC9C,SAAK,YAAY,WAAW,QAAQ,WAAW,YAAY;AAE3D,UAAM,KAAK,OAAO,uBAAuB,aAAa,qBAAqB;AAC3E,QAAI,IAAI;AACN,qBAAe,MAAM,GAAG,CAAC;AAAA,IAC3B;AAEA,WAAO;AAAA,EACT;AAEA,OAAK,GAAG,WAAW,OAAO,sBAA+B;AACvD,UAAM,UAAU,mBAAmB,iBAAiB;AAGpD,QACE,WAAW,QACX,OAAO,YAAY,YACnB,EAAE,QAAQ,YACV,EAAE,YAAY,YACd,EAAE,YAAY,UACd;AACA,UAAI;AACJ,UAAI;AACF,qBAAa,KAAK,UAAU,OAAO;AAAA,MACrC,QAAQ;AACN,qBAAa,OAAO,OAAO;AAAA,MAC7B;AACA,YAAM,gBAAgC;AAAA,QACpC,MAAM;AAAA,QACN,SAAS,EAAE,IAAI,WAAW,QAAQ,WAAW,QAAQ,CAAC,EAAE;AAAA,QACxD,MAAM,IAAI,QAAQ,oEAAkB,UAAU,EAAE;AAAA,MAClD;AACA,YAAM,aAAa,mBAAmB,aAAa;AACnD,WAAK,YAAY,WAAW,QAAQ,WAAW,YAAY;AAC3D;AAAA,IACF;AACA,UAAM,UAAU;AAEhB,UAAM,WAAW,QAAQ,QAAQ,MAAM;AAEvC,QAAI,YAAY,MAAM;AACpB,YAAM,WAA2B;AAAA,QAC/B;AAAA,QACA,MAAM;AAAA,QACN,MAAM,IAAI,QAAQ,kDAAe,QAAQ,MAAM,EAAE;AAAA,MACnD;AAEA,YAAM,aAAa,mBAAmB,QAAQ;AAC9C,WAAK,YAAY,WAAW,QAAQ,WAAW,YAAY;AAC3D;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,SAAS,GAAG,QAAQ,MAAM;AAE/C,YAAM,WAA2B;AAAA,QAC/B;AAAA,QACA,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAEA,YAAM,aAAa,mBAAmB,QAAQ;AAC9C,WAAK,YAAY,WAAW,QAAQ,WAAW,YAAY;AAAA,IAC7D,SAAS,KAAK;AACZ,YAAM,WAA2B;AAAA,QAC/B;AAAA,QACA,MAAM;AAAA,QACN,MAAM,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAAA,MAC1D;AAEA,YAAM,aAAa,mBAAmB,QAAQ;AAC9C,WAAK,YAAY,WAAW,QAAQ,WAAW,YAAY;AAAA,IAC7D;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,WAAW;AAAA,IACX,UAAU,CAAC;AAAA,IACX,KAAuC,OAAU,MAAmB;AAClE,YAAM,WAA2B;AAAA,QAC/B,MAAM;AAAA,QACN;AAAA,QACA,MAAM;AAAA,MACR;AAEA,YAAM,aAAa,mBAAmB,QAAQ;AAC9C,WAAK,YAAY,WAAW,QAAQ,WAAW,YAAY;AAAA,IAC7D;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,142 @@
1
+ import { EventEmitter, transferableDecode, transferableEncode, Uuid } from "@simplysm/core-common";
2
+ import { createConsola } from "consola";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { Worker as WorkerRaw } from "worker_threads";
6
+ const logger = createConsola().withTag("sd-worker");
7
+ class WorkerInternal extends EventEmitter {
8
+ _worker;
9
+ _isTerminated = false;
10
+ _pendingRequests = /* @__PURE__ */ new Map();
11
+ constructor(filePath, opt) {
12
+ super();
13
+ const ext = path.extname(import.meta.filename);
14
+ const envObj = opt?.env != null && typeof opt.env === "object" ? opt.env : {};
15
+ if (ext === ".ts") {
16
+ this._worker = new WorkerRaw(path.resolve(import.meta.dirname, "../../lib/worker-dev-proxy.js"), {
17
+ stdout: true,
18
+ stderr: true,
19
+ ...opt,
20
+ env: {
21
+ ...process.env,
22
+ ...envObj
23
+ },
24
+ argv: [filePath, ...opt?.argv ?? []]
25
+ });
26
+ } else {
27
+ const workerPath = filePath.startsWith("file://") ? fileURLToPath(filePath) : filePath;
28
+ this._worker = new WorkerRaw(workerPath, {
29
+ stdout: true,
30
+ stderr: true,
31
+ ...opt,
32
+ env: {
33
+ ...process.env,
34
+ ...envObj
35
+ }
36
+ });
37
+ }
38
+ this._worker.stdout.pipe(process.stdout);
39
+ this._worker.stderr.pipe(process.stderr);
40
+ this._worker.on("exit", (code) => {
41
+ if (!this._isTerminated && code !== 0) {
42
+ logger.error(`\uC6CC\uCEE4\uAC00 \uC624\uB958\uC640 \uD568\uAED8 \uB2EB\uD798 (code: ${code})`);
43
+ this._rejectAllPending(new Error(`\uC6CC\uCEE4\uAC00 \uBE44\uC815\uC0C1 \uC885\uB8CC\uB428 (code: ${code})`));
44
+ }
45
+ });
46
+ this._worker.on("error", (err) => {
47
+ logger.error("\uC6CC\uCEE4 \uC624\uB958:", err);
48
+ this._rejectAllPending(err);
49
+ });
50
+ this._worker.on("message", (serializedResponse) => {
51
+ const decoded = transferableDecode(serializedResponse);
52
+ if (decoded == null || typeof decoded !== "object" || !("type" in decoded)) {
53
+ logger.warn("\uC6CC\uCEE4\uC5D0\uC11C \uC798\uBABB\uB41C \uD615\uC2DD\uC758 \uC751\uB2F5:", decoded);
54
+ return;
55
+ }
56
+ const response = decoded;
57
+ if (response.type === "event") {
58
+ this.emit(response.event, response.body);
59
+ } else if (response.type === "log") {
60
+ process.stdout.write(response.body);
61
+ } else if (response.type === "return") {
62
+ const pending = this._pendingRequests.get(response.request.id);
63
+ if (pending) {
64
+ this._pendingRequests.delete(response.request.id);
65
+ pending.resolve(response.body);
66
+ }
67
+ } else {
68
+ const pending = this._pendingRequests.get(response.request.id);
69
+ if (pending) {
70
+ this._pendingRequests.delete(response.request.id);
71
+ pending.reject(response.body);
72
+ }
73
+ }
74
+ });
75
+ }
76
+ /**
77
+ * 대기 중인 모든 요청을 reject합니다.
78
+ */
79
+ _rejectAllPending(err) {
80
+ for (const [_id, { method, reject }] of this._pendingRequests) {
81
+ reject(new Error(`${err.message} (method: ${method})`));
82
+ }
83
+ this._pendingRequests.clear();
84
+ }
85
+ /**
86
+ * 워커 메서드 호출.
87
+ */
88
+ call(method, params) {
89
+ return new Promise((resolve, reject) => {
90
+ const request = {
91
+ id: Uuid.new().toString(),
92
+ method,
93
+ params
94
+ };
95
+ this._pendingRequests.set(request.id, { method, resolve, reject });
96
+ const serialized = transferableEncode(request);
97
+ this._worker.postMessage(serialized.result, serialized.transferList);
98
+ });
99
+ }
100
+ /**
101
+ * 워커 종료.
102
+ */
103
+ async terminate() {
104
+ this._isTerminated = true;
105
+ this._rejectAllPending(new Error("\uC6CC\uCEE4\uAC00 \uC885\uB8CC\uB428"));
106
+ await this._worker.terminate();
107
+ }
108
+ }
109
+ const Worker = {
110
+ /**
111
+ * 타입 안전한 Worker Proxy 생성.
112
+ *
113
+ * @param filePath - 워커 파일 경로 (file:// URL 또는 절대 경로)
114
+ * @param opt - Worker 옵션
115
+ * @returns Proxy 객체 (메서드 직접 호출, on(), terminate() 지원)
116
+ */
117
+ create(filePath, opt) {
118
+ const internal = new WorkerInternal(filePath, opt);
119
+ return new Proxy({}, {
120
+ get(_target, prop) {
121
+ if (prop === "on") {
122
+ return (event, listener) => {
123
+ internal.on(event, listener);
124
+ };
125
+ }
126
+ if (prop === "off") {
127
+ return (event, listener) => {
128
+ internal.off(event, listener);
129
+ };
130
+ }
131
+ if (prop === "terminate") {
132
+ return () => internal.terminate();
133
+ }
134
+ return (...args) => internal.call(prop, args);
135
+ }
136
+ });
137
+ }
138
+ };
139
+ export {
140
+ Worker
141
+ };
142
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/worker/worker.ts"],
4
+ "sourcesContent": ["import { EventEmitter, transferableDecode, transferableEncode, Uuid } from \"@simplysm/core-common\";\nimport { createConsola } from \"consola\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport type { WorkerOptions as WorkerRawOptions } from \"worker_threads\";\nimport { Worker as WorkerRaw } from \"worker_threads\";\nimport type { WorkerModule, WorkerProxy, WorkerRequest, WorkerResponse } from \"./types\";\n\nconst logger = createConsola().withTag(\"sd-worker\");\n\n//#region WorkerInternal\n\n/**\n * Worker \uB0B4\uBD80 \uAD6C\uD604 \uD074\uB798\uC2A4.\n * Proxy\uB97C \uD1B5\uD574 \uC678\uBD80\uC5D0 \uB178\uCD9C\uB428.\n *\n * \uAC1C\uBC1C \uD658\uACBD(.ts)\uC5D0\uC11C\uB294 tsx\uB97C \uD1B5\uD574 TypeScript \uC6CC\uCEE4 \uD30C\uC77C\uC744 \uC2E4\uD589\uD558\uACE0,\n * \uD504\uB85C\uB355\uC158 \uD658\uACBD(.js)\uC5D0\uC11C\uB294 \uC9C1\uC811 Worker\uB97C \uC0DD\uC131\uD55C\uB2E4.\n */\nclass WorkerInternal extends EventEmitter<Record<string, unknown>> {\n private readonly _worker: WorkerRaw;\n private _isTerminated = false;\n private readonly _pendingRequests = new Map<\n string,\n { method: string; resolve: (value: unknown) => void; reject: (err: Error) => void }\n >();\n\n constructor(filePath: string, opt?: Omit<WorkerRawOptions, \"stdout\" | \"stderr\">) {\n super();\n\n const ext = path.extname(import.meta.filename);\n\n // \uD0C0\uC785 \uAC00\uB4DC\uB97C \uD1B5\uD55C env \uAC1D\uCCB4 \uCD94\uCD9C\n const envObj = opt?.env != null && typeof opt.env === \"object\" ? opt.env : {};\n\n // \uAC1C\uBC1C \uD658\uACBD (.ts \uD30C\uC77C)\uC778 \uACBD\uC6B0 tsx\uB97C \uD1B5\uD574 \uC2E4\uD589\n // worker-dev-proxy.js: tsx\uB85C TypeScript \uC6CC\uCEE4 \uD30C\uC77C\uC744 \uB3D9\uC801\uC73C\uB85C \uB85C\uB4DC\uD558\uB294 \uD504\uB85D\uC2DC\n if (ext === \".ts\") {\n this._worker = new WorkerRaw(path.resolve(import.meta.dirname, \"../../lib/worker-dev-proxy.js\"), {\n stdout: true,\n stderr: true,\n ...opt,\n env: {\n ...process.env,\n ...envObj,\n },\n argv: [filePath, ...(opt?.argv ?? [])],\n });\n } else {\n // \uD504\uB85C\uB355\uC158 \uD658\uACBD (.js \uD30C\uC77C)\n // file:// URL\uC778 \uACBD\uC6B0 \uBCC0\uD658, \uC774\uBBF8 \uC808\uB300 \uACBD\uB85C\uC778 \uACBD\uC6B0 \uADF8\uB300\uB85C \uC0AC\uC6A9\n const workerPath = filePath.startsWith(\"file://\") ? fileURLToPath(filePath) : filePath;\n this._worker = new WorkerRaw(workerPath, {\n stdout: true,\n stderr: true,\n ...opt,\n env: {\n ...process.env,\n ...envObj,\n },\n });\n }\n\n // \uC6CC\uCEE4\uC758 stdout/stderr\uB97C \uBA54\uC778\uC5D0 \uCD9C\uB825\n this._worker.stdout.pipe(process.stdout);\n this._worker.stderr.pipe(process.stderr);\n\n this._worker.on(\"exit\", (code) => {\n if (!this._isTerminated && code !== 0) {\n logger.error(`\uC6CC\uCEE4\uAC00 \uC624\uB958\uC640 \uD568\uAED8 \uB2EB\uD798 (code: ${code})`);\n // \uBE44\uC815\uC0C1 \uC885\uB8CC \uC2DC \uB300\uAE30 \uC911\uC778 \uBAA8\uB4E0 \uC694\uCCAD reject\n this._rejectAllPending(new Error(`\uC6CC\uCEE4\uAC00 \uBE44\uC815\uC0C1 \uC885\uB8CC\uB428 (code: ${code})`));\n }\n });\n\n this._worker.on(\"error\", (err) => {\n logger.error(\"\uC6CC\uCEE4 \uC624\uB958:\", err);\n // \uC6CC\uCEE4 \uC5D0\uB7EC \uC2DC \uB300\uAE30 \uC911\uC778 \uBAA8\uB4E0 \uC694\uCCAD reject\n this._rejectAllPending(err);\n });\n\n this._worker.on(\"message\", (serializedResponse: unknown) => {\n const decoded = transferableDecode(serializedResponse);\n\n // \uC751\uB2F5 \uAD6C\uC870 \uAC80\uC99D\n if (decoded == null || typeof decoded !== \"object\" || !(\"type\" in decoded)) {\n logger.warn(\"\uC6CC\uCEE4\uC5D0\uC11C \uC798\uBABB\uB41C \uD615\uC2DD\uC758 \uC751\uB2F5:\", decoded);\n return;\n }\n const response = decoded as WorkerResponse;\n\n if (response.type === \"event\") {\n this.emit(response.event, response.body);\n } else if (response.type === \"log\") {\n process.stdout.write(response.body);\n } else if (response.type === \"return\") {\n const pending = this._pendingRequests.get(response.request.id);\n if (pending) {\n this._pendingRequests.delete(response.request.id);\n pending.resolve(response.body);\n }\n } else {\n // response.type === \"error\"\n const pending = this._pendingRequests.get(response.request.id);\n if (pending) {\n this._pendingRequests.delete(response.request.id);\n pending.reject(response.body);\n }\n }\n });\n }\n\n /**\n * \uB300\uAE30 \uC911\uC778 \uBAA8\uB4E0 \uC694\uCCAD\uC744 reject\uD569\uB2C8\uB2E4.\n */\n private _rejectAllPending(err: Error): void {\n for (const [_id, { method, reject }] of this._pendingRequests) {\n reject(new Error(`${err.message} (method: ${method})`));\n }\n this._pendingRequests.clear();\n }\n\n /**\n * \uC6CC\uCEE4 \uBA54\uC11C\uB4DC \uD638\uCD9C.\n */\n call(method: string, params: unknown[]): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const request: WorkerRequest = {\n id: Uuid.new().toString(),\n method,\n params,\n };\n\n this._pendingRequests.set(request.id, { method, resolve, reject });\n\n const serialized = transferableEncode(request);\n this._worker.postMessage(serialized.result, serialized.transferList);\n });\n }\n\n /**\n * \uC6CC\uCEE4 \uC885\uB8CC.\n */\n async terminate(): Promise<void> {\n this._isTerminated = true;\n this._rejectAllPending(new Error(\"\uC6CC\uCEE4\uAC00 \uC885\uB8CC\uB428\"));\n await this._worker.terminate();\n }\n}\n\n//#endregion\n\n//#region Worker\n\n/**\n * \uD0C0\uC785 \uC548\uC804\uD55C Worker \uB798\uD37C.\n *\n * @example\n * // worker.ts\n * export default createWorker({\n * add: (a: number, b: number) => a + b,\n * });\n *\n * // main.ts\n * const worker = Worker.create<typeof import(\"./worker\")>(\"./worker.ts\");\n * const result = await worker.add(10, 20); // 30\n * await worker.terminate();\n */\nexport const Worker = {\n /**\n * \uD0C0\uC785 \uC548\uC804\uD55C Worker Proxy \uC0DD\uC131.\n *\n * @param filePath - \uC6CC\uCEE4 \uD30C\uC77C \uACBD\uB85C (file:// URL \uB610\uB294 \uC808\uB300 \uACBD\uB85C)\n * @param opt - Worker \uC635\uC158\n * @returns Proxy \uAC1D\uCCB4 (\uBA54\uC11C\uB4DC \uC9C1\uC811 \uD638\uCD9C, on(), terminate() \uC9C0\uC6D0)\n */\n create<TModule extends WorkerModule>(\n filePath: string,\n opt?: Omit<WorkerRawOptions, \"stdout\" | \"stderr\">,\n ): WorkerProxy<TModule> {\n const internal = new WorkerInternal(filePath, opt);\n\n return new Proxy({} as WorkerProxy<TModule>, {\n get(_target, prop: string) {\n // \uC608\uC57D\uB41C \uBA54\uC11C\uB4DC: on, off, terminate\n if (prop === \"on\") {\n return (event: string, listener: (data: unknown) => void) => {\n internal.on(event, listener);\n };\n }\n if (prop === \"off\") {\n return (event: string, listener: (data: unknown) => void) => {\n internal.off(event, listener);\n };\n }\n if (prop === \"terminate\") {\n return () => internal.terminate();\n }\n\n // \uADF8 \uC678\uB294 \uC6CC\uCEE4 \uBA54\uC11C\uB4DC\uB85C \uCC98\uB9AC\n return (...args: unknown[]) => internal.call(prop, args);\n },\n });\n },\n};\n\n//#endregion\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { tsImport } from "tsx/esm/api";
2
+ import { pathToFileURL } from "url";
3
+
4
+ // argv[2]에 진짜 워커 파일 경로가 들어온다고 가정
5
+ const workerFile = process.argv[2];
6
+ if (!workerFile) {
7
+ throw new Error("Worker file path is required as argument!");
8
+ }
9
+
10
+ // Windows에서 절대 경로는 file:// URL로 변환해야 ESM 로더가 처리 가능
11
+ const workerFileUrl = pathToFileURL(workerFile).href;
12
+ await tsImport(workerFileUrl, import.meta.url);
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@simplysm/core-node",
3
+ "version": "13.0.0-beta.1",
4
+ "description": "심플리즘 패키지 - 코어 모듈 (node)",
5
+ "author": "김석래",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/kslhunter/simplysm.git",
9
+ "directory": "packages/core-node"
10
+ },
11
+ "license": "Apache-2.0",
12
+ "type": "module",
13
+ "sideEffects": false,
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "dependencies": {
17
+ "@simplysm/core-common": "workspace:*",
18
+ "chokidar": "^5.0.0",
19
+ "consola": "^3.4.0",
20
+ "glob": "^13.0.0",
21
+ "tsx": "^4.21.0"
22
+ }
23
+ }
@@ -0,0 +1,176 @@
1
+ import { DebounceQueue } from "@simplysm/core-common";
2
+ import * as chokidar from "chokidar";
3
+ import { createConsola } from "consola";
4
+ import type { EventName } from "chokidar/handler.js";
5
+ import { type NormPath, pathNorm } from "../utils/path";
6
+
7
+ //#region Types
8
+
9
+ /**
10
+ * 지원하는 파일 변경 이벤트 타입 목록.
11
+ */
12
+ const FS_WATCHER_EVENTS = ["add", "addDir", "change", "unlink", "unlinkDir"] as const;
13
+
14
+ /**
15
+ * 파일 변경 이벤트 타입.
16
+ */
17
+ export type FsWatcherEvent = (typeof FS_WATCHER_EVENTS)[number];
18
+
19
+ /**
20
+ * 파일 변경 정보.
21
+ */
22
+ export interface FsWatcherChangeInfo {
23
+ /** 변경 이벤트 타입 */
24
+ event: FsWatcherEvent;
25
+ /** 변경된 파일/디렉토리 경로 (정규화됨) */
26
+ path: NormPath;
27
+ }
28
+
29
+ //#endregion
30
+
31
+ //#region FsWatcher
32
+
33
+ /**
34
+ * chokidar 기반 파일 시스템 감시 래퍼.
35
+ * 짧은 시간 내 발생한 이벤트를 병합하여 콜백 호출.
36
+ *
37
+ * **주의**: chokidar의 `ignoreInitial` 옵션은 내부적으로 항상 `true`로 설정된다.
38
+ * `options.ignoreInitial: false`를 전달하면 `onChange` 첫 호출 시 빈 배열로
39
+ * 콜백이 호출되지만, 실제 초기 파일 목록은 포함되지 않는다.
40
+ * 이는 이벤트 병합 로직과의 충돌을 방지하기 위한 의도된 동작이다.
41
+ *
42
+ * @example
43
+ * const watcher = await FsWatcher.watch(["src/**\/*.ts"]);
44
+ * watcher.onChange({ delay: 300 }, (changes) => {
45
+ * for (const { path, event } of changes) {
46
+ * console.log(`${event}: ${path}`);
47
+ * }
48
+ * });
49
+ *
50
+ * // 종료
51
+ * await watcher.close();
52
+ */
53
+ export class FsWatcher {
54
+ /**
55
+ * 파일 감시 시작 (비동기).
56
+ * ready 이벤트가 발생할 때까지 대기.
57
+ *
58
+ * @param paths - 감시할 파일/디렉토리 경로 또는 glob 패턴 배열
59
+ * @param options - chokidar 옵션
60
+ */
61
+ static async watch(paths: string[], options?: chokidar.ChokidarOptions): Promise<FsWatcher> {
62
+ return new Promise<FsWatcher>((resolve, reject) => {
63
+ const watcher = new FsWatcher(paths, options);
64
+ watcher._watcher.on("ready", () => {
65
+ resolve(watcher);
66
+ });
67
+ watcher._watcher.on("error", reject);
68
+ });
69
+ }
70
+
71
+ private readonly _watcher: chokidar.FSWatcher;
72
+ private readonly _ignoreInitial: boolean = true;
73
+ private readonly _debounceQueues: DebounceQueue[] = [];
74
+
75
+ private readonly _logger = createConsola().withTag("sd-fs-watcher");
76
+
77
+ private constructor(paths: string[], options?: chokidar.ChokidarOptions) {
78
+ this._watcher = chokidar.watch(paths, {
79
+ persistent: true,
80
+ ...options,
81
+ ignoreInitial: true,
82
+ });
83
+ this._ignoreInitial = options?.ignoreInitial ?? this._ignoreInitial;
84
+
85
+ // 감시 중 발생하는 에러 로깅
86
+ this._watcher.on("error", (err) => {
87
+ this._logger.error("FsWatcher error:", err);
88
+ });
89
+ }
90
+
91
+ /**
92
+ * 파일 변경 이벤트 핸들러 등록.
93
+ * 지정된 delay 시간 동안 이벤트를 모아서 한 번에 콜백 호출.
94
+ *
95
+ * @param opt.delay - 이벤트 병합 대기 시간 (ms)
96
+ * @param cb - 변경 이벤트 콜백
97
+ */
98
+ onChange(opt: { delay?: number }, cb: (changeInfos: FsWatcherChangeInfo[]) => void | Promise<void>): this {
99
+ const fnQ = new DebounceQueue(opt.delay);
100
+ this._debounceQueues.push(fnQ);
101
+
102
+ let changeInfoMap = new Map<string, EventName>();
103
+
104
+ // ignoreInitial이 false면 초기에 빈 배열로 콜백 호출
105
+ if (!this._ignoreInitial) {
106
+ fnQ.run(async () => {
107
+ await cb([]);
108
+ });
109
+ }
110
+
111
+ this._watcher.on("all", (event, filePath) => {
112
+ // 지원하는 이벤트만 처리
113
+ if (!FS_WATCHER_EVENTS.includes(event as FsWatcherEvent)) return;
114
+
115
+ /*
116
+ * 이벤트 병합 전략:
117
+ * 짧은 시간 내 같은 파일에 대해 여러 이벤트가 발생하면 최종 상태만 전달한다.
118
+ * - add + change → add (생성 직후 수정은 생성으로 간주)
119
+ * - add + unlink → 삭제 (생성 후 즉시 삭제는 변경 없음)
120
+ * - unlink + add → add (삭제 후 재생성은 생성으로 간주)
121
+ * - 그 외 → 최신 이벤트로 덮어씀
122
+ */
123
+ if (!changeInfoMap.has(filePath)) {
124
+ changeInfoMap.set(filePath, event);
125
+ }
126
+ const prevEvent = changeInfoMap.get(filePath)!;
127
+
128
+ if (prevEvent === "add" && event === "change") {
129
+ // add 후 change → add 유지
130
+ changeInfoMap.set(filePath, "add");
131
+ } else if ((prevEvent === "add" && event === "unlink") || (prevEvent === "addDir" && event === "unlinkDir")) {
132
+ // add 후 unlink → 변경 없음 (삭제)
133
+ changeInfoMap.delete(filePath);
134
+ } else if (prevEvent === "unlink" && (event === "add" || event === "change")) {
135
+ // unlink 후 add/change → add (파일 재생성)
136
+ changeInfoMap.set(filePath, "add");
137
+ } else if (prevEvent === "unlinkDir" && event === "addDir") {
138
+ // unlinkDir 후 addDir → addDir (디렉토리 재생성)
139
+ changeInfoMap.set(filePath, "addDir");
140
+ } else {
141
+ changeInfoMap.set(filePath, event);
142
+ }
143
+
144
+ fnQ.run(async () => {
145
+ if (changeInfoMap.size === 0) return;
146
+
147
+ const currChangeInfoMap = changeInfoMap;
148
+ changeInfoMap = new Map<string, EventName>();
149
+
150
+ const changeInfos = Array.from(currChangeInfoMap.entries()).map(
151
+ ([path, evt]): FsWatcherChangeInfo => ({
152
+ path: pathNorm(path),
153
+ event: evt as FsWatcherEvent,
154
+ }),
155
+ );
156
+
157
+ await cb(changeInfos);
158
+ });
159
+ });
160
+
161
+ return this;
162
+ }
163
+
164
+ /**
165
+ * 파일 감시 종료.
166
+ */
167
+ async close(): Promise<void> {
168
+ for (const q of this._debounceQueues) {
169
+ q.dispose();
170
+ }
171
+ this._debounceQueues.length = 0;
172
+ await this._watcher.close();
173
+ }
174
+ }
175
+
176
+ //#endregion
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ // Utils
2
+ export * from "./utils/fs";
3
+ export * from "./utils/path";
4
+
5
+ // Features
6
+ export * from "./features/fs-watcher";
7
+
8
+ // Worker
9
+ export * from "./worker/types";
10
+ export * from "./worker/worker";
11
+ export * from "./worker/create-worker";