shadowly 1.0.4 → 1.0.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadowly",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "TypeScript JSON Database",
5
5
  "keywords": [
6
6
  "database",
package/src/class.ts ADDED
@@ -0,0 +1,178 @@
1
+ import * as fs from "node:fs";
2
+ import { ShadowlyError } from "./error";
3
+
4
+ function isValidJson(text: string): boolean {
5
+ if (typeof text !== "string") return false;
6
+ try {
7
+ JSON.parse(text);
8
+ return true;
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ type GetKey<T> =
15
+ T extends readonly any[] ? number :
16
+ T extends object ? keyof T :
17
+ never;
18
+
19
+ type GetValue<T, K> =
20
+ T extends readonly (infer U)[] ? U :
21
+ T extends Record<PropertyKey, any> ? (K extends keyof T ? T[K] : never) :
22
+ never;
23
+
24
+ type RemoveKey<T> =
25
+ T extends readonly any[] ? number :
26
+ T extends object ? keyof T :
27
+ never;
28
+
29
+ export default class Shadowly<TRoot, TNode = TRoot> {
30
+ public readonly DB_PATH: string;
31
+ private readonly path: string;
32
+
33
+ private content: TNode;
34
+
35
+ private parent?: Shadowly<TRoot, any>;
36
+ private parentKey?: PropertyKey;
37
+
38
+ /**
39
+ * Shadowly의 새 인스턴스를 만듭니다.
40
+ * @param path JSON 파일 경로
41
+ */
42
+ constructor(path: string) {
43
+ this.path = path;
44
+ this.DB_PATH = path;
45
+
46
+ if (!fs.existsSync(path))
47
+ throw new ShadowlyError("ENOTFOUND", path + " not found.");
48
+
49
+ const file = fs.readFileSync(path, "utf-8");
50
+ if (!isValidJson(file))
51
+ throw new ShadowlyError("EINVALID", path + " is not valid.");
52
+
53
+ this.content = JSON.parse(file) as any;
54
+ }
55
+
56
+ /**
57
+ * 새 JSON을 만듭니다.
58
+ * @param path JSON 파일 경로
59
+ * @param isArray true면 []를 쓰고, false면 {}를 씁니다.
60
+ */
61
+ public static generateNewJson(path: string, isArray?: boolean) {
62
+ fs.writeFileSync(path, isArray ? "[]" : "{}", "utf-8");
63
+ }
64
+
65
+ private static create<TRoot, T>(
66
+ DB_PATH: string,
67
+ path: string,
68
+ content: T,
69
+ parent?: Shadowly<TRoot, any>,
70
+ parentKey?: PropertyKey
71
+ ): Shadowly<TRoot, T> {
72
+ const inst = Object.create(Shadowly.prototype) as Shadowly<TRoot, T>;
73
+ (inst as any).DB_PATH = DB_PATH;
74
+ (inst as any).path = path;
75
+ (inst as any).content = content;
76
+ (inst as any).parent = parent;
77
+ (inst as any).parentKey = parentKey;
78
+ return inst;
79
+ }
80
+
81
+ /**
82
+ * key로 이동합니다.
83
+ * @param key 이동할 키 이름
84
+ * @returns 작업 수행 후 Shadowly 인스턴스
85
+ */
86
+ get<K extends GetKey<TNode>>(key: K): Shadowly<TRoot, GetValue<TNode, K>> {
87
+ const nextContent = (this.content as any)[key];
88
+ return Shadowly.create<TRoot, GetValue<TNode, K>>(
89
+ this.DB_PATH,
90
+ this.path,
91
+ nextContent,
92
+ this,
93
+ key as any
94
+ );
95
+ }
96
+
97
+ /**
98
+ * 현재 key의 value를 반환합니다.
99
+ * @returns 현재 key의 value
100
+ */
101
+ value(): TNode {
102
+ return this.content;
103
+ }
104
+
105
+ /**
106
+ * 현재 key에 값을 씁니다.
107
+ * @param value 쓸 값
108
+ * @returns 작업 수행 후 Shadowly 인스턴스
109
+ */
110
+ set(value: TNode): this {
111
+ this.content = value;
112
+
113
+ if (this.parent && this.parentKey !== undefined) {
114
+ (this.parent.content as any)[this.parentKey] = value;
115
+ }
116
+
117
+ this.persist();
118
+ return this;
119
+ }
120
+
121
+ /**
122
+ * key를 삭제합니다.
123
+ * @param key 삭제할 key
124
+ * @returns 작업 수행 후 Shadowly 인스턴스
125
+ */
126
+ remove<K extends RemoveKey<TNode>>(key: K): this {
127
+ const cur: any = this.content;
128
+
129
+ if (Array.isArray(cur)) {
130
+ const idx = key as unknown as number;
131
+ if (Number.isInteger(idx) && idx >= 0 && idx < cur.length) cur.splice(idx, 1);
132
+ } else if (cur && typeof cur === "object") {
133
+ delete cur[key as any];
134
+ } else {
135
+ throw new ShadowlyError("ETYPE", "Current value is not an object/array.");
136
+ }
137
+
138
+ this.persist();
139
+ return this;
140
+ }
141
+
142
+ /**
143
+ * 한 번 뒤로 이동합니다.
144
+ * @returns 작업 수행 후 Shadowly 인스턴스
145
+ */
146
+ back(): Shadowly<TRoot, any> {
147
+ return this.parent ?? this;
148
+ }
149
+
150
+ /**
151
+ * steps만큼 뒤로 이동합니다.
152
+ * @param steps 이동할 횟수
153
+ * @returns 작업 수행 후 Shadowly 인스턴스
154
+ */
155
+ up(steps: number = 1): Shadowly<TRoot, any> {
156
+ let cur: Shadowly<TRoot, any> = this;
157
+ for (let i = 0; i < steps; i++) {
158
+ if (!cur.parent) break;
159
+ cur = cur.parent;
160
+ }
161
+ return cur;
162
+ }
163
+
164
+ /**
165
+ * JSON의 루트로 이동합니다.
166
+ * @returns 작업 수행 후 Shadowly 인스턴스
167
+ */
168
+ root(): Shadowly<TRoot, TRoot> {
169
+ let cur: Shadowly<TRoot, any> = this;
170
+ while (cur.parent) cur = cur.parent;
171
+ return cur as Shadowly<TRoot, TRoot>;
172
+ }
173
+
174
+ private persist(): void {
175
+ const r = this.root();
176
+ fs.writeFileSync(r.DB_PATH, JSON.stringify(r.content, null, 2), "utf-8");
177
+ }
178
+ }
package/src/error.ts ADDED
@@ -0,0 +1,15 @@
1
+ export class ShadowlyError extends Error {
2
+ public readonly code!: string;
3
+
4
+ constructor(
5
+ code: string,
6
+ message: string
7
+ ) {
8
+ super(code + ": " + message);
9
+
10
+ this.name = "Shadowly";
11
+ this.code = code;
12
+
13
+ Error.captureStackTrace(this, this.constructor);
14
+ }
15
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import Shadowly from "./class";
2
+
3
+ export default Shadowly;