From 8086ec0b4ba213a50662ef4f03251b7575e9b467 Mon Sep 17 00:00:00 2001 From: toothlessdev Date: Fri, 30 Jan 2026 21:04:22 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20types,=20utils=20=EB=94=94?= =?UTF-8?q?=EB=A0=89=ED=86=A0=EB=A6=AC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/partition/createNode.ts | 37 ------------------- .../{partition.ts => types/hashNode.ts} | 5 --- .../src/partition/types/hashObject.ts | 4 ++ .../src/partition/types/partitionedSpec.ts | 8 ++++ 4 files changed, 12 insertions(+), 42 deletions(-) delete mode 100644 packages/patchlogr-core/src/partition/createNode.ts rename packages/patchlogr-core/src/partition/{partition.ts => types/hashNode.ts} (59%) create mode 100644 packages/patchlogr-core/src/partition/types/hashObject.ts create mode 100644 packages/patchlogr-core/src/partition/types/partitionedSpec.ts diff --git a/packages/patchlogr-core/src/partition/createNode.ts b/packages/patchlogr-core/src/partition/createNode.ts deleted file mode 100644 index 9299f68..0000000 --- a/packages/patchlogr-core/src/partition/createNode.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { HashNode } from "./partition"; -import { createSHA256Hash } from "../utils/createHash"; -import stableStringify from "fast-json-stable-stringify"; - -export function createNode( - key: K, - hash: string, - children: HashNode[], -): HashNode { - return { type: "node", key, hash, children }; -} - -export function createLeafNode( - key: K, - hash: string, - value: V, -): HashNode { - return { type: "leaf", key, hash, value }; -} -export function createHashedLeaf(key: K, value: V): HashNode { - const hash = createSHA256Hash(stableStringify({ key, value })); - return { type: "leaf", key, hash, value }; -} - -export function createHashedNode( - key: K, - children: HashNode[], -): HashNode { - const sortedChildData = children - .map((child) => ({ key: child.key, hash: child.hash })) - .sort((a, b) => String(a.key).localeCompare(String(b.key))); - - const hash = createSHA256Hash( - stableStringify({ key, children: sortedChildData }), - ); - return { type: "node", key, hash, children }; -} diff --git a/packages/patchlogr-core/src/partition/partition.ts b/packages/patchlogr-core/src/partition/types/hashNode.ts similarity index 59% rename from packages/patchlogr-core/src/partition/partition.ts rename to packages/patchlogr-core/src/partition/types/hashNode.ts index 28b2111..04b0ef6 100644 --- a/packages/patchlogr-core/src/partition/partition.ts +++ b/packages/patchlogr-core/src/partition/types/hashNode.ts @@ -7,8 +7,3 @@ export type HashNode = { children?: HashNode[]; value?: V; }; - -export type PartitionedSpec = { - root: HashNode; - metadata: Record; -}; diff --git a/packages/patchlogr-core/src/partition/types/hashObject.ts b/packages/patchlogr-core/src/partition/types/hashObject.ts new file mode 100644 index 0000000..73b2745 --- /dev/null +++ b/packages/patchlogr-core/src/partition/types/hashObject.ts @@ -0,0 +1,4 @@ +export type HashObject = { + hash: string; + data: T; +}; diff --git a/packages/patchlogr-core/src/partition/types/partitionedSpec.ts b/packages/patchlogr-core/src/partition/types/partitionedSpec.ts new file mode 100644 index 0000000..9d00f2b --- /dev/null +++ b/packages/patchlogr-core/src/partition/types/partitionedSpec.ts @@ -0,0 +1,8 @@ +import type { HashNode } from "./hashNode"; +import type { HashObject } from "./hashObject"; + +export type PartitionedSpec = { + root: HashNode; + metadata: Record; + hashObjects: HashObject[]; +}; From ba26b29555d33347213f94e72fb6c9706782363e Mon Sep 17 00:00:00 2001 From: toothlessdev Date: Fri, 30 Jan 2026 21:05:15 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20createNode=20=ED=97=AC=ED=8D=BC?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/partition/utils/createNode.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 packages/patchlogr-core/src/partition/utils/createNode.ts diff --git a/packages/patchlogr-core/src/partition/utils/createNode.ts b/packages/patchlogr-core/src/partition/utils/createNode.ts new file mode 100644 index 0000000..e7a848d --- /dev/null +++ b/packages/patchlogr-core/src/partition/utils/createNode.ts @@ -0,0 +1,37 @@ +import type { HashNode } from "../types/hashNode"; +import { createSHA256Hash } from "../../utils/createHash"; +import stableStringify from "fast-json-stable-stringify"; + +export function createNode( + key: K, + hash: string, + children: HashNode[], +): HashNode { + return { type: "node", key, hash, children }; +} + +export function createLeafNode( + key: K, + hash: string, + value: V, +): HashNode { + return { type: "leaf", key, hash, value }; +} +export function createHashedLeaf(key: K, value: V): HashNode { + const hash = createSHA256Hash(stableStringify({ key, value })); + return { type: "leaf", key, hash, value }; +} + +export function createHashedNode( + key: K, + children: HashNode[], +): HashNode { + const sortedChildData = children + .map((child) => ({ key: child.key, hash: child.hash })) + .sort((a, b) => String(a.key).localeCompare(String(b.key))); + + const hash = createSHA256Hash( + stableStringify({ key, children: sortedChildData }), + ); + return { type: "node", key, hash, children }; +} From 35cf52829ca39b079b36d4959235e7b28ca5c541 Mon Sep 17 00:00:00 2001 From: toothlessdev Date: Fri, 30 Jan 2026 21:05:40 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feature:=20partitionBy*=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EC=97=90=EC=84=9C=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=EB=A6=AC=ED=94=84=EB=85=B8=EB=93=9C=EC=97=90=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EB=90=98=EB=8A=94=20CanonicalOperation=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EB=A5=BC=20=EB=B3=84=EB=8F=84=EB=A1=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=84=B4=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/partitionByMethod.test.ts | 76 +++++++++++++++++++ .../__tests__/partitionByTag.test.ts | 76 +++++++++++++++++++ .../patchlogr-core/src/partition/index.ts | 5 +- .../src/partition/partitionByMethod.ts | 22 ++++-- .../src/partition/partitionByTag.ts | 30 ++++---- 5 files changed, 187 insertions(+), 22 deletions(-) diff --git a/packages/patchlogr-core/src/partition/__tests__/partitionByMethod.test.ts b/packages/patchlogr-core/src/partition/__tests__/partitionByMethod.test.ts index ef5872c..06878f8 100644 --- a/packages/patchlogr-core/src/partition/__tests__/partitionByMethod.test.ts +++ b/packages/patchlogr-core/src/partition/__tests__/partitionByMethod.test.ts @@ -87,4 +87,80 @@ describe("partitionByMethod", () => { expect(postMethodNode?.children).toHaveLength(1); expect(postMethodNode?.children?.[0]?.key).toBe("POST /auth/login"); }); + + test("hashObjects를 리턴한다", () => { + const spec: CanonicalSpec = { + operations: { + "GET /user": { + key: "GET /user", + doc: { tags: ["user"] }, + method: "GET", + path: "/user", + request: { params: [] }, + responses: {}, + }, + "POST /user": { + key: "POST /user", + doc: { tags: ["user"] }, + method: "POST", + path: "/user", + request: { params: [] }, + responses: {}, + }, + }, + }; + + const result = partitionByMethod(spec); + + expect(result.hashObjects).toBeDefined(); + expect(result.hashObjects).toHaveLength(2); + }); + + test("리프노드에는 value가 포함되지 않는다", () => { + const spec: CanonicalSpec = { + operations: { + "GET /user": { + key: "GET /user", + doc: { tags: ["user"] }, + method: "GET", + path: "/user", + request: { params: [] }, + responses: {}, + }, + }, + }; + + const result = partitionByMethod(spec); + const leafNode = result.root.children?.[0]?.children?.[0]; + + expect(leafNode?.type).toBe("leaf"); + expect(leafNode?.hash).toBeDefined(); + expect(leafNode?.value).toBeUndefined(); + }); + + test("hashObjects에 리프노드에 대한 CanonicalOperation 이 매핑된다", () => { + const operation = { + key: "GET /user" as const, + doc: { tags: ["user"] }, + method: "GET" as const, + path: "/user", + request: { params: [] }, + responses: {}, + }; + + const spec: CanonicalSpec = { + operations: { + "GET /user": operation, + }, + }; + + const result = partitionByMethod(spec); + const leafNode = result.root.children?.[0]?.children?.[0]; + const hashObject = result.hashObjects.find( + (ho) => ho.hash === leafNode?.hash, + ); + + expect(hashObject).toBeDefined(); + expect(hashObject?.data).toEqual(operation); + }); }); diff --git a/packages/patchlogr-core/src/partition/__tests__/partitionByTag.test.ts b/packages/patchlogr-core/src/partition/__tests__/partitionByTag.test.ts index c5e3220..69ade15 100644 --- a/packages/patchlogr-core/src/partition/__tests__/partitionByTag.test.ts +++ b/packages/patchlogr-core/src/partition/__tests__/partitionByTag.test.ts @@ -112,4 +112,80 @@ describe("partitionByTag", () => { expect(defaultTagNode?.children).toHaveLength(1); expect(defaultTagNode?.children?.[0]?.key).toBe("GET /user"); }); + + test("hashObjects를 리턴한다", () => { + const spec: CanonicalSpec = { + operations: { + "GET /user": { + key: "GET /user", + doc: { tags: ["user"] }, + method: "GET", + path: "/user", + request: { params: [] }, + responses: {}, + }, + "POST /user": { + key: "POST /user", + doc: { tags: ["user"] }, + method: "POST", + path: "/user", + request: { params: [] }, + responses: {}, + }, + }, + }; + + const result = partitionByTag(spec); + + expect(result.hashObjects).toBeDefined(); + expect(result.hashObjects).toHaveLength(2); + }); + + test("리프노드에는 value가 포함되지 않는다", () => { + const spec: CanonicalSpec = { + operations: { + "GET /user": { + key: "GET /user", + doc: { tags: ["user"] }, + method: "GET", + path: "/user", + request: { params: [] }, + responses: {}, + }, + }, + }; + + const result = partitionByTag(spec); + const leafNode = result.root.children?.[0]?.children?.[0]; + + expect(leafNode?.type).toBe("leaf"); + expect(leafNode?.hash).toBeDefined(); + expect(leafNode?.value).toBeUndefined(); + }); + + test("hashObjects에 리프노드에 대한 CanonicalOperation 이 매핑된다", () => { + const operation = { + key: "GET /user" as const, + doc: { tags: ["user"] }, + method: "GET" as const, + path: "/user", + request: { params: [] }, + responses: {}, + }; + + const spec: CanonicalSpec = { + operations: { + "GET /user": operation, + }, + }; + + const result = partitionByTag(spec); + const leafNode = result.root.children?.[0]?.children?.[0]; + const hashObject = result.hashObjects.find( + (ho) => ho.hash === leafNode?.hash, + ); + + expect(hashObject).toBeDefined(); + expect(hashObject?.data).toEqual(operation); + }); }); diff --git a/packages/patchlogr-core/src/partition/index.ts b/packages/patchlogr-core/src/partition/index.ts index 386e43a..d34c01f 100644 --- a/packages/patchlogr-core/src/partition/index.ts +++ b/packages/patchlogr-core/src/partition/index.ts @@ -1,4 +1,5 @@ -export type { Hash, HashNode, PartitionedSpec } from "./partition"; +export type { Hash, HashNode } from "./types/hashNode"; +export type { PartitionedSpec } from "./types/partitionedSpec"; export { partitionByMethod } from "./partitionByMethod"; export { partitionByTag } from "./partitionByTag"; @@ -8,4 +9,4 @@ export { createLeafNode, createHashedLeaf, createHashedNode, -} from "./createNode"; +} from "./utils/createNode"; diff --git a/packages/patchlogr-core/src/partition/partitionByMethod.ts b/packages/patchlogr-core/src/partition/partitionByMethod.ts index b43af50..fdfdf2a 100644 --- a/packages/patchlogr-core/src/partition/partitionByMethod.ts +++ b/packages/patchlogr-core/src/partition/partitionByMethod.ts @@ -3,7 +3,10 @@ import type { HTTPMethod, CanonicalOperation, } from "@patchlogr/types"; -import type { PartitionedSpec, HashNode } from "./partition"; + +import type { HashNode } from "./types/hashNode"; +import type { PartitionedSpec } from "./types/partitionedSpec"; +import type { HashObject } from "./types/hashObject"; import { createSHA256Hash } from "../utils/createHash"; import stableStringify from "fast-json-stable-stringify"; @@ -15,6 +18,7 @@ export function partitionByMethod( HTTPMethod, Array<{ key: string; operation: CanonicalOperation }> >(); + const hashObjects: HashObject[] = []; Object.entries(spec.operations).forEach(([key, operation]) => { if (!methodGroups.has(operation.method)) { @@ -30,12 +34,15 @@ export function partitionByMethod( methodGroups.forEach((operations, method) => { const operationLeaves: HashNode[] = - operations.map(({ key, operation }) => ({ - type: "leaf", - key, - hash: createSHA256Hash(stableStringify(operation)), - value: operation, - })); + operations.map(({ key, operation }) => { + const hash = createSHA256Hash(stableStringify(operation)); + hashObjects.push({ hash, data: operation }); + return { + type: "leaf" as const, + key, + hash, + }; + }); const methodHash = createSHA256Hash( stableStringify(operationLeaves.map((leaf) => leaf.hash)), @@ -66,5 +73,6 @@ export function partitionByMethod( ...spec.info, ...spec.security, }, + hashObjects, }; } diff --git a/packages/patchlogr-core/src/partition/partitionByTag.ts b/packages/patchlogr-core/src/partition/partitionByTag.ts index d762558..944eee8 100644 --- a/packages/patchlogr-core/src/partition/partitionByTag.ts +++ b/packages/patchlogr-core/src/partition/partitionByTag.ts @@ -1,9 +1,8 @@ -import type { - CanonicalSpec, - CanonicalOperation, - OperationKey, -} from "@patchlogr/types"; -import type { PartitionedSpec, HashNode } from "./partition"; +import type { CanonicalSpec, CanonicalOperation } from "@patchlogr/types"; + +import type { PartitionedSpec } from "./types/partitionedSpec"; +import type { HashNode } from "./types/hashNode"; +import type { HashObject } from "./types/hashObject"; import { createSHA256Hash } from "../utils/createHash"; import stableStringify from "fast-json-stable-stringify"; @@ -17,6 +16,7 @@ export function partitionByTag( string, Array<{ key: string; operation: CanonicalOperation }> >(); + const hashObjects: HashObject[] = []; Object.entries(spec.operations).forEach(([key, operation]) => { const tag = operation.doc?.tags?.[0] || DEFAULT_TAG; @@ -25,7 +25,7 @@ export function partitionByTag( tagGroups.set(tag, []); } tagGroups.get(tag)?.push({ - key: key as OperationKey, + key: key, operation, }); }); @@ -34,12 +34,15 @@ export function partitionByTag( tagGroups.forEach((operations, tag) => { const operationLeaves: HashNode[] = - operations.map(({ key, operation }) => ({ - type: "leaf", - key, - hash: createSHA256Hash(stableStringify(operation)), - value: operation, - })); + operations.map(({ key, operation }) => { + const hash = createSHA256Hash(stableStringify(operation)); + hashObjects.push({ hash, data: operation }); + return { + type: "leaf", + key, + hash, + }; + }); const tagHash = createSHA256Hash( stableStringify(operationLeaves.map((leaf) => leaf.hash)), @@ -70,5 +73,6 @@ export function partitionByTag( ...spec.info, ...spec.security, }, + hashObjects, }; }