sonamu 0.8.20 → 0.8.21

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.
@@ -3,6 +3,8 @@ import type { z } from "zod";
3
3
  export declare function createSSEFactory<T extends z.ZodObject>(socket: FastifyRequest["socket"], reply: FastifyReply, _events: T): SSEConnection<T>;
4
4
  export declare function createMockSSEFactory<T extends z.ZodObject>(_events: T): SSEConnection<T>;
5
5
  export interface SSEConnection<T extends z.ZodObject> {
6
+ get closed(): boolean;
7
+ onClose(callback: () => void): void;
6
8
  publish<K extends keyof z.infer<T>>(event: K, data: z.infer<T>[K]): void;
7
9
  end(): Promise<void>;
8
10
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../src/stream/sse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,EACpD,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,EAChC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,CAAC,GACT,aAAa,CAAC,CAAC,CAAC,CAElB;AAED,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAKxF;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;IAClD,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACzE,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB"}
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../src/stream/sse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,EACpD,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,EAChC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,CAAC,GACT,aAAa,CAAC,CAAC,CAAC,CAElB;AAED,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CASxF;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;IAClD,IAAI,MAAM,IAAI,OAAO,CAAC;IACtB,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACzE,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB"}
@@ -4,6 +4,10 @@ export function createSSEFactory(socket, reply, _events) {
4
4
  }
5
5
  export function createMockSSEFactory(_events) {
6
6
  return {
7
+ get closed () {
8
+ return false;
9
+ },
10
+ onClose: (_callback)=>{},
7
11
  publish: (_event, _data)=>{},
8
12
  end: ()=>Promise.resolve()
9
13
  };
@@ -12,9 +16,25 @@ class SSEConnectionImpl {
12
16
  socket;
13
17
  reply;
14
18
  _closed = false;
19
+ _closeCallbacks = [];
15
20
  markClosed = ()=>{
16
21
  this._closed = true;
22
+ this.fireCloseCallbacks();
17
23
  };
24
+ get closed() {
25
+ return this._closed;
26
+ }
27
+ onClose(callback) {
28
+ this._closeCallbacks.push(callback);
29
+ }
30
+ // 콜백을 한 번만 실행하고 배열을 비워 중복 호출을 방지
31
+ fireCloseCallbacks() {
32
+ const callbacks = this._closeCallbacks;
33
+ this._closeCallbacks = [];
34
+ for (const cb of callbacks){
35
+ cb();
36
+ }
37
+ }
18
38
  constructor(socket, reply){
19
39
  this.socket = socket;
20
40
  this.reply = reply;
@@ -37,6 +57,7 @@ class SSEConnectionImpl {
37
57
  this._closed = true;
38
58
  this.socket.off("close", this.markClosed);
39
59
  this.socket.off("error", this.markClosed);
60
+ this.fireCloseCallbacks();
40
61
  this.reply.sse({
41
62
  event: "end",
42
63
  data: "END"
@@ -46,4 +67,4 @@ class SSEConnectionImpl {
46
67
  }
47
68
  }
48
69
 
49
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zdHJlYW0vc3NlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIHsgRmFzdGlmeVJlcGx5LCBGYXN0aWZ5UmVxdWVzdCB9IGZyb20gXCJmYXN0aWZ5XCI7XG5pbXBvcnQgdHlwZSB7IHogfSBmcm9tIFwiem9kXCI7XG5cbi8vIE5PVEUoSGF6ZSwgMjUxMTA2KTogY29udGV4dCBwcm92aWRlcuyXkOyEnCDsnbjsnpDrpbwg7LGE7JuM7KO866m0IGNyZWF0ZVNTRShldmVudHMp66eM7Jy866GcIOyCrOyaqSDqsIDriqVcbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVTU0VGYWN0b3J5PFQgZXh0ZW5kcyB6LlpvZE9iamVjdD4oXG4gIHNvY2tldDogRmFzdGlmeVJlcXVlc3RbXCJzb2NrZXRcIl0sXG4gIHJlcGx5OiBGYXN0aWZ5UmVwbHksXG4gIF9ldmVudHM6IFQsXG4pOiBTU0VDb25uZWN0aW9uPFQ+IHtcbiAgcmV0dXJuIG5ldyBTU0VDb25uZWN0aW9uSW1wbDxUPihzb2NrZXQsIHJlcGx5KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZU1vY2tTU0VGYWN0b3J5PFQgZXh0ZW5kcyB6LlpvZE9iamVjdD4oX2V2ZW50czogVCk6IFNTRUNvbm5lY3Rpb248VD4ge1xuICByZXR1cm4ge1xuICAgIHB1Ymxpc2g6IChfZXZlbnQsIF9kYXRhKSA9PiB7fSxcbiAgICBlbmQ6ICgpID0+IFByb21pc2UucmVzb2x2ZSgpLFxuICB9O1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFNTRUNvbm5lY3Rpb248VCBleHRlbmRzIHouWm9kT2JqZWN0PiB7XG4gIHB1Ymxpc2g8SyBleHRlbmRzIGtleW9mIHouaW5mZXI8VD4+KGV2ZW50OiBLLCBkYXRhOiB6LmluZmVyPFQ+W0tdKTogdm9pZDtcbiAgZW5kKCk6IFByb21pc2U8dm9pZD47XG59XG5cbmNsYXNzIFNTRUNvbm5lY3Rpb25JbXBsPFQgZXh0ZW5kcyB6LlpvZE9iamVjdD4gaW1wbGVtZW50cyBTU0VDb25uZWN0aW9uPFQ+IHtcbiAgcHJpdmF0ZSBfY2xvc2VkID0gZmFsc2U7XG4gIHByaXZhdGUgcmVhZG9ubHkgbWFya0Nsb3NlZCA9ICgpID0+IHtcbiAgICB0aGlzLl9jbG9zZWQgPSB0cnVlO1xuICB9O1xuXG4gIGNvbnN0cnVjdG9yKFxuICAgIHByaXZhdGUgcmVhZG9ubHkgc29ja2V0OiBGYXN0aWZ5UmVxdWVzdFtcInNvY2tldFwiXSxcbiAgICBwcml2YXRlIHJlYWRvbmx5IHJlcGx5OiBGYXN0aWZ5UmVwbHksXG4gICkge1xuICAgIHRoaXMuc29ja2V0Lm9uKFwiY2xvc2VcIiwgdGhpcy5tYXJrQ2xvc2VkKTtcbiAgICB0aGlzLnNvY2tldC5vbihcImVycm9yXCIsIHRoaXMubWFya0Nsb3NlZCk7XG4gIH1cblxuICBwdWJsaXNoPEsgZXh0ZW5kcyBrZXlvZiB6LmluZmVyPFQ+PihldmVudDogSywgZGF0YTogei5pbmZlcjxUPltLXSk6IHZvaWQge1xuICAgIGlmICh0aGlzLl9jbG9zZWQpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0aGlzLnJlcGx5LnNzZSh7XG4gICAgICBldmVudDogZXZlbnQgYXMgc3RyaW5nLFxuICAgICAgZGF0YTogSlNPTi5zdHJpbmdpZnkoZGF0YSksXG4gICAgfSk7XG4gIH1cblxuICBhc3luYyBlbmQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKHRoaXMuX2Nsb3NlZCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHRoaXMuX2Nsb3NlZCA9IHRydWU7XG4gICAgdGhpcy5zb2NrZXQub2ZmKFwiY2xvc2VcIiwgdGhpcy5tYXJrQ2xvc2VkKTtcbiAgICB0aGlzLnNvY2tldC5vZmYoXCJlcnJvclwiLCB0aGlzLm1hcmtDbG9zZWQpO1xuXG4gICAgdGhpcy5yZXBseS5zc2Uoe1xuICAgICAgZXZlbnQ6IFwiZW5kXCIsXG4gICAgICBkYXRhOiBcIkVORFwiLFxuICAgIH0pO1xuXG4gICAgYXdhaXQgbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgMjAwKSk7XG4gICAgdGhpcy5yZXBseS5yYXcuZW5kKCk7XG4gIH1cbn1cbiJdLCJuYW1lcyI6WyJjcmVhdGVTU0VGYWN0b3J5Iiwic29ja2V0IiwicmVwbHkiLCJfZXZlbnRzIiwiU1NFQ29ubmVjdGlvbkltcGwiLCJjcmVhdGVNb2NrU1NFRmFjdG9yeSIsInB1Ymxpc2giLCJfZXZlbnQiLCJfZGF0YSIsImVuZCIsIlByb21pc2UiLCJyZXNvbHZlIiwiX2Nsb3NlZCIsIm1hcmtDbG9zZWQiLCJvbiIsImV2ZW50IiwiZGF0YSIsInNzZSIsIkpTT04iLCJzdHJpbmdpZnkiLCJvZmYiLCJzZXRUaW1lb3V0IiwicmF3Il0sIm1hcHBpbmdzIjoiQUFHQSw2RUFBNkU7QUFDN0UsT0FBTyxTQUFTQSxpQkFDZEMsTUFBZ0MsRUFDaENDLEtBQW1CLEVBQ25CQyxPQUFVO0lBRVYsT0FBTyxJQUFJQyxrQkFBcUJILFFBQVFDO0FBQzFDO0FBRUEsT0FBTyxTQUFTRyxxQkFBNENGLE9BQVU7SUFDcEUsT0FBTztRQUNMRyxTQUFTLENBQUNDLFFBQVFDLFNBQVc7UUFDN0JDLEtBQUssSUFBTUMsUUFBUUMsT0FBTztJQUM1QjtBQUNGO0FBT0EsTUFBTVA7OztJQUNJUSxVQUFVLE1BQU07SUFDUEMsYUFBYTtRQUM1QixJQUFJLENBQUNELE9BQU8sR0FBRztJQUNqQixFQUFFO0lBRUYsWUFDRSxBQUFpQlgsTUFBZ0MsRUFDakQsQUFBaUJDLEtBQW1CLENBQ3BDO2FBRmlCRCxTQUFBQTthQUNBQyxRQUFBQTtRQUVqQixJQUFJLENBQUNELE1BQU0sQ0FBQ2EsRUFBRSxDQUFDLFNBQVMsSUFBSSxDQUFDRCxVQUFVO1FBQ3ZDLElBQUksQ0FBQ1osTUFBTSxDQUFDYSxFQUFFLENBQUMsU0FBUyxJQUFJLENBQUNELFVBQVU7SUFDekM7SUFFQVAsUUFBb0NTLEtBQVEsRUFBRUMsSUFBbUIsRUFBUTtRQUN2RSxJQUFJLElBQUksQ0FBQ0osT0FBTyxFQUFFO1lBQ2hCO1FBQ0Y7UUFFQSxJQUFJLENBQUNWLEtBQUssQ0FBQ2UsR0FBRyxDQUFDO1lBQ2JGLE9BQU9BO1lBQ1BDLE1BQU1FLEtBQUtDLFNBQVMsQ0FBQ0g7UUFDdkI7SUFDRjtJQUVBLE1BQU1QLE1BQXFCO1FBQ3pCLElBQUksSUFBSSxDQUFDRyxPQUFPLEVBQUU7WUFDaEI7UUFDRjtRQUVBLElBQUksQ0FBQ0EsT0FBTyxHQUFHO1FBQ2YsSUFBSSxDQUFDWCxNQUFNLENBQUNtQixHQUFHLENBQUMsU0FBUyxJQUFJLENBQUNQLFVBQVU7UUFDeEMsSUFBSSxDQUFDWixNQUFNLENBQUNtQixHQUFHLENBQUMsU0FBUyxJQUFJLENBQUNQLFVBQVU7UUFFeEMsSUFBSSxDQUFDWCxLQUFLLENBQUNlLEdBQUcsQ0FBQztZQUNiRixPQUFPO1lBQ1BDLE1BQU07UUFDUjtRQUVBLE1BQU0sSUFBSU4sUUFBUSxDQUFDQyxVQUFZVSxXQUFXVixTQUFTO1FBQ25ELElBQUksQ0FBQ1QsS0FBSyxDQUFDb0IsR0FBRyxDQUFDYixHQUFHO0lBQ3BCO0FBQ0YifQ==
70
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zdHJlYW0vc3NlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIHsgRmFzdGlmeVJlcGx5LCBGYXN0aWZ5UmVxdWVzdCB9IGZyb20gXCJmYXN0aWZ5XCI7XG5pbXBvcnQgdHlwZSB7IHogfSBmcm9tIFwiem9kXCI7XG5cbi8vIE5PVEUoSGF6ZSwgMjUxMTA2KTogY29udGV4dCBwcm92aWRlcuyXkOyEnCDsnbjsnpDrpbwg7LGE7JuM7KO866m0IGNyZWF0ZVNTRShldmVudHMp66eM7Jy866GcIOyCrOyaqSDqsIDriqVcbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVTU0VGYWN0b3J5PFQgZXh0ZW5kcyB6LlpvZE9iamVjdD4oXG4gIHNvY2tldDogRmFzdGlmeVJlcXVlc3RbXCJzb2NrZXRcIl0sXG4gIHJlcGx5OiBGYXN0aWZ5UmVwbHksXG4gIF9ldmVudHM6IFQsXG4pOiBTU0VDb25uZWN0aW9uPFQ+IHtcbiAgcmV0dXJuIG5ldyBTU0VDb25uZWN0aW9uSW1wbDxUPihzb2NrZXQsIHJlcGx5KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZU1vY2tTU0VGYWN0b3J5PFQgZXh0ZW5kcyB6LlpvZE9iamVjdD4oX2V2ZW50czogVCk6IFNTRUNvbm5lY3Rpb248VD4ge1xuICByZXR1cm4ge1xuICAgIGdldCBjbG9zZWQoKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfSxcbiAgICBvbkNsb3NlOiAoX2NhbGxiYWNrKSA9PiB7fSxcbiAgICBwdWJsaXNoOiAoX2V2ZW50LCBfZGF0YSkgPT4ge30sXG4gICAgZW5kOiAoKSA9PiBQcm9taXNlLnJlc29sdmUoKSxcbiAgfTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBTU0VDb25uZWN0aW9uPFQgZXh0ZW5kcyB6LlpvZE9iamVjdD4ge1xuICBnZXQgY2xvc2VkKCk6IGJvb2xlYW47XG4gIG9uQ2xvc2UoY2FsbGJhY2s6ICgpID0+IHZvaWQpOiB2b2lkO1xuICBwdWJsaXNoPEsgZXh0ZW5kcyBrZXlvZiB6LmluZmVyPFQ+PihldmVudDogSywgZGF0YTogei5pbmZlcjxUPltLXSk6IHZvaWQ7XG4gIGVuZCgpOiBQcm9taXNlPHZvaWQ+O1xufVxuXG5jbGFzcyBTU0VDb25uZWN0aW9uSW1wbDxUIGV4dGVuZHMgei5ab2RPYmplY3Q+IGltcGxlbWVudHMgU1NFQ29ubmVjdGlvbjxUPiB7XG4gIHByaXZhdGUgX2Nsb3NlZCA9IGZhbHNlO1xuICBwcml2YXRlIF9jbG9zZUNhbGxiYWNrczogQXJyYXk8KCkgPT4gdm9pZD4gPSBbXTtcblxuICBwcml2YXRlIHJlYWRvbmx5IG1hcmtDbG9zZWQgPSAoKSA9PiB7XG4gICAgdGhpcy5fY2xvc2VkID0gdHJ1ZTtcbiAgICB0aGlzLmZpcmVDbG9zZUNhbGxiYWNrcygpO1xuICB9O1xuXG4gIGdldCBjbG9zZWQoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMuX2Nsb3NlZDtcbiAgfVxuXG4gIG9uQ2xvc2UoY2FsbGJhY2s6ICgpID0+IHZvaWQpOiB2b2lkIHtcbiAgICB0aGlzLl9jbG9zZUNhbGxiYWNrcy5wdXNoKGNhbGxiYWNrKTtcbiAgfVxuXG4gIC8vIOy9nOuwseydhCDtlZwg67KI66eMIOyLpO2Wie2VmOqzoCDrsLDsl7TsnYQg67mE7JuMIOykkeuztSDtmLjstpzsnYQg67Cp7KeAXG4gIHByaXZhdGUgZmlyZUNsb3NlQ2FsbGJhY2tzKCk6IHZvaWQge1xuICAgIGNvbnN0IGNhbGxiYWNrcyA9IHRoaXMuX2Nsb3NlQ2FsbGJhY2tzO1xuICAgIHRoaXMuX2Nsb3NlQ2FsbGJhY2tzID0gW107XG4gICAgZm9yIChjb25zdCBjYiBvZiBjYWxsYmFja3MpIHtcbiAgICAgIGNiKCk7XG4gICAgfVxuICB9XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSByZWFkb25seSBzb2NrZXQ6IEZhc3RpZnlSZXF1ZXN0W1wic29ja2V0XCJdLFxuICAgIHByaXZhdGUgcmVhZG9ubHkgcmVwbHk6IEZhc3RpZnlSZXBseSxcbiAgKSB7XG4gICAgdGhpcy5zb2NrZXQub24oXCJjbG9zZVwiLCB0aGlzLm1hcmtDbG9zZWQpO1xuICAgIHRoaXMuc29ja2V0Lm9uKFwiZXJyb3JcIiwgdGhpcy5tYXJrQ2xvc2VkKTtcbiAgfVxuXG4gIHB1Ymxpc2g8SyBleHRlbmRzIGtleW9mIHouaW5mZXI8VD4+KGV2ZW50OiBLLCBkYXRhOiB6LmluZmVyPFQ+W0tdKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuX2Nsb3NlZCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHRoaXMucmVwbHkuc3NlKHtcbiAgICAgIGV2ZW50OiBldmVudCBhcyBzdHJpbmcsXG4gICAgICBkYXRhOiBKU09OLnN0cmluZ2lmeShkYXRhKSxcbiAgICB9KTtcbiAgfVxuXG4gIGFzeW5jIGVuZCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBpZiAodGhpcy5fY2xvc2VkKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdGhpcy5fY2xvc2VkID0gdHJ1ZTtcbiAgICB0aGlzLnNvY2tldC5vZmYoXCJjbG9zZVwiLCB0aGlzLm1hcmtDbG9zZWQpO1xuICAgIHRoaXMuc29ja2V0Lm9mZihcImVycm9yXCIsIHRoaXMubWFya0Nsb3NlZCk7XG4gICAgdGhpcy5maXJlQ2xvc2VDYWxsYmFja3MoKTtcblxuICAgIHRoaXMucmVwbHkuc3NlKHtcbiAgICAgIGV2ZW50OiBcImVuZFwiLFxuICAgICAgZGF0YTogXCJFTkRcIixcbiAgICB9KTtcblxuICAgIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIDIwMCkpO1xuICAgIHRoaXMucmVwbHkucmF3LmVuZCgpO1xuICB9XG59XG4iXSwibmFtZXMiOlsiY3JlYXRlU1NFRmFjdG9yeSIsInNvY2tldCIsInJlcGx5IiwiX2V2ZW50cyIsIlNTRUNvbm5lY3Rpb25JbXBsIiwiY3JlYXRlTW9ja1NTRUZhY3RvcnkiLCJjbG9zZWQiLCJvbkNsb3NlIiwiX2NhbGxiYWNrIiwicHVibGlzaCIsIl9ldmVudCIsIl9kYXRhIiwiZW5kIiwiUHJvbWlzZSIsInJlc29sdmUiLCJfY2xvc2VkIiwiX2Nsb3NlQ2FsbGJhY2tzIiwibWFya0Nsb3NlZCIsImZpcmVDbG9zZUNhbGxiYWNrcyIsImNhbGxiYWNrIiwicHVzaCIsImNhbGxiYWNrcyIsImNiIiwib24iLCJldmVudCIsImRhdGEiLCJzc2UiLCJKU09OIiwic3RyaW5naWZ5Iiwib2ZmIiwic2V0VGltZW91dCIsInJhdyJdLCJtYXBwaW5ncyI6IkFBR0EsNkVBQTZFO0FBQzdFLE9BQU8sU0FBU0EsaUJBQ2RDLE1BQWdDLEVBQ2hDQyxLQUFtQixFQUNuQkMsT0FBVTtJQUVWLE9BQU8sSUFBSUMsa0JBQXFCSCxRQUFRQztBQUMxQztBQUVBLE9BQU8sU0FBU0cscUJBQTRDRixPQUFVO0lBQ3BFLE9BQU87UUFDTCxJQUFJRyxVQUFTO1lBQ1gsT0FBTztRQUNUO1FBQ0FDLFNBQVMsQ0FBQ0MsYUFBZTtRQUN6QkMsU0FBUyxDQUFDQyxRQUFRQyxTQUFXO1FBQzdCQyxLQUFLLElBQU1DLFFBQVFDLE9BQU87SUFDNUI7QUFDRjtBQVNBLE1BQU1WOzs7SUFDSVcsVUFBVSxNQUFNO0lBQ2hCQyxrQkFBcUMsRUFBRSxDQUFDO0lBRS9CQyxhQUFhO1FBQzVCLElBQUksQ0FBQ0YsT0FBTyxHQUFHO1FBQ2YsSUFBSSxDQUFDRyxrQkFBa0I7SUFDekIsRUFBRTtJQUVGLElBQUlaLFNBQWtCO1FBQ3BCLE9BQU8sSUFBSSxDQUFDUyxPQUFPO0lBQ3JCO0lBRUFSLFFBQVFZLFFBQW9CLEVBQVE7UUFDbEMsSUFBSSxDQUFDSCxlQUFlLENBQUNJLElBQUksQ0FBQ0Q7SUFDNUI7SUFFQSxpQ0FBaUM7SUFDekJELHFCQUEyQjtRQUNqQyxNQUFNRyxZQUFZLElBQUksQ0FBQ0wsZUFBZTtRQUN0QyxJQUFJLENBQUNBLGVBQWUsR0FBRyxFQUFFO1FBQ3pCLEtBQUssTUFBTU0sTUFBTUQsVUFBVztZQUMxQkM7UUFDRjtJQUNGO0lBRUEsWUFDRSxBQUFpQnJCLE1BQWdDLEVBQ2pELEFBQWlCQyxLQUFtQixDQUNwQzthQUZpQkQsU0FBQUE7YUFDQUMsUUFBQUE7UUFFakIsSUFBSSxDQUFDRCxNQUFNLENBQUNzQixFQUFFLENBQUMsU0FBUyxJQUFJLENBQUNOLFVBQVU7UUFDdkMsSUFBSSxDQUFDaEIsTUFBTSxDQUFDc0IsRUFBRSxDQUFDLFNBQVMsSUFBSSxDQUFDTixVQUFVO0lBQ3pDO0lBRUFSLFFBQW9DZSxLQUFRLEVBQUVDLElBQW1CLEVBQVE7UUFDdkUsSUFBSSxJQUFJLENBQUNWLE9BQU8sRUFBRTtZQUNoQjtRQUNGO1FBRUEsSUFBSSxDQUFDYixLQUFLLENBQUN3QixHQUFHLENBQUM7WUFDYkYsT0FBT0E7WUFDUEMsTUFBTUUsS0FBS0MsU0FBUyxDQUFDSDtRQUN2QjtJQUNGO0lBRUEsTUFBTWIsTUFBcUI7UUFDekIsSUFBSSxJQUFJLENBQUNHLE9BQU8sRUFBRTtZQUNoQjtRQUNGO1FBRUEsSUFBSSxDQUFDQSxPQUFPLEdBQUc7UUFDZixJQUFJLENBQUNkLE1BQU0sQ0FBQzRCLEdBQUcsQ0FBQyxTQUFTLElBQUksQ0FBQ1osVUFBVTtRQUN4QyxJQUFJLENBQUNoQixNQUFNLENBQUM0QixHQUFHLENBQUMsU0FBUyxJQUFJLENBQUNaLFVBQVU7UUFDeEMsSUFBSSxDQUFDQyxrQkFBa0I7UUFFdkIsSUFBSSxDQUFDaEIsS0FBSyxDQUFDd0IsR0FBRyxDQUFDO1lBQ2JGLE9BQU87WUFDUEMsTUFBTTtRQUNSO1FBRUEsTUFBTSxJQUFJWixRQUFRLENBQUNDLFVBQVlnQixXQUFXaEIsU0FBUztRQUNuRCxJQUFJLENBQUNaLEtBQUssQ0FBQzZCLEdBQUcsQ0FBQ25CLEdBQUc7SUFDcEI7QUFDRiJ9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonamu",
3
- "version": "0.8.20",
3
+ "version": "0.8.21",
4
4
  "description": "Sonamu — TypeScript Fullstack API Framework",
5
5
  "keywords": [
6
6
  "typescript",
@@ -124,9 +124,9 @@
124
124
  "vite": "7.3.0",
125
125
  "vitest": "^4.0.10",
126
126
  "@sonamu-kit/hmr-hook": "^0.4.1",
127
- "@sonamu-kit/hmr-runner": "^0.1.1",
127
+ "@sonamu-kit/tasks": "^0.2.0",
128
128
  "@sonamu-kit/ts-loader": "^2.1.3",
129
- "@sonamu-kit/tasks": "^0.2.0"
129
+ "@sonamu-kit/hmr-runner": "^0.1.1"
130
130
  },
131
131
  "devDependencies": {
132
132
  "@biomejs/biome": "^2.3.13",
@@ -86,7 +86,7 @@ export async function fetch(options: AxiosRequestConfig) {
86
86
  });
87
87
  return res.data;
88
88
  } catch (e: unknown) {
89
- if (axios.isAxiosError(e) && e.response && e.response.data) {
89
+ if (axios.isAxiosError(e) && e.response?.data) {
90
90
  const d = e.response.data as {
91
91
  message: string;
92
92
  issues: core.$ZodIssue[];
@@ -61,7 +61,7 @@ export async function fetch(options: AxiosRequestConfig) {
61
61
  });
62
62
  return res.data;
63
63
  } catch (e: unknown) {
64
- if (axios.isAxiosError(e) && e.response && e.response.data) {
64
+ if (axios.isAxiosError(e) && e.response?.data) {
65
65
  const d = e.response.data as {
66
66
  message: string;
67
67
  issues: core.$ZodIssue[];
package/src/stream/sse.ts CHANGED
@@ -12,22 +12,48 @@ export function createSSEFactory<T extends z.ZodObject>(
12
12
 
13
13
  export function createMockSSEFactory<T extends z.ZodObject>(_events: T): SSEConnection<T> {
14
14
  return {
15
+ get closed() {
16
+ return false;
17
+ },
18
+ onClose: (_callback) => {},
15
19
  publish: (_event, _data) => {},
16
20
  end: () => Promise.resolve(),
17
21
  };
18
22
  }
19
23
 
20
24
  export interface SSEConnection<T extends z.ZodObject> {
25
+ get closed(): boolean;
26
+ onClose(callback: () => void): void;
21
27
  publish<K extends keyof z.infer<T>>(event: K, data: z.infer<T>[K]): void;
22
28
  end(): Promise<void>;
23
29
  }
24
30
 
25
31
  class SSEConnectionImpl<T extends z.ZodObject> implements SSEConnection<T> {
26
32
  private _closed = false;
33
+ private _closeCallbacks: Array<() => void> = [];
34
+
27
35
  private readonly markClosed = () => {
28
36
  this._closed = true;
37
+ this.fireCloseCallbacks();
29
38
  };
30
39
 
40
+ get closed(): boolean {
41
+ return this._closed;
42
+ }
43
+
44
+ onClose(callback: () => void): void {
45
+ this._closeCallbacks.push(callback);
46
+ }
47
+
48
+ // 콜백을 한 번만 실행하고 배열을 비워 중복 호출을 방지
49
+ private fireCloseCallbacks(): void {
50
+ const callbacks = this._closeCallbacks;
51
+ this._closeCallbacks = [];
52
+ for (const cb of callbacks) {
53
+ cb();
54
+ }
55
+ }
56
+
31
57
  constructor(
32
58
  private readonly socket: FastifyRequest["socket"],
33
59
  private readonly reply: FastifyReply,
@@ -55,6 +81,7 @@ class SSEConnectionImpl<T extends z.ZodObject> implements SSEConnection<T> {
55
81
  this._closed = true;
56
82
  this.socket.off("close", this.markClosed);
57
83
  this.socket.off("error", this.markClosed);
84
+ this.fireCloseCallbacks();
58
85
 
59
86
  this.reply.sse({
60
87
  event: "end",