bansa 0.0.24 → 0.0.25

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/.oxfmtrc.jsonc ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/refs/heads/main/npm/oxfmt/configuration_schema.json",
3
+ }
package/README.ko.md CHANGED
@@ -21,11 +21,11 @@ Bansa는 파생 상태, 비동기 값, 의존성과 구독, 생명 주기, 사
21
21
  일반 값(숫자/문자열/객체 등)을 `$` 함수에 전달하여 생성합니다.
22
22
 
23
23
  ```javascript
24
- import { $ } from 'bansa';
24
+ import { $ } from "bansa";
25
25
 
26
26
  const $count = $(42);
27
27
 
28
- const $user = $({ name: 'John Doe', age: 30 });
28
+ const $user = $({ name: "John Doe", age: 30 });
29
29
  ```
30
30
 
31
31
  #### 파생 상태
@@ -38,13 +38,13 @@ const $user = $({ name: 'John Doe', age: 30 });
38
38
  const $countDouble = $((get) => get($count) * 2);
39
39
 
40
40
  const $userMessage = $((get) => {
41
- if (get($count) < 50) return 'no hello.';
42
- return `Hello, ${get($user).name}!`;
41
+ if (get($count) < 50) return "no hello.";
42
+ return `Hello, ${get($user).name}!`;
43
43
  });
44
44
 
45
45
  const $signalExample = $((_, { signal }) => {
46
- signal.then(() => console.log("$signalExample died"));
47
- return fetch(`/users/${get($count)}`, { signal }).then((res) => res.json());
46
+ signal.then(() => console.log("$signalExample died"));
47
+ return fetch(`/users/${get($count)}`, { signal }).then((res) => res.json());
48
48
  });
49
49
  ```
50
50
 
@@ -62,10 +62,10 @@ const $signalExample = $((_, { signal }) => {
62
62
 
63
63
  ```typescript
64
64
  type AtomState<Value> =
65
- | { promise: undefined; error: undefined; value: Value; } // 성공
66
- | { promise: undefined; error: any; value?: Value; } // 에러
67
- | { promise: PromiseLike<Value>; error: any; value?: Value; } // 로딩
68
- | { promise: typeof inactive; error: undefined; value?: Value; } // 비활성
65
+ | { promise: undefined; error: undefined; value: Value } // 성공
66
+ | { promise: undefined; error: any; value?: Value } // 에러
67
+ | { promise: PromiseLike<Value>; error: any; value?: Value } // 로딩
68
+ | { promise: typeof inactive; error: undefined; value?: Value }; // 비활성
69
69
  ```
70
70
 
71
71
  ##### 활성 상태로 유지하기
@@ -89,7 +89,6 @@ console.log($countDouble.state); // { promise: undefined, error: undefined, valu
89
89
 
90
90
  `.get()` 메서드는 상태가 비활성화된 경우, 매우 잠시 동안 해당 상태를 살아 있는 상태로 전환합니다. 당연히 해당 상태와 모든 의존성이 새로 실행됩니다. 매우 잠시 동안은 적어도 현재 마이크로태스크가 끝나기까지를 의미합니다. 즉, 동기적으로 연속해서 `.get()`을 호출하더라도 매번 모든 것이 다시 실행되지는 않습니다.
91
91
 
92
-
93
92
  ### 상태 업데이트
94
93
 
95
94
  `.set(updater)` 메서드를 통해 원시 상태의 값을 업데이트할 수 있으며, `updater`가 일반 값이라면 해당 값으로 업데이트하고, 함수라면 상태의 '예비 값' `nextValue`에 대해 `updater(nextValue)`로 업데이트합니다.
@@ -131,8 +130,8 @@ $count.set(increment);
131
130
  ```javascript
132
131
  const $count = $(0);
133
132
  const unsubscribe = $count.subscribe((value, { signal }) => {
134
- console.log('value', value);
135
- signal.then(() => console.log('value end', value));
133
+ console.log("value", value);
134
+ signal.then(() => console.log("value end", value));
136
135
  });
137
136
 
138
137
  // value 0
@@ -154,8 +153,8 @@ $count.set(2);
154
153
 
155
154
  ```javascript
156
155
  const $merged = $((get) => ({
157
- count: get($count),
158
- countDouble: get($countDouble),
156
+ count: get($count),
157
+ countDouble: get($countDouble),
159
158
  }));
160
159
 
161
160
  $merged.subscribe(({ count, countDouble }) => console.log(`${count} * 2 = ${countDouble}`));
@@ -167,21 +166,21 @@ $merged.subscribe(({ count, countDouble }) => console.log(`${count} * 2 = ${coun
167
166
 
168
167
  ```javascript
169
168
  const $user = $(async (get) => {
170
- const response = await fetch(`/users/${get($count)}`);
171
- if (!response.ok) throw new Error('Failed to fetch user');
172
- return response.json();
169
+ const response = await fetch(`/users/${get($count)}`);
170
+ if (!response.ok) throw new Error("Failed to fetch user");
171
+ return response.json();
173
172
  });
174
173
  $user.watch(() => {
175
- console.log($user.state);
174
+ console.log($user.state);
176
175
  });
177
176
 
178
177
  const $userName = $((get) => get($user).name);
179
178
 
180
- const faultyAtom = $(() => Promise.reject(new Error('Something went wrong')));
179
+ const faultyAtom = $(() => Promise.reject(new Error("Something went wrong")));
181
180
  faultyAtom.watch(() => {
182
- if (!faultyAtom.state.promise && faultyAtom.state.error) {
183
- console.error('An error occurred:', faultyAtom.state.error.message);
184
- }
181
+ if (!faultyAtom.state.promise && faultyAtom.state.error) {
182
+ console.error("An error occurred:", faultyAtom.state.error.message);
183
+ }
185
184
  });
186
185
  ```
187
186
 
@@ -193,12 +192,12 @@ faultyAtom.watch(() => {
193
192
 
194
193
  ```javascript
195
194
  const $user = $(async (get, { signal }) => {
196
- const count = get($count);
197
- const json = await fetch(`/users/${count}`, { signal }).then((res) => res.json());
198
- signal.then(() => {
199
- console.log(count, json, "not used");
200
- });
201
- return json;
195
+ const count = get($count);
196
+ const json = await fetch(`/users/${count}`, { signal }).then((res) => res.json());
197
+ signal.then(() => {
198
+ console.log(count, json, "not used");
199
+ });
200
+ return json;
202
201
  });
203
202
  ```
204
203
 
@@ -207,19 +206,13 @@ const $user = $(async (get, { signal }) => {
207
206
  기본적으로 `Object.is`로 동등성을 체크하므로 객체나 배열의 경우 참조가 다르면 내용이 같더라도 업데이트가 발생할 수 있으며, 추가로 동등성을 확인하기 위해 상태 선언 시 `equals`를 옵션으로 줄 수 있습니다. 이 경우 `Object.is`로 같은지 확인하고, 다르다면 `equals` 함수로 다시 확인합니다. 둘 중 하나라도 참을 반환하는 경우 값 변경은 무시됩니다.
208
207
 
209
208
  ```javascript
210
- const $user = $(
211
- { id: 1, name: 'Alice' },
212
- { equals: (next, prev) => next.id === prev.id },
213
- );
209
+ const $user = $({ id: 1, name: "Alice" }, { equals: (next, prev) => next.id === prev.id });
214
210
 
215
- const $user2 = $(
216
- (get) => get($user),
217
- { equals: (next, prev) => next.name === prev.name },
218
- );
211
+ const $user2 = $((get) => get($user), { equals: (next, prev) => next.name === prev.name });
219
212
 
220
- userAtom.set({ id: 1, name: 'Bob' });
213
+ userAtom.set({ id: 1, name: "Bob" });
221
214
 
222
- userAtom.set({ id: 2, name: 'Alice' });
215
+ userAtom.set({ id: 2, name: "Alice" });
223
216
  ```
224
217
 
225
218
  위 예제에서 첫 번째 업데이트는 `id`가 같으므로 무시됩니다. 두 번째 업데이트는 `id`가 다르므로 `$user`를 업데이트하지만, `name`이 같으므로 `$user2`는 업데이트되지 않습니다.
@@ -243,7 +236,10 @@ $timers.subscribe(() => console.timeEnd());
243
236
  ```javascript
244
237
  const o = () => o;
245
238
  const toUndefined = () => undefined;
246
- Object.setPrototypeOf(o, new Proxy(o, { get: (_, k) => k === Symbol.toPrimitive ? toUndefined : o }));
239
+ Object.setPrototypeOf(
240
+ o,
241
+ new Proxy(o, { get: (_, k) => (k === Symbol.toPrimitive ? toUndefined : o) }),
242
+ );
247
243
  ```
248
244
 
249
245
  이 코드의 `o`는 아무리 프로퍼티 접근 및 호출을 해도 같은 값을 반환합니다. 예를 들어, `o.a.b.c().d()().asdf()()()() === o`는 `true`입니다. 따라서, 셀렉터와 filter/map/reduce 등 간단한 메서드로 이뤄진 대부분의 상태 병합 함수에서 문제 없이 전체 코드를 실행할 수 있게 만듭니다. 하지만 만능은 아니므로 약간의 주의가 필요하며, 가급적 상태 병합에만 사용해야 합니다.
@@ -252,24 +248,24 @@ Object.setPrototypeOf(o, new Proxy(o, { get: (_, k) => k === Symbol.toPrimitive
252
248
 
253
249
  ```typescript
254
250
  const shallowEquals = (a: any, b: any): boolean => {
255
- if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) return false;
256
- const c = a.constructor;
257
- if (c !== b.constructor) return false;
258
-
259
- if (c === Array) {
260
- let i = a.length;
261
- if (i !== b.length) return false;
262
- while ((i = i - 1 | 0) >= 0) if (!Object.is(a[i], b[i])) return false;
263
- return true;
264
- }
265
-
266
- let n = 0;
267
- for (const k in a) {
268
- if (!(k in b && Object.is(a[k], b[k]))) return false;
269
- n = n + 1 | 0;
270
- }
271
- for (const _ in b) if ((n = n - 1 | 0) < 0) return false;
272
- return true;
251
+ if (typeof a !== "object" || typeof b !== "object" || !a || !b) return false;
252
+ const c = a.constructor;
253
+ if (c !== b.constructor) return false;
254
+
255
+ if (c === Array) {
256
+ let i = a.length;
257
+ if (i !== b.length) return false;
258
+ while ((i = (i - 1) | 0) >= 0) if (!Object.is(a[i], b[i])) return false;
259
+ return true;
260
+ }
261
+
262
+ let n = 0;
263
+ for (const k in a) {
264
+ if (!(k in b && Object.is(a[k], b[k]))) return false;
265
+ n = (n + 1) | 0;
266
+ }
267
+ for (const _ in b) if ((n = (n - 1) | 0) < 0) return false;
268
+ return true;
273
269
  };
274
270
  ```
275
271
 
@@ -291,11 +287,11 @@ const shallowEquals = (a: any, b: any): boolean => {
291
287
  const $userId = $(123);
292
288
  const $postId = $(456);
293
289
  const $pageData = $(async (get, { signal }) => {
294
- const user = await fetch(`/users/${get($userId)}`, { signal }).then((res) => res.json());
295
- const post = await fetch(`/posts/${get($postId)}`, { signal }).then((res) => res.json());
296
- userElm.innerHTML = user.name;
297
- postElm.innerHTML = post.html;
298
- commentElm.innerHTML = `Hello ${user.name}! Comment to ${post.author}.`;
290
+ const user = await fetch(`/users/${get($userId)}`, { signal }).then((res) => res.json());
291
+ const post = await fetch(`/posts/${get($postId)}`, { signal }).then((res) => res.json());
292
+ userElm.innerHTML = user.name;
293
+ postElm.innerHTML = post.html;
294
+ commentElm.innerHTML = `Hello ${user.name}! Comment to ${post.author}.`;
299
295
  });
300
296
  ```
301
297
 
@@ -304,17 +300,23 @@ const $pageData = $(async (get, { signal }) => {
304
300
  ```javascript
305
301
  const $userId = $(123);
306
302
  const $user = $((get) => fetch(`/users/${get($userId)}`, { signal }).then((res) => res.json()));
307
- $user.subscribe((user) => { userElm.innerHTML = user.name; });
303
+ $user.subscribe((user) => {
304
+ userElm.innerHTML = user.name;
305
+ });
308
306
 
309
307
  const $postId = $(456);
310
308
  const $post = $((get) => fetch(`/posts/${get($postId)}`, { signal }).then((res) => res.json()));
311
- $post.subscribe((post) => { postElm.innerHTML = post.html; });
309
+ $post.subscribe((post) => {
310
+ postElm.innerHTML = post.html;
311
+ });
312
312
 
313
313
  const $pageData = $$((get) => ({
314
- userName: get($user).name,
315
- postAuthor: get($post).author,
314
+ userName: get($user).name,
315
+ postAuthor: get($post).author,
316
316
  }));
317
- $pageData.subscribe(({ userName, postAuthor }) => { commentElm.innerHTML = `Hello ${userName}! Comment to ${postAuthor}.`; });
317
+ $pageData.subscribe(({ userName, postAuthor }) => {
318
+ commentElm.innerHTML = `Hello ${userName}! Comment to ${postAuthor}.`;
319
+ });
318
320
  ```
319
321
 
320
322
  코드 줄 수가 약간 늘어났지만, 앞서 언급한 문제들이 해결되었습니다.
@@ -404,30 +406,32 @@ const $reader = $((get) => {
404
406
 
405
407
  ```javascript
406
408
  const delayedState = (initial, minDelay, maxDelay) => {
407
- const $value = $(initial);
408
- const $delayedValue = $(initial);
409
-
410
- const $eventStartTime = $(0);
411
- const $eventLastTime = $(0);
412
- const $delayedTime = $((get) => Math.min(get($eventStartTime) + maxDelay, get($eventLastTime) + minDelay));
413
- const $delayedInfo = $((get) => ({
414
- value: get($value),
415
- time: get($delayedTime),
416
- }));
417
- $delayedInfo.subscribe(({ value, time }, { signal }) => {
418
- const timeout = Math.max(0, time - Date.now());
419
- const timer = setTimeout(() => $delayedValue.set(value), timeout);
420
- signal.then(() => clearTimeout(timer));
421
- });
422
-
423
- const update = (value, eager = false) => {
424
- const now = eager ? -Infinity : Date.now();
425
- if ($value.get() === $delayedValue.get()) $eventStartTime.set(now);
426
- $eventLastTime.set(now);
427
- $value.set(value);
428
- };
429
-
430
- return [$delayedValue, update];
409
+ const $value = $(initial);
410
+ const $delayedValue = $(initial);
411
+
412
+ const $eventStartTime = $(0);
413
+ const $eventLastTime = $(0);
414
+ const $delayedTime = $((get) =>
415
+ Math.min(get($eventStartTime) + maxDelay, get($eventLastTime) + minDelay),
416
+ );
417
+ const $delayedInfo = $((get) => ({
418
+ value: get($value),
419
+ time: get($delayedTime),
420
+ }));
421
+ $delayedInfo.subscribe(({ value, time }, { signal }) => {
422
+ const timeout = Math.max(0, time - Date.now());
423
+ const timer = setTimeout(() => $delayedValue.set(value), timeout);
424
+ signal.then(() => clearTimeout(timer));
425
+ });
426
+
427
+ const update = (value, eager = false) => {
428
+ const now = eager ? -Infinity : Date.now();
429
+ if ($value.get() === $delayedValue.get()) $eventStartTime.set(now);
430
+ $eventLastTime.set(now);
431
+ $value.set(value);
432
+ };
433
+
434
+ return [$delayedValue, update];
431
435
  };
432
436
  ```
433
437
 
@@ -441,46 +445,46 @@ $inputValue.subscribe(console.log);
441
445
 
442
446
  ```javascript
443
447
  const $windowScroll = $((_, { signal }) => {
444
- let lastTime = Date.now();
445
- const $scrollY = $(window.scrollY);
446
- const $scrollOnTop = $((get) => get($scrollY) === 0);
447
-
448
- const $scrollMovingAvgY = $(0);
449
- const $scrollDirectionY = $((get) => Math.sign(get($scrollMovingAvgY)));
450
-
451
- const onScrollChange = () => {
452
- const now = Date.now();
453
- lastTime = now;
454
-
455
- const alpha = 0.995 ** (now - lastTime);
456
- const scrollY = window.scrollY;
457
- const deltaY = scrollY - $scrollY.get();
458
- const movingAvgY = alpha * $scrollMovingAvgY.get() + (1 - alpha) * deltaY;
459
-
460
- $scrollY.set(scrollY);
461
- $scrollMovingAvgY.set(movingAvgY);
462
- };
463
- window.addEventListener('scroll', onScrollChange, {
464
- passive: true,
465
- signal,
466
- });
467
- window.addEventListener('resize', onScrollChange, {
468
- passive: true,
469
- signal,
470
- });
471
-
472
- return {
473
- $scrollY,
474
- $scrollOnTop,
475
- $scrollMovingAvgY,
476
- $scrollDirectionY,
477
- };
448
+ let lastTime = Date.now();
449
+ const $scrollY = $(window.scrollY);
450
+ const $scrollOnTop = $((get) => get($scrollY) === 0);
451
+
452
+ const $scrollMovingAvgY = $(0);
453
+ const $scrollDirectionY = $((get) => Math.sign(get($scrollMovingAvgY)));
454
+
455
+ const onScrollChange = () => {
456
+ const now = Date.now();
457
+ lastTime = now;
458
+
459
+ const alpha = 0.995 ** (now - lastTime);
460
+ const scrollY = window.scrollY;
461
+ const deltaY = scrollY - $scrollY.get();
462
+ const movingAvgY = alpha * $scrollMovingAvgY.get() + (1 - alpha) * deltaY;
463
+
464
+ $scrollY.set(scrollY);
465
+ $scrollMovingAvgY.set(movingAvgY);
466
+ };
467
+ window.addEventListener("scroll", onScrollChange, {
468
+ passive: true,
469
+ signal,
470
+ });
471
+ window.addEventListener("resize", onScrollChange, {
472
+ passive: true,
473
+ signal,
474
+ });
475
+
476
+ return {
477
+ $scrollY,
478
+ $scrollOnTop,
479
+ $scrollMovingAvgY,
480
+ $scrollDirectionY,
481
+ };
478
482
  });
479
483
 
480
484
  const $navHidden = $((get) => {
481
- const { $scrollOnTop, $scrollDirectionY } = get($windowScroll);
482
- const scrollOnTop = get($scrollOnTop);
483
- const directionY = get($scrollDirectionY);
484
- return !scrollOnTop && directionY > 0;
485
+ const { $scrollOnTop, $scrollDirectionY } = get($windowScroll);
486
+ const scrollOnTop = get($scrollOnTop);
487
+ const directionY = get($scrollDirectionY);
488
+ return !scrollOnTop && directionY > 0;
485
489
  });
486
490
  ```