@firtoz/socka 2.0.0 → 3.0.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.
Files changed (67) hide show
  1. package/README.md +195 -42
  2. package/dist/SockaWebSocketSession-B1w7RAid.d.ts +209 -0
  3. package/dist/bun/index.d.ts +30 -5
  4. package/dist/bun/index.js +28 -5
  5. package/dist/bun/index.js.map +1 -1
  6. package/dist/{chunk-MZCQHJXY.js → chunk-IFIGKR3W.js} +45 -8
  7. package/dist/chunk-IFIGKR3W.js.map +1 -0
  8. package/dist/{chunk-45D4T232.js → chunk-LVVCHLNW.js} +74 -9
  9. package/dist/chunk-LVVCHLNW.js.map +1 -0
  10. package/dist/{chunk-AM7PB26G.js → chunk-P3JEEOJL.js} +192 -10
  11. package/dist/chunk-P3JEEOJL.js.map +1 -0
  12. package/dist/chunk-QGURL3DJ.js +8 -0
  13. package/dist/chunk-QGURL3DJ.js.map +1 -0
  14. package/dist/client/index.d.ts +59 -3
  15. package/dist/client/index.js +2 -2
  16. package/dist/core/index.d.ts +2 -21
  17. package/dist/core/index.js +1 -1
  18. package/dist/core/index.js.map +1 -1
  19. package/dist/do/index.d.ts +20 -2
  20. package/dist/do/index.js +36 -2
  21. package/dist/do/index.js.map +1 -1
  22. package/dist/hono/cloudflare-workers.d.ts +4 -4
  23. package/dist/hono/cloudflare-workers.js +4 -3
  24. package/dist/hono/cloudflare-workers.js.map +1 -1
  25. package/dist/hono/index.d.ts +22 -6
  26. package/dist/hono/index.js +5 -3
  27. package/dist/hono/index.js.map +1 -1
  28. package/dist/react/index.d.ts +43 -4
  29. package/dist/react/index.js +103 -9
  30. package/dist/react/index.js.map +1 -1
  31. package/dist/server/index.d.ts +18 -5
  32. package/dist/server/index.js +24 -4
  33. package/dist/server/index.js.map +1 -1
  34. package/dist/{socka-report-error-DzFI2Tr7.d.ts → socka-report-error-CXwpAUgl.d.ts} +80 -8
  35. package/dist/test/index.d.ts +11 -0
  36. package/dist/test/index.js +84 -0
  37. package/dist/test/index.js.map +1 -0
  38. package/docs/README.md +16 -7
  39. package/docs/auth.md +27 -0
  40. package/docs/backpressure.md +16 -0
  41. package/docs/client.md +48 -3
  42. package/docs/comparison.md +2 -2
  43. package/docs/durable-objects.md +3 -3
  44. package/docs/getting-started.md +143 -84
  45. package/docs/history.md +26 -0
  46. package/docs/internals.md +56 -0
  47. package/docs/lifecycle.md +3 -3
  48. package/docs/multi-room.md +10 -8
  49. package/docs/peers.md +11 -7
  50. package/docs/presence.md +43 -0
  51. package/docs/{events.md → pushes.md} +1 -1
  52. package/docs/recipes.md +77 -0
  53. package/docs/reconnection.md +44 -0
  54. package/docs/reference.md +27 -32
  55. package/docs/server.md +19 -3
  56. package/docs/testing.md +20 -0
  57. package/docs/wire-format.md +29 -0
  58. package/examples/minimal-socka.ts +56 -3
  59. package/package.json +14 -10
  60. package/roadmap.md +2 -2
  61. package/skills/socka/core-rpc/SKILL.md +2 -2
  62. package/skills/socka/do-session/SKILL.md +2 -2
  63. package/skills/socka/standard-schema/SKILL.md +1 -1
  64. package/dist/SockaWebSocketSession-Bru8yFcK.d.ts +0 -107
  65. package/dist/chunk-45D4T232.js.map +0 -1
  66. package/dist/chunk-AM7PB26G.js.map +0 -1
  67. package/dist/chunk-MZCQHJXY.js.map +0 -1
package/dist/do/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { SockaWebSocketSession } from '../chunk-45D4T232.js';
2
- import { reportSockaError } from '../chunk-MZCQHJXY.js';
1
+ import { SockaWebSocketSession } from '../chunk-LVVCHLNW.js';
2
+ import { reportSockaError } from '../chunk-IFIGKR3W.js';
3
3
  import { BaseSession, BaseWebSocketDO } from '@firtoz/websocket-do';
4
4
 
5
5
  function runSockaDoSessionOnAttached(config, session) {
@@ -52,6 +52,7 @@ var SockaDoSession = class extends BaseSession {
52
52
  }
53
53
  );
