@no-k/hermes 0.1.0
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/LICENSE +15 -0
- package/README.md +88 -0
- package/bin/hermes.mjs +159 -0
- package/catalog.json +1084 -0
- package/lib/catalog.mjs +51 -0
- package/package.json +28 -0
- package/templates/algorithm/geometry/ccw/ccw.ts +25 -0
- package/templates/algorithm/geometry/ccw/index.ts +1 -0
- package/templates/algorithm/geometry/convex-hull/convex-hull.ts +43 -0
- package/templates/algorithm/geometry/convex-hull/index.ts +1 -0
- package/templates/algorithm/geometry/polygon-area/index.ts +1 -0
- package/templates/algorithm/geometry/polygon-area/polygon-area.ts +13 -0
- package/templates/algorithm/graph/bellman-ford/bellman-ford.ts +46 -0
- package/templates/algorithm/graph/bellman-ford/index.ts +1 -0
- package/templates/algorithm/graph/bfs/bfs.ts +21 -0
- package/templates/algorithm/graph/bfs/index.ts +1 -0
- package/templates/algorithm/graph/dfs/dfs.ts +22 -0
- package/templates/algorithm/graph/dfs/index.ts +1 -0
- package/templates/algorithm/graph/dijkstra/dijkstra.ts +37 -0
- package/templates/algorithm/graph/dijkstra/index.ts +1 -0
- package/templates/algorithm/graph/floyd-warshall/floyd-warshall.ts +17 -0
- package/templates/algorithm/graph/floyd-warshall/index.ts +1 -0
- package/templates/algorithm/graph/kruskal/index.ts +1 -0
- package/templates/algorithm/graph/kruskal/kruskal.ts +29 -0
- package/templates/algorithm/graph/prim/index.ts +1 -0
- package/templates/algorithm/graph/prim/prim.ts +44 -0
- package/templates/algorithm/graph/topology-sort/index.ts +1 -0
- package/templates/algorithm/graph/topology-sort/topology-sort.ts +31 -0
- package/templates/algorithm/search/binary-search/binary-search.ts +74 -0
- package/templates/algorithm/search/binary-search/index.ts +1 -0
- package/templates/algorithm/string/kmp/index.ts +1 -0
- package/templates/algorithm/string/kmp/kmp.ts +46 -0
- package/templates/algorithm/string/z-algorithm/index.ts +1 -0
- package/templates/algorithm/string/z-algorithm/z-algorithm.ts +41 -0
- package/templates/algorithm/tree/avl-tree/avl-tree.ts +197 -0
- package/templates/algorithm/tree/avl-tree/index.ts +1 -0
- package/templates/algorithm/tree/binary-search-tree/binary-search-tree.ts +144 -0
- package/templates/algorithm/tree/binary-search-tree/index.ts +1 -0
- package/templates/algorithm/tree/lca/index.ts +1 -0
- package/templates/algorithm/tree/lca/lca.ts +119 -0
- package/templates/array/chunk/chunk.ts +13 -0
- package/templates/array/chunk/index.ts +1 -0
- package/templates/array/compact/compact.ts +8 -0
- package/templates/array/compact/index.ts +1 -0
- package/templates/array/count-by/count-by.ts +12 -0
- package/templates/array/count-by/index.ts +1 -0
- package/templates/array/difference/difference.ts +6 -0
- package/templates/array/difference/index.ts +1 -0
- package/templates/array/drop/drop.ts +7 -0
- package/templates/array/drop/index.ts +1 -0
- package/templates/array/group-by/group-by.ts +18 -0
- package/templates/array/group-by/index.ts +1 -0
- package/templates/array/intersection/index.ts +1 -0
- package/templates/array/intersection/intersection.ts +15 -0
- package/templates/array/key-by/index.ts +1 -0
- package/templates/array/key-by/key-by.ts +11 -0
- package/templates/array/max-by/index.ts +1 -0
- package/templates/array/max-by/max-by.ts +20 -0
- package/templates/array/min-by/index.ts +1 -0
- package/templates/array/min-by/min-by.ts +20 -0
- package/templates/array/partition/index.ts +1 -0
- package/templates/array/partition/partition.ts +16 -0
- package/templates/array/range/index.ts +1 -0
- package/templates/array/range/range.ts +22 -0
- package/templates/array/sample/index.ts +1 -0
- package/templates/array/sample/sample.ts +10 -0
- package/templates/array/shuffle/index.ts +1 -0
- package/templates/array/shuffle/shuffle.ts +15 -0
- package/templates/array/sort-by/index.ts +1 -0
- package/templates/array/sort-by/sort-by.ts +32 -0
- package/templates/array/sum-by/index.ts +1 -0
- package/templates/array/sum-by/sum-by.ts +9 -0
- package/templates/array/take/index.ts +1 -0
- package/templates/array/take/take.ts +7 -0
- package/templates/array/union/index.ts +1 -0
- package/templates/array/union/union.ts +5 -0
- package/templates/array/unique/index.ts +1 -0
- package/templates/array/unique/unique.ts +5 -0
- package/templates/array/unique-by/index.ts +1 -0
- package/templates/array/unique-by/unique-by.ts +19 -0
- package/templates/array/windowed/index.ts +1 -0
- package/templates/array/windowed/windowed.ts +15 -0
- package/templates/array/zip/index.ts +1 -0
- package/templates/array/zip/zip.ts +11 -0
- package/templates/boolean/parse-boolean/index.ts +1 -0
- package/templates/boolean/parse-boolean/parse-boolean.ts +17 -0
- package/templates/boolean/toggle/index.ts +1 -0
- package/templates/boolean/toggle/toggle.ts +2 -0
- package/templates/boolean/xor/index.ts +1 -0
- package/templates/boolean/xor/xor.ts +2 -0
- package/templates/data-structure/deque/deque.ts +79 -0
- package/templates/data-structure/deque/index.ts +1 -0
- package/templates/data-structure/fenwick-tree/fenwick-tree.ts +76 -0
- package/templates/data-structure/fenwick-tree/index.ts +1 -0
- package/templates/data-structure/heap/heap.ts +88 -0
- package/templates/data-structure/heap/index.ts +3 -0
- package/templates/data-structure/heap/max-heap.ts +7 -0
- package/templates/data-structure/heap/min-heap.ts +7 -0
- package/templates/data-structure/priority-queue/index.ts +1 -0
- package/templates/data-structure/priority-queue/priority-queue.ts +44 -0
- package/templates/data-structure/queue/index.ts +1 -0
- package/templates/data-structure/queue/queue.ts +66 -0
- package/templates/data-structure/segment-tree/index.ts +1 -0
- package/templates/data-structure/segment-tree/segment-tree.ts +79 -0
- package/templates/data-structure/trie/index.ts +1 -0
- package/templates/data-structure/trie/trie.ts +104 -0
- package/templates/data-structure/union-find/index.ts +1 -0
- package/templates/data-structure/union-find/union-find.ts +79 -0
- package/templates/dom/attribute/attribute.ts +24 -0
- package/templates/dom/attribute/index.ts +1 -0
- package/templates/dom/class-name/class-name.ts +48 -0
- package/templates/dom/class-name/index.ts +1 -0
- package/templates/dom/is-element/index.ts +1 -0
- package/templates/dom/is-element/is-element.ts +20 -0
- package/templates/dom/query/index.ts +1 -0
- package/templates/dom/query/query.ts +7 -0
- package/templates/http/content-type/content-type.ts +130 -0
- package/templates/http/content-type/index.ts +1 -0
- package/templates/http/header/header.ts +56 -0
- package/templates/http/header/index.ts +1 -0
- package/templates/http/method/index.ts +1 -0
- package/templates/http/method/method.ts +25 -0
- package/templates/http/status/index.ts +1 -0
- package/templates/http/status/status.ts +191 -0
- package/templates/number/clamp/clamp.ts +11 -0
- package/templates/number/clamp/index.ts +1 -0
- package/templates/number/factorial/factorial.ts +15 -0
- package/templates/number/factorial/index.ts +1 -0
- package/templates/number/format-thousands/format-thousands.ts +24 -0
- package/templates/number/format-thousands/index.ts +1 -0
- package/templates/number/gcd/gcd.ts +13 -0
- package/templates/number/gcd/index.ts +1 -0
- package/templates/number/in-range/in-range.ts +3 -0
- package/templates/number/in-range/index.ts +1 -0
- package/templates/number/is-even/index.ts +1 -0
- package/templates/number/is-even/is-even.ts +5 -0
- package/templates/number/is-odd/index.ts +1 -0
- package/templates/number/is-odd/is-odd.ts +7 -0
- package/templates/number/lcm/index.ts +1 -0
- package/templates/number/lcm/lcm.ts +12 -0
- package/templates/number/lerp/index.ts +1 -0
- package/templates/number/lerp/lerp.ts +3 -0
- package/templates/number/percentage/index.ts +1 -0
- package/templates/number/percentage/percentage.ts +7 -0
- package/templates/number/random/index.ts +1 -0
- package/templates/number/random/random.ts +9 -0
- package/templates/number/random-int/index.ts +1 -0
- package/templates/number/random-int/random-int.ts +9 -0
- package/templates/number/round/index.ts +1 -0
- package/templates/number/round/round.ts +22 -0
- package/templates/number/safe-parse-number/index.ts +1 -0
- package/templates/number/safe-parse-number/safe-parse-number.ts +29 -0
- package/templates/number/to-ordinal/index.ts +1 -0
- package/templates/number/to-ordinal/to-ordinal.ts +28 -0
- package/templates/object/deep-clone/deep-clone.ts +47 -0
- package/templates/object/deep-clone/index.ts +1 -0
- package/templates/object/deep-equal/deep-equal.ts +66 -0
- package/templates/object/deep-equal/index.ts +1 -0
- package/templates/object/deep-merge/deep-merge.ts +49 -0
- package/templates/object/deep-merge/index.ts +1 -0
- package/templates/object/defaults/defaults.ts +14 -0
- package/templates/object/defaults/index.ts +1 -0
- package/templates/object/filter-object/filter-object.ts +11 -0
- package/templates/object/filter-object/index.ts +1 -0
- package/templates/object/get/get.ts +75 -0
- package/templates/object/get/index.ts +1 -0
- package/templates/object/has/has.ts +60 -0
- package/templates/object/has/index.ts +1 -0
- package/templates/object/invert/index.ts +1 -0
- package/templates/object/invert/invert.ts +13 -0
- package/templates/object/map-keys/index.ts +1 -0
- package/templates/object/map-keys/map-keys.ts +16 -0
- package/templates/object/map-values/index.ts +1 -0
- package/templates/object/map-values/map-values.ts +14 -0
- package/templates/object/omit/index.ts +1 -0
- package/templates/object/omit/omit.ts +15 -0
- package/templates/object/omit-by/index.ts +1 -0
- package/templates/object/omit-by/omit-by.ts +19 -0
- package/templates/object/pick/index.ts +1 -0
- package/templates/object/pick/pick.ts +15 -0
- package/templates/object/pick-by/index.ts +1 -0
- package/templates/object/pick-by/pick-by.ts +19 -0
- package/templates/object/set/index.ts +1 -0
- package/templates/object/set/set.ts +118 -0
- package/templates/string/capitalize/capitalize.ts +4 -0
- package/templates/string/capitalize/index.ts +1 -0
- package/templates/string/change-case/change-case.ts +98 -0
- package/templates/string/change-case/index.ts +1 -0
- package/templates/string/char/char.ts +14 -0
- package/templates/string/char/index.ts +1 -0
- package/templates/string/count-occurrences/count-occurrences.ts +20 -0
- package/templates/string/count-occurrences/index.ts +1 -0
- package/templates/string/escape-html/escape-html.ts +16 -0
- package/templates/string/escape-html/index.ts +1 -0
- package/templates/string/get-decorated-string/get-decorated-string.ts +7 -0
- package/templates/string/get-decorated-string/index.ts +1 -0
- package/templates/string/is-blank/index.ts +1 -0
- package/templates/string/is-blank/is-blank.ts +6 -0
- package/templates/string/mask/index.ts +1 -0
- package/templates/string/mask/mask.ts +30 -0
- package/templates/string/pad/index.ts +1 -0
- package/templates/string/pad/pad.ts +17 -0
- package/templates/string/random-string/index.ts +1 -0
- package/templates/string/random-string/random-string.ts +20 -0
- package/templates/string/reverse/index.ts +1 -0
- package/templates/string/reverse/reverse.ts +6 -0
- package/templates/string/slugify/index.ts +1 -0
- package/templates/string/slugify/slugify.ts +11 -0
- package/templates/string/strip-tags/index.ts +1 -0
- package/templates/string/strip-tags/strip-tags.ts +8 -0
- package/templates/string/template/index.ts +1 -0
- package/templates/string/template/template.ts +14 -0
- package/templates/string/truncate/index.ts +1 -0
- package/templates/string/truncate/truncate.ts +17 -0
- package/templates/string/truncate-middle/index.ts +1 -0
- package/templates/string/truncate-middle/truncate-middle.ts +25 -0
- package/templates/string/uuid/index.ts +1 -0
- package/templates/string/uuid/uuid.ts +9 -0
- package/templates/tailwindcss/class-variant/class-variant.ts +35 -0
- package/templates/tailwindcss/class-variant/index.ts +1 -0
- package/templates/tailwindcss/cn/cn.ts +29 -0
- package/templates/tailwindcss/cn/index.ts +1 -0
- package/templates/tailwindcss/create-tailwind-css/create-tailwind-css.ts +16 -0
- package/templates/tailwindcss/create-tailwind-css/index.ts +1 -0
- package/templates/tailwindcss/tw-merge-config/index.ts +1 -0
- package/templates/tailwindcss/tw-merge-config/tw-merge-config.ts +14 -0
- package/templates/type/get-type/get-type.ts +27 -0
- package/templates/type/get-type/index.ts +1 -0
- package/templates/type/is-type-of/index.ts +1 -0
- package/templates/type/is-type-of/is-type-of.ts +26 -0
- package/templates/util/mutex/index.ts +1 -0
- package/templates/util/mutex/mutex.ts +109 -0
- package/templates/util/url-parser/index.ts +1 -0
- package/templates/util/url-parser/url-parser.ts +44 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
export const HTTP_STATUS = {
|
|
2
|
+
_100_CONTINUE: 100,
|
|
3
|
+
_101_SWITCHING_PROTOCOLS: 101,
|
|
4
|
+
_102_PROCESSING: 102,
|
|
5
|
+
_103_EARLY_HINTS: 103,
|
|
6
|
+
_200_OK: 200,
|
|
7
|
+
_201_CREATED: 201,
|
|
8
|
+
_202_ACCEPTED: 202,
|
|
9
|
+
_203_NON_AUTHORITATIVE_INFORMATION: 203,
|
|
10
|
+
_204_NO_CONTENT: 204,
|
|
11
|
+
_205_RESET_CONTENT: 205,
|
|
12
|
+
_206_PARTIAL_CONTENT: 206,
|
|
13
|
+
_207_MULTI_STATUS: 207,
|
|
14
|
+
_208_ALREADY_REPORTED: 208,
|
|
15
|
+
_226_IM_USED: 226,
|
|
16
|
+
_300_MULTIPLE_CHOICES: 300,
|
|
17
|
+
_301_MOVED_PERMANENTLY: 301,
|
|
18
|
+
_302_FOUND: 302,
|
|
19
|
+
_303_SEE_OTHER: 303,
|
|
20
|
+
_304_NOT_MODIFIED: 304,
|
|
21
|
+
_305_USE_PROXY: 305,
|
|
22
|
+
_307_TEMPORARY_REDIRECT: 307,
|
|
23
|
+
_308_PERMANENT_REDIRECT: 308,
|
|
24
|
+
_400_BAD_REQUEST: 400,
|
|
25
|
+
_401_UNAUTHORIZED: 401,
|
|
26
|
+
_402_PAYMENT_REQUIRED: 402,
|
|
27
|
+
_403_FORBIDDEN: 403,
|
|
28
|
+
_404_NOT_FOUND: 404,
|
|
29
|
+
_405_METHOD_NOT_ALLOWED: 405,
|
|
30
|
+
_406_NOT_ACCEPTABLE: 406,
|
|
31
|
+
_407_PROXY_AUTHENTICATION_REQUIRED: 407,
|
|
32
|
+
_408_REQUEST_TIMEOUT: 408,
|
|
33
|
+
_409_CONFLICT: 409,
|
|
34
|
+
_410_GONE: 410,
|
|
35
|
+
_411_LENGTH_REQUIRED: 411,
|
|
36
|
+
_412_PRECONDITION_FAILED: 412,
|
|
37
|
+
_413_PAYLOAD_TOO_LARGE: 413,
|
|
38
|
+
_414_URI_TOO_LONG: 414,
|
|
39
|
+
_415_UNSUPPORTED_MEDIA_TYPE: 415,
|
|
40
|
+
_416_RANGE_NOT_SATISFIABLE: 416,
|
|
41
|
+
_417_EXPECTATION_FAILED: 417,
|
|
42
|
+
_418_IM_A_TEAPOT: 418,
|
|
43
|
+
_421_MISDIRECTED_REQUEST: 421,
|
|
44
|
+
_422_UNPROCESSABLE_CONTENT: 422,
|
|
45
|
+
_423_LOCKED: 423,
|
|
46
|
+
_424_FAILED_DEPENDENCY: 424,
|
|
47
|
+
_425_TOO_EARLY: 425,
|
|
48
|
+
_426_UPGRADE_REQUIRED: 426,
|
|
49
|
+
_428_PRECONDITION_REQUIRED: 428,
|
|
50
|
+
_429_TOO_MANY_REQUESTS: 429,
|
|
51
|
+
_431_REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
|
|
52
|
+
_451_UNAVAILABLE_FOR_LEGAL_REASONS: 451,
|
|
53
|
+
_500_INTERNAL_SERVER_ERROR: 500,
|
|
54
|
+
_501_NOT_IMPLEMENTED: 501,
|
|
55
|
+
_502_BAD_GATEWAY: 502,
|
|
56
|
+
_503_SERVICE_UNAVAILABLE: 503,
|
|
57
|
+
_504_GATEWAY_TIMEOUT: 504,
|
|
58
|
+
_505_HTTP_VERSION_NOT_SUPPORTED: 505,
|
|
59
|
+
_506_VARIANT_ALSO_NEGOTIATES: 506,
|
|
60
|
+
_507_INSUFFICIENT_STORAGE: 507,
|
|
61
|
+
_508_LOOP_DETECTED: 508,
|
|
62
|
+
_510_NOT_EXTENDED: 510,
|
|
63
|
+
_511_NETWORK_AUTHENTICATION_REQUIRED: 511,
|
|
64
|
+
} as const;
|
|
65
|
+
|
|
66
|
+
export type KnownHttpStatusCode = (typeof HTTP_STATUS)[keyof typeof HTTP_STATUS];
|
|
67
|
+
|
|
68
|
+
export type HttpStatusGroup = "informational" | "success" | "redirection" | "client-error" | "server-error";
|
|
69
|
+
|
|
70
|
+
export const HTTP_STATUS_TEXT: Record<KnownHttpStatusCode, string> = {
|
|
71
|
+
[HTTP_STATUS._100_CONTINUE]: "Continue",
|
|
72
|
+
[HTTP_STATUS._101_SWITCHING_PROTOCOLS]: "Switching Protocols",
|
|
73
|
+
[HTTP_STATUS._102_PROCESSING]: "Processing",
|
|
74
|
+
[HTTP_STATUS._103_EARLY_HINTS]: "Early Hints",
|
|
75
|
+
[HTTP_STATUS._200_OK]: "OK",
|
|
76
|
+
[HTTP_STATUS._201_CREATED]: "Created",
|
|
77
|
+
[HTTP_STATUS._202_ACCEPTED]: "Accepted",
|
|
78
|
+
[HTTP_STATUS._203_NON_AUTHORITATIVE_INFORMATION]: "Non-Authoritative Information",
|
|
79
|
+
[HTTP_STATUS._204_NO_CONTENT]: "No Content",
|
|
80
|
+
[HTTP_STATUS._205_RESET_CONTENT]: "Reset Content",
|
|
81
|
+
[HTTP_STATUS._206_PARTIAL_CONTENT]: "Partial Content",
|
|
82
|
+
[HTTP_STATUS._207_MULTI_STATUS]: "Multi-Status",
|
|
83
|
+
[HTTP_STATUS._208_ALREADY_REPORTED]: "Already Reported",
|
|
84
|
+
[HTTP_STATUS._226_IM_USED]: "IM Used",
|
|
85
|
+
[HTTP_STATUS._300_MULTIPLE_CHOICES]: "Multiple Choices",
|
|
86
|
+
[HTTP_STATUS._301_MOVED_PERMANENTLY]: "Moved Permanently",
|
|
87
|
+
[HTTP_STATUS._302_FOUND]: "Found",
|
|
88
|
+
[HTTP_STATUS._303_SEE_OTHER]: "See Other",
|
|
89
|
+
[HTTP_STATUS._304_NOT_MODIFIED]: "Not Modified",
|
|
90
|
+
[HTTP_STATUS._305_USE_PROXY]: "Use Proxy",
|
|
91
|
+
[HTTP_STATUS._307_TEMPORARY_REDIRECT]: "Temporary Redirect",
|
|
92
|
+
[HTTP_STATUS._308_PERMANENT_REDIRECT]: "Permanent Redirect",
|
|
93
|
+
[HTTP_STATUS._400_BAD_REQUEST]: "Bad Request",
|
|
94
|
+
[HTTP_STATUS._401_UNAUTHORIZED]: "Unauthorized",
|
|
95
|
+
[HTTP_STATUS._402_PAYMENT_REQUIRED]: "Payment Required",
|
|
96
|
+
[HTTP_STATUS._403_FORBIDDEN]: "Forbidden",
|
|
97
|
+
[HTTP_STATUS._404_NOT_FOUND]: "Not Found",
|
|
98
|
+
[HTTP_STATUS._405_METHOD_NOT_ALLOWED]: "Method Not Allowed",
|
|
99
|
+
[HTTP_STATUS._406_NOT_ACCEPTABLE]: "Not Acceptable",
|
|
100
|
+
[HTTP_STATUS._407_PROXY_AUTHENTICATION_REQUIRED]: "Proxy Authentication Required",
|
|
101
|
+
[HTTP_STATUS._408_REQUEST_TIMEOUT]: "Request Timeout",
|
|
102
|
+
[HTTP_STATUS._409_CONFLICT]: "Conflict",
|
|
103
|
+
[HTTP_STATUS._410_GONE]: "Gone",
|
|
104
|
+
[HTTP_STATUS._411_LENGTH_REQUIRED]: "Length Required",
|
|
105
|
+
[HTTP_STATUS._412_PRECONDITION_FAILED]: "Precondition Failed",
|
|
106
|
+
[HTTP_STATUS._413_PAYLOAD_TOO_LARGE]: "Payload Too Large",
|
|
107
|
+
[HTTP_STATUS._414_URI_TOO_LONG]: "URI Too Long",
|
|
108
|
+
[HTTP_STATUS._415_UNSUPPORTED_MEDIA_TYPE]: "Unsupported Media Type",
|
|
109
|
+
[HTTP_STATUS._416_RANGE_NOT_SATISFIABLE]: "Range Not Satisfiable",
|
|
110
|
+
[HTTP_STATUS._417_EXPECTATION_FAILED]: "Expectation Failed",
|
|
111
|
+
[HTTP_STATUS._418_IM_A_TEAPOT]: "I'm a Teapot",
|
|
112
|
+
[HTTP_STATUS._421_MISDIRECTED_REQUEST]: "Misdirected Request",
|
|
113
|
+
[HTTP_STATUS._422_UNPROCESSABLE_CONTENT]: "Unprocessable Content",
|
|
114
|
+
[HTTP_STATUS._423_LOCKED]: "Locked",
|
|
115
|
+
[HTTP_STATUS._424_FAILED_DEPENDENCY]: "Failed Dependency",
|
|
116
|
+
[HTTP_STATUS._425_TOO_EARLY]: "Too Early",
|
|
117
|
+
[HTTP_STATUS._426_UPGRADE_REQUIRED]: "Upgrade Required",
|
|
118
|
+
[HTTP_STATUS._428_PRECONDITION_REQUIRED]: "Precondition Required",
|
|
119
|
+
[HTTP_STATUS._429_TOO_MANY_REQUESTS]: "Too Many Requests",
|
|
120
|
+
[HTTP_STATUS._431_REQUEST_HEADER_FIELDS_TOO_LARGE]: "Request Header Fields Too Large",
|
|
121
|
+
[HTTP_STATUS._451_UNAVAILABLE_FOR_LEGAL_REASONS]: "Unavailable For Legal Reasons",
|
|
122
|
+
[HTTP_STATUS._500_INTERNAL_SERVER_ERROR]: "Internal Server Error",
|
|
123
|
+
[HTTP_STATUS._501_NOT_IMPLEMENTED]: "Not Implemented",
|
|
124
|
+
[HTTP_STATUS._502_BAD_GATEWAY]: "Bad Gateway",
|
|
125
|
+
[HTTP_STATUS._503_SERVICE_UNAVAILABLE]: "Service Unavailable",
|
|
126
|
+
[HTTP_STATUS._504_GATEWAY_TIMEOUT]: "Gateway Timeout",
|
|
127
|
+
[HTTP_STATUS._505_HTTP_VERSION_NOT_SUPPORTED]: "HTTP Version Not Supported",
|
|
128
|
+
[HTTP_STATUS._506_VARIANT_ALSO_NEGOTIATES]: "Variant Also Negotiates",
|
|
129
|
+
[HTTP_STATUS._507_INSUFFICIENT_STORAGE]: "Insufficient Storage",
|
|
130
|
+
[HTTP_STATUS._508_LOOP_DETECTED]: "Loop Detected",
|
|
131
|
+
[HTTP_STATUS._510_NOT_EXTENDED]: "Not Extended",
|
|
132
|
+
[HTTP_STATUS._511_NETWORK_AUTHENTICATION_REQUIRED]: "Network Authentication Required",
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const KNOWN_HTTP_STATUS_CODE_SET = new Set<number>(Object.values(HTTP_STATUS));
|
|
136
|
+
|
|
137
|
+
const isStatusInRange = (statusCode: number, min: number, max: number): boolean => {
|
|
138
|
+
return Number.isInteger(statusCode) && statusCode >= min && statusCode <= max;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const isHttpStatusCode = (statusCode: number): boolean => {
|
|
142
|
+
return isStatusInRange(statusCode, 100, 599);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const isKnownHttpStatusCode = (statusCode: number): statusCode is KnownHttpStatusCode => {
|
|
146
|
+
return KNOWN_HTTP_STATUS_CODE_SET.has(statusCode);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const getHttpStatusGroup = (statusCode: number): HttpStatusGroup | undefined => {
|
|
150
|
+
if (isStatusInRange(statusCode, 100, 199)) {
|
|
151
|
+
return "informational";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (isStatusInRange(statusCode, 200, 299)) {
|
|
155
|
+
return "success";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (isStatusInRange(statusCode, 300, 399)) {
|
|
159
|
+
return "redirection";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (isStatusInRange(statusCode, 400, 499)) {
|
|
163
|
+
return "client-error";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (isStatusInRange(statusCode, 500, 599)) {
|
|
167
|
+
return "server-error";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return undefined;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const isInformationalStatus = (statusCode: number): boolean => {
|
|
174
|
+
return getHttpStatusGroup(statusCode) === "informational";
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const isSuccessStatus = (statusCode: number): boolean => {
|
|
178
|
+
return getHttpStatusGroup(statusCode) === "success";
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const isRedirectionStatus = (statusCode: number): boolean => {
|
|
182
|
+
return getHttpStatusGroup(statusCode) === "redirection";
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export const isClientErrorStatus = (statusCode: number): boolean => {
|
|
186
|
+
return getHttpStatusGroup(statusCode) === "client-error";
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export const isServerErrorStatus = (statusCode: number): boolean => {
|
|
190
|
+
return getHttpStatusGroup(statusCode) === "server-error";
|
|
191
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./clamp";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// 계승(factorial). n! = 1 * 2 * ... * n.
|
|
2
|
+
// 음수는 정의되지 않으므로 RangeError를 던진다. 0! = 1 (빈 곱의 항등원).
|
|
3
|
+
// 반복문으로 구현해 깊은 재귀의 스택 비용을 피한다.
|
|
4
|
+
export const factorial = (n: number): number => {
|
|
5
|
+
if (n < 0) {
|
|
6
|
+
throw new RangeError(`factorial is not defined for negative numbers: ${n}`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let result = 1;
|
|
10
|
+
for (let i = 2; i <= n; i += 1) {
|
|
11
|
+
result *= i;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return result;
|
|
15
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./factorial";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// 정수부에 천 단위 콤마를 넣는다. 1234567 -> "1,234,567".
|
|
2
|
+
// 콤마는 정수부에만 붙이고 소수부와 음수 부호는 그대로 보존한다
|
|
3
|
+
// (-1234.5 -> "-1,234.5").
|
|
4
|
+
//
|
|
5
|
+
// Intl.NumberFormat 대신 직접 구현한 이유: Intl은 로케일/환경에 따라 그룹
|
|
6
|
+
// 구분자가 달라지고(공백·점 등) maximumFractionDigits 기본값이 소수부를 잘라
|
|
7
|
+
// 결과가 미묘하게 흔들린다. 여기선 콤마 고정 + 소수부 완전 보존이 계약이라
|
|
8
|
+
// 문자열을 직접 다룬다.
|
|
9
|
+
//
|
|
10
|
+
// 정규식 `\B(?=(\d{3})+(?!\d))`는 "오른쪽으로 3자리씩 묶었을 때 그 경계이면서
|
|
11
|
+
// 단어 경계가 아닌 위치" 앞에 콤마를 끼운다. 정수부에만 적용해 소수점 이후로는
|
|
12
|
+
// 번지지 않게 한다.
|
|
13
|
+
export const formatThousands = (value: number): string => {
|
|
14
|
+
if (!Number.isFinite(value)) {
|
|
15
|
+
return String(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const sign = value < 0 ? "-" : "";
|
|
19
|
+
// 부호는 따로 떼어 두고 절댓값 문자열만 가공한다.
|
|
20
|
+
const [integer, fraction] = Math.abs(value).toString().split(".");
|
|
21
|
+
const grouped = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
22
|
+
|
|
23
|
+
return `${sign}${grouped}${fraction === undefined ? "" : `.${fraction}`}`;
|
|
24
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./format-thousands";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// 최대공약수(GCD). 유클리드 호제법으로 구한다.
|
|
2
|
+
// 부호는 결과에 의미가 없으므로 입력을 절댓값으로 정규화한다 (gcd(-12, 8) === gcd(12, 8)).
|
|
3
|
+
// 한쪽이 0이면 다른 쪽의 절댓값이 곧 GCD다 (gcd(0, n) === |n|, gcd(0, 0) === 0).
|
|
4
|
+
export const gcd = (a: number, b: number): number => {
|
|
5
|
+
let x = Math.abs(a);
|
|
6
|
+
let y = Math.abs(b);
|
|
7
|
+
|
|
8
|
+
while (y !== 0) {
|
|
9
|
+
[x, y] = [y, x % y];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return x;
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./gcd";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./in-range";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./is-even";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./is-odd";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./lcm";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { gcd } from "../gcd/index";
|
|
2
|
+
|
|
3
|
+
// 최소공배수(LCM). lcm(a, b) = |a * b| / gcd(a, b) 로 구한다.
|
|
4
|
+
// 한쪽이 0이면 공배수가 0뿐이므로 0을 반환한다 (gcd가 0이 되어 나눗셈을 피하는 의미도 있다).
|
|
5
|
+
// 부호는 GCD와 마찬가지로 결과에 의미가 없어 절댓값으로 정규화한다.
|
|
6
|
+
export const lcm = (a: number, b: number): number => {
|
|
7
|
+
if (a === 0 || b === 0) {
|
|
8
|
+
return 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return Math.abs(a / gcd(a, b)) * Math.abs(b);
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./lerp";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./percentage";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./random";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// [min, max) 구간의 실수 난수를 런타임 난수로 뽑는다. min은 포함, max는 제외다.
|
|
2
|
+
//
|
|
3
|
+
// 매번 새 값을 계산해 반환하며 인자는 변형하지 않는다. 난수원은 Math.random이라
|
|
4
|
+
// 암호학적으로 안전하지 않다 (가벼운 무작위 실수 생성 용도).
|
|
5
|
+
// min === max면 구간이 비어 있어 항상 min을 반환한다. min > max도 막지 않으므로
|
|
6
|
+
// 호출 측에서 min <= max를 보장한다.
|
|
7
|
+
export const random = (min: number, max: number): number => {
|
|
8
|
+
return min + Math.random() * (max - min);
|
|
9
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./random-int";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// [min, max] 구간의 정수 난수를 런타임 난수로 뽑는다. 양끝(min, max)을 모두 포함한다.
|
|
2
|
+
//
|
|
3
|
+
// min, max는 정수라고 가정한다. 구간 크기는 max - min + 1이라 max 쪽도 동일 확률로 뽑힌다.
|
|
4
|
+
// 매번 새 값을 계산해 반환하며 인자는 변형하지 않는다. 난수원은 Math.random이라
|
|
5
|
+
// 암호학적으로 안전하지 않다 (주사위/인덱스 같은 가벼운 무작위 정수 생성 용도).
|
|
6
|
+
// min === max면 항상 그 값을 반환한다. min <= max는 호출 측에서 보장한다.
|
|
7
|
+
export const randomInt = (min: number, max: number): number => {
|
|
8
|
+
return min + Math.floor(Math.random() * (max - min + 1));
|
|
9
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./round";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// value를 digits 자리수로 반올림한다. digits 기본값 0은 정수 반올림.
|
|
2
|
+
// Math.round(value * 10**digits) / 10**digits 의 단순식은 부동소수점 오차로
|
|
3
|
+
// 흔히 어긋난다 (예: 1.005 * 100 === 100.49999999999999 -> 100). 그래서
|
|
4
|
+
// 지수 표기(toExponential)로 가수의 소수점만 옮겨 반올림한 뒤 되돌린다.
|
|
5
|
+
// 이렇게 하면 곱셈으로 생기는 표현 오차를 거치지 않아 1.005 같은 경계값이
|
|
6
|
+
// 기대대로 반올림된다.
|
|
7
|
+
// digits는 정수로 강제하고, 음수 digits(10의 자리 등 정수부 반올림)도 지원한다.
|
|
8
|
+
export const round = (value: number, digits = 0): number => {
|
|
9
|
+
if (!Number.isFinite(value)) {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const shift = Math.trunc(digits);
|
|
14
|
+
|
|
15
|
+
// 가수와 지수를 분리해 지수에 shift를 더한 뒤 그 자리에서 반올림하고,
|
|
16
|
+
// 다시 같은 크기만큼 지수를 빼서 원래 스케일로 되돌린다.
|
|
17
|
+
const [mantissa, exponent] = value.toExponential().split("e");
|
|
18
|
+
const rounded = Math.round(Number(`${mantissa}e${Number(exponent) + shift}`));
|
|
19
|
+
const [resultMantissa, resultExponent] = rounded.toExponential().split("e");
|
|
20
|
+
|
|
21
|
+
return Number(`${resultMantissa}e${Number(resultExponent) - shift}`);
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./safe-parse-number";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// 임의의 입력을 숫자로 안전하게 파싱한다. 파싱이 의미 없거나 실패하면
|
|
2
|
+
// fallback(기본 0)을 돌려준다.
|
|
3
|
+
//
|
|
4
|
+
// Number()의 함정을 피하려고 사례별로 분기한다:
|
|
5
|
+
// - Number("") === 0, Number(" ") === 0, Number(null) === 0 -> 의도와 다름.
|
|
6
|
+
// "빈 입력"은 0이 아니라 fallback이어야 하므로 문자열은 trim 후 빈값을 거른다.
|
|
7
|
+
// - Number([]) === 0, Number([5]) === 5 -> 배열/객체는 숫자로 보지 않고 fallback.
|
|
8
|
+
// - Number(true) === 1 -> boolean도 숫자가 아니므로 fallback.
|
|
9
|
+
// number는 NaN만 거르고(±Infinity는 유효한 수로 통과), 그 외 타입은 문자열로
|
|
10
|
+
// 만들어 trim한 뒤 Number로 변환한다. 결과가 NaN이면 fallback.
|
|
11
|
+
export const safeParseNumber = (value: unknown, fallback = 0): number => {
|
|
12
|
+
if (typeof value === "number") {
|
|
13
|
+
return Number.isNaN(value) ? fallback : value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// boolean/배열/객체 등은 Number()가 우연히 숫자를 뱉으므로 명시적으로 막는다.
|
|
17
|
+
if (typeof value !== "string" && typeof value !== "bigint") {
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const text = String(value).trim();
|
|
22
|
+
if (text === "") {
|
|
23
|
+
return fallback;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const parsed = Number(text);
|
|
27
|
+
|
|
28
|
+
return Number.isNaN(parsed) ? fallback : parsed;
|
|
29
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./to-ordinal";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// 정수를 영어 서수 문자열로 바꾼다. 1 -> "1st", 2 -> "2nd", 3 -> "3rd",
|
|
2
|
+
// 그 외에는 "th". 음수 부호와 0은 그대로 보존한다 (0 -> "0th", -1 -> "-1st").
|
|
3
|
+
//
|
|
4
|
+
// 영어 서수 규칙의 핵심 예외는 11·12·13이다. 끝자리만 보면 각각 1·2·3이라
|
|
5
|
+
// "st/nd/rd"가 될 것 같지만, 실제로는 "11th/12th/13th"다. 그래서 마지막 두
|
|
6
|
+
// 자리(mod 100)가 11~13이면 무조건 "th"로 처리하고, 그 밖에서만 끝자리
|
|
7
|
+
// (mod 10)로 "st/nd/rd"를 고른다.
|
|
8
|
+
// 비교는 절댓값으로 한다 (-21 의 끝자리 판정이 부호에 흔들리지 않게).
|
|
9
|
+
export const toOrdinal = (value: number): string => {
|
|
10
|
+
const integer = Math.trunc(value);
|
|
11
|
+
const abs = Math.abs(integer);
|
|
12
|
+
const lastTwo = abs % 100;
|
|
13
|
+
const lastOne = abs % 10;
|
|
14
|
+
|
|
15
|
+
let suffix = "th";
|
|
16
|
+
// 11~13은 예외라 끝자리와 무관하게 "th". 그 외에만 끝자리로 분기한다.
|
|
17
|
+
if (lastTwo < 11 || lastTwo > 13) {
|
|
18
|
+
if (lastOne === 1) {
|
|
19
|
+
suffix = "st";
|
|
20
|
+
} else if (lastOne === 2) {
|
|
21
|
+
suffix = "nd";
|
|
22
|
+
} else if (lastOne === 3) {
|
|
23
|
+
suffix = "rd";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return `${integer}${suffix}`;
|
|
28
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// plain object와 array를 재귀 복제해 입력과 구조를 공유하지 않는 새 값을 만든다.
|
|
2
|
+
// Date는 같은 시각의 새 인스턴스로 복제하고, 원시·함수 등 그 외 값은 참조를 그대로 유지한다.
|
|
3
|
+
// 순환 참조는 고려하지 않는다.
|
|
4
|
+
|
|
5
|
+
// 재귀 복제 대상인지 가르는 술어. array는 별도로 처리하므로 여기선 plain object만.
|
|
6
|
+
// Object.prototype 또는 프로토타입이 없는 객체만 plain object로 본다.
|
|
7
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
|
8
|
+
if (typeof value !== "object" || value === null) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const prototype = Object.getPrototypeOf(value);
|
|
12
|
+
return prototype === Object.prototype || prototype === null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* value를 재귀 복제한 새 값을 반환한다.
|
|
17
|
+
*
|
|
18
|
+
* - plain object와 array는 새로 만들어 각 원소/값을 재귀 복제한다.
|
|
19
|
+
* - Date는 같은 시각(getTime)의 새 Date로 복제한다.
|
|
20
|
+
* - 그 외(원시·함수·클래스 인스턴스 등)는 복제하지 않고 참조를 그대로 둔다.
|
|
21
|
+
*
|
|
22
|
+
* 복제된 plain object/array를 수정해도 원본에 영향이 가지 않는다(중첩까지 분리된다).
|
|
23
|
+
* 순환 참조가 있는 입력에는 무한 재귀로 빠질 수 있어 지원하지 않는다.
|
|
24
|
+
*/
|
|
25
|
+
export const deepClone = <T>(value: T): T => {
|
|
26
|
+
// array: 새 배열에 원소를 하나씩 재귀 복제해 담는다.
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return value.map((element) => deepClone(element)) as T;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Date: 내부 시각만 옮긴 새 인스턴스. (Date는 plain object가 아니라 위에서 따로 처리)
|
|
32
|
+
if (value instanceof Date) {
|
|
33
|
+
return new Date(value.getTime()) as T;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// plain object: 키별로 값을 재귀 복제한 새 객체를 만든다.
|
|
37
|
+
if (isPlainObject(value)) {
|
|
38
|
+
const result: Record<string, unknown> = {};
|
|
39
|
+
for (const key of Object.keys(value)) {
|
|
40
|
+
result[key] = deepClone(value[key]);
|
|
41
|
+
}
|
|
42
|
+
return result as T;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 원시·함수·그 외 객체: 복제하지 않고 참조(또는 값)를 그대로 반환한다.
|
|
46
|
+
return value;
|
|
47
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./deep-clone";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// 두 값의 깊은 동등성을 판정한다. plain object와 array는 구조를 재귀 비교하고,
|
|
2
|
+
// 그 외(원시·Date·함수 등)는 자기 규칙으로 비교한다. 순환 참조는 고려하지 않는다.
|
|
3
|
+
|
|
4
|
+
// 더 깊이 들어갈 대상인지 가르는 술어. array는 별도로 처리하므로 여기선 plain object만.
|
|
5
|
+
// Object.prototype 또는 프로토타입이 없는 객체만 plain object로 본다.
|
|
6
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
|
7
|
+
if (typeof value !== "object" || value === null) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const prototype = Object.getPrototypeOf(value);
|
|
11
|
+
return prototype === Object.prototype || prototype === null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* a와 b가 깊은 값으로 동등한지 판정한다.
|
|
16
|
+
*
|
|
17
|
+
* - 원시는 SameValueZero로 비교한다(NaN끼리는 같다고, +0과 -0은 같다고 본다).
|
|
18
|
+
* - array는 길이와 각 원소를 순서대로 재귀 비교한다.
|
|
19
|
+
* - plain object는 키 집합과 각 키의 값을 재귀 비교한다.
|
|
20
|
+
* - Date는 같은 시각(getTime)이면 같다고 본다.
|
|
21
|
+
* - 한쪽만 array/object이거나 종류가 다르면 같지 않다.
|
|
22
|
+
*
|
|
23
|
+
* 순환 참조가 있는 입력에는 무한 재귀로 빠질 수 있어 지원하지 않는다.
|
|
24
|
+
*/
|
|
25
|
+
export const deepEqual = (a: unknown, b: unknown): boolean => {
|
|
26
|
+
// 같은 참조이거나 NaN끼리처럼 원시가 동일하면 즉시 참(SameValueZero).
|
|
27
|
+
if (Object.is(a, b)) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 여기부터 둘 다 객체여야 깊이 비교할 수 있다. 하나라도 객체가 아니면 다르다.
|
|
32
|
+
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Date는 같은 시각이면 같다. 한쪽만 Date면 종류가 달라 같지 않다.
|
|
37
|
+
const aIsDate = a instanceof Date;
|
|
38
|
+
const bIsDate = b instanceof Date;
|
|
39
|
+
if (aIsDate || bIsDate) {
|
|
40
|
+
return aIsDate && bIsDate && a.getTime() === b.getTime();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// array는 array끼리만 같을 수 있다. 길이와 원소를 순서대로 재귀 비교한다.
|
|
44
|
+
const aIsArray = Array.isArray(a);
|
|
45
|
+
const bIsArray = Array.isArray(b);
|
|
46
|
+
if (aIsArray || bIsArray) {
|
|
47
|
+
if (!aIsArray || !bIsArray || a.length !== b.length) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return a.every((value, index) => deepEqual(value, b[index]));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 남은 건 plain object. 그 외 객체(Map/Set/클래스 인스턴스 등)는 비교 규칙이 없어 다르게 본다.
|
|
54
|
+
if (!isPlainObject(a) || !isPlainObject(b)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const aKeys = Object.keys(a);
|
|
59
|
+
const bKeys = Object.keys(b);
|
|
60
|
+
if (aKeys.length !== bKeys.length) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 키 집합이 같고(= b가 같은 키를 모두 가짐) 각 값이 깊게 같아야 한다.
|
|
65
|
+
return aKeys.every((key) => Object.hasOwn(b, key) && deepEqual(a[key], b[key]));
|
|
66
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./deep-equal";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// 중첩 plain object를 재귀 병합한다. 양쪽 모두 plain object인 키만 더 깊이 들어가고,
|
|
2
|
+
// 배열·원시·Date 등 그 외 값은 source 쪽으로 통째 교체한다(병합하지 않는다).
|
|
3
|
+
// 입력을 변형하지 않고 항상 새 객체를 반환하는 순수 함수다.
|
|
4
|
+
|
|
5
|
+
// 재귀 경계를 정하는 술어. 더 깊이 병합할 수 있는 건 "plain object"뿐이다.
|
|
6
|
+
// 배열·null·Date·Map 같은 특수 객체는 부분 병합하면 의미가 깨지므로 통째 교체 대상으로 둔다.
|
|
7
|
+
// Object.prototype 또는 프로토타입이 없는(Object.create(null)) 객체만 plain object로 본다.
|
|
8
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
|
9
|
+
if (typeof value !== "object" || value === null) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const prototype = Object.getPrototypeOf(value);
|
|
13
|
+
return prototype === Object.prototype || prototype === null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* target과 source를 재귀 병합한 새 객체를 반환한다.
|
|
18
|
+
*
|
|
19
|
+
* - 같은 키에서 양쪽이 모두 plain object면 그 안으로 재귀해 병합한다.
|
|
20
|
+
* - 그 외(배열·원시·Date·null 등)는 source 값으로 교체한다. 배열은 원소별 병합하지 않는다.
|
|
21
|
+
* - source에만 있는 키는 그대로 추가되고, target에만 있는 키는 보존된다.
|
|
22
|
+
*
|
|
23
|
+
* target·source 어느 쪽도 변형하지 않는다. 중첩 객체도 새로 만들어 채우므로,
|
|
24
|
+
* 반환값을 수정해도 입력에 영향이 가지 않는다.
|
|
25
|
+
*/
|
|
26
|
+
export const deepMerge = <T extends object, S extends object>(target: T, source: S): T & S => {
|
|
27
|
+
// 한쪽이라도 plain object가 아니면 병합할 구조가 없다. source를 우선해 교체한다.
|
|
28
|
+
if (!isPlainObject(target) || !isPlainObject(source)) {
|
|
29
|
+
return source as unknown as T & S;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const result: Record<string, unknown> = { ...target };
|
|
33
|
+
|
|
34
|
+
for (const key of Object.keys(source)) {
|
|
35
|
+
const sourceValue = source[key as keyof S];
|
|
36
|
+
const targetValue = result[key];
|
|
37
|
+
|
|
38
|
+
// 양쪽이 모두 plain object일 때만 한 단계 더 깊이 병합한다.
|
|
39
|
+
if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
|
|
40
|
+
result[key] = deepMerge(targetValue, sourceValue);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 그 외에는 source 값으로 교체한다.
|
|
45
|
+
result[key] = sourceValue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return result as T & S;
|
|
49
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./deep-merge";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// obj에서 값이 없는(undefined) 키를 defaultValues로 채운 새 객체를 만든다.
|
|
2
|
+
// obj에 이미 값이 있으면(undefined가 아니면) 그 값을 그대로 둔다.
|
|
3
|
+
// 입력 객체들은 변형하지 않고, obj의 얕은 복사본 위에 빈 칸만 메운다.
|
|
4
|
+
export const defaults = <T extends object>(obj: Partial<T>, defaultValues: T): T => {
|
|
5
|
+
const result = { ...obj } as T;
|
|
6
|
+
|
|
7
|
+
for (const key of Object.keys(defaultValues) as (keyof T)[]) {
|
|
8
|
+
if (result[key] === undefined) {
|
|
9
|
+
result[key] = defaultValues[key];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return result;
|
|
14
|
+
};
|