@simplysm/sd-claude 14.0.91 → 14.0.92
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.
- package/claude/references/sd-simplysm14/README.md +7 -6
- package/claude/references/sd-simplysm14/apis/angular/README.md +59 -39
- package/claude/references/sd-simplysm14/apis/angular/controls.md +119 -186
- package/claude/references/sd-simplysm14/apis/angular/crud.md +70 -31
- package/claude/references/sd-simplysm14/apis/angular/directives.md +55 -57
- package/claude/references/sd-simplysm14/apis/angular/features.md +86 -105
- package/claude/references/sd-simplysm14/apis/angular/infra.md +48 -57
- package/claude/references/sd-simplysm14/apis/angular/layout.md +37 -47
- package/claude/references/sd-simplysm14/apis/angular/overlay.md +82 -74
- package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +61 -50
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +74 -57
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +63 -72
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +23 -18
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +21 -19
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +23 -18
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +72 -32
- package/claude/references/sd-simplysm14/apis/core-browser/README.md +18 -18
- package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +29 -29
- package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +41 -41
- package/claude/references/sd-simplysm14/apis/core-common/README.md +97 -90
- package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +75 -51
- package/claude/references/sd-simplysm14/apis/core-common/collection-ext.md +81 -0
- package/claude/references/sd-simplysm14/apis/core-common/errors.md +27 -29
- package/claude/references/sd-simplysm14/apis/core-common/obj.md +44 -45
- package/claude/references/sd-simplysm14/apis/core-common/serialization.md +34 -33
- package/claude/references/sd-simplysm14/apis/core-common/value-types.md +86 -0
- package/claude/references/sd-simplysm14/apis/core-node/README.md +6 -6
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +3 -0
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +2 -2
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +1 -1
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +2 -2
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +6 -3
- package/claude/references/sd-simplysm14/apis/excel/README.md +10 -10
- package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +4 -2
- package/claude/references/sd-simplysm14/apis/excel/utils.md +1 -1
- package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +6 -6
- package/claude/references/sd-simplysm14/apis/lint/README.md +6 -32
- package/claude/references/sd-simplysm14/apis/lint/recommended.md +60 -0
- package/claude/references/sd-simplysm14/apis/lint/rules.md +17 -17
- package/claude/references/sd-simplysm14/apis/orm-common/README.md +15 -6
- package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +68 -102
- package/claude/references/sd-simplysm14/apis/orm-common/expr.md +75 -89
- package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +87 -99
- package/claude/references/sd-simplysm14/apis/orm-common/schema.md +110 -147
- package/claude/references/sd-simplysm14/apis/orm-common/types.md +48 -51
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +8 -13
- package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +5 -5
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +9 -6
- package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +9 -8
- package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +23 -19
- package/claude/references/sd-simplysm14/apis/service-client/README.md +20 -12
- package/claude/references/sd-simplysm14/apis/service-client/orm.md +6 -6
- package/claude/references/sd-simplysm14/apis/service-client/transport.md +1 -1
- package/claude/references/sd-simplysm14/apis/service-common/README.md +35 -32
- package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +23 -22
- package/claude/references/sd-simplysm14/apis/service-common/protocol.md +23 -23
- package/claude/references/sd-simplysm14/apis/service-server/README.md +51 -43
- package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +6 -6
- package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +31 -21
- package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +8 -8
- package/claude/references/sd-simplysm14/apis/storage/README.md +55 -49
- package/claude/references/sd-simplysm14/manuals/client-component.md +843 -740
- package/claude/references/sd-simplysm14/manuals/client-crud.md +8 -0
- package/claude/references/sd-simplysm14/manuals/client-demo.md +6 -16
- package/claude/references/sd-simplysm14/manuals/client-shared-data.md +26 -0
- package/claude/references/sd-simplysm14/manuals/logging.md +1 -1
- package/claude/references/sd-simplysm14/manuals/orm.md +15 -1
- package/claude/rules/sd-design-rules.md +7 -0
- package/claude/sd-system-prompt.md +5 -8
- package/claude/skills/sd-debug/SKILL.md +43 -0
- package/claude/skills/sd-debug/workflow.js +390 -0
- package/claude/skills/sd-demo/SKILL.md +18 -20
- package/claude/skills/sd-dev/SKILL.md +127 -24
- package/claude/skills/sd-docs/SKILL.md +5 -3
- package/claude/skills/sd-docs/references/subagent-prompt.md +2 -3
- package/claude/skills/sd-impl/SKILL.md +18 -18
- package/claude/skills/sd-manual/SKILL.md +1 -0
- package/claude/skills/sd-review/SKILL.md +24 -18
- package/claude/skills/sd-review/workflow.js +324 -0
- package/claude/skills/sd-spec/SKILL.md +96 -679
- package/claude/skills/sd-spec/references/example-spec.md +28 -50
- package/claude/skills/sd-spec/references/format-analyze.md +232 -0
- package/claude/skills/sd-spec/references/format-design.md +248 -0
- package/claude/skills/sd-spec/workflow-analyze.js +615 -0
- package/claude/skills/sd-spec/workflow-design.js +667 -0
- package/claude/skills/sd-unpack/scripts/handlers/office_com.py +5 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +157 -18
- package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +0 -68
- package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +0 -77
- package/claude/references/sd-simplysm14/apis/core-common/datetime.md +0 -86
- package/claude/skills/sd-skill/SKILL.md +0 -245
- package/claude/skills/sd-skill/scripts/run_eval.py +0 -380
|
@@ -1070,7 +1070,11 @@ def _convert_legacy(input_path: Path, target_path: Path) -> None:
|
|
|
1070
1070
|
"""
|
|
1071
1071
|
target_ext = target_path.suffix
|
|
1072
1072
|
with _common.com_lock(), _common.temp_workdir() as tmp:
|
|
1073
|
-
|
|
1073
|
+
# Excel SaveAs 등 Office COM 은 파일명에 `< > ? [ ] : | * "` 를 거부 (대괄호는
|
|
1074
|
+
# 워크북 참조 구문으로 해석). 임시 변환 파일명에서만 치환 — 최종 결과는
|
|
1075
|
+
# target_path 의 원본 이름으로 copy 되므로 출력명에는 영향 없음.
|
|
1076
|
+
safe_stem = re.sub(r'[<>?\[\]:|*"]', "_", input_path.stem)
|
|
1077
|
+
tmp_target = tmp / (safe_stem + target_ext)
|
|
1074
1078
|
_run_worker(
|
|
1075
1079
|
"convert_legacy", input_path.suffix.lower(), str(input_path), str(tmp_target),
|
|
1076
1080
|
timeout=600,
|
package/package.json
CHANGED
package/scripts/postinstall.mjs
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Installs Claude Code assets to the project's .claude/ directory.
|
|
4
4
|
* postinstall hook — 실패해도 pnpm install을 차단하지 않는다.
|
|
5
|
+
*
|
|
6
|
+
* 동기화 전략 (증분, sync.mjs와 동일):
|
|
7
|
+
* - 동일 콘텐츠(mtime+size 일치) 파일은 건드리지 않는다.
|
|
8
|
+
* - 변경된 파일만 unlink 후 copy. utimesSync로 src mtime을 보존해 다음 설치에서 동일로 판정.
|
|
9
|
+
* - 소스에서 사라진 sd-* 엔트리(고아)만 제거하고, .claude의 다른 파일은 보존한다.
|
|
10
|
+
*
|
|
11
|
+
* 통삭제(전체 rm 후 전체 copy) 방식은 삭제~복사 사이에 파일이 잠시 존재하지 않는 창을
|
|
12
|
+
* 만든다. 그 순간 Claude Code의 PreToolUse 훅(python .claude/sd-check-*.py)이 실행되면
|
|
13
|
+
* "No such file"로 도구 호출이 차단된다(특히 dev/watch 중 잦은 재설치 시 빈번). 그래서
|
|
14
|
+
* sync.mjs와 동일한 증분 방식을 쓴다.
|
|
5
15
|
*/
|
|
6
16
|
import fs from "fs";
|
|
7
17
|
import path from "path";
|
|
@@ -30,7 +40,14 @@ try {
|
|
|
30
40
|
process.exit(0);
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
|
|
43
|
+
// 버전별 references(`references/sd-simplysm<major>`)는 프로젝트가 선언한
|
|
44
|
+
// @simplysm/sd-cli major가 일치할 때만 설치한다. sd-cli 미선언·범위 파싱 불가 시 전부 제외.
|
|
45
|
+
const cliMajor = getSimplysmCliMajor(projectRoot);
|
|
46
|
+
const sourceEntries = collectSdEntries(sourceDir).filter((rel) => {
|
|
47
|
+
const matched = rel.replace(/\\/g, "/").match(/^references\/sd-simplysm(\d+)$/);
|
|
48
|
+
if (matched == null) return true;
|
|
49
|
+
return cliMajor != null && Number(matched[1]) === cliMajor;
|
|
50
|
+
});
|
|
34
51
|
// settings.json도 함께 복사
|
|
35
52
|
if (fs.existsSync(path.join(sourceDir, "settings.json"))) {
|
|
36
53
|
sourceEntries.push("settings.json");
|
|
@@ -43,11 +60,36 @@ try {
|
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
const targetDir = path.join(projectRoot, ".claude");
|
|
63
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
64
|
+
|
|
65
|
+
// 관리 대상(sd-* 엔트리 + settings.json/simplysm.json) 하위의 모든 상대경로 + 부모 수집.
|
|
66
|
+
const expected = new Set();
|
|
67
|
+
for (const entry of sourceEntries) {
|
|
68
|
+
collectExpected(path.join(sourceDir, entry), sourceDir, expected);
|
|
69
|
+
}
|
|
46
70
|
|
|
47
|
-
|
|
48
|
-
|
|
71
|
+
// 소스에서 사라진 sd-* 엔트리(고아)만 제거. .claude의 다른(사용자) 파일은 건드리지 않는다.
|
|
72
|
+
forEachSdEntry(targetDir, (rel) => {
|
|
73
|
+
if (!expected.has(rel)) {
|
|
74
|
+
fs.rmSync(path.join(targetDir, rel), { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
49
77
|
|
|
50
|
-
|
|
78
|
+
// 증분 복사. 동일 파일은 건드리지 않으므로 삭제 창이 생기지 않는다.
|
|
79
|
+
let copiedFiles = 0;
|
|
80
|
+
for (const entry of sourceEntries) {
|
|
81
|
+
const src = path.join(sourceDir, entry);
|
|
82
|
+
const dest = path.join(targetDir, entry);
|
|
83
|
+
// 관리 디렉토리 엔트리 내부에서 소스에 없어진 파일(고아) 정리.
|
|
84
|
+
if (fs.existsSync(src) && fs.statSync(src).isDirectory()) {
|
|
85
|
+
pruneDest(dest, targetDir, expected);
|
|
86
|
+
}
|
|
87
|
+
copiedFiles += syncTree(src, dest);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(
|
|
91
|
+
`[@simplysm/sd-claude] Installed ${sourceEntries.length} entries (${copiedFiles} files updated).`,
|
|
92
|
+
);
|
|
51
93
|
} catch (err) {
|
|
52
94
|
// Ignore errors to prevent postinstall failure from blocking the entire pnpm install
|
|
53
95
|
console.warn("[@simplysm/sd-claude] postinstall warning:", err.message);
|
|
@@ -88,22 +130,119 @@ function isSimplysmMonorepoSameMajor(projectRoot, pkgRoot) {
|
|
|
88
130
|
return projectMajor != null && projectMajor === sdClaudeMajor;
|
|
89
131
|
}
|
|
90
132
|
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
133
|
+
/**
|
|
134
|
+
* 소비 프로젝트가 선언한 @simplysm/sd-cli 의존성의 major 버전을 반환한다.
|
|
135
|
+
* dependencies/devDependencies 범위 문자열의 첫 숫자를 major로 본다(예: "^14.0.91" → 14).
|
|
136
|
+
* 미선언이거나 숫자가 없는 값(workspace:* 등)이면 null.
|
|
137
|
+
*/
|
|
138
|
+
function getSimplysmCliMajor(projectRoot) {
|
|
139
|
+
const projectPkgPath = path.join(projectRoot, "package.json");
|
|
140
|
+
if (!fs.existsSync(projectPkgPath)) return null;
|
|
141
|
+
|
|
142
|
+
let projectPkg;
|
|
143
|
+
try {
|
|
144
|
+
projectPkg = JSON.parse(fs.readFileSync(projectPkgPath, "utf-8"));
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const range =
|
|
150
|
+
projectPkg.dependencies?.["@simplysm/sd-cli"] ?? projectPkg.devDependencies?.["@simplysm/sd-cli"];
|
|
151
|
+
if (range == null) return null;
|
|
152
|
+
|
|
153
|
+
const matched = String(range).match(/\d+/);
|
|
154
|
+
return matched == null ? null : Number(matched[0]);
|
|
97
155
|
}
|
|
98
156
|
|
|
99
|
-
/**
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
157
|
+
/** eval 자산은 소비처에 설치하지 않는다. */
|
|
158
|
+
function filter(source) {
|
|
159
|
+
const sdName = path.basename(source);
|
|
160
|
+
return sdName !== "SKILL.eval.md" && !sdName.startsWith("eval_");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** src/dest가 동일 콘텐츠(size+mtime 일치)인지. */
|
|
164
|
+
function isSameFile(srcPath, destPath) {
|
|
165
|
+
try {
|
|
166
|
+
const ss = fs.statSync(srcPath);
|
|
167
|
+
const ds = fs.statSync(destPath);
|
|
168
|
+
return ss.size === ds.size && ss.mtimeMs === ds.mtimeMs;
|
|
169
|
+
} catch {
|
|
170
|
+
return false;
|
|
107
171
|
}
|
|
108
172
|
}
|
|
109
173
|
|
|
174
|
+
/**
|
|
175
|
+
* src 트리(filter 통과)의 모든 상대경로 + 부모 디렉토리들을 expected에 수집.
|
|
176
|
+
*/
|
|
177
|
+
function collectExpected(srcPath, srcRoot, expected) {
|
|
178
|
+
if (!filter(srcPath)) return;
|
|
179
|
+
let stat;
|
|
180
|
+
try {
|
|
181
|
+
stat = fs.statSync(srcPath);
|
|
182
|
+
} catch {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const rel = path.relative(srcRoot, srcPath);
|
|
186
|
+
if (rel !== "") {
|
|
187
|
+
expected.add(rel);
|
|
188
|
+
let parent = path.dirname(rel);
|
|
189
|
+
while (parent !== "" && parent !== ".") {
|
|
190
|
+
expected.add(parent);
|
|
191
|
+
parent = path.dirname(parent);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (stat.isDirectory()) {
|
|
195
|
+
for (const sdName of fs.readdirSync(srcPath)) {
|
|
196
|
+
collectExpected(path.join(srcPath, sdName), srcRoot, expected);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* dest 트리에서 expected에 없는 항목만 삭제(고아 정리).
|
|
203
|
+
*/
|
|
204
|
+
function pruneDest(destPath, destRoot, expected) {
|
|
205
|
+
if (!fs.existsSync(destPath)) return;
|
|
206
|
+
for (const dirent of fs.readdirSync(destPath, { withFileTypes: true })) {
|
|
207
|
+
const childPath = path.join(destPath, dirent.name);
|
|
208
|
+
const rel = path.relative(destRoot, childPath);
|
|
209
|
+
if (!expected.has(rel)) {
|
|
210
|
+
fs.rmSync(childPath, { recursive: true, force: true });
|
|
211
|
+
} else if (dirent.isDirectory()) {
|
|
212
|
+
pruneDest(childPath, destRoot, expected);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* src → dest 증분 동기화. 동일 콘텐츠 파일은 건드리지 않는다.
|
|
219
|
+
* 갱신된 파일 수 반환.
|
|
220
|
+
*/
|
|
221
|
+
function syncTree(srcPath, destPath) {
|
|
222
|
+
if (!filter(srcPath)) return 0;
|
|
223
|
+
let stat;
|
|
224
|
+
try {
|
|
225
|
+
stat = fs.statSync(srcPath);
|
|
226
|
+
} catch {
|
|
227
|
+
return 0;
|
|
228
|
+
}
|
|
229
|
+
if (stat.isDirectory()) {
|
|
230
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
231
|
+
let n = 0;
|
|
232
|
+
for (const sdName of fs.readdirSync(srcPath)) {
|
|
233
|
+
n += syncTree(path.join(srcPath, sdName), path.join(destPath, sdName));
|
|
234
|
+
}
|
|
235
|
+
return n;
|
|
236
|
+
}
|
|
237
|
+
if (isSameFile(srcPath, destPath)) return 0;
|
|
238
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
239
|
+
try {
|
|
240
|
+
fs.unlinkSync(destPath);
|
|
241
|
+
} catch {
|
|
242
|
+
// 없으면 무시
|
|
243
|
+
}
|
|
244
|
+
fs.copyFileSync(srcPath, destPath);
|
|
245
|
+
// src의 mtime을 dest에 그대로 적용 → 다음 동기화에서 동일로 판정되어 재복사 방지.
|
|
246
|
+
fs.utimesSync(destPath, stat.atime, stat.mtime);
|
|
247
|
+
return 1;
|
|
248
|
+
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# @simplysm/angular — selection/sorting/expanding 매니저 (use* 컴포저블)
|
|
2
|
-
|
|
3
|
-
커스텀 목록 컴포넌트에서 선택·정렬·트리펼침 상태 로직을 signal 기반으로 합성하는 함수 컴포저블 군. `sd-sheet` 가 이들을 조합해 만들어졌고, 직접 그리드/리스트를 만들 때 같은 로직을 재사용. 모두 함수 호출로 signal·메서드 묶음을 반환(컴포넌트 필드에 보관).
|
|
4
|
-
|
|
5
|
-
## useSelectionManager<TItem, TKey>
|
|
6
|
-
|
|
7
|
-
행 선택(single/multi)·전체선택·선택 가능 여부 로직.
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
useSelectionManager<TItem, TKey>(options: {
|
|
11
|
-
displayItems: Signal<TItem[]>;
|
|
12
|
-
selectedKeys: WritableSignal<TKey[]>;
|
|
13
|
-
selectMode: Signal<"single" | "multi" | undefined>;
|
|
14
|
-
getItemSelectableFn: Signal<((item: TItem) => boolean | string) | undefined>;
|
|
15
|
-
trackByFn: Signal<(item: TItem, index: number) => TKey>;
|
|
16
|
-
}): { ... }
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
- `options.displayItems` — 현재 표시 항목. `selectedKeys` — 선택 키(WritableSignal, 키는 `trackByFn` 반환값). `selectMode` — 모드(undefined 면 선택 비활성). `getItemSelectableFn` — 행별 선택 가능: `true`=가능, `false`=불가, 문자열=불가+사유. `trackByFn` — 항목→키.
|
|
20
|
-
- 반환:
|
|
21
|
-
- `hasSelectable: Signal<boolean>` — 선택 모드가 켜졌는지.
|
|
22
|
-
- `isAllSelected: Signal<boolean>` — 선택 가능한 항목이 모두 선택됐는지(전체선택 체크 상태).
|
|
23
|
-
- `getSelectable(item): true | string | undefined` — 항목 선택 가능 여부(문자열=사유 툴팁).
|
|
24
|
-
- `getCanChangeFn(item): () => boolean` — 체크박스 `canChangeFn` 에 넘길 가드.
|
|
25
|
-
- `select`/`deselect`/`toggle(item)` — 선택 조작(single 은 단일 키로 대체).
|
|
26
|
-
- `toggleAll()` — 선택 가능 항목 전체 토글.
|
|
27
|
-
- `isSelected(item): boolean`.
|
|
28
|
-
- 키 비교는 `===` 후 `obj.equal`(복합 키 지원).
|
|
29
|
-
|
|
30
|
-
## useSortingManager
|
|
31
|
-
|
|
32
|
-
정렬 상태(다중 컬럼) 토글·적용. `sd-sheet` 의 `sorts` 와 `SortingDef` 를 공유.
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
useSortingManager(options: { sorts: WritableSignal<SortingDef[]> }): {
|
|
36
|
-
defMap: Signal<Map<string, { indexText?: string; desc: boolean }>>;
|
|
37
|
-
toggle(key: string, multiple: boolean): void;
|
|
38
|
-
sort<T>(items: T[]): T[];
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
- `SortingDef = { key: string; desc: boolean }` — 한 정렬 기준. `key`=컬럼 키, `desc`=내림차순 여부.
|
|
43
|
-
- `defMap` — 키별 정렬 상태(헤더 아이콘 표시용; `indexText` 는 다중 정렬 시 순번).
|
|
44
|
-
- `toggle(key, multiple)` — 정렬 토글. 한 키를 누를 때마다 없음→오름차순→내림차순→해제 순환. `multiple`(Shift) true 면 기존 정렬 유지하고 추가, false 면 단일 정렬로 대체.
|
|
45
|
-
- `sort<T>(items)` — 현재 정렬을 적용한 새 배열 반환. null 은 가장 앞, 문자열은 localeCompare, 숫자는 수치 비교. 클라이언트 정렬 시 사용.
|
|
46
|
-
|
|
47
|
-
## useExpandingManager<T>
|
|
48
|
-
|
|
49
|
-
트리 항목 펼침/접힘 + 표시 항목 평탄화.
|
|
50
|
-
|
|
51
|
-
```ts
|
|
52
|
-
useExpandingManager<T>(binding: {
|
|
53
|
-
items: Signal<T[]>;
|
|
54
|
-
expandedItems: WritableSignal<T[]>;
|
|
55
|
-
getChildrenFn: Signal<((item: T, index: number) => T[] | undefined) | undefined>;
|
|
56
|
-
sort: (items: T[]) => T[];
|
|
57
|
-
}): { ... }
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
- `binding.items` — 루트 항목. `expandedItems` — 펼쳐진 항목(WritableSignal). `getChildrenFn` — 자식 조회(undefined 면 자식 없음). `sort` — 각 레벨 정렬 함수(보통 `useSortingManager.sort`).
|
|
61
|
-
- 반환:
|
|
62
|
-
- `displayItems: Signal<T[]>` — 펼침 상태를 반영해 평탄화·정렬된 표시 항목.
|
|
63
|
-
- `hasExpandable: Signal<boolean>` — 펼칠 수 있는 항목이 있는지(토글 컬럼 표시 기준).
|
|
64
|
-
- `isAllExpanded: Signal<boolean>` — 전체 펼침 상태.
|
|
65
|
-
- `toggle(item)` / `toggleAll()` — 펼침 토글.
|
|
66
|
-
- `isVisible(item): boolean` — 조상이 모두 펼쳐져 보이는지.
|
|
67
|
-
- `def(item): ExpandItemDef<T>` — 항목 메타(못 찾으면 throw).
|
|
68
|
-
- `ExpandItemDef<T> = { item: T; parentDef: ExpandItemDef<T> | undefined; hasChildren: boolean; depth: number }` — 항목의 부모·자식유무·깊이. 들여쓰기·토글 렌더에 사용.
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# @simplysm/core-common — 배열 확장 메서드
|
|
2
|
-
|
|
3
|
-
`@simplysm/core-common` import 시 `Array.prototype` 에 설치되는 확장 메서드. 배열을 조회·그룹화·정렬·중복제거·Map/객체/트리 변환·diff/merge·집계할 때 함께 읽힘. 모두 `array.method(...)` 로 직접 호출. enumerable=false 로 설치되어 `for...in`·`Object.keys` 에 노출되지 않음.
|
|
4
|
-
|
|
5
|
-
표기: **읽기 전용**(원본 불변, 새 배열/값 반환) vs **@mutates**(원본 직접 수정). 정렬/중복제거는 두 버전(예: `orderBy` vs `orderByThis`)이 있음.
|
|
6
|
-
|
|
7
|
-
## 조회 / 필터
|
|
8
|
-
|
|
9
|
-
- `single(predicate?)`: → `T | undefined` — 조건에 맞는 단 하나 반환. **2개 이상이면 ArgumentError throw**. 0개면 undefined. "유일해야 한다"는 불변식 검증에 사용.
|
|
10
|
-
- `first(predicate?)`: → `T | undefined` — 첫 요소(predicate 있으면 첫 매칭). 없으면 undefined.
|
|
11
|
-
- `last(predicate?)`: → `T | undefined` — 마지막 요소(predicate 있으면 뒤에서 첫 매칭).
|
|
12
|
-
- `filterExists()`: → `NonNullable<T>[]` — null/undefined 제거(타입도 좁혀짐).
|
|
13
|
-
- `ofType(type)`: → 좁혀진 배열 — PrimitiveTypeStr(`"string"`·`"number"`·`"boolean"`·`"DateTime"`·`"DateOnly"`·`"Time"`·`"Uuid"`·`"Bytes"`) 또는 생성자(`Type<N>`)로 필터. 문자열이면 typeof/instanceof, 생성자면 `instanceof` 또는 `constructor` 일치. 혼합 배열에서 특정 타입만 뽑을 때.
|
|
14
|
-
- `filterAsync(predicate)`: → `Promise<T[]>` — 비동기 조건 필터(**순차** 실행).
|
|
15
|
-
|
|
16
|
-
## 비동기 매핑
|
|
17
|
-
|
|
18
|
-
- `mapAsync(selector)`: → `Promise<R[]>` — 비동기 매핑(**순차** 실행, 순서 보존).
|
|
19
|
-
- `parallelAsync(fn)`: → `Promise<R[]>` — `Promise.all` 기반 **병렬** 실행. 하나라도 reject 면 전체 reject. 독립 IO 를 동시에 돌릴 때.
|
|
20
|
-
- `mapMany(selector?)`: → 평탄화 배열 — selector 매핑 후 1단계 flat + `filterExists`. selector 없으면 자신을 flat. 중첩 배열 펼칠 때.
|
|
21
|
-
- `mapManyAsync(selector?)`: → `Promise<...>` — 비동기 매핑 후 평탄화(순차).
|
|
22
|
-
|
|
23
|
-
## 그룹화 / Map·객체 변환
|
|
24
|
-
|
|
25
|
-
- `groupBy(keySelector, valueSelector?)`: → `{ key, values }[]` — key 별 그룹. 원시 key 는 O(n), 객체 key 는 깊은 비교 O(n²). 객체 key 가 불필요하면 `toArrayMap` 권장.
|
|
26
|
-
- `toMap(keySelector, valueSelector?)`: → `Map<K, V|T>` — key→단일값. **중복 key 면 ArgumentError**. 1:1 인덱싱에 사용.
|
|
27
|
-
- `toMapAsync(keySelector, valueSelector?)`: → `Promise<Map>` — 위의 비동기(순차) 버전(selector 가 Promise 반환 허용).
|
|
28
|
-
- `toArrayMap(keySelector, valueSelector?)`: → `Map<K, (V|T)[]>` — key→값 배열(중복 허용). O(n) 그룹화의 기본 선택지.
|
|
29
|
-
- `toSetMap(keySelector, valueSelector?)`: → `Map<K, Set<V|T>>` — key→Set(중복 자동 제거).
|
|
30
|
-
- `toMapValues(keySelector, valueSelector)`: → `Map<K, V>` — key 별로 모은 **항목 배열 전체**를 valueSelector(items)→집계값으로 변환. 그룹별 합계 등.
|
|
31
|
-
- `toObject(keySelector, valueSelector?)`: → `Record<string, V|T>` — key(string)→값 객체. 중복 key(둘 다 non-null)면 ArgumentError.
|
|
32
|
-
|
|
33
|
-
## 트리화
|
|
34
|
-
|
|
35
|
-
- `toTree(keyProp, parentKey)`: → `TreeArray<T>[]` — 평면 배열을 트리로. `parentKey` 값이 null/undefined 인 항목이 루트, 각 노드에 `children` 추가(항목은 clone 됨). 내부 `toArrayMap` 으로 O(n). `TreeArray<T> = T & { children: TreeArray<T>[] }`.
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
items.toTree("id", "parentId");
|
|
39
|
-
// [{ id: 1, name: "root", children: [{ id: 2, ..., children: [] }] }]
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## 정렬 / 중복제거
|
|
43
|
-
|
|
44
|
-
- `orderBy(selector?)` / `orderByDesc(selector?)`: → `T[]` (새 배열) — selector 가 반환한 string/number/boolean/DateTime/DateOnly/Time/undefined 로 정렬. null/undefined 는 오름차순 앞·내림차순 뒤. 날짜타입은 tick 비교, 문자열은 `localeCompare`. selector 없으면 요소 자체.
|
|
45
|
-
- `orderByThis(selector?)` / `orderByDescThis(selector?)`: → `T[]` @mutates — 원본을 in-place 정렬.
|
|
46
|
-
- `distinct(options?)`: → `T[]` (새 배열) — 중복 제거. options: `true`/`{ matchAddress: true }`=참조 비교 O(n), `{ keyFn }`=커스텀 key O(n), 미지정=원시는 값·객체는 깊은 비교 O(n²). 대량 객체 배열엔 `keyFn` 권장.
|
|
47
|
-
- `distinctThis(options?)`: → `T[]` @mutates — 원본에서 중복 제거(첫 등장만 유지).
|
|
48
|
-
|
|
49
|
-
## diff / merge
|
|
50
|
-
|
|
51
|
-
- `diffs(target, options?)`: → `ArrayDiffsResult<T,P>[]` — 두 배열 비교. 결과 항목은 `{ source: undefined, target }`(INSERT) / `{ source, target: undefined }`(DELETE) / `{ source, target }`(UPDATE). options: `keys`=동일성 판정 key(없으면 전체 깊은 비교), `excludes`=비교 제외 속성. target 에 중복 key 면 첫 매칭만.
|
|
52
|
-
- `oneWayDiffs(orgItems, keyPropNameOrGetValFn, options?)`: → `ArrayOneWayDiffResult<T>[]` — 현재 배열을 원본(배열 또는 `Map<key, item>`) 대비 분류. 결과 type: `"create"`(key 값 없거나 원본에 없음)·`"update"`(값 다름)·`"same"`(같음, `includeSame: true` 일 때만 포함). keyPropNameOrGetValFn 은 key 속성명 또는 `(item)=>키값` 함수. options.excludes/includes 로 비교 범위 조정. 저장 시 INSERT/UPDATE 판정에 사용.
|
|
53
|
-
- `merge(target, options?)`: → `(T|P|(T&P))[]` (새 배열) — `diffs` 결과로 source 를 기준 삼아 병합. UPDATE 는 `obj.merge` 로 깊은 병합, INSERT 는 추가, DELETE 는 유지(원본 보존). options 는 `diffs` 와 동일.
|
|
54
|
-
|
|
55
|
-
`ArrayDiffsResult`·`ArrayOneWayDiffResult`·`TreeArray`·`ComparableType`(=`string | number | boolean | DateTime | DateOnly | Time | undefined`) 타입이 함께 export 됨.
|
|
56
|
-
|
|
57
|
-
## 집계
|
|
58
|
-
|
|
59
|
-
- `sum(selector?)`: → number — 합계(빈 배열 0). 숫자 아닌 값이면 ArgumentError.
|
|
60
|
-
- `min(selector?)` / `max(selector?)`: → `string | number | undefined` — 최소/최대. 숫자·문자열만 허용(아니면 ArgumentError). 빈 배열 undefined.
|
|
61
|
-
|
|
62
|
-
## 그 외 @mutates
|
|
63
|
-
|
|
64
|
-
- `insert(index, ...items)`: → this — index 위치에 삽입.
|
|
65
|
-
- `remove(item)` / `remove(selector)`: → this — 일치 항목(또는 조건 매칭) 모두 제거(역순 순회 O(n)).
|
|
66
|
-
- `toggle(item)`: → this — 있으면 제거·없으면 push.
|
|
67
|
-
- `clear()`: → this — 전체 비움.
|
|
68
|
-
|
|
69
|
-
## shuffle
|
|
70
|
-
|
|
71
|
-
- `shuffle()`: → `T[]` (새 배열) — Fisher–Yates 무작위 섞기. 원본 불변.
|
|
72
|
-
|
|
73
|
-
```ts
|
|
74
|
-
const grouped = users.toArrayMap((u) => u.teamId); // Map<teamId, User[]>
|
|
75
|
-
const sorted = items.orderBy((x) => x.createdAt); // DateTime 기준 오름차순
|
|
76
|
-
const diff = next.oneWayDiffs(prev, "id"); // create/update/same 분류
|
|
77
|
-
```
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# @simplysm/core-common — 날짜·시간 값 타입
|
|
2
|
-
|
|
3
|
-
날짜·시간을 **불변(immutable)** 값으로 다룰 때 함께 읽히는 묶음. JS `Date` 대신 사용. `set*`·`add*` 메서드는 모두 새 인스턴스를 반환하고 원본을 바꾸지 않음. 모두 로컬 타임존 기준으로 동작. ORM 컬럼 타입(`DateTime`/`DateOnly`/`Time`)·JSON/Worker 직렬화에서 1급 지원됨.
|
|
4
|
-
|
|
5
|
-
세 클래스 공통:
|
|
6
|
-
- `parse(str)` 정적 메서드로 문자열 파싱(미지원 형식이면 ArgumentError).
|
|
7
|
-
- `tick` getter — 내부 밀리초 값. `equal`·정렬·복제의 동등성 기준.
|
|
8
|
-
- `isValid` getter — 유효 값 여부.
|
|
9
|
-
- `toFormatString(formatStr)` / `toString()` — 포맷 문자열 변환(아래 `dt` 네임스페이스의 포맷 토큰 사용).
|
|
10
|
-
|
|
11
|
-
## DateTime
|
|
12
|
-
|
|
13
|
-
날짜+시간(밀리초 정밀도) 불변 클래스.
|
|
14
|
-
|
|
15
|
-
생성자 오버로드:
|
|
16
|
-
- `new DateTime()` — 현재 시각.
|
|
17
|
-
- `new DateTime(year, month, day, hour?, minute?, second?, millisecond?)` — month 는 1~12(내부에서 0-base 변환). 시·분·초·밀리초 생략 시 0.
|
|
18
|
-
- `new DateTime(tick)` — 밀리초 tick.
|
|
19
|
-
- `new DateTime(date)` — JS Date 복제.
|
|
20
|
-
|
|
21
|
-
- `DateTime.parse(str)`: → DateTime — 지원 형식: `yyyy-MM-dd HH:mm:ss[.fff]`, `yyyyMMddHHmmss`, `yyyy-MM-dd AM/PM HH:mm:ss`, 한국어 `yyyy-MM-dd 오전/오후 HH:mm:ss`, ISO 8601.
|
|
22
|
-
|
|
23
|
-
getter: `year`·`month`(1~12)·`day`·`hour`·`minute`·`second`·`millisecond`·`tick`·`dayOfWeek`(일~토=0~6)·`timezoneOffsetMinutes`(UTC 대비 분, KST=+540)·`isValid`·`date`(내부 Date, 읽기 전용).
|
|
24
|
-
|
|
25
|
-
불변 변환(새 인스턴스): `setYear`·`setMonth`·`setDay`·`setHour`·`setMinute`·`setSecond`·`setMillisecond`. 산술(새 인스턴스): `addYears`·`addMonths`·`addDays`·`addHours`·`addMinutes`·`addSeconds`·`addMilliseconds`.
|
|
26
|
-
|
|
27
|
-
- `setMonth(month)` / `addMonths` — 대상 월의 일수가 현재 일보다 적으면 그 달 마지막 일로 보정(1/31 → setMonth(2) → 2/28|29). 범위 밖 월은 연도로 캐리.
|
|
28
|
-
- `setDay(day)` — 범위 밖 일은 JS Date 규칙대로 다음/이전 월로 넘어감.
|
|
29
|
-
|
|
30
|
-
```ts
|
|
31
|
-
import { DateTime } from "@simplysm/core-common";
|
|
32
|
-
const at = DateTime.parse("2025-01-15 10:30:00");
|
|
33
|
-
at.addDays(3).toFormatString("yyyy-MM-dd HH:mm"); // "2025-01-18 10:30"
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## DateOnly
|
|
37
|
-
|
|
38
|
-
시간 없는 날짜(yyyy-MM-dd) 불변 클래스.
|
|
39
|
-
|
|
40
|
-
생성자: `new DateOnly()`(오늘) / `(year, month, day)` / `(tick)` / `(date)`.
|
|
41
|
-
|
|
42
|
-
- `DateOnly.parse(str)`: → DateOnly — `yyyy-MM-dd`·`yyyyMMdd`(타임존 무관, 문자열에서 직접 추출)·ISO 8601(UTC 해석 후 로컬 변환). 서버/클라 타임존이 다르면 `yyyy-MM-dd` 형식 권장.
|
|
43
|
-
|
|
44
|
-
getter: `year`·`month`·`day`·`tick`·`dayOfWeek`·`isValid`·`date`. 불변 변환: `setYear`·`setMonth`·`setDay`(DateTime 과 동일한 월말/캐리 보정). 산술: `addYears`·`addMonths`·`addDays`.
|
|
45
|
-
|
|
46
|
-
주차 계산(ISO 8601 기본: weekStartDay=1 월요일, minDaysInFirstWeek=4):
|
|
47
|
-
- `getWeekSeqOfYear(weekStartDay?, minDaysInFirstWeek?)`: → `{ year, weekSeq }` — 해당 연도 내 주차 번호.
|
|
48
|
-
- `getWeekSeqOfMonth(weekStartDay?, minDaysInFirstWeek?)`: → `{ year, monthSeq, weekSeq }` — 해당 월 내 주차 번호.
|
|
49
|
-
- `getWeekSeqStartDate(weekStartDay?, minDaysInFirstWeek?)`: → DateOnly — 이 날짜가 속한 주의 시작일.
|
|
50
|
-
- `getBaseYearMonthSeqForWeekSeq(weekStartDay?, minDaysInFirstWeek?)`: → `{ year, monthSeq }` — 주차 귀속 기준 연·월(주가 걸친 경우 어느 달로 셈할지).
|
|
51
|
-
- `DateOnly.getDateByYearWeekSeq(arg, weekStartDay?, minDaysInFirstWeek?)`: → DateOnly — `arg = { year, month?, weekSeq }` 로 그 주의 시작일을 역산.
|
|
52
|
-
|
|
53
|
-
옵션 풀이:
|
|
54
|
-
- weekStartDay: 0~6 — 주 시작 요일. 0=일요일(미국식), 1=월요일(ISO, 기본). 달력 표시 기준에 맞춤.
|
|
55
|
-
- minDaysInFirstWeek: 1~7 — 첫 주로 인정할 최소 일수. 4=ISO(주의 과반), 1=시작일 포함 즉시 1주차(미국식).
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
import { DateOnly } from "@simplysm/core-common";
|
|
59
|
-
new DateOnly(2025, 1, 6).getWeekSeqOfYear(); // { year: 2025, weekSeq: 2 }
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Time
|
|
63
|
-
|
|
64
|
-
날짜 없는 시간(HH:mm:ss.fff) 불변 클래스. 24시간 초과·음수 tick 은 자동으로 0~24h 범위로 순환 정규화됨.
|
|
65
|
-
|
|
66
|
-
생성자: `new Time()`(현재 시각) / `(hour, minute, second?, millisecond?)` / `(tick)` / `(date)`(Date 의 시간부만).
|
|
67
|
-
|
|
68
|
-
- `Time.parse(str)`: → Time — `HH:mm:ss[.fff]`·`AM/PM HH:mm:ss`·ISO 8601(시간부만 추출).
|
|
69
|
-
|
|
70
|
-
getter: `hour`·`minute`·`second`·`millisecond`·`tick`·`isValid`. 불변 변환: `setHour`·`setMinute`·`setSecond`·`setMillisecond`. 산술: `addHours`·`addMinutes`·`addSeconds`·`addMilliseconds` (모두 24시간 순환 — 23:30 에 +1h → 00:30).
|
|
71
|
-
|
|
72
|
-
## dt 네임스페이스 (날짜/시간 포맷)
|
|
73
|
-
|
|
74
|
-
`toFormatString` 이 내부적으로 쓰는 포맷 토큰 정의. 직접 호출도 가능.
|
|
75
|
-
|
|
76
|
-
- `dt.format(formatString, args)`: → string — `args = { year?, month?, day?, hour?, minute?, second?, millisecond?, timezoneOffsetMinutes? }` 중 주어진 구성요소만 치환. C# 스타일 토큰 사용.
|
|
77
|
-
- `dt.normalizeMonth(year, month, day)`: → `{ year, month, day }` — 범위 밖 월을 연도로 캐리하고 일을 월말로 보정. `set*Month` 구현 기반.
|
|
78
|
-
- `dt.convert12To24(rawHour, isPM)`: → number — 12시간제(1~12)+오전/오후 → 24시간제(0~23). 12 AM=0, 12 PM=12.
|
|
79
|
-
- 타입 `dt.DtNormalizedMonth` = `{ year; month; day }`.
|
|
80
|
-
|
|
81
|
-
포맷 토큰: `yyyy`/`yy`(연), `MM`/`M`(월), `ddd`(요일 일~토)/`dd`/`d`(일), `tt`(AM/PM), `hh`/`h`(12시간), `HH`/`H`(24시간), `mm`/`m`(분), `ss`/`s`(초), `fff`/`ff`/`f`(밀리초), `zzz`(±HH:mm)/`zz`(±HH)/`z`(±H, 타임존 오프셋).
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
import { dt } from "@simplysm/core-common";
|
|
85
|
-
dt.format("yyyy-MM-dd (ddd)", { year: 2024, month: 3, day: 15 }); // "2024-03-15 (금)"
|
|
86
|
-
```
|