54
54
  const sockaConfig = {
55
+ strictUpgradeRequest: false,
55
56
  contract: config.contract,
56
57
  wireFormat,
57
58
  handlers: wrapHandlersForInnerSockaEngine(
@@ -91,6 +92,39 @@ var SockaDoSession = class extends BaseSession {
91
92
  broadcastPush(name, body, excludeSelf = false) {
92
93
  return this.socka.broadcastPush(name, body, excludeSelf);
93
94
  }
95
+ /**
96
+ * {@link SockaWebSocketSession.listPeers} for this Durable Object room.
97
+ */
98
+ listPeers(options) {
99
+ const out = [];
100
+ for (const [ws, s] of this.sessions) {
101
+ if (options?.excludeSelf && ws === this.websocket) continue;
102
+ out.push(s.data);
103
+ }
104
+ return out;
105
+ }
106
+ /**
107
+ * Like {@link listPeers} but maps each peer {@link SockaDoSession}.
108
+ */
109
+ listPeersWith(map, options) {
110
+ const out = [];
111
+ for (const [ws, s] of this.sessions) {
112
+ if (options?.excludeSelf && ws === this.websocket) continue;
113
+ out.push(map(s));
114
+ }
115
+ return out;
116
+ }
117
+ peerCount(options) {
118
+ let n = 0;
119
+ for (const [ws] of this.sessions) {
120
+ if (options?.excludeSelf && ws === this.websocket) continue;
121
+ n += 1;
122
+ }
123
+ return n;
124
+ }
125
+ hasPeers(options) {
126
+ return this.peerCount(options) > 0;
127
+ }
94
128
  };
95
129
  var SockaWebSocketDO = class extends BaseWebSocketDO {
96
130
  constructor(ctx, env, options) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/do/SockaDoSession.ts","../../src/do/SockaWebSocketDO.ts","../../src/do/dispatch.ts"],"names":[],"mappings":";;;;AA8EA,SAAS,2BAAA,CAKR,QACA,OAAA,EACO;AACP,EAAA,MAAM,KAAK,MAAA,CAAO,UAAA;AAClB,EAAA,IAAI,CAAC,EAAA,EAAI;AACT,EAAA,IAAI;AACH,IAAA,MAAM,MAAA,GAAS,GAAG,OAAO,CAAA;AACzB,IAAA,KAAK,QAAQ,OAAA,CAAQ,MAAM,CAAA,CAAE,KAAA,CAAM,CAAC,KAAA,KAAmB;AACtD,MAAA,gBAAA,CAAiB,OAAO,WAAA,EAAa;AAAA,QACpC,IAAA,EAAM,kBAAA;AAAA,QACN;AAAA,OACA,CAAA;AAAA,IACF,CAAC,CAAA;AAAA,EACF,SAAS,KAAA,EAAO;AACf,IAAA,gBAAA,CAAiB,OAAO,WAAA,EAAa,EAAE,IAAA,EAAM,kBAAA,EAAoB,OAAO,CAAA;AAAA,EACzE;AACD;AAEA,SAAS,+BAAA,CAKR,QAAA,EACA,YAAA,EAIA,KAAA,EAIC;AACD,EAAA,MAAM,QAAQ,QAAA,CAAS,KAAA;AACvB,EAAA,MAAM,MASF,EAAC;AAEL,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,EAAyC;AAC3E,IAAA,MAAM,IAAA,GAAO,MAAM,GAAG,CAAA;AACtB,IAAA,MAAM,MAAA,GAAS,aAAa,GAAgC,CAAA;AAC5D,IAAA,IAAI,KAAK,KAAA,EAAO;AACf,MAAA,GAAA,CAAI,GAAG,CAAA,GAAI,CACV,OACA,MAAA,KAGC,MAAA,CAIC,OAAO,KAAK,CAAA;AAAA,IAChB,CAAA,MAAO;AACN,MAAA,GAAA,CAAI,GAAG,CAAA,GAAI,CACV,MAAA,KAGC,OAGC,KAAK,CAAA;AAAA,IACT;AAAA,EACD;AAEA,EAAA,OAAO,GAAA;AAIR;AAOO,IAAM,cAAA,GAAN,cAKE,WAAA,CAET;AAAA,EAGC,WAAA,CACC,SAAA,EACA,QAAA,EACA,MAAA,EACC;AACD,IAAA,MAAM,UAAA,GAAa,OAAO,UAAA,IAAc,MAAA;AACxC,IAAA,KAAA;AAAA,MACC,SAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,QACC,UAAA,EACC,MAAA,CAAO,UAAA,KACN,CAAC,UAAuC,EAAC,CAAA,CAAA;AAAA,QAC3C,eAAe,YAAY;AAAA,QAE3B,CAAA;AAAA,QACA,mBAAA,EAAqB,OAAO,OAAA,KAAY;AACvC,UAAA,MAAM,IAAA,CAAK,KAAA,CAAM,mBAAA,CAAoB,OAAO,CAAA;AAAA,QAC7C,CAAA;AAAA,QACA,WAAA,EAAa,OAAO,WAAA,KAAgB;AACnC,UAAA,MAAM,MAAA,CAAO,WAAA;AAAA,YACZ;AAAA,WACD;AAAA,QACD;AAAA;AACD,KACD;AACA,IAAA,MAAM,WAAA,GAGF;AAAA,MACH,UAAU,MAAA,CAAO,QAAA;AAAA,MACjB,UAAA;AAAA,MACA,QAAA,EAAU,+BAAA;AAAA,QACT,MAAA,CAAO,QAAA;AAAA,QACP,MAAA,CAAO,QAAA;AAAA,QACP;AAAA,OACD;AAAA,MACA,aAAa,YAAY;AAAA,MAEzB,CAAA;AAAA,MACA,gBAAgB,MAAA,CAAO,cAAA,GACpB,CAAC,GAAA,EAAK,OAAA,EAAS,OAAO,MAAA,KAAW;AACjC,QAAA,MAAA,CAAO,cAAA,GAAiB,GAAA,EAAK,OAAA,EAAS,KAAA,EAAO,IAAI,CAAA;AAAA,MAClD,CAAA,GACC,MAAA;AAAA,MACH,mBAAmB,MAAA,CAAO,iBAAA;AAAA,MAC1B,eAAe,MAAA,CAAO,aAAA;AAAA,MACtB,iBAAiB,MAAA,CAAO;AAAA,KACzB;AACA,IAAA,IAAA,CAAK,QAAQ,IAAI,qBAAA;AAAA,MAChB,SAAA;AAAA,MACA,QAAA;AAAA,MAIA;AAAA,KACD;AAIA,IAAA,cAAA,CAAe,MAAM;AACpB,MAAA,cAAA,CAAe,MAAM;AACpB,QAAA,2BAAA,CAA4B,QAAQ,IAAI,CAAA;AAAA,MACzC,CAAC,CAAA;AAAA,IACF,CAAC,CAAA;AAAA,EACF;AAAA,EAEA,MAAa,iBAAiB,UAAA,EAAmC;AAChE,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,gBAAA,CAAiB,UAAU,CAAA;AAAA,EAC9C;AAAA,EAEO,aAAA,CAAc,OAAe,IAAA,EAAqB;AACxD,IAAA,IAAA,CAAK,KAAA,CAAM,aAAA,CAAc,KAAA,EAAO,IAAI,CAAA;AAAA,EACrC;AAAA,EAEO,QAAA,CACN,MACA,IAAA,EACgB;AAChB,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,QAAA,CAAS,IAAA,EAAM,IAAI,CAAA;AAAA,EACtC;AAAA,EAEO,aAAA,CACN,IAAA,EACA,IAAA,EACA,WAAA,GAAc,KAAA,EACE;AAChB,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,aAAA,CAAc,IAAA,EAAM,MAAM,WAAW,CAAA;AAAA,EACxD;AACD;AC5OO,IAAe,gBAAA,GAAf,cAgBG,eAAA,CAAgC;AAAA,EACzC,WAAA,CACC,GAAA,EACA,GAAA,EACA,OAAA,EACC;AACD,IAAA,KAAA,CAAM,KAAK,GAAA,EAAK;AAAA,MACf,eAAe,CAAC,UAAA,EAAY,cAC3B,OAAA,CAAQ,kBAAA,CAAmB,YAAY,SAAS;AAAA,KACjD,CAAA;AAAA,EACF;AACD;;;ACnDO,SAAS,YAAA,CACf,IACA,KAAA,EAKC;AACD,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,EAAA,EAAI,KAAA,EAAM;AACnC","file":"index.js","sourcesContent":["import type { Context } from \"hono\";\nimport { BaseSession } from \"@firtoz/websocket-do\";\nimport type {\n\tInferSockaPushPayload,\n\tSockaContract,\n\tSockaContractConfig,\n\tInferSockaHandlers,\n} from \"../core/contract\";\nimport {\n\tSockaWebSocketSession,\n\ttype SockaPushSession,\n\ttype SockaWebSocketSessionConfig,\n} from \"../server/SockaWebSocketSession\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport type { SockaReportError } from \"../core/socka-report-error\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\n\n/** Session data with no fields — `createData` may be omitted (defaults to `{}`). */\ntype EmptySockaSessionData = Record<string, never>;\n\ntype SockaDoOuterSession<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTEnv extends object,\n> = import(\"./SockaDoSession\").SockaDoSession<TContract, TData, TEnv>;\n\ntype SockaDoSessionCreateData<TData, TEnv extends object> = [TData] extends [\n\tEmptySockaSessionData,\n]\n\t? {\n\t\t\tcreateData?: (ctx: Context<{ Bindings: TEnv }>) => TData;\n\t\t}\n\t: {\n\t\t\tcreateData: (ctx: Context<{ Bindings: TEnv }>) => TData;\n\t\t};\n\nexport type SockaDoSessionConfig<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTEnv extends object,\n> = {\n\tcontract: TContract;\n\t/** Default `\"json\"`. Use `\"msgpack\"` for binary frames (must match client). */\n\twireFormat?: SockaWireFormat;\n\thandlers: InferSockaHandlers<\n\t\tTContract,\n\t\tSockaDoOuterSession<TContract, TData, TEnv>\n\t>;\n\thandleClose: (\n\t\tsession: SockaDoOuterSession<TContract, TData, TEnv>,\n\t) => Promise<void>;\n\tonHandlerError?: (\n\t\terror: unknown,\n\t\trpcName: string,\n\t\tinput: unknown,\n\t\tsession: SockaDoOuterSession<TContract, TData, TEnv>,\n\t) => void;\n\tonValidationError?: (\n\t\terror: unknown,\n\t\toriginalMessage: unknown,\n\t) => Promise<void>;\n\t/**\n\t * Optional sink for non-RPC failures (e.g. `onAttached`). Defaults to\n\t * `console.error`; see `SockaReportError` in `@firtoz/socka/core`.\n\t */\n\treportError?: (event: SockaReportError) => void;\n\t/**\n\t * Called after this session is registered in the DO `sessions` map (next\n\t * microtask). Use for join broadcasts and other logic that must run only\n\t * when peers can see this connection.\n\t */\n\tonAttached?: (\n\t\tsession: SockaDoOuterSession<TContract, TData, TEnv>,\n\t) => void | Promise<void>;\n\tserializeJson?: (value: unknown) => string;\n\tdeserializeJson?: (raw: string) => unknown;\n} & SockaDoSessionCreateData<TData, TEnv>;\n\nfunction runSockaDoSessionOnAttached<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTEnv extends object,\n>(\n\tconfig: SockaDoSessionConfig<TContract, TData, TEnv>,\n\tsession: SockaDoSession<TContract, TData, TEnv>,\n): void {\n\tconst cb = config.onAttached;\n\tif (!cb) return;\n\ttry {\n\t\tconst result = cb(session);\n\t\tvoid Promise.resolve(result).catch((error: unknown) => {\n\t\t\treportSockaError(config.reportError, {\n\t\t\t\tkind: \"serverOnAttached\",\n\t\t\t\terror,\n\t\t\t});\n\t\t});\n\t} catch (error) {\n\t\treportSockaError(config.reportError, { kind: \"serverOnAttached\", error });\n\t}\n}\n\nfunction wrapHandlersForInnerSockaEngine<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTEnv extends object,\n>(\n\tcontract: TContract,\n\tuserHandlers: InferSockaHandlers<\n\t\tTContract,\n\t\tSockaDoSession<TContract, TData, TEnv>\n\t>,\n\touter: SockaDoSession<TContract, TData, TEnv>,\n): InferSockaHandlers<\n\tTContract,\n\tSockaWebSocketSession<TContract, EmptySockaSessionData>\n> {\n\tconst calls = contract.calls;\n\tconst out: Record<\n\t\tstring,\n\t\t| ((\n\t\t\t\tinput: unknown,\n\t\t\t\tinner: SockaWebSocketSession<TContract, EmptySockaSessionData>,\n\t\t ) => unknown | Promise<unknown>)\n\t\t| ((\n\t\t\t\tinner: SockaWebSocketSession<TContract, EmptySockaSessionData>,\n\t\t ) => unknown | Promise<unknown>)\n\t> = {};\n\n\tfor (const key of Object.keys(calls) as Array<keyof typeof calls & string>) {\n\t\tconst proc = calls[key];\n\t\tconst userFn = userHandlers[key as keyof typeof userHandlers];\n\t\tif (proc.input) {\n\t\t\tout[key] = (\n\t\t\t\tinput,\n\t\t\t\t_inner: SockaWebSocketSession<TContract, EmptySockaSessionData>,\n\t\t\t) =>\n\t\t\t\t(\n\t\t\t\t\tuserFn as (\n\t\t\t\t\t\ti: unknown,\n\t\t\t\t\t\ts: SockaDoSession<TContract, TData, TEnv>,\n\t\t\t\t\t) => unknown | Promise<unknown>\n\t\t\t\t)(input, outer);\n\t\t} else {\n\t\t\tout[key] = (\n\t\t\t\t_inner: SockaWebSocketSession<TContract, EmptySockaSessionData>,\n\t\t\t) =>\n\t\t\t\t(\n\t\t\t\t\tuserFn as (\n\t\t\t\t\t\ts: SockaDoSession<TContract, TData, TEnv>,\n\t\t\t\t\t) => unknown | Promise<unknown>\n\t\t\t\t)(outer);\n\t\t}\n\t}\n\n\treturn out as InferSockaHandlers<\n\t\tTContract,\n\t\tSockaWebSocketSession<TContract, EmptySockaSessionData>\n\t>;\n}\n\n/**\n * Durable Object WebSocket session driven by a socka contract.\n * Dispatches client requests to typed handler functions, validates\n * input/output via Standard Schema, and auto-sends response/error frames.\n */\nexport class SockaDoSession<\n\t\tTContract extends SockaContract<SockaContractConfig>,\n\t\tTData = EmptySockaSessionData,\n\t\tTEnv extends object = Cloudflare.Env,\n\t>\n\textends BaseSession<TData, unknown, unknown, TEnv>\n\timplements SockaPushSession<TContract>\n{\n\tprivate socka!: SockaWebSocketSession<TContract, EmptySockaSessionData>;\n\n\tconstructor(\n\t\twebsocket: WebSocket,\n\t\tsessions: Map<WebSocket, SockaDoSession<TContract, TData, TEnv>>,\n\t\tconfig: SockaDoSessionConfig<TContract, TData, TEnv>,\n\t) {\n\t\tconst wireFormat = config.wireFormat ?? \"json\";\n\t\tsuper(\n\t\t\twebsocket,\n\t\t\tsessions as Map<WebSocket, BaseSession<TData, unknown, unknown, TEnv>>,\n\t\t\t{\n\t\t\t\tcreateData:\n\t\t\t\t\tconfig.createData ??\n\t\t\t\t\t((_ctx: Context<{ Bindings: TEnv }>) => ({}) as TData),\n\t\t\t\thandleMessage: async () => {\n\t\t\t\t\t// Raw message handling goes through handleRawMessage / handleBufferMessage\n\t\t\t\t},\n\t\t\t\thandleBufferMessage: async (message) => {\n\t\t\t\t\tawait this.socka.handleBinaryMessage(message);\n\t\t\t\t},\n\t\t\t\thandleClose: async (baseSession) => {\n\t\t\t\t\tawait config.handleClose(\n\t\t\t\t\t\tbaseSession as SockaDoSession<TContract, TData, TEnv>,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t\tconst sockaConfig: SockaWebSocketSessionConfig<\n\t\t\tTContract,\n\t\t\tEmptySockaSessionData\n\t\t> = {\n\t\t\tcontract: config.contract,\n\t\t\twireFormat,\n\t\t\thandlers: wrapHandlersForInnerSockaEngine(\n\t\t\t\tconfig.contract,\n\t\t\t\tconfig.handlers,\n\t\t\t\tthis,\n\t\t\t),\n\t\t\thandleClose: async () => {\n\t\t\t\t// Outer DO lifecycle uses SockaDoSessionConfig.handleClose; inner engine no-op.\n\t\t\t},\n\t\t\tonHandlerError: config.onHandlerError\n\t\t\t\t? (err, rpcName, input, _inner) => {\n\t\t\t\t\t\tconfig.onHandlerError?.(err, rpcName, input, this);\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t\t\tonValidationError: config.onValidationError,\n\t\t\tserializeJson: config.serializeJson,\n\t\t\tdeserializeJson: config.deserializeJson,\n\t\t};\n\t\tthis.socka = new SockaWebSocketSession(\n\t\t\twebsocket,\n\t\t\tsessions as unknown as Map<\n\t\t\t\tWebSocket,\n\t\t\t\tSockaWebSocketSession<TContract, EmptySockaSessionData>\n\t\t\t>,\n\t\t\tsockaConfig,\n\t\t);\n\t\t// Defer past the outer `await createSession()` continuation so\n\t\t// `BaseSession.startFresh` has run and `session.data` exists (single\n\t\t// `queueMicrotask` runs before that continuation).\n\t\tqueueMicrotask(() => {\n\t\t\tqueueMicrotask(() => {\n\t\t\t\trunSockaDoSessionOnAttached(config, this);\n\t\t\t});\n\t\t});\n\t}\n\n\tpublic async handleRawMessage(rawMessage: string): Promise<void> {\n\t\treturn this.socka.handleRawMessage(rawMessage);\n\t}\n\n\tpublic emitWireEvent(event: string, body: unknown): void {\n\t\tthis.socka.emitWireEvent(event, body);\n\t}\n\n\tpublic emitPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t): Promise<void> {\n\t\treturn this.socka.emitPush(name, body);\n\t}\n\n\tpublic broadcastPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t\texcludeSelf = false,\n\t): Promise<void> {\n\t\treturn this.socka.broadcastPush(name, body, excludeSelf);\n\t}\n}\n","import type { Context } from \"hono\";\nimport type { SessionEnv } from \"@firtoz/websocket-do\";\nimport { BaseWebSocketDO } from \"@firtoz/websocket-do\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport type { SockaDoSession } from \"./SockaDoSession\";\n\nexport type SockaWebSocketDOOptions<\n\tTEnv extends object,\n\t// `any` contract slot: concrete sessions use `defineSocka` contracts that do\n\t// not assign to `SockaContract<SockaContractConfig>` under strict generics.\n\tTSession extends SockaDoSession<\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\tTEnv\n\t>,\n> = {\n\tcreateSockaSession: (\n\t\tctx: Context<{ Bindings: TEnv }> | undefined,\n\t\twebsocket: WebSocket,\n\t) => TSession | Promise<TSession>;\n};\n\n/**\n * Durable Object base class for WebSocket apps using {@link SockaDoSession}\n * (Standard Schema contract-driven).\n */\nexport abstract class SockaWebSocketDO<\n\tTSession extends SockaDoSession<\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany\n\t> = SockaDoSession<\n\t\tSockaContract<SockaContractConfig>,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany\n\t>,\n\tTEnv extends SessionEnv<TSession> = SessionEnv<TSession>,\n> extends BaseWebSocketDO<TSession, TEnv> {\n\tconstructor(\n\t\tctx: DurableObjectState,\n\t\tenv: TEnv,\n\t\toptions: SockaWebSocketDOOptions<TEnv, TSession>,\n\t) {\n\t\tsuper(ctx, env, {\n\t\t\tcreateSession: (sessionCtx, websocket) =>\n\t\t\t\toptions.createSockaSession(sessionCtx, websocket),\n\t\t});\n\t}\n}\n","/**\n * Shared error reply shape for correlate-by-id RPC when the handler throws.\n * Narrow at the call site to your server message union if needed.\n */\nexport function toErrorReply(\n\tid: string,\n\terror: string,\n): {\n\ttype: \"error\";\n\tid: string;\n\terror: string;\n} {\n\treturn { type: \"error\", id, error };\n}\n"]}
1
+ {"version":3,"sources":["../../src/do/SockaDoSession.ts","../../src/do/SockaWebSocketDO.ts","../../src/do/dispatch.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Context } from \"hono\";\nimport { BaseSession } from \"@firtoz/websocket-do\";\nimport type {\n\tInferSockaPushPayload,\n\tSockaContract,\n\tSockaContractConfig,\n\tInferSockaHandlers,\n} from \"../core/contract\";\nimport {\n\tSockaWebSocketSession,\n\ttype SockaPushSession,\n\ttype SockaWebSocketSessionConfigLoose,\n} from \"../server/SockaWebSocketSession\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport type { SockaReportError } from \"../core/socka-report-error\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\n\n/** Session data with no fields — `createData` may be omitted (defaults to `{}`). */\ntype EmptySockaSessionData = Record<string, never>;\n\ntype SockaDoOuterSession<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTEnv extends object,\n> = import(\"./SockaDoSession\").SockaDoSession<TContract, TData, TEnv>;\n\ntype SockaDoSessionCreateData<TData, TEnv extends object> = [TData] extends [\n\tEmptySockaSessionData,\n]\n\t? {\n\t\t\tcreateData?: (ctx: Context<{ Bindings: TEnv }>) => TData;\n\t\t}\n\t: {\n\t\t\tcreateData: (ctx: Context<{ Bindings: TEnv }>) => TData;\n\t\t};\n\nexport type SockaDoSessionConfig<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTEnv extends object,\n> = {\n\tcontract: TContract;\n\t/** Default `\"json\"`. Use `\"msgpack\"` for binary frames (must match client). */\n\twireFormat?: SockaWireFormat;\n\thandlers: InferSockaHandlers<\n\t\tTContract,\n\t\tSockaDoOuterSession<TContract, TData, TEnv>\n\t>;\n\thandleClose: (\n\t\tsession: SockaDoOuterSession<TContract, TData, TEnv>,\n\t) => Promise<void>;\n\tonHandlerError?: (\n\t\terror: unknown,\n\t\trpcName: string,\n\t\tinput: unknown,\n\t\tsession: SockaDoOuterSession<TContract, TData, TEnv>,\n\t) => void;\n\tonValidationError?: (\n\t\terror: unknown,\n\t\toriginalMessage: unknown,\n\t) => Promise<void>;\n\t/**\n\t * Optional sink for non-RPC failures (e.g. `onAttached`). Defaults to\n\t * `console.error`; see `SockaReportError` in `@firtoz/socka/core`.\n\t */\n\treportError?: (event: SockaReportError) => void;\n\t/**\n\t * Called after this session is registered in the DO `sessions` map (next\n\t * microtask). Use for join broadcasts and other logic that must run only\n\t * when peers can see this connection.\n\t */\n\tonAttached?: (\n\t\tsession: SockaDoOuterSession<TContract, TData, TEnv>,\n\t) => void | Promise<void>;\n\tserializeJson?: (value: unknown) => string;\n\tdeserializeJson?: (raw: string) => unknown;\n} & SockaDoSessionCreateData<TData, TEnv>;\n\nfunction runSockaDoSessionOnAttached<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTEnv extends object,\n>(\n\tconfig: SockaDoSessionConfig<TContract, TData, TEnv>,\n\tsession: SockaDoSession<TContract, TData, TEnv>,\n): void {\n\tconst cb = config.onAttached;\n\tif (!cb) return;\n\ttry {\n\t\tconst result = cb(session);\n\t\tvoid Promise.resolve(result).catch((error: unknown) => {\n\t\t\treportSockaError(config.reportError, {\n\t\t\t\tkind: \"serverOnAttached\",\n\t\t\t\terror,\n\t\t\t});\n\t\t});\n\t} catch (error) {\n\t\treportSockaError(config.reportError, { kind: \"serverOnAttached\", error });\n\t}\n}\n\nfunction wrapHandlersForInnerSockaEngine<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTEnv extends object,\n>(\n\tcontract: TContract,\n\tuserHandlers: InferSockaHandlers<\n\t\tTContract,\n\t\tSockaDoSession<TContract, TData, TEnv>\n\t>,\n\touter: SockaDoSession<TContract, TData, TEnv>,\n): InferSockaHandlers<\n\tTContract,\n\tSockaWebSocketSession<TContract, EmptySockaSessionData>\n> {\n\tconst calls = contract.calls;\n\tconst out: Record<\n\t\tstring,\n\t\t| ((\n\t\t\t\tinput: unknown,\n\t\t\t\tinner: SockaWebSocketSession<TContract, EmptySockaSessionData>,\n\t\t ) => unknown | Promise<unknown>)\n\t\t| ((\n\t\t\t\tinner: SockaWebSocketSession<TContract, EmptySockaSessionData>,\n\t\t ) => unknown | Promise<unknown>)\n\t> = {};\n\n\tfor (const key of Object.keys(calls) as Array<keyof typeof calls & string>) {\n\t\tconst proc = calls[key];\n\t\tconst userFn = userHandlers[key as keyof typeof userHandlers];\n\t\tif (proc.input) {\n\t\t\tout[key] = (\n\t\t\t\tinput,\n\t\t\t\t_inner: SockaWebSocketSession<TContract, EmptySockaSessionData>,\n\t\t\t) =>\n\t\t\t\t(\n\t\t\t\t\tuserFn as (\n\t\t\t\t\t\ti: unknown,\n\t\t\t\t\t\ts: SockaDoSession<TContract, TData, TEnv>,\n\t\t\t\t\t) => unknown | Promise<unknown>\n\t\t\t\t)(input, outer);\n\t\t} else {\n\t\t\tout[key] = (\n\t\t\t\t_inner: SockaWebSocketSession<TContract, EmptySockaSessionData>,\n\t\t\t) =>\n\t\t\t\t(\n\t\t\t\t\tuserFn as (\n\t\t\t\t\t\ts: SockaDoSession<TContract, TData, TEnv>,\n\t\t\t\t\t) => unknown | Promise<unknown>\n\t\t\t\t)(outer);\n\t\t}\n\t}\n\n\treturn out as InferSockaHandlers<\n\t\tTContract,\n\t\tSockaWebSocketSession<TContract, EmptySockaSessionData>\n\t>;\n}\n\n/**\n * Durable Object WebSocket session driven by a socka contract.\n * Dispatches client requests to typed handler functions, validates\n * input/output via Standard Schema, and auto-sends response/error frames.\n */\nexport class SockaDoSession<\n\t\tTContract extends SockaContract<SockaContractConfig>,\n\t\tTData = EmptySockaSessionData,\n\t\tTEnv extends object = Cloudflare.Env,\n\t>\n\textends BaseSession<TData, unknown, unknown, TEnv>\n\timplements SockaPushSession<TContract>\n{\n\tprivate socka!: SockaWebSocketSession<TContract, EmptySockaSessionData>;\n\n\tconstructor(\n\t\twebsocket: WebSocket,\n\t\tsessions: Map<WebSocket, SockaDoSession<TContract, TData, TEnv>>,\n\t\tconfig: SockaDoSessionConfig<TContract, TData, TEnv>,\n\t) {\n\t\tconst wireFormat = config.wireFormat ?? \"json\";\n\t\tsuper(\n\t\t\twebsocket,\n\t\t\tsessions as Map<WebSocket, BaseSession<TData, unknown, unknown, TEnv>>,\n\t\t\t{\n\t\t\t\tcreateData:\n\t\t\t\t\tconfig.createData ??\n\t\t\t\t\t((_ctx: Context<{ Bindings: TEnv }>) => ({}) as TData),\n\t\t\t\thandleMessage: async () => {\n\t\t\t\t\t// Raw message handling goes through handleRawMessage / handleBufferMessage\n\t\t\t\t},\n\t\t\t\thandleBufferMessage: async (message) => {\n\t\t\t\t\tawait this.socka.handleBinaryMessage(message);\n\t\t\t\t},\n\t\t\t\thandleClose: async (baseSession) => {\n\t\t\t\t\tawait config.handleClose(\n\t\t\t\t\t\tbaseSession as SockaDoSession<TContract, TData, TEnv>,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t\tconst sockaConfig: SockaWebSocketSessionConfigLoose<\n\t\t\tTContract,\n\t\t\tEmptySockaSessionData\n\t\t> = {\n\t\t\tstrictUpgradeRequest: false,\n\t\t\tcontract: config.contract,\n\t\t\twireFormat,\n\t\t\thandlers: wrapHandlersForInnerSockaEngine(\n\t\t\t\tconfig.contract,\n\t\t\t\tconfig.handlers,\n\t\t\t\tthis,\n\t\t\t),\n\t\t\thandleClose: async () => {\n\t\t\t\t// Outer DO lifecycle uses SockaDoSessionConfig.handleClose; inner engine no-op.\n\t\t\t},\n\t\t\tonHandlerError: config.onHandlerError\n\t\t\t\t? (err, rpcName, input, _inner) => {\n\t\t\t\t\t\tconfig.onHandlerError?.(err, rpcName, input, this);\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t\t\tonValidationError: config.onValidationError,\n\t\t\tserializeJson: config.serializeJson,\n\t\t\tdeserializeJson: config.deserializeJson,\n\t\t};\n\t\tthis.socka = new SockaWebSocketSession(\n\t\t\twebsocket,\n\t\t\tsessions as unknown as Map<\n\t\t\t\tWebSocket,\n\t\t\t\tSockaWebSocketSession<TContract, EmptySockaSessionData>\n\t\t\t>,\n\t\t\tsockaConfig,\n\t\t);\n\t\t// Defer past the outer `await createSession()` continuation so\n\t\t// `BaseSession.startFresh` has run and `session.data` exists (single\n\t\t// `queueMicrotask` runs before that continuation).\n\t\tqueueMicrotask(() => {\n\t\t\tqueueMicrotask(() => {\n\t\t\t\trunSockaDoSessionOnAttached(config, this);\n\t\t\t});\n\t\t});\n\t}\n\n\tpublic async handleRawMessage(rawMessage: string): Promise<void> {\n\t\treturn this.socka.handleRawMessage(rawMessage);\n\t}\n\n\tpublic emitWireEvent(event: string, body: unknown): void {\n\t\tthis.socka.emitWireEvent(event, body);\n\t}\n\n\tpublic emitPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t): Promise<void> {\n\t\treturn this.socka.emitPush(name, body);\n\t}\n\n\tpublic broadcastPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t\texcludeSelf = false,\n\t): Promise<void> {\n\t\treturn this.socka.broadcastPush(name, body, excludeSelf);\n\t}\n\n\t/**\n\t * {@link SockaWebSocketSession.listPeers} for this Durable Object room.\n\t */\n\tpublic listPeers(options?: { excludeSelf?: boolean }): TData[] {\n\t\tconst out: TData[] = [];\n\t\tfor (const [ws, s] of this.sessions) {\n\t\t\tif (options?.excludeSelf && ws === this.websocket) continue;\n\t\t\tout.push(s.data);\n\t\t}\n\t\treturn out;\n\t}\n\n\t/**\n\t * Like {@link listPeers} but maps each peer {@link SockaDoSession}.\n\t */\n\tpublic listPeersWith<R>(\n\t\tmap: (session: SockaDoSession<TContract, TData, TEnv>) => R,\n\t\toptions?: { excludeSelf?: boolean },\n\t): R[] {\n\t\tconst out: R[] = [];\n\t\tfor (const [ws, s] of this.sessions) {\n\t\t\tif (options?.excludeSelf && ws === this.websocket) continue;\n\t\t\tout.push(map(s as SockaDoSession<TContract, TData, TEnv>));\n\t\t}\n\t\treturn out;\n\t}\n\n\tpublic peerCount(options?: { excludeSelf?: boolean }): number {\n\t\tlet n = 0;\n\t\tfor (const [ws] of this.sessions) {\n\t\t\tif (options?.excludeSelf && ws === this.websocket) continue;\n\t\t\tn += 1;\n\t\t}\n\t\treturn n;\n\t}\n\n\tpublic hasPeers(options?: { excludeSelf?: boolean }): boolean {\n\t\treturn this.peerCount(options) > 0;\n\t}\n}\n","import type { Context } from \"hono\";\nimport type { SessionEnv } from \"@firtoz/websocket-do\";\nimport { BaseWebSocketDO } from \"@firtoz/websocket-do\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport type { SockaDoSession } from \"./SockaDoSession\";\n\nexport type SockaWebSocketDOOptions<\n\tTEnv extends object,\n\t// `any` contract slot: concrete sessions use `defineSocka` contracts that do\n\t// not assign to `SockaContract<SockaContractConfig>` under strict generics.\n\tTSession extends SockaDoSession<\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\tTEnv\n\t>,\n> = {\n\tcreateSockaSession: (\n\t\tctx: Context<{ Bindings: TEnv }> | undefined,\n\t\twebsocket: WebSocket,\n\t) => TSession | Promise<TSession>;\n};\n\n/**\n * Durable Object base class for WebSocket apps using {@link SockaDoSession}\n * (Standard Schema contract-driven).\n */\nexport abstract class SockaWebSocketDO<\n\tTSession extends SockaDoSession<\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany\n\t> = SockaDoSession<\n\t\tSockaContract<SockaContractConfig>,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany\n\t>,\n\tTEnv extends SessionEnv<TSession> = SessionEnv<TSession>,\n> extends BaseWebSocketDO<TSession, TEnv> {\n\tconstructor(\n\t\tctx: DurableObjectState,\n\t\tenv: TEnv,\n\t\toptions: SockaWebSocketDOOptions<TEnv, TSession>,\n\t) {\n\t\tsuper(ctx, env, {\n\t\t\tcreateSession: (sessionCtx, websocket) =>\n\t\t\t\toptions.createSockaSession(sessionCtx, websocket),\n\t\t});\n\t}\n}\n","/**\n * Shared error reply shape for correlate-by-id RPC when the handler throws.\n * Narrow at the call site to your server message union if needed.\n */\nexport function toErrorReply(\n\tid: string,\n\terror: string,\n): {\n\ttype: \"error\";\n\tid: string;\n\terror: string;\n} {\n\treturn { type: \"error\", id, error };\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { Context } from 'hono';
2
2
  import { WSEvents } from 'hono/ws';
3
- import { S as SockaContract, a as SockaContractConfig } from '../socka-report-error-DzFI2Tr7.js';
4
- import { S as SockaWebSocketSession, b as SockaWebSocketInit, a as SockaWebSocketSessionConfig } from '../SockaWebSocketSession-Bru8yFcK.js';
3
+ import { S as SockaContract, a as SockaContractConfig } from '../socka-report-error-CXwpAUgl.js';
4
+ import { a as SockaWebSocketSession, c as SockaWebSocketInit, b as SockaWebSocketSessionConfigUnion } from '../SockaWebSocketSession-B1w7RAid.js';
5
5
  import '@standard-schema/spec';
6
6
 
7
7
  type SockaHonoCloudflareOptions<TContract extends SockaContract<SockaContractConfig>, TData> = {
@@ -9,13 +9,13 @@ type SockaHonoCloudflareOptions<TContract extends SockaContract<SockaContractCon
9
9
  sockaInit?: (c: Context) => SockaWebSocketInit | undefined;
10
10
  resolveScope?: (c: Context) => {
11
11
  sessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;
12
- config: SockaWebSocketSessionConfig<TContract, TData>;
12
+ config: SockaWebSocketSessionConfigUnion<TContract, TData>;
13
13
  };
14
14
  };
15
15
  /**
16
16
  * Callback for `upgradeWebSocket` from `hono/cloudflare-workers` (no `onOpen`;
17
17
  * the session is created on first `onMessage`).
18
18
  */
19
- declare function sockaHonoCloudflare<TContract extends SockaContract<SockaContractConfig>, TData>(config: SockaWebSocketSessionConfig<TContract, TData>, options?: SockaHonoCloudflareOptions<TContract, TData>): (c: Context) => Omit<WSEvents<WebSocket>, "onOpen">;
19
+ declare function sockaHonoCloudflare<TContract extends SockaContract<SockaContractConfig>, TData>(config: SockaWebSocketSessionConfigUnion<TContract, TData>, options?: SockaHonoCloudflareOptions<TContract, TData>): (c: Context) => Omit<WSEvents<WebSocket>, "onOpen">;
20
20
 
21
21
  export { type SockaHonoCloudflareOptions, sockaHonoCloudflare };
@@ -1,6 +1,7 @@
1
+ import { sockaHonoStrictInitFromContext } from '../chunk-QGURL3DJ.js';
1
2
  import { dispatchSockaInboundMessage } from '../chunk-5WQTYLIC.js';
2
- import { SockaWebSocketSession, runSockaSessionOnAttached } from '../chunk-45D4T232.js';
3
- import { reportSockaError } from '../chunk-MZCQHJXY.js';
3
+ import { SockaWebSocketSession, runSockaSessionOnAttached } from '../chunk-LVVCHLNW.js';
4
+ import { reportSockaError } from '../chunk-IFIGKR3W.js';
4
5
 
5
6
  // src/hono/cloudflare-workers.ts
6
7
  function sockaHonoCloudflare(config, options) {
@@ -16,7 +17,7 @@ function sockaHonoCloudflare(config, options) {
16
17
  let session = sessions.get(domWs);
17
18
  const cfg = scopeConfig;
18
19
  if (!session) {
19
- const init = options?.sockaInit?.(c);
20
+ const init = options?.sockaInit?.(c) ?? sockaHonoStrictInitFromContext(c);
20
21
  session = new SockaWebSocketSession(domWs, sessions, cfg, init);
21
22
  sessions.set(domWs, session);
22
23
  runSockaSessionOnAttached(cfg, session);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hono/cloudflare-workers.ts"],"names":[],"mappings":";;;;;AA6BO,SAAS,mBAAA,CAIf,QACA,OAAA,EACsD;AACtD,EAAA,MAAM,cAAA,GACL,OAAA,EAAS,QAAA,oBACT,IAAI,GAAA,EAAwD;AAC7D,EAAA,MAAM,YAAA,GAAe,MAAA;AACrB,EAAA,MAAM,eAAe,OAAA,EAAS,YAAA;AAE9B,EAAA,OAAO,CAAC,CAAA,MAAgB;AAAA,IACvB,SAAA,CAAU,KAAK,KAAA,EAAO;AACrB,MAAA,MAAM,MAAM,KAAA,CAAM,GAAA;AAClB,MAAA,IAAI,CAAC,GAAA,EAAK;AACV,MAAA,MAAM,KAAA,GAAQ,GAAA;AACd,MAAA,MAAM,EAAE,QAAA,EAAU,MAAA,EAAQ,WAAA,EAAY,GAAI,YAAA,GACvC,YAAA,CAAa,CAAC,CAAA,GACd,EAAE,QAAA,EAAU,cAAA,EAAgB,QAAQ,YAAA,EAAa;AACpD,MAAA,IAAI,OAAA,GAAU,QAAA,CAAS,GAAA,CAAI,KAAK,CAAA;AAChC,MAAA,MAAM,GAAA,GAAM,WAAA;AACZ,MAAA,IAAI,CAAC,OAAA,EAAS;AACb,QAAA,MAAM,IAAA,GAAO,OAAA,EAAS,SAAA,GAAY,CAAC,CAAA;AACnC,QAAA,OAAA,GAAU,IAAI,qBAAA,CAAsB,KAAA,EAAO,QAAA,EAAU,KAAK,IAAI,CAAA;AAC9D,QAAA,QAAA,CAAS,GAAA,CAAI,OAAO,OAAO,CAAA;AAC3B,QAAA,yBAAA,CAA0B,KAAK,OAAO,CAAA;AAAA,MACvC;AACA,MAAA,MAAM,UAAA,GAA8B,IAAI,UAAA,IAAc,MAAA;AACtD,MAAA,KAAK,2BAAA,CAA4B,OAAA,EAAS,UAAA,EAAY,GAAA,CAAI,IAAI,CAAA,CAAE,KAAA;AAAA,QAC/D,CAAC,KAAA,KAAmB;AACnB,UAAA,gBAAA,CAAiB,IAAI,WAAA,EAAa;AAAA,YACjC,IAAA,EAAM,sBAAA;AAAA,YACN,OAAA,EAAS,MAAA;AAAA,YACT;AAAA,WACA,CAAA;AAAA,QACF;AAAA,OACD;AAAA,IACD,CAAA;AAAA,IACA,OAAA,CAAQ,MAAM,KAAA,EAAO;AACpB,MAAA,MAAM,MAAM,KAAA,CAAM,GAAA;AAClB,MAAA,IAAI,CAAC,GAAA,EAAK;AACV,MAAA,MAAM,KAAA,GAAQ,GAAA;AACd,MAAA,KAAA,CAAM,YAA2B;AAChC,QAAA,MAAM,EAAE,QAAA,EAAU,MAAA,EAAQ,WAAA,EAAY,GAAI,YAAA,GACvC,YAAA,CAAa,CAAC,CAAA,GACd,EAAE,QAAA,EAAU,cAAA,EAAgB,QAAQ,YAAA,EAAa;AACpD,QAAA,MAAM,GAAA,GAAM,WAAA;AAIZ,QAAA,MAAM,OAAA,GAAU,QAAA,CAAS,GAAA,CAAI,KAAK,CAAA;AAClC,QAAA,IAAI;AACH,UAAA,IAAI,OAAA,EAAS;AACZ,YAAA,MAAM,QAAQ,iBAAA,EAAkB;AAAA,UACjC;AAAA,QACD,SAAS,KAAA,EAAO;AACf,UAAA,gBAAA,CAAiB,IAAI,WAAA,EAAa;AAAA,YACjC,IAAA,EAAM,mBAAA;AAAA,YACN;AAAA,WACA,CAAA;AAAA,QACF,CAAA,SAAE;AACD,UAAA,QAAA,CAAS,OAAO,KAAK,CAAA;AAAA,QACtB;AAAA,MACD,CAAA,GAAG,CAAE,KAAA,CAAM,CAAC,KAAA,KAAmB;AAC9B,QAAA,gBAAA,CAAiB,aAAa,WAAA,EAAa;AAAA,UAC1C,IAAA,EAAM,gBAAA;AAAA,UACN,OAAA,EAAS,MAAA;AAAA,UACT;AAAA,SACA,CAAA;AAAA,MACF,CAAC,CAAA;AAAA,IACF;AAAA,GACD,CAAA;AACD","file":"cloudflare-workers.js","sourcesContent":["import type { Context } from \"hono\";\nimport type { WSEvents } from \"hono/ws\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\nimport { dispatchSockaInboundMessage } from \"../server/dispatchSockaInboundMessage\";\nimport {\n\tSockaWebSocketSession,\n\trunSockaSessionOnAttached,\n\ttype SockaWebSocketInit,\n\ttype SockaWebSocketSessionConfig,\n} from \"../server/SockaWebSocketSession\";\n\nexport type SockaHonoCloudflareOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n> = {\n\tsessions?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\tsockaInit?: (c: Context) => SockaWebSocketInit | undefined;\n\tresolveScope?: (c: Context) => {\n\t\tsessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t\tconfig: SockaWebSocketSessionConfig<TContract, TData>;\n\t};\n};\n\n/**\n * Callback for `upgradeWebSocket` from `hono/cloudflare-workers` (no `onOpen`;\n * the session is created on first `onMessage`).\n */\nexport function sockaHonoCloudflare<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>,\n\toptions?: SockaHonoCloudflareOptions<TContract, TData>,\n): (c: Context) => Omit<WSEvents<WebSocket>, \"onOpen\"> {\n\tconst staticSessions =\n\t\toptions?.sessions ??\n\t\tnew Map<WebSocket, SockaWebSocketSession<TContract, TData>>();\n\tconst staticConfig = config;\n\tconst resolveScope = options?.resolveScope;\n\n\treturn (c: Context) => ({\n\t\tonMessage(evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as WebSocket;\n\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t? resolveScope(c)\n\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\tlet session = sessions.get(domWs);\n\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfig<TContract, TData>;\n\t\t\tif (!session) {\n\t\t\t\tconst init = options?.sockaInit?.(c);\n\t\t\t\tsession = new SockaWebSocketSession(domWs, sessions, cfg, init);\n\t\t\t\tsessions.set(domWs, session);\n\t\t\t\trunSockaSessionOnAttached(cfg, session);\n\t\t\t}\n\t\t\tconst wireFormat: SockaWireFormat = cfg.wireFormat ?? \"json\";\n\t\t\tvoid dispatchSockaInboundMessage(session, wireFormat, evt.data).catch(\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\treportSockaError(cfg.reportError, {\n\t\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\t\tadapter: \"hono\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tonClose(_evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as WebSocket;\n\t\t\tvoid (async (): Promise<void> => {\n\t\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t\t? resolveScope(c)\n\t\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfig<\n\t\t\t\t\tTContract,\n\t\t\t\t\tTData\n\t\t\t\t>;\n\t\t\t\tconst session = sessions.get(domWs);\n\t\t\t\ttry {\n\t\t\t\t\tif (session) {\n\t\t\t\t\t\tawait session.invokeHandleClose();\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\treportSockaError(cfg.reportError, {\n\t\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tsessions.delete(domWs);\n\t\t\t\t}\n\t\t\t})().catch((error: unknown) => {\n\t\t\t\treportSockaError(staticConfig.reportError, {\n\t\t\t\t\tkind: \"serverShutdown\",\n\t\t\t\t\tadapter: \"hono\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"sources":["../../src/hono/cloudflare-workers.ts"],"names":[],"mappings":";;;;;;AA8BO,SAAS,mBAAA,CAIf,QACA,OAAA,EACsD;AACtD,EAAA,MAAM,cAAA,GACL,OAAA,EAAS,QAAA,oBACT,IAAI,GAAA,EAAwD;AAC7D,EAAA,MAAM,YAAA,GAAe,MAAA;AACrB,EAAA,MAAM,eAAe,OAAA,EAAS,YAAA;AAE9B,EAAA,OAAO,CAAC,CAAA,MAAgB;AAAA,IACvB,SAAA,CAAU,KAAK,KAAA,EAAO;AACrB,MAAA,MAAM,MAAM,KAAA,CAAM,GAAA;AAClB,MAAA,IAAI,CAAC,GAAA,EAAK;AACV,MAAA,MAAM,KAAA,GAAQ,GAAA;AACd,MAAA,MAAM,EAAE,QAAA,EAAU,MAAA,EAAQ,WAAA,EAAY,GAAI,YAAA,GACvC,YAAA,CAAa,CAAC,CAAA,GACd,EAAE,QAAA,EAAU,cAAA,EAAgB,QAAQ,YAAA,EAAa;AACpD,MAAA,IAAI,OAAA,GAAU,QAAA,CAAS,GAAA,CAAI,KAAK,CAAA;AAChC,MAAA,MAAM,GAAA,GAAM,WAAA;AAIZ,MAAA,IAAI,CAAC,OAAA,EAAS;AACb,QAAA,MAAM,OACL,OAAA,EAAS,SAAA,GAAY,CAAC,CAAA,IAAK,+BAA+B,CAAC,CAAA;AAC5D,QAAA,OAAA,GAAU,IAAI,qBAAA,CAAsB,KAAA,EAAO,QAAA,EAAU,KAAK,IAAI,CAAA;AAC9D,QAAA,QAAA,CAAS,GAAA,CAAI,OAAO,OAAO,CAAA;AAC3B,QAAA,yBAAA,CAA0B,KAAK,OAAO,CAAA;AAAA,MACvC;AACA,MAAA,MAAM,UAAA,GAA8B,IAAI,UAAA,IAAc,MAAA;AACtD,MAAA,KAAK,2BAAA,CAA4B,OAAA,EAAS,UAAA,EAAY,GAAA,CAAI,IAAI,CAAA,CAAE,KAAA;AAAA,QAC/D,CAAC,KAAA,KAAmB;AACnB,UAAA,gBAAA,CAAiB,IAAI,WAAA,EAAa;AAAA,YACjC,IAAA,EAAM,sBAAA;AAAA,YACN,OAAA,EAAS,MAAA;AAAA,YACT;AAAA,WACA,CAAA;AAAA,QACF;AAAA,OACD;AAAA,IACD,CAAA;AAAA,IACA,OAAA,CAAQ,MAAM,KAAA,EAAO;AACpB,MAAA,MAAM,MAAM,KAAA,CAAM,GAAA;AAClB,MAAA,IAAI,CAAC,GAAA,EAAK;AACV,MAAA,MAAM,KAAA,GAAQ,GAAA;AACd,MAAA,KAAA,CAAM,YAA2B;AAChC,QAAA,MAAM,EAAE,QAAA,EAAU,MAAA,EAAQ,WAAA,EAAY,GAAI,YAAA,GACvC,YAAA,CAAa,CAAC,CAAA,GACd,EAAE,QAAA,EAAU,cAAA,EAAgB,QAAQ,YAAA,EAAa;AACpD,QAAA,MAAM,GAAA,GAAM,WAAA;AAIZ,QAAA,MAAM,OAAA,GAAU,QAAA,CAAS,GAAA,CAAI,KAAK,CAAA;AAClC,QAAA,IAAI;AACH,UAAA,IAAI,OAAA,EAAS;AACZ,YAAA,MAAM,QAAQ,iBAAA,EAAkB;AAAA,UACjC;AAAA,QACD,SAAS,KAAA,EAAO;AACf,UAAA,gBAAA,CAAiB,IAAI,WAAA,EAAa;AAAA,YACjC,IAAA,EAAM,mBAAA;AAAA,YACN;AAAA,WACA,CAAA;AAAA,QACF,CAAA,SAAE;AACD,UAAA,QAAA,CAAS,OAAO,KAAK,CAAA;AAAA,QACtB;AAAA,MACD,CAAA,GAAG,CAAE,KAAA,CAAM,CAAC,KAAA,KAAmB;AAC9B,QAAA,gBAAA,CAAiB,aAAa,WAAA,EAAa;AAAA,UAC1C,IAAA,EAAM,gBAAA;AAAA,UACN,OAAA,EAAS,MAAA;AAAA,UACT;AAAA,SACA,CAAA;AAAA,MACF,CAAC,CAAA;AAAA,IACF;AAAA,GACD,CAAA;AACD","file":"cloudflare-workers.js","sourcesContent":["import type { Context } from \"hono\";\nimport type { WSEvents } from \"hono/ws\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\nimport { dispatchSockaInboundMessage } from \"../server/dispatchSockaInboundMessage\";\nimport {\n\tSockaWebSocketSession,\n\trunSockaSessionOnAttached,\n\ttype SockaWebSocketInit,\n\ttype SockaWebSocketSessionConfigUnion,\n} from \"../server/SockaWebSocketSession\";\nimport { sockaHonoStrictInitFromContext } from \"./strict-init-context\";\n\nexport type SockaHonoCloudflareOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n> = {\n\tsessions?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\tsockaInit?: (c: Context) => SockaWebSocketInit | undefined;\n\tresolveScope?: (c: Context) => {\n\t\tsessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t\tconfig: SockaWebSocketSessionConfigUnion<TContract, TData>;\n\t};\n};\n\n/**\n * Callback for `upgradeWebSocket` from `hono/cloudflare-workers` (no `onOpen`;\n * the session is created on first `onMessage`).\n */\nexport function sockaHonoCloudflare<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfigUnion<TContract, TData>,\n\toptions?: SockaHonoCloudflareOptions<TContract, TData>,\n): (c: Context) => Omit<WSEvents<WebSocket>, \"onOpen\"> {\n\tconst staticSessions =\n\t\toptions?.sessions ??\n\t\tnew Map<WebSocket, SockaWebSocketSession<TContract, TData>>();\n\tconst staticConfig = config;\n\tconst resolveScope = options?.resolveScope;\n\n\treturn (c: Context) => ({\n\t\tonMessage(evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as WebSocket;\n\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t? resolveScope(c)\n\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\tlet session = sessions.get(domWs);\n\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfigUnion<\n\t\t\t\tTContract,\n\t\t\t\tTData\n\t\t\t>;\n\t\t\tif (!session) {\n\t\t\t\tconst init: SockaWebSocketInit | undefined =\n\t\t\t\t\toptions?.sockaInit?.(c) ?? sockaHonoStrictInitFromContext(c);\n\t\t\t\tsession = new SockaWebSocketSession(domWs, sessions, cfg, init);\n\t\t\t\tsessions.set(domWs, session);\n\t\t\t\trunSockaSessionOnAttached(cfg, session);\n\t\t\t}\n\t\t\tconst wireFormat: SockaWireFormat = cfg.wireFormat ?? \"json\";\n\t\t\tvoid dispatchSockaInboundMessage(session, wireFormat, evt.data).catch(\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\treportSockaError(cfg.reportError, {\n\t\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\t\tadapter: \"hono\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tonClose(_evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as WebSocket;\n\t\t\tvoid (async (): Promise<void> => {\n\t\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t\t? resolveScope(c)\n\t\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfigUnion<\n\t\t\t\t\tTContract,\n\t\t\t\t\tTData\n\t\t\t\t>;\n\t\t\t\tconst session = sessions.get(domWs);\n\t\t\t\ttry {\n\t\t\t\t\tif (session) {\n\t\t\t\t\t\tawait session.invokeHandleClose();\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\treportSockaError(cfg.reportError, {\n\t\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tsessions.delete(domWs);\n\t\t\t\t}\n\t\t\t})().catch((error: unknown) => {\n\t\t\t\treportSockaError(staticConfig.reportError, {\n\t\t\t\t\tkind: \"serverShutdown\",\n\t\t\t\t\tadapter: \"hono\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t});\n}\n"]}
@@ -1,14 +1,30 @@
1
1
  import { Context } from 'hono';
2
+ import { S as SockaStrictWebSocketInit, b as SockaWebSocketSessionConfigUnion, a as SockaWebSocketSession, c as SockaWebSocketInit } from '../SockaWebSocketSession-B1w7RAid.js';
2
3
  import { WSEvents } from 'hono/ws';
3
4
  import { WebSocket as WebSocket$1 } from 'ws';
4
- import { S as SockaContract, a as SockaContractConfig } from '../socka-report-error-DzFI2Tr7.js';
5
- import { a as SockaWebSocketSessionConfig, S as SockaWebSocketSession, b as SockaWebSocketInit } from '../SockaWebSocketSession-Bru8yFcK.js';
5
+ import { S as SockaContract, a as SockaContractConfig } from '../socka-report-error-CXwpAUgl.js';
6
6
  import '@standard-schema/spec';
7
7
 
8
+ /**
9
+ * Build {@link SockaStrictWebSocketInit} from a Hono {@link Context} by synthesizing a
10
+ * **`Request`** from **`c.req.url`** (method GET; URL matches the incoming upgrade).
11
+ *
12
+ * Used when **`sockaHonoNodeWs` / `sockaHonoCloudflare`** omit a custom **`sockaInit`** and
13
+ * strict upgrade is the default: **`createData`** then always receives
14
+ * **`init.request`** without you writing **`sockaInit: (ctx) => ({ request: new Request(ctx.req.url) })`** by hand.
15
+ *
16
+ * For full fidelity to the original upgrade (headers, method), pass your own **`sockaInit`**
17
+ * that forwards the real **`Request`** from your runtime instead of this helper.
18
+ */
19
+ declare function sockaHonoStrictInitFromContext(c: Context): SockaStrictWebSocketInit;
20
+
8
21
  type SockaHonoNodeWsOptions<TContract extends SockaContract<SockaContractConfig>, TData> = {
9
22
  /** Shared map; default is a new `Map`. */
10
23
  sessions?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;
11
- /** Per-upgrade init for `createData` (e.g. `{ request: c.req.raw }` when it is a `Request`). */
24
+ /**
25
+ * Per-upgrade init for `createData`. When omitted, defaults to
26
+ * {@link sockaHonoStrictInitFromContext} so `Request` is always available.
27
+ */
12
28
  sockaInit?: (c: Context) => SockaWebSocketInit | undefined;
13
29
  /**
14
30
  * Resolve the session map and config from this upgrade’s Hono context (e.g. multi-room
@@ -17,7 +33,7 @@ type SockaHonoNodeWsOptions<TContract extends SockaContract<SockaContractConfig>
17
33
  */
18
34
  resolveScope?: (c: Context) => {
19
35
  sessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;
20
- config: SockaWebSocketSessionConfig<TContract, TData>;
36
+ config: SockaWebSocketSessionConfigUnion<TContract, TData>;
21
37
  };
22
38
  };
23
39
  /**
@@ -25,6 +41,6 @@ type SockaHonoNodeWsOptions<TContract extends SockaContract<SockaContractConfig>
25
41
  * {@link https://github.com/honojs/middleware/tree/main/packages/node-ws @hono/node-ws}
26
42
  * `createNodeWebSocket({ app }).upgradeWebSocket`.
27
43
  */
28
- declare function sockaHonoNodeWs<TContract extends SockaContract<SockaContractConfig>, TData>(config: SockaWebSocketSessionConfig<TContract, TData>, options?: SockaHonoNodeWsOptions<TContract, TData>): (c: Context) => WSEvents<WebSocket$1>;
44
+ declare function sockaHonoNodeWs<TContract extends SockaContract<SockaContractConfig>, TData>(config: SockaWebSocketSessionConfigUnion<TContract, TData>, options?: SockaHonoNodeWsOptions<TContract, TData>): (c: Context) => WSEvents<WebSocket$1>;
29
45
 
30
- export { type SockaHonoNodeWsOptions, sockaHonoNodeWs };
46
+ export { type SockaHonoNodeWsOptions, sockaHonoNodeWs, sockaHonoStrictInitFromContext };
@@ -1,6 +1,8 @@
1
+ import { sockaHonoStrictInitFromContext } from '../chunk-QGURL3DJ.js';
2
+ export { sockaHonoStrictInitFromContext } from '../chunk-QGURL3DJ.js';
1
3
  import { dispatchSockaInboundMessage } from '../chunk-5WQTYLIC.js';
2
- import { SockaWebSocketSession, runSockaSessionOnAttached } from '../chunk-45D4T232.js';
3
- import { reportSockaError } from '../chunk-MZCQHJXY.js';
4
+ import { SockaWebSocketSession, runSockaSessionOnAttached } from '../chunk-LVVCHLNW.js';
5
+ import { reportSockaError } from '../chunk-IFIGKR3W.js';
4
6
 
5
7
  // src/hono/node-ws.ts
6
8
  function sockaHonoNodeWs(config, options) {
@@ -13,7 +15,7 @@ function sockaHonoNodeWs(config, options) {
13
15
  if (!raw) return;
14
16
  const domWs = raw;
15
17
  const { sessions, config: scopeConfig } = resolveScope ? resolveScope(c) : { sessions: staticSessions, config: staticConfig };
16
- const init = options?.sockaInit?.(c);
18
+ const init = options?.sockaInit?.(c) ?? sockaHonoStrictInitFromContext(c);
17
19
  const cfg = scopeConfig;
18
20
  const session = new SockaWebSocketSession(domWs, sessions, cfg, init);
19
21
  sessions.set(domWs, session);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hono/node-ws.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Context } from \"hono\";\nimport type { WSEvents } from \"hono/ws\";\nimport type { WebSocket as NodeWebSocket } from \"ws\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\nimport { dispatchSockaInboundMessage } from \"../server/dispatchSockaInboundMessage\";\nimport {\n\tSockaWebSocketSession,\n\trunSockaSessionOnAttached,\n\ttype SockaWebSocketInit,\n\ttype SockaWebSocketSessionConfig,\n} from \"../server/SockaWebSocketSession\";\n\nexport type SockaHonoNodeWsOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n> = {\n\t/** Shared map; default is a new `Map`. */\n\tsessions?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t/** Per-upgrade init for `createData` (e.g. `{ request: c.req.raw }` when it is a `Request`). */\n\tsockaInit?: (c: Context) => SockaWebSocketInit | undefined;\n\t/**\n\t * Resolve the session map and config from this upgrade’s Hono context (e.g. multi-room\n\t * from `c.req.param(\"roomId\")`). When set, overrides the outer `config` / static `sessions`\n\t * for each connection.\n\t */\n\tresolveScope?: (c: Context) => {\n\t\tsessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t\tconfig: SockaWebSocketSessionConfig<TContract, TData>;\n\t};\n};\n\n/**\n * Returns the callback passed to `upgradeWebSocket` from\n * {@link https://github.com/honojs/middleware/tree/main/packages/node-ws @hono/node-ws}\n * `createNodeWebSocket({ app }).upgradeWebSocket`.\n */\nexport function sockaHonoNodeWs<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>,\n\toptions?: SockaHonoNodeWsOptions<TContract, TData>,\n): (c: Context) => WSEvents<NodeWebSocket> {\n\tconst staticSessions =\n\t\toptions?.sessions ??\n\t\tnew Map<WebSocket, SockaWebSocketSession<TContract, TData>>();\n\tconst staticConfig = config;\n\tconst resolveScope = options?.resolveScope;\n\n\treturn (c: Context) => ({\n\t\tonOpen(_evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as unknown as WebSocket;\n\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t? resolveScope(c)\n\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\tconst init = options?.sockaInit?.(c);\n\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfig<TContract, TData>;\n\t\t\tconst session = new SockaWebSocketSession(domWs, sessions, cfg, init);\n\t\t\tsessions.set(domWs, session);\n\t\t\trunSockaSessionOnAttached(cfg, session);\n\t\t},\n\t\tonMessage(evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as unknown as WebSocket;\n\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t? resolveScope(c)\n\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\tconst session = sessions.get(domWs);\n\t\t\tif (!session) return;\n\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfig<TContract, TData>;\n\t\t\tconst wireFormat: SockaWireFormat = cfg.wireFormat ?? \"json\";\n\t\t\tvoid dispatchSockaInboundMessage(session, wireFormat, evt.data).catch(\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\treportSockaError(cfg.reportError, {\n\t\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\t\tadapter: \"hono\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tonClose(_evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as unknown as WebSocket;\n\t\t\tvoid (async (): Promise<void> => {\n\t\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t\t? resolveScope(c)\n\t\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfig<\n\t\t\t\t\tTContract,\n\t\t\t\t\tTData\n\t\t\t\t>;\n\t\t\t\tconst session = sessions.get(domWs);\n\t\t\t\ttry {\n\t\t\t\t\tif (session) {\n\t\t\t\t\t\tawait session.invokeHandleClose();\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\treportSockaError(cfg.reportError, {\n\t\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tsessions.delete(domWs);\n\t\t\t\t}\n\t\t\t})().catch((error: unknown) => {\n\t\t\t\treportSockaError(staticConfig.reportError, {\n\t\t\t\t\tkind: \"serverShutdown\",\n\t\t\t\t\tadapter: \"hono\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"sources":["../../src/hono/node-ws.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Context } from \"hono\";\nimport type { WSEvents } from \"hono/ws\";\nimport type { WebSocket as NodeWebSocket } from \"ws\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\nimport { dispatchSockaInboundMessage } from \"../server/dispatchSockaInboundMessage\";\nimport {\n\tSockaWebSocketSession,\n\trunSockaSessionOnAttached,\n\ttype SockaWebSocketInit,\n\ttype SockaWebSocketSessionConfigUnion,\n} from \"../server/SockaWebSocketSession\";\nimport { sockaHonoStrictInitFromContext } from \"./strict-init-context\";\n\nexport { sockaHonoStrictInitFromContext } from \"./strict-init-context\";\n\nexport type SockaHonoNodeWsOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n> = {\n\t/** Shared map; default is a new `Map`. */\n\tsessions?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t/**\n\t * Per-upgrade init for `createData`. When omitted, defaults to\n\t * {@link sockaHonoStrictInitFromContext} so `Request` is always available.\n\t */\n\tsockaInit?: (c: Context) => SockaWebSocketInit | undefined;\n\t/**\n\t * Resolve the session map and config from this upgrade’s Hono context (e.g. multi-room\n\t * from `c.req.param(\"roomId\")`). When set, overrides the outer `config` / static `sessions`\n\t * for each connection.\n\t */\n\tresolveScope?: (c: Context) => {\n\t\tsessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t\tconfig: SockaWebSocketSessionConfigUnion<TContract, TData>;\n\t};\n};\n\n/**\n * Returns the callback passed to `upgradeWebSocket` from\n * {@link https://github.com/honojs/middleware/tree/main/packages/node-ws @hono/node-ws}\n * `createNodeWebSocket({ app }).upgradeWebSocket`.\n */\nexport function sockaHonoNodeWs<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfigUnion<TContract, TData>,\n\toptions?: SockaHonoNodeWsOptions<TContract, TData>,\n): (c: Context) => WSEvents<NodeWebSocket> {\n\tconst staticSessions =\n\t\toptions?.sessions ??\n\t\tnew Map<WebSocket, SockaWebSocketSession<TContract, TData>>();\n\tconst staticConfig = config;\n\tconst resolveScope = options?.resolveScope;\n\n\treturn (c: Context) => ({\n\t\tonOpen(_evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as unknown as WebSocket;\n\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t? resolveScope(c)\n\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\tconst init: SockaWebSocketInit | undefined =\n\t\t\t\toptions?.sockaInit?.(c) ?? sockaHonoStrictInitFromContext(c);\n\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfigUnion<\n\t\t\t\tTContract,\n\t\t\t\tTData\n\t\t\t>;\n\t\t\tconst session = new SockaWebSocketSession(domWs, sessions, cfg, init);\n\t\t\tsessions.set(domWs, session);\n\t\t\trunSockaSessionOnAttached(cfg, session);\n\t\t},\n\t\tonMessage(evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as unknown as WebSocket;\n\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t? resolveScope(c)\n\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\tconst session = sessions.get(domWs);\n\t\t\tif (!session) return;\n\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfigUnion<\n\t\t\t\tTContract,\n\t\t\t\tTData\n\t\t\t>;\n\t\t\tconst wireFormat: SockaWireFormat = cfg.wireFormat ?? \"json\";\n\t\t\tvoid dispatchSockaInboundMessage(session, wireFormat, evt.data).catch(\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\treportSockaError(cfg.reportError, {\n\t\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\t\tadapter: \"hono\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tonClose(_evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as unknown as WebSocket;\n\t\t\tvoid (async (): Promise<void> => {\n\t\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t\t? resolveScope(c)\n\t\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfigUnion<\n\t\t\t\t\tTContract,\n\t\t\t\t\tTData\n\t\t\t\t>;\n\t\t\t\tconst session = sessions.get(domWs);\n\t\t\t\ttry {\n\t\t\t\t\tif (session) {\n\t\t\t\t\t\tawait session.invokeHandleClose();\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\treportSockaError(cfg.reportError, {\n\t\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tsessions.delete(domWs);\n\t\t\t\t}\n\t\t\t})().catch((error: unknown) => {\n\t\t\t\treportSockaError(staticConfig.reportError, {\n\t\t\t\t\tkind: \"serverShutdown\",\n\t\t\t\t\tadapter: \"hono\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t});\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { DependencyList, RefObject, ReactNode, ReactElement } from 'react';
2
- import { S as SockaContract, a as SockaContractConfig, f as InferSockaPushHandlers, I as InferSockaSend } from '../socka-report-error-DzFI2Tr7.js';
3
- import { SockaSessionOptions, SockaSession } from '../client/index.js';
2
+ import { S as SockaContract, a as SockaContractConfig, e as InferSockaPushHandlers, I as InferSockaSend, f as InferSockaPushPayload } from '../socka-report-error-CXwpAUgl.js';
3
+ import { SockaSessionOptions, SockaSession, SockaConnectionStatus } from '../client/index.js';
4
4
  import '@standard-schema/spec';
5
5
 
6
6
  /** Options for {@link useSocka}. */
@@ -12,6 +12,9 @@ type UseSockaOptions<TContract extends SockaContract<SockaContractConfig>> = Soc
12
12
  declare function useSocka<TContract extends SockaContract<SockaContractConfig>>(options: UseSockaOptions<TContract>, deps: DependencyList): {
13
13
  ready: boolean;
14
14
  sessionRef: RefObject<SockaSession<TContract> | null>;
15
+ status: SockaConnectionStatus;
16
+ reconnecting: boolean;
17
+ reconnectAttempt: number;
15
18
  };
16
19
 
17
20
  type UseSockaSessionOptions<TContract extends SockaContract<SockaContractConfig>> = Omit<UseSockaOptions<TContract>, "contract" | "pushHandlers"> & {
@@ -25,13 +28,43 @@ declare function createSockaSendProxyFromSession<TContract extends SockaContract
25
28
  /**
26
29
  * ```tsx
27
30
  * const { ready, send } = useSockaSession(myContract, { url }, deps);
28
- * await send.echo({ text: "hi" });
31
+ * await send.echo({ message: "hi" });
29
32
  * ```
30
33
  */
31
34
  declare function useSockaSession<TContract extends SockaContract<SockaContractConfig>>(contract: TContract, options: UseSockaSessionOptions<TContract>, deps: DependencyList): {
32
35
  ready: boolean;
33
36
  send: InferSockaSend<TContract>;
34
37
  sessionRef: RefObject<SockaSession<TContract> | null>;
38
+ status: SockaConnectionStatus;
39
+ reconnecting: boolean;
40
+ reconnectAttempt: number;
41
+ };
42
+
43
+ type SockaPresenceOptions<TContract extends SockaContract<SockaContractConfig>, TUser extends {
44
+ userId: string;
45
+ }, KJoin extends keyof TContract["pushes"] & string, KLeave extends keyof TContract["pushes"] & string> = {
46
+ snapshot: () => Promise<{
47
+ selfUserId: string;
48
+ users: TUser[];
49
+ }>;
50
+ joinPush: KJoin;
51
+ leavePush: KLeave;
52
+ mapJoinUser: (p: InferSockaPushPayload<TContract, KJoin>) => TUser;
53
+ mapLeaveUserId: (p: InferSockaPushPayload<TContract, KLeave>) => string;
54
+ /** Optional display order after each update (e.g. by `displayName`). */
55
+ sortUsers?: (a: TUser, b: TUser) => number;
56
+ };
57
+ /**
58
+ * Loads a presence snapshot RPC once, then merges **`joinPush`** / **`leavePush`** deltas.
59
+ * Pass the same **`deps`** you use for {@link useSocka} when room identity changes.
60
+ * Options are read from a ref so you do not need to memoize the **`options`** object.
61
+ */
62
+ declare function useSockaPresence<TContract extends SockaContract<SockaContractConfig>, TUser extends {
63
+ userId: string;
64
+ }, KJoin extends keyof TContract["pushes"] & string, KLeave extends keyof TContract["pushes"] & string>(sessionRef: RefObject<SockaSession<TContract> | null>, ready: boolean, options: SockaPresenceOptions<TContract, TUser, KJoin, KLeave>, deps: DependencyList): {
65
+ users: TUser[];
66
+ selfUserId: string | undefined;
67
+ loading: boolean;
35
68
  };
36
69
 
37
70
  type AnySockaContract = SockaContract<SockaContractConfig>;
@@ -44,6 +77,9 @@ type SockaSessionContextValue<TContract extends SockaContract<SockaContractConfi
44
77
  readonly contract: TContract;
45
78
  readonly ready: boolean;
46
79
  readonly sessionRef: RefObject<SockaSession<TContract> | null>;
80
+ readonly status: SockaConnectionStatus;
81
+ readonly reconnecting: boolean;
82
+ readonly reconnectAttempt: number;
47
83
  };
48
84
  type SockaSessionProviderProps<TContract extends SockaContract<SockaContractConfig>> = {
49
85
  readonly contract: TContract;
@@ -67,6 +103,9 @@ declare function useSockaSessionContext<TContract extends SockaContract<SockaCon
67
103
  ready: boolean;
68
104
  send: InferSockaSend<TContract>;
69
105
  sessionRef: RefObject<SockaSession<TContract> | null>;
106
+ status: SockaConnectionStatus;
107
+ reconnecting: boolean;
108
+ reconnectAttempt: number;
70
109
  };
71
110
 
72
- export { type SockaSessionContextValue, SockaSessionProvider, type SockaSessionProviderProps, type UseSockaOptions, type UseSockaSessionOptions, createSockaSendProxyFromSession, useSocka, useSockaSession, useSockaSessionContext };
111
+ export { type SockaPresenceOptions, type SockaSessionContextValue, SockaSessionProvider, type SockaSessionProviderProps, type UseSockaOptions, type UseSockaSessionOptions, createSockaSendProxyFromSession, useSocka, useSockaPresence, useSockaSession, useSockaSessionContext };
@@ -1,20 +1,29 @@
1
- import { SockaSession } from '../chunk-AM7PB26G.js';
1
+ import { SockaSession } from '../chunk-P3JEEOJL.js';
2
2
  import '../chunk-YMT4HAH7.js';
3
- import '../chunk-MZCQHJXY.js';
3
+ import '../chunk-IFIGKR3W.js';
4
4
  import { createContext, useRef, useState, useEffect, useMemo, useContext } from 'react';
5
5
  import { jsx } from 'react/jsx-runtime';
6
6
 
7
7
  function useSocka(options, deps) {
8
- const { onOpen, onClose, ...restOptions } = options;
8
+ const { onOpen, onClose, onReconnecting, onReconnected, ...restOptions } = options;
9
9
  const onOpenRef = useRef(onOpen);
10
10
  onOpenRef.current = onOpen;
11
11
  const onCloseRef = useRef(onClose);
12
12
  onCloseRef.current = onClose;
13
+ const onReconnectingRef = useRef(onReconnecting);
14
+ onReconnectingRef.current = onReconnecting;
15
+ const onReconnectedRef = useRef(onReconnected);
16
+ onReconnectedRef.current = onReconnected;
13
17
  const [ready, setReady] = useState(false);
18
+ const [status, setStatus] = useState(
19
+ () => options.autoConnect === false ? "idle" : "connecting"
20
+ );
21
+ const [reconnectAttempt, setReconnectAttempt] = useState(0);
14
22
  const sessionRef = useRef(null);
15
23
  useEffect(() => {
16
24
  let cancelled = false;
17
25
  setReady(false);
26
+ setReconnectAttempt(0);
18
27
  const session = new SockaSession({
19
28
  ...restOptions,
20
29
  onOpen: (event) => {
@@ -28,8 +37,19 @@ function useSocka(options, deps) {
28
37
  setReady(false);
29
38
  }
30
39
  onCloseRef.current?.(event);
40
+ },
41
+ onReconnecting: (info) => {
42
+ setReconnectAttempt(info.attempt);
43
+ onReconnectingRef.current?.(info);
44
+ },
45
+ onReconnected: (info) => {
46
+ setReconnectAttempt(0);
47
+ onReconnectedRef.current?.(info);
31
48
  }
32
49
  });
50
+ const unsubStatus = session.onStatusChange((s) => {
51
+ if (!cancelled) setStatus(s);
52
+ });
33
53
  sessionRef.current = session;
34
54
  void session.client.connect().then(
35
55
  () => {
@@ -42,12 +62,19 @@ function useSocka(options, deps) {
42
62
  );
43
63
  return () => {
44
64
  cancelled = true;
65
+ unsubStatus();
45
66
  sessionRef.current = null;
46
67
  session.rejectAllPending(new Error("WebSocket closed"));
47
68
  session.close();
48
69
  };
49
70
  }, deps);
50
- return { ready, sessionRef };
71
+ return {
72
+ ready,
73
+ sessionRef,
74
+ status,
75
+ reconnecting: status === "reconnecting",
76
+ reconnectAttempt
77
+ };
51
78
  }
52
79
 
53
80
  // src/react/useSockaSession.ts
@@ -69,7 +96,7 @@ function createSockaSendProxyFromSession(contract, sessionRef) {
69
96
  }
70
97
  function useSockaSession(contract, options, deps) {
71
98
  const { pushHandlers, ...sockaOpts } = options;
72
- const { ready, sessionRef } = useSocka(
99
+ const { ready, sessionRef, status, reconnecting, reconnectAttempt } = useSocka(
73
100
  {
74
101
  ...sockaOpts,
75
102
  contract,
@@ -81,7 +108,68 @@ function useSockaSession(contract, options, deps) {
81
108
  () => createSockaSendProxyFromSession(contract, sessionRef),
82
109
  [contract, sessionRef]
83
110
  );
84
- return { ready, send, sessionRef };
111
+ return {
112
+ ready,
113
+ send,
114
+ sessionRef,
115
+ status,
116
+ reconnecting,
117
+ reconnectAttempt
118
+ };
119
+ }
120
+ function useSockaPresence(sessionRef, ready, options, deps) {
121
+ const [users, setUsers] = useState([]);
122
+ const [selfUserId, setSelfUserId] = useState();
123
+ const [loading, setLoading] = useState(true);
124
+ const optionsRef = useRef(options);
125
+ optionsRef.current = options;
126
+ useEffect(() => {
127
+ if (!ready) {
128
+ setUsers([]);
129
+ setSelfUserId(void 0);
130
+ setLoading(true);
131
+ return;
132
+ }
133
+ const s = sessionRef.current;
134
+ if (!s) {
135
+ setLoading(false);
136
+ return;
137
+ }
138
+ const o = optionsRef.current;
139
+ let cancelled = false;
140
+ void (async () => {
141
+ setLoading(true);
142
+ try {
143
+ const snap = await o.snapshot();
144
+ if (cancelled) return;
145
+ setSelfUserId(snap.selfUserId);
146
+ setUsers(o.sortUsers ? [...snap.users].sort(o.sortUsers) : snap.users);
147
+ } finally {
148
+ if (!cancelled) setLoading(false);
149
+ }
150
+ })();
151
+ const onJoin = (p) => {
152
+ const cur = optionsRef.current;
153
+ const u = cur.mapJoinUser(p);
154
+ setUsers((prev) => {
155
+ const next = prev.filter((x) => x.userId !== u.userId);
156
+ const merged = [...next, u];
157
+ return cur.sortUsers ? merged.sort(cur.sortUsers) : merged;
158
+ });
159
+ };
160
+ const onLeave = (p) => {
161
+ const id = optionsRef.current.mapLeaveUserId(p);
162
+ setUsers((prev) => prev.filter((x) => x.userId !== id));
163
+ };
164
+ s.subscribe.on(o.joinPush, onJoin);
165
+ s.subscribe.on(o.leavePush, onLeave);
166
+ return () => {
167
+ cancelled = true;
168
+ s.subscribe.off(o.joinPush, onJoin);
169
+ s.subscribe.off(o.leavePush, onLeave);
170
+ };
171
+ }, [ready, sessionRef, ...deps]);
172
+ return { users, selfUserId, loading };
85
173
  }
86
174
  var SockaSessionContext = createContext(null);
87
175
  function contextMatchesContract(ctx, contract) {
@@ -93,7 +181,10 @@ function SockaSessionProvider(props) {
93
181
  const merged = {
94
182
  contract,
95
183
  ready: value.ready,
96
- sessionRef: value.sessionRef
184
+ sessionRef: value.sessionRef,
185
+ status: value.status,
186
+ reconnecting: value.reconnecting,
187
+ reconnectAttempt: value.reconnectAttempt
97
188
  };
98
189
  return /* @__PURE__ */ jsx(SockaSessionContext.Provider, { value: merged, children });
99
190
  }
@@ -117,10 +208,13 @@ function useSockaSessionContext(contract) {
117
208
  return {
118
209
  ready: ctx.ready,
119
210
  send,
120
- sessionRef: ctx.sessionRef
211
+ sessionRef: ctx.sessionRef,
212
+ status: ctx.status,
213
+ reconnecting: ctx.reconnecting,
214
+ reconnectAttempt: ctx.reconnectAttempt
121
215
  };
122
216
  }
123
217
 
124
- export { SockaSessionProvider, createSockaSendProxyFromSession, useSocka, useSockaSession, useSockaSessionContext };
218
+ export { SockaSessionProvider, createSockaSendProxyFromSession, useSocka, useSockaPresence, useSockaSession, useSockaSessionContext };
125
219
  //# sourceMappingURL=index.js.map
126
220
  //# sourceMappingURL=index.js.map