@meekojs/meeko 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +21 -19
  2. package/dist/autoimports-ts.d.ts +4 -4
  3. package/dist/autoimports-ts.js +86 -30
  4. package/dist/autoimports-ts.js.map +1 -1
  5. package/dist/autoimports.d.ts +2 -2
  6. package/dist/autoimports.js +23 -7
  7. package/dist/autoimports.js.map +1 -1
  8. package/dist/check.js +39 -10
  9. package/dist/check.js.map +1 -1
  10. package/dist/cli.js +37 -4
  11. package/dist/cli.js.map +1 -1
  12. package/dist/compiler/events.d.ts +2 -0
  13. package/dist/compiler/events.js +6 -0
  14. package/dist/compiler/events.js.map +1 -0
  15. package/dist/compiler/props.d.ts +12 -0
  16. package/dist/compiler/props.js +32 -0
  17. package/dist/compiler/props.js.map +1 -0
  18. package/dist/compiler.js +55 -12
  19. package/dist/compiler.js.map +1 -1
  20. package/dist/component-errors.d.ts +18 -0
  21. package/dist/component-errors.js +65 -0
  22. package/dist/component-errors.js.map +1 -0
  23. package/dist/devtools.js +2 -4
  24. package/dist/devtools.js.map +1 -1
  25. package/dist/dom.d.ts +11 -5
  26. package/dist/dom.js +288 -88
  27. package/dist/dom.js.map +1 -1
  28. package/dist/error-observer.d.ts +6 -0
  29. package/dist/error-observer.js +41 -0
  30. package/dist/error-observer.js.map +1 -0
  31. package/dist/framework/check-app.js +160 -5
  32. package/dist/framework/check-app.js.map +1 -1
  33. package/dist/framework/check-project.d.ts +4 -0
  34. package/dist/framework/check-project.js +42 -0
  35. package/dist/framework/check-project.js.map +1 -0
  36. package/dist/framework/client.d.ts +3 -0
  37. package/dist/framework/client.js +136 -33
  38. package/dist/framework/client.js.map +1 -1
  39. package/dist/framework/clients.js +12 -4
  40. package/dist/framework/clients.js.map +1 -1
  41. package/dist/framework/contracts-generate.js +66 -3
  42. package/dist/framework/contracts-generate.js.map +1 -1
  43. package/dist/framework/init.js +21 -200
  44. package/dist/framework/init.js.map +1 -1
  45. package/dist/framework/middleware.d.ts +14 -0
  46. package/dist/framework/middleware.js +16 -0
  47. package/dist/framework/middleware.js.map +1 -0
  48. package/dist/framework/page-contracts-check.d.ts +8 -0
  49. package/dist/framework/page-contracts-check.js +110 -0
  50. package/dist/framework/page-contracts-check.js.map +1 -0
  51. package/dist/framework/page-contracts-generate.d.ts +2 -0
  52. package/dist/framework/page-contracts-generate.js +94 -0
  53. package/dist/framework/page-contracts-generate.js.map +1 -0
  54. package/dist/framework/page.d.ts +7 -3
  55. package/dist/framework/page.js +3 -1
  56. package/dist/framework/page.js.map +1 -1
  57. package/dist/framework/prerender.js +11 -5
  58. package/dist/framework/prerender.js.map +1 -1
  59. package/dist/framework/project-templates.d.ts +3 -0
  60. package/dist/framework/project-templates.js +119 -0
  61. package/dist/framework/project-templates.js.map +1 -0
  62. package/dist/framework/project.d.ts +9 -0
  63. package/dist/framework/project.js +272 -0
  64. package/dist/framework/project.js.map +1 -0
  65. package/dist/framework/router.d.ts +3 -1
  66. package/dist/framework/router.js +94 -8
  67. package/dist/framework/router.js.map +1 -1
  68. package/dist/framework/server.d.ts +17 -3
  69. package/dist/framework/server.js +71 -24
  70. package/dist/framework/server.js.map +1 -1
  71. package/dist/framework/socket-contracts-generate.js +8 -5
  72. package/dist/framework/socket-contracts-generate.js.map +1 -1
  73. package/dist/framework/socket.d.ts +2 -0
  74. package/dist/framework/socket.js +119 -17
  75. package/dist/framework/socket.js.map +1 -1
  76. package/dist/framework/standard-schema-types.d.ts +7 -0
  77. package/dist/framework/standard-schema-types.js +69 -0
  78. package/dist/framework/standard-schema-types.js.map +1 -0
  79. package/dist/framework/state.d.ts +7 -0
  80. package/dist/framework/state.js +78 -14
  81. package/dist/framework/state.js.map +1 -1
  82. package/dist/framework/styles.d.ts +2 -0
  83. package/dist/framework/styles.js +6 -0
  84. package/dist/framework/styles.js.map +1 -0
  85. package/dist/framework/type-declarations.d.ts +5 -0
  86. package/dist/framework/type-declarations.js +84 -0
  87. package/dist/framework/type-declarations.js.map +1 -0
  88. package/dist/framework-server/contracts.d.ts +11 -2
  89. package/dist/framework-server/contracts.js +62 -7
  90. package/dist/framework-server/contracts.js.map +1 -1
  91. package/dist/framework-server/discovery.d.ts +4 -1
  92. package/dist/framework-server/discovery.js +21 -16
  93. package/dist/framework-server/discovery.js.map +1 -1
  94. package/dist/framework-server/local.d.ts +2 -0
  95. package/dist/framework-server/local.js +5 -3
  96. package/dist/framework-server/local.js.map +1 -1
  97. package/dist/framework-server/middleware.d.ts +4 -0
  98. package/dist/framework-server/middleware.js +56 -0
  99. package/dist/framework-server/middleware.js.map +1 -0
  100. package/dist/framework-server/node-upgrade.js +27 -9
  101. package/dist/framework-server/node-upgrade.js.map +1 -1
  102. package/dist/framework-server/node-websocket.js +4 -2
  103. package/dist/framework-server/node-websocket.js.map +1 -1
  104. package/dist/framework-server/session-stores.d.ts +9 -2
  105. package/dist/framework-server/session-stores.js +37 -11
  106. package/dist/framework-server/session-stores.js.map +1 -1
  107. package/dist/framework-server/sessions.d.ts +6 -0
  108. package/dist/framework-server/sessions.js +41 -10
  109. package/dist/framework-server/sessions.js.map +1 -1
  110. package/dist/framework-server/socket.js +62 -27
  111. package/dist/framework-server/socket.js.map +1 -1
  112. package/dist/framework-server.d.ts +2 -0
  113. package/dist/framework-server.js +1 -0
  114. package/dist/framework-server.js.map +1 -1
  115. package/dist/framework.d.ts +3 -1
  116. package/dist/framework.js +1 -0
  117. package/dist/framework.js.map +1 -1
  118. package/dist/hmr.js +74 -24
  119. package/dist/hmr.js.map +1 -1
  120. package/dist/index.d.ts +2 -0
  121. package/dist/index.js +1 -0
  122. package/dist/index.js.map +1 -1
  123. package/dist/init.js +27 -1
  124. package/dist/init.js.map +1 -1
  125. package/dist/internal.d.ts +2 -2
  126. package/dist/internal.js +2 -2
  127. package/dist/internal.js.map +1 -1
  128. package/dist/node.d.ts +5 -1
  129. package/dist/node.js +80 -10
  130. package/dist/node.js.map +1 -1
  131. package/dist/reactivity.d.ts +12 -3
  132. package/dist/reactivity.js +94 -17
  133. package/dist/reactivity.js.map +1 -1
  134. package/dist/types.d.ts +2 -0
  135. package/dist/types.js +3 -1
  136. package/dist/types.js.map +1 -1
  137. package/dist/vite.js +29 -9
  138. package/dist/vite.js.map +1 -1
  139. package/package.json +2 -2
@@ -7,8 +7,19 @@ export function defineSocketChannel(definition) {
7
7
  export class LocalSocketPubSub {
8
8
  listeners = new Map();
9
9
  async publish(channel, room, message) {
10
- for (const listener of this.listeners.get(`${channel}\0${room}`) ?? [])
11
- await listener(message);
10
+ const errors = [];
11
+ for (const listener of this.listeners.get(`${channel}\0${room}`) ?? []) {
12
+ try {
13
+ await listener(message);
14
+ }
15
+ catch (error) {
16
+ errors.push(error);
17
+ }
18
+ }
19
+ if (errors.length === 1)
20
+ throw errors[0];
21
+ if (errors.length > 1)
22
+ throw new AggregateError(errors, "Socket delivery failed for multiple peers");
12
23
  }
13
24
  subscribe(channel, room, listener) {
14
25
  const key = `${channel}\0${room}`;
@@ -74,10 +85,8 @@ export function createSocketHub(options) {
74
85
  throw new ApiError("UNKNOWN_SOCKET_EVENT", 404);
75
86
  const checked = await validate(schema, payload);
76
87
  const fresh = await options.sessions.load(request);
77
- if (definition.auth === "required" && !fresh.current) {
78
- close(1008, "Session expired");
79
- throw new ApiError("UNAUTHENTICATED", 401);
80
- }
88
+ if (definition.auth === "required" && !fresh.current)
89
+ failAndClose(new ApiError("UNAUTHENTICATED", 401), 1008, "Session expired");
81
90
  const sendContext = createRequestContext(request, services, fresh);
82
91
  if (definition.authorize && !(await definition.authorize({ context: sendContext, ...(roomId === undefined ? {} : { roomId }) })))
83
92
  throw new ApiError("FORBIDDEN", 403);
@@ -86,19 +95,53 @@ export function createSocketHub(options) {
86
95
  const message = serializeJson({ event, payload: checked });
87
96
  const messageBytes = new TextEncoder().encode(message).byteLength;
88
97
  if (messageBytes > maxMessageBytes || (peer.bufferedAmount ?? 0) + messageBytes > maxBufferBytes) {
89
- close(1009, "Message too large");
90
- throw new ApiError("SOCKET_LIMIT_EXCEEDED", 413);
98
+ failAndClose(new ApiError("SOCKET_LIMIT_EXCEEDED", 413), 1009, "Message too large");
99
+ }
100
+ try {
101
+ await peer.send(message);
102
+ }
103
+ catch (error) {
104
+ try {
105
+ close(1011, "Send failed");
106
+ }
107
+ catch (closeError) {
108
+ throw new AggregateError([error, closeError], "Socket send and close failed");
109
+ }
110
+ throw error;
91
111
  }
92
- await peer.send(message);
93
112
  };
94
113
  const release = pubsub.subscribe(name, roomId ?? "", ({ event, payload }) => emit(event, payload));
95
114
  const close = (code, reason) => {
96
115
  if (closed)
97
116
  return;
98
117
  closed = true;
99
- release();
100
- peer.close(code, reason);
118
+ const failures = [];
119
+ try {
120
+ release();
121
+ }
122
+ catch (error) {
123
+ failures.push(error);
124
+ }
125
+ try {
126
+ peer.close(code, reason);
127
+ }
128
+ catch (error) {
129
+ failures.push(error);
130
+ }
131
+ if (failures.length === 1)
132
+ throw failures[0];
133
+ if (failures.length > 1)
134
+ throw new AggregateError(failures, "Socket unsubscribe and peer close failed");
101
135
  };
136
+ function failAndClose(error, code, reason) {
137
+ try {
138
+ close(code, reason);
139
+ }
140
+ catch (cleanupError) {
141
+ error.cause = cleanupError;
142
+ }
143
+ throw error;
144
+ }
102
145
  return {
103
146
  emit,
104
147
  close,
@@ -106,35 +149,27 @@ export function createSocketHub(options) {
106
149
  if (closed)
107
150
  throw new ApiError("SOCKET_DISCONNECTED", 409);
108
151
  const bytes = typeof raw === "string" ? new TextEncoder().encode(raw) : raw;
109
- if (bytes.byteLength > maxMessageBytes) {
110
- close(1009, "Message too large");
111
- throw new ApiError("SOCKET_LIMIT_EXCEEDED", 413);
112
- }
152
+ if (bytes.byteLength > maxMessageBytes)
153
+ failAndClose(new ApiError("SOCKET_LIMIT_EXCEEDED", 413), 1009, "Message too large");
113
154
  let envelope;
114
155
  try {
115
156
  envelope = JSON.parse(typeof raw === "string" ? raw : new TextDecoder("utf-8", { fatal: true }).decode(raw));
116
157
  }
117
158
  catch {
118
- close(1008, "Invalid message");
119
- throw new ApiError("INVALID_SOCKET_MESSAGE", 400);
159
+ failAndClose(new ApiError("INVALID_SOCKET_MESSAGE", 400), 1008, "Invalid message");
120
160
  }
121
161
  if (!envelope || typeof envelope !== "object" || typeof envelope.event !== "string" || !("payload" in envelope)) {
122
- close(1008, "Invalid message");
123
- throw new ApiError("INVALID_SOCKET_MESSAGE", 400);
162
+ failAndClose(new ApiError("INVALID_SOCKET_MESSAGE", 400), 1008, "Invalid message");
124
163
  }
125
164
  const event = envelope.event;
126
165
  const schema = definition.clientToServer[event];
127
- if (!schema) {
128
- close(1008, "Unknown event");
129
- throw new ApiError("UNKNOWN_SOCKET_EVENT", 404);
130
- }
166
+ if (!schema)
167
+ failAndClose(new ApiError("UNKNOWN_SOCKET_EVENT", 404), 1008, "Unknown event");
131
168
  const payload = await validate(schema, envelope.payload);
132
169
  // Re-read storage and policy for every event, including after a session revocation.
133
170
  const fresh = await options.sessions.load(request);
134
- if (definition.auth === "required" && !fresh.current) {
135
- close(1008, "Session expired");
136
- throw new ApiError("UNAUTHENTICATED", 401);
137
- }
171
+ if (definition.auth === "required" && !fresh.current)
172
+ failAndClose(new ApiError("UNAUTHENTICATED", 401), 1008, "Session expired");
138
173
  const eventContext = createRequestContext(request, services, fresh);
139
174
  if (definition.authorize && !(await definition.authorize({ context: eventContext, ...(roomId === undefined ? {} : { roomId }) })))
140
175
  throw new ApiError("FORBIDDEN", 403);
@@ -1 +1 @@
1
- {"version":3,"file":"socket.js","sourceRoot":"","sources":["../../src/framework-server/socket.ts"],"names":[],"mappings":"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","sourcesContent":["import { createRequestContext, type RequestContext } from \"./context.js\";\nimport { ApiError, type Schema } from \"./contracts.js\";\nimport { serializeJson } from \"../server.js\";\nimport type { SessionManager } from \"./sessions.js\";\n\ntype Output<S> = S extends Schema<unknown, infer O> ? O : never;\ntype SchemaMap = Readonly<Record<string, Schema>>;\n\nexport interface SocketPeer {\n send(message: string): void | Promise<void>;\n close(code?: number, reason?: string): void;\n readonly bufferedAmount?: number;\n}\n\nexport interface SocketChannelDefinition<C extends SchemaMap, S extends SchemaMap, TServices = unknown> {\n clientToServer: C;\n serverToClient: S;\n auth?: \"required\" | \"optional\";\n authorize?: (args: { context: RequestContext<TServices>; roomId?: string }) => boolean | Promise<boolean>;\n authorizeEvent?: (args: { event: keyof C & string; payload: unknown; context: RequestContext<TServices>; roomId?: string }) => boolean | Promise<boolean>;\n authorizeSend?: (args: { event: keyof S & string; payload: unknown; context: RequestContext<TServices>; roomId?: string }) => boolean | Promise<boolean>;\n handlers?: { [K in keyof C]?: (args: { payload: Output<C[K]>; context: RequestContext<TServices>; roomId?: string; emit: <E extends keyof S & string>(event: E, payload: Output<S[E]>) => Promise<void> }) => void | Promise<void> };\n}\n\nexport function defineSocketChannel<C extends SchemaMap, S extends SchemaMap, TServices = unknown>(definition: SocketChannelDefinition<C, S, TServices>): SocketChannelDefinition<C, S, TServices> {\n return definition;\n}\n\nexport interface SocketPubSub {\n publish(channel: string, room: string, message: { event: string; payload: unknown }): Promise<void>;\n subscribe(channel: string, room: string, listener: (message: { event: string; payload: unknown }) => void | Promise<void>): () => void;\n}\n\nexport class LocalSocketPubSub implements SocketPubSub {\n private readonly listeners = new Map<string, Set<(message: { event: string; payload: unknown }) => void | Promise<void>>>();\n async publish(channel: string, room: string, message: { event: string; payload: unknown }): Promise<void> {\n for (const listener of this.listeners.get(`${channel}\\0${room}`) ?? []) await listener(message);\n }\n subscribe(channel: string, room: string, listener: (message: { event: string; payload: unknown }) => void | Promise<void>): () => void {\n const key = `${channel}\\0${room}`;\n let listeners = this.listeners.get(key);\n if (!listeners) { listeners = new Set(); this.listeners.set(key, listeners); }\n listeners.add(listener);\n return () => {\n listeners!.delete(listener);\n if (!listeners!.size) this.listeners.delete(key);\n };\n }\n}\n\nexport interface SocketConnection {\n receive(message: string | Uint8Array): Promise<void>;\n emit(event: string, payload: unknown): Promise<void>;\n close(code?: number, reason?: string): void;\n}\n\nexport interface SocketHubOptions<TServices = unknown> {\n channels: Readonly<Record<string, unknown>>;\n sessions: SessionManager;\n services: TServices | ((request: Request) => TServices | Promise<TServices>);\n allowedOrigins?: readonly string[];\n maxMessageBytes?: number;\n maxBufferBytes?: number;\n pubsub?: SocketPubSub;\n}\n\nexport interface SocketHub {\n preflight(name: string, request: Request, roomId?: string): Promise<void>;\n accept(name: string, request: Request, peer: SocketPeer, roomId?: string): Promise<SocketConnection>;\n broadcast(name: string, roomId: string, event: string, payload: unknown): Promise<void>;\n}\n\nasync function validate(schema: Schema, value: unknown): Promise<unknown> {\n const result = await schema[\"~standard\"].validate(value);\n if (\"issues\" in result && result.issues) throw new ApiError(\"INVALID_SOCKET_PAYLOAD\", 400);\n return (result as { value: unknown }).value;\n}\n\nfunction channelDefinition(value: unknown): SocketChannelDefinition<SchemaMap, SchemaMap> {\n if (!value || typeof value !== \"object\" || !(\"clientToServer\" in value) || !(\"serverToClient\" in value)) throw new ApiError(\"UNKNOWN_SOCKET_CHANNEL\", 404);\n return value as SocketChannelDefinition<SchemaMap, SchemaMap>;\n}\n\nfunction originAllowed(request: Request, allowedOrigins: readonly string[]): boolean {\n const origin = request.headers.get(\"origin\");\n return !!origin && (origin === new URL(request.url).origin || allowedOrigins.includes(origin));\n}\n\n/** The host performs the HTTP upgrade, then passes its connected peer here. */\nexport function createSocketHub<TServices>(options: SocketHubOptions<TServices>): SocketHub {\n const maxMessageBytes = options.maxMessageBytes ?? 64 * 1024;\n const maxBufferBytes = options.maxBufferBytes ?? 1024 * 1024;\n if (!Number.isSafeInteger(maxMessageBytes) || maxMessageBytes <= 0 || !Number.isSafeInteger(maxBufferBytes) || maxBufferBytes <= 0) throw new RangeError(\"Invalid socket limits\");\n const pubsub = options.pubsub ?? new LocalSocketPubSub();\n const getDefinition = (name: string) => channelDefinition(options.channels[name]);\n const authorizeHandshake = async (name: string, request: Request, roomId?: string) => {\n const definition = getDefinition(name);\n if (!originAllowed(request, options.allowedOrigins ?? [])) throw new ApiError(\"INVALID_ORIGIN\", 403);\n const session = await options.sessions.load(request);\n if (definition.auth === \"required\" && !session.current) throw new ApiError(\"UNAUTHENTICATED\", 401);\n const services = typeof options.services === \"function\" ? await (options.services as (request: Request) => TServices | Promise<TServices>)(request) : options.services;\n const context = createRequestContext(request, services, session);\n if (definition.authorize && !(await definition.authorize({ context, ...(roomId === undefined ? {} : { roomId }) }))) throw new ApiError(\"FORBIDDEN\", 403);\n return { definition, services };\n };\n return {\n async preflight(name, request, roomId) { await authorizeHandshake(name, request, roomId); },\n async accept(name: string, request: Request, peer: SocketPeer, roomId?: string): Promise<SocketConnection> {\n const { definition, services } = await authorizeHandshake(name, request, roomId);\n let closed = false;\n const emit = async (event: string, payload: unknown): Promise<void> => {\n if (closed) throw new ApiError(\"SOCKET_DISCONNECTED\", 409);\n const schema = definition.serverToClient[event];\n if (!schema) throw new ApiError(\"UNKNOWN_SOCKET_EVENT\", 404);\n const checked = await validate(schema, payload);\n const fresh = await options.sessions.load(request);\n if (definition.auth === \"required\" && !fresh.current) { close(1008, \"Session expired\"); throw new ApiError(\"UNAUTHENTICATED\", 401); }\n const sendContext = createRequestContext(request, services, fresh);\n if (definition.authorize && !(await definition.authorize({ context: sendContext, ...(roomId === undefined ? {} : { roomId }) }))) throw new ApiError(\"FORBIDDEN\", 403);\n if (definition.authorizeSend && !(await definition.authorizeSend({ event, payload: checked, context: sendContext, ...(roomId === undefined ? {} : { roomId }) }))) throw new ApiError(\"FORBIDDEN\", 403);\n const message = serializeJson({ event, payload: checked });\n const messageBytes = new TextEncoder().encode(message).byteLength;\n if (messageBytes > maxMessageBytes || (peer.bufferedAmount ?? 0) + messageBytes > maxBufferBytes) {\n close(1009, \"Message too large\");\n throw new ApiError(\"SOCKET_LIMIT_EXCEEDED\", 413);\n }\n await peer.send(message);\n };\n const release = pubsub.subscribe(name, roomId ?? \"\", ({ event, payload }) => emit(event, payload));\n const close = (code?: number, reason?: string): void => {\n if (closed) return;\n closed = true;\n release();\n peer.close(code, reason);\n };\n return {\n emit,\n close,\n async receive(raw) {\n if (closed) throw new ApiError(\"SOCKET_DISCONNECTED\", 409);\n const bytes = typeof raw === \"string\" ? new TextEncoder().encode(raw) : raw;\n if (bytes.byteLength > maxMessageBytes) { close(1009, \"Message too large\"); throw new ApiError(\"SOCKET_LIMIT_EXCEEDED\", 413); }\n let envelope: unknown;\n try { envelope = JSON.parse(typeof raw === \"string\" ? raw : new TextDecoder(\"utf-8\", { fatal: true }).decode(raw)) as unknown; }\n catch { close(1008, \"Invalid message\"); throw new ApiError(\"INVALID_SOCKET_MESSAGE\", 400); }\n if (!envelope || typeof envelope !== \"object\" || typeof (envelope as { event?: unknown }).event !== \"string\" || !(\"payload\" in envelope)) {\n close(1008, \"Invalid message\");\n throw new ApiError(\"INVALID_SOCKET_MESSAGE\", 400);\n }\n const event = (envelope as { event?: unknown }).event as string;\n const schema = definition.clientToServer[event];\n if (!schema) { close(1008, \"Unknown event\"); throw new ApiError(\"UNKNOWN_SOCKET_EVENT\", 404); }\n const payload = await validate(schema, (envelope as { payload: unknown }).payload);\n // Re-read storage and policy for every event, including after a session revocation.\n const fresh = await options.sessions.load(request);\n if (definition.auth === \"required\" && !fresh.current) { close(1008, \"Session expired\"); throw new ApiError(\"UNAUTHENTICATED\", 401); }\n const eventContext = createRequestContext(request, services, fresh);\n if (definition.authorize && !(await definition.authorize({ context: eventContext, ...(roomId === undefined ? {} : { roomId }) }))) throw new ApiError(\"FORBIDDEN\", 403);\n if (definition.authorizeEvent && !(await definition.authorizeEvent({ event, payload, context: eventContext, ...(roomId === undefined ? {} : { roomId }) }))) throw new ApiError(\"FORBIDDEN\", 403);\n const handler = definition.handlers?.[event];\n if (!handler) throw new ApiError(\"UNHANDLED_SOCKET_EVENT\", 404);\n await handler({ payload, context: eventContext, ...(roomId === undefined ? {} : { roomId }), emit });\n },\n };\n },\n async broadcast(name: string, roomId: string, event: string, payload: unknown): Promise<void> {\n const definition = getDefinition(name);\n const schema = definition.serverToClient[event];\n if (!schema) throw new ApiError(\"UNKNOWN_SOCKET_EVENT\", 404);\n const checked = await validate(schema, payload);\n serializeJson({ event, payload: checked });\n await pubsub.publish(name, roomId, { event, payload: checked });\n },\n };\n}\n"]}
1
+ {"version":3,"file":"socket.js","sourceRoot":"","sources":["../../src/framework-server/socket.ts"],"names":[],"mappings":"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","sourcesContent":["import { createRequestContext, type RequestContext } from \"./context.js\";\nimport { ApiError, type Schema } from \"./contracts.js\";\nimport { serializeJson } from \"../server.js\";\nimport type { SessionManager } from \"./sessions.js\";\n\ntype Output<S> = S extends Schema<unknown, infer O> ? O : never;\ntype SchemaMap = Readonly<Record<string, Schema>>;\n\nexport interface SocketPeer {\n send(message: string): void | Promise<void>;\n close(code?: number, reason?: string): void;\n readonly bufferedAmount?: number;\n}\n\nexport interface SocketChannelDefinition<C extends SchemaMap, S extends SchemaMap, TServices = unknown> {\n clientToServer: C;\n serverToClient: S;\n auth?: \"required\" | \"optional\";\n authorize?: (args: { context: RequestContext<TServices>; roomId?: string }) => boolean | Promise<boolean>;\n authorizeEvent?: (args: { event: keyof C & string; payload: unknown; context: RequestContext<TServices>; roomId?: string }) => boolean | Promise<boolean>;\n authorizeSend?: (args: { event: keyof S & string; payload: unknown; context: RequestContext<TServices>; roomId?: string }) => boolean | Promise<boolean>;\n handlers?: { [K in keyof C]?: (args: { payload: Output<C[K]>; context: RequestContext<TServices>; roomId?: string; emit: <E extends keyof S & string>(event: E, payload: Output<S[E]>) => Promise<void> }) => void | Promise<void> };\n}\n\nexport function defineSocketChannel<C extends SchemaMap, S extends SchemaMap, TServices = unknown>(definition: SocketChannelDefinition<C, S, TServices>): SocketChannelDefinition<C, S, TServices> {\n return definition;\n}\n\nexport interface SocketPubSub {\n publish(channel: string, room: string, message: { event: string; payload: unknown }): Promise<void>;\n subscribe(channel: string, room: string, listener: (message: { event: string; payload: unknown }) => void | Promise<void>): () => void;\n}\n\nexport class LocalSocketPubSub implements SocketPubSub {\n private readonly listeners = new Map<string, Set<(message: { event: string; payload: unknown }) => void | Promise<void>>>();\n async publish(channel: string, room: string, message: { event: string; payload: unknown }): Promise<void> {\n const errors: unknown[] = [];\n for (const listener of this.listeners.get(`${channel}\\0${room}`) ?? []) {\n try { await listener(message); }\n catch (error) { errors.push(error); }\n }\n if (errors.length === 1) throw errors[0];\n if (errors.length > 1) throw new AggregateError(errors, \"Socket delivery failed for multiple peers\");\n }\n subscribe(channel: string, room: string, listener: (message: { event: string; payload: unknown }) => void | Promise<void>): () => void {\n const key = `${channel}\\0${room}`;\n let listeners = this.listeners.get(key);\n if (!listeners) { listeners = new Set(); this.listeners.set(key, listeners); }\n listeners.add(listener);\n return () => {\n listeners!.delete(listener);\n if (!listeners!.size) this.listeners.delete(key);\n };\n }\n}\n\nexport interface SocketConnection {\n receive(message: string | Uint8Array): Promise<void>;\n emit(event: string, payload: unknown): Promise<void>;\n close(code?: number, reason?: string): void;\n}\n\nexport interface SocketHubOptions<TServices = unknown> {\n channels: Readonly<Record<string, unknown>>;\n sessions: SessionManager;\n services: TServices | ((request: Request) => TServices | Promise<TServices>);\n allowedOrigins?: readonly string[];\n maxMessageBytes?: number;\n maxBufferBytes?: number;\n pubsub?: SocketPubSub;\n}\n\nexport interface SocketHub {\n preflight(name: string, request: Request, roomId?: string): Promise<void>;\n accept(name: string, request: Request, peer: SocketPeer, roomId?: string): Promise<SocketConnection>;\n broadcast(name: string, roomId: string, event: string, payload: unknown): Promise<void>;\n}\n\nasync function validate(schema: Schema, value: unknown): Promise<unknown> {\n const result = await schema[\"~standard\"].validate(value);\n if (\"issues\" in result && result.issues) throw new ApiError(\"INVALID_SOCKET_PAYLOAD\", 400);\n return (result as { value: unknown }).value;\n}\n\nfunction channelDefinition(value: unknown): SocketChannelDefinition<SchemaMap, SchemaMap> {\n if (!value || typeof value !== \"object\" || !(\"clientToServer\" in value) || !(\"serverToClient\" in value)) throw new ApiError(\"UNKNOWN_SOCKET_CHANNEL\", 404);\n return value as SocketChannelDefinition<SchemaMap, SchemaMap>;\n}\n\nfunction originAllowed(request: Request, allowedOrigins: readonly string[]): boolean {\n const origin = request.headers.get(\"origin\");\n return !!origin && (origin === new URL(request.url).origin || allowedOrigins.includes(origin));\n}\n\n/** The host performs the HTTP upgrade, then passes its connected peer here. */\nexport function createSocketHub<TServices>(options: SocketHubOptions<TServices>): SocketHub {\n const maxMessageBytes = options.maxMessageBytes ?? 64 * 1024;\n const maxBufferBytes = options.maxBufferBytes ?? 1024 * 1024;\n if (!Number.isSafeInteger(maxMessageBytes) || maxMessageBytes <= 0 || !Number.isSafeInteger(maxBufferBytes) || maxBufferBytes <= 0) throw new RangeError(\"Invalid socket limits\");\n const pubsub = options.pubsub ?? new LocalSocketPubSub();\n const getDefinition = (name: string) => channelDefinition(options.channels[name]);\n const authorizeHandshake = async (name: string, request: Request, roomId?: string) => {\n const definition = getDefinition(name);\n if (!originAllowed(request, options.allowedOrigins ?? [])) throw new ApiError(\"INVALID_ORIGIN\", 403);\n const session = await options.sessions.load(request);\n if (definition.auth === \"required\" && !session.current) throw new ApiError(\"UNAUTHENTICATED\", 401);\n const services = typeof options.services === \"function\" ? await (options.services as (request: Request) => TServices | Promise<TServices>)(request) : options.services;\n const context = createRequestContext(request, services, session);\n if (definition.authorize && !(await definition.authorize({ context, ...(roomId === undefined ? {} : { roomId }) }))) throw new ApiError(\"FORBIDDEN\", 403);\n return { definition, services };\n };\n return {\n async preflight(name, request, roomId) { await authorizeHandshake(name, request, roomId); },\n async accept(name: string, request: Request, peer: SocketPeer, roomId?: string): Promise<SocketConnection> {\n const { definition, services } = await authorizeHandshake(name, request, roomId);\n let closed = false;\n const emit = async (event: string, payload: unknown): Promise<void> => {\n if (closed) throw new ApiError(\"SOCKET_DISCONNECTED\", 409);\n const schema = definition.serverToClient[event];\n if (!schema) throw new ApiError(\"UNKNOWN_SOCKET_EVENT\", 404);\n const checked = await validate(schema, payload);\n const fresh = await options.sessions.load(request);\n if (definition.auth === \"required\" && !fresh.current) failAndClose(new ApiError(\"UNAUTHENTICATED\", 401), 1008, \"Session expired\");\n const sendContext = createRequestContext(request, services, fresh);\n if (definition.authorize && !(await definition.authorize({ context: sendContext, ...(roomId === undefined ? {} : { roomId }) }))) throw new ApiError(\"FORBIDDEN\", 403);\n if (definition.authorizeSend && !(await definition.authorizeSend({ event, payload: checked, context: sendContext, ...(roomId === undefined ? {} : { roomId }) }))) throw new ApiError(\"FORBIDDEN\", 403);\n const message = serializeJson({ event, payload: checked });\n const messageBytes = new TextEncoder().encode(message).byteLength;\n if (messageBytes > maxMessageBytes || (peer.bufferedAmount ?? 0) + messageBytes > maxBufferBytes) {\n failAndClose(new ApiError(\"SOCKET_LIMIT_EXCEEDED\", 413), 1009, \"Message too large\");\n }\n try { await peer.send(message); }\n catch (error) {\n try { close(1011, \"Send failed\"); }\n catch (closeError) { throw new AggregateError([error, closeError], \"Socket send and close failed\"); }\n throw error;\n }\n };\n const release = pubsub.subscribe(name, roomId ?? \"\", ({ event, payload }) => emit(event, payload));\n const close = (code?: number, reason?: string): void => {\n if (closed) return;\n closed = true;\n const failures: unknown[] = [];\n try { release(); }\n catch (error) { failures.push(error); }\n try { peer.close(code, reason); }\n catch (error) { failures.push(error); }\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) throw new AggregateError(failures, \"Socket unsubscribe and peer close failed\");\n };\n function failAndClose(error: ApiError, code: number, reason: string): never {\n try { close(code, reason); }\n catch (cleanupError) { error.cause = cleanupError; }\n throw error;\n }\n return {\n emit,\n close,\n async receive(raw) {\n if (closed) throw new ApiError(\"SOCKET_DISCONNECTED\", 409);\n const bytes = typeof raw === \"string\" ? new TextEncoder().encode(raw) : raw;\n if (bytes.byteLength > maxMessageBytes) failAndClose(new ApiError(\"SOCKET_LIMIT_EXCEEDED\", 413), 1009, \"Message too large\");\n let envelope: unknown;\n try { envelope = JSON.parse(typeof raw === \"string\" ? raw : new TextDecoder(\"utf-8\", { fatal: true }).decode(raw)) as unknown; }\n catch { failAndClose(new ApiError(\"INVALID_SOCKET_MESSAGE\", 400), 1008, \"Invalid message\"); }\n if (!envelope || typeof envelope !== \"object\" || typeof (envelope as { event?: unknown }).event !== \"string\" || !(\"payload\" in envelope)) {\n failAndClose(new ApiError(\"INVALID_SOCKET_MESSAGE\", 400), 1008, \"Invalid message\");\n }\n const event = (envelope as { event?: unknown }).event as string;\n const schema = definition.clientToServer[event];\n if (!schema) failAndClose(new ApiError(\"UNKNOWN_SOCKET_EVENT\", 404), 1008, \"Unknown event\");\n const payload = await validate(schema, (envelope as { payload: unknown }).payload);\n // Re-read storage and policy for every event, including after a session revocation.\n const fresh = await options.sessions.load(request);\n if (definition.auth === \"required\" && !fresh.current) failAndClose(new ApiError(\"UNAUTHENTICATED\", 401), 1008, \"Session expired\");\n const eventContext = createRequestContext(request, services, fresh);\n if (definition.authorize && !(await definition.authorize({ context: eventContext, ...(roomId === undefined ? {} : { roomId }) }))) throw new ApiError(\"FORBIDDEN\", 403);\n if (definition.authorizeEvent && !(await definition.authorizeEvent({ event, payload, context: eventContext, ...(roomId === undefined ? {} : { roomId }) }))) throw new ApiError(\"FORBIDDEN\", 403);\n const handler = definition.handlers?.[event];\n if (!handler) throw new ApiError(\"UNHANDLED_SOCKET_EVENT\", 404);\n await handler({ payload, context: eventContext, ...(roomId === undefined ? {} : { roomId }), emit });\n },\n };\n },\n async broadcast(name: string, roomId: string, event: string, payload: unknown): Promise<void> {\n const definition = getDefinition(name);\n const schema = definition.serverToClient[event];\n if (!schema) throw new ApiError(\"UNKNOWN_SOCKET_EVENT\", 404);\n const checked = await validate(schema, payload);\n serializeJson({ event, payload: checked });\n await pubsub.publish(name, roomId, { event, payload: checked });\n },\n };\n}\n"]}
@@ -1,4 +1,6 @@
1
1
  export { createRequestContext, useRequestState } from "./framework-server/context.js";
2
+ export { defineServerMiddleware, composeServerMiddleware } from "./framework-server/middleware.js";
3
+ export type { ServerMiddleware } from "./framework-server/middleware.js";
2
4
  export type { RequestContext, ServerFetch } from "./framework-server/context.js";
3
5
  export { createServerClientOptions } from "./framework-server/auto-clients.js";
4
6
  export { MemorySessionStore, SessionHandle, SessionManager, SessionSecurityError } from "./framework-server/sessions.js";
@@ -1,4 +1,5 @@
1
1
  export { createRequestContext, useRequestState } from "./framework-server/context.js";
2
+ export { defineServerMiddleware, composeServerMiddleware } from "./framework-server/middleware.js";
2
3
  export { createServerClientOptions } from "./framework-server/auto-clients.js";
3
4
  export { MemorySessionStore, SessionHandle, SessionManager, SessionSecurityError } from "./framework-server/sessions.js";
4
5
  export { RedisSessionStore, SqlSessionStore } from "./framework-server/session-stores.js";
@@ -1 +1 @@
1
- {"version":3,"file":"framework-server.js","sourceRoot":"","sources":["../src/framework-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEtF,OAAO,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAEzH,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAE1F,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,aAAa,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAEjK,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAE7I,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEvG,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAC;AAEpF,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC","sourcesContent":["export { createRequestContext, useRequestState } from \"./framework-server/context.js\";\nexport type { RequestContext, ServerFetch } from \"./framework-server/context.js\";\nexport { createServerClientOptions } from \"./framework-server/auto-clients.js\";\nexport { MemorySessionStore, SessionHandle, SessionManager, SessionSecurityError } from \"./framework-server/sessions.js\";\nexport type { SessionOptions, SessionRecord, SessionStore } from \"./framework-server/sessions.js\";\nexport { RedisSessionStore, SqlSessionStore } from \"./framework-server/session-stores.js\";\nexport type { RedisSessionClient, RedisSessionStoreOptions, SqlSessionDriver, SqlSessionRow, SqlSessionStoreOptions } from \"./framework-server/session-stores.js\";\nexport { ApiError, defineApiRoute, defineRpc, dispatchApiRoute, dispatchRpc, errorResponse, executeApiRoute, executeRpc } from \"./framework-server/contracts.js\";\nexport type { ApiRouteDefinition, RpcDefinition, Schema } from \"./framework-server/contracts.js\";\nexport { createLocalServerFetch, createServerDispatcher, discoverServerManifest, loadSocketChannels } from \"./framework-server/discovery.js\";\nexport type { ApiEndpoint, ApiMethod, RpcEndpoint, SocketEndpoint, ServerDispatcherOptions, ServerManifest } from \"./framework-server/discovery.js\";\nexport { createSocketHub, defineSocketChannel, LocalSocketPubSub } from \"./framework-server/socket.js\";\nexport type { SocketChannelDefinition, SocketConnection, SocketHubOptions, SocketPeer, SocketPubSub } from \"./framework-server/socket.js\";\nexport { createNodeSocketUpgradeHandler } from \"./framework-server/node-upgrade.js\";\nexport type { NodeAcceptedSocket, NodeSocketUpgradeOptions } from \"./framework-server/node-upgrade.js\";\nexport { attachNodeWebSocketServer } from \"./framework-server/node-websocket.js\";\nexport type { NodeWebSocketOptions, NodeWebSocketServer } from \"./framework-server/node-websocket.js\";\nexport { generateOpenApi } from \"./framework-server/openapi.js\";\nexport type { OpenApiOptions } from \"./framework-server/openapi.js\";\nexport { createLocalCalls } from \"./framework-server/local.js\";\nexport type { LocalApiInput, LocalCallOptions } from \"./framework-server/local.js\";\n"]}
1
+ {"version":3,"file":"framework-server.js","sourceRoot":"","sources":["../src/framework-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAGnG,OAAO,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAEzH,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAE1F,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,aAAa,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAEjK,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAE7I,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEvG,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAC;AAEpF,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC","sourcesContent":["export { createRequestContext, useRequestState } from \"./framework-server/context.js\";\nexport { defineServerMiddleware, composeServerMiddleware } from \"./framework-server/middleware.js\";\nexport type { ServerMiddleware } from \"./framework-server/middleware.js\";\nexport type { RequestContext, ServerFetch } from \"./framework-server/context.js\";\nexport { createServerClientOptions } from \"./framework-server/auto-clients.js\";\nexport { MemorySessionStore, SessionHandle, SessionManager, SessionSecurityError } from \"./framework-server/sessions.js\";\nexport type { SessionOptions, SessionRecord, SessionStore } from \"./framework-server/sessions.js\";\nexport { RedisSessionStore, SqlSessionStore } from \"./framework-server/session-stores.js\";\nexport type { RedisSessionClient, RedisSessionStoreOptions, SqlSessionDriver, SqlSessionRow, SqlSessionStoreOptions } from \"./framework-server/session-stores.js\";\nexport { ApiError, defineApiRoute, defineRpc, dispatchApiRoute, dispatchRpc, errorResponse, executeApiRoute, executeRpc } from \"./framework-server/contracts.js\";\nexport type { ApiRouteDefinition, RpcDefinition, Schema } from \"./framework-server/contracts.js\";\nexport { createLocalServerFetch, createServerDispatcher, discoverServerManifest, loadSocketChannels } from \"./framework-server/discovery.js\";\nexport type { ApiEndpoint, ApiMethod, RpcEndpoint, SocketEndpoint, ServerDispatcherOptions, ServerManifest } from \"./framework-server/discovery.js\";\nexport { createSocketHub, defineSocketChannel, LocalSocketPubSub } from \"./framework-server/socket.js\";\nexport type { SocketChannelDefinition, SocketConnection, SocketHubOptions, SocketPeer, SocketPubSub } from \"./framework-server/socket.js\";\nexport { createNodeSocketUpgradeHandler } from \"./framework-server/node-upgrade.js\";\nexport type { NodeAcceptedSocket, NodeSocketUpgradeOptions } from \"./framework-server/node-upgrade.js\";\nexport { attachNodeWebSocketServer } from \"./framework-server/node-websocket.js\";\nexport type { NodeWebSocketOptions, NodeWebSocketServer } from \"./framework-server/node-websocket.js\";\nexport { generateOpenApi } from \"./framework-server/openapi.js\";\nexport type { OpenApiOptions } from \"./framework-server/openapi.js\";\nexport { createLocalCalls } from \"./framework-server/local.js\";\nexport type { LocalApiInput, LocalCallOptions } from \"./framework-server/local.js\";\n"]}
@@ -8,6 +8,8 @@ export type { ClientApp, ClientAppOptions, ClientComponent, ClientRoute } from "
8
8
  export { definePage, PageInputError } from "./framework/page.js";
9
9
  export type { PageDefinition, PageLoadContext, PageSchema } from "./framework/page.js";
10
10
  export { StateStore, useState } from "./framework/state.js";
11
+ export { defineRouteMiddleware } from "./framework/middleware.js";
12
+ export type { RouteMiddleware, RouteMiddlewareContext } from "./framework/middleware.js";
11
13
  export { ClientError, createApiClient, createBrowserClientOptions, createPublicServerManifest, createRpcClient } from "./framework/clients.js";
12
14
  export type { ApiClient, ApiOperation, ClientOptions, PublicApiEndpoint, PublicRpcEndpoint, PublicServerManifest, RpcClient, RpcOperation } from "./framework/clients.js";
13
15
  export { createSocketClient, createSocketRegistry, useSocket, useSocketChannel } from "./framework/socket.js";
@@ -18,7 +20,7 @@ export interface GeneratedRoutes {
18
20
  type RouteKey = keyof GeneratedRoutes extends never ? string : keyof GeneratedRoutes & string;
19
21
  type RouteParams<K extends string> = K extends keyof GeneratedRoutes ? GeneratedRoutes[K] : Record<string, string>;
20
22
  type HrefOptions<K extends string> = (keyof RouteParams<K> extends never ? {
21
- params?: RouteParams<K>;
23
+ params?: never;
22
24
  } : {
23
25
  params: RouteParams<K>;
24
26
  }) & {
package/dist/framework.js CHANGED
@@ -4,6 +4,7 @@ export { defineMeekoConfig } from "./framework/config-type.js";
4
4
  export { mountClientApp } from "./framework/client.js";
5
5
  export { definePage, PageInputError } from "./framework/page.js";
6
6
  export { StateStore, useState } from "./framework/state.js";
7
+ export { defineRouteMiddleware } from "./framework/middleware.js";
7
8
  export { ClientError, createApiClient, createBrowserClientOptions, createPublicServerManifest, createRpcClient } from "./framework/clients.js";
8
9
  export { createSocketClient, createSocketRegistry, useSocket, useSocketChannel } from "./framework/socket.js";
9
10
  /** Builds a checked URL when the project's route declarations are installed. */
@@ -1 +1 @@
1
- {"version":3,"file":"framework.js","sourceRoot":"","sources":["../src/framework.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAG/D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE/I,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAiB9G,gFAAgF;AAChF,MAAM,UAAU,IAAI,CAAqB,MAA8B,EAAE,EAAK,EAAE,GAAG,CAAC,OAAO,CAAmB;IAC5G,OAAO,WAAW,CAAC,MAAM,EAAE,EAAE,EAAE,OAAwD,CAAC,CAAC;AAC3F,CAAC","sourcesContent":["import { href as runtimeHref } from \"./framework/router.js\";\nimport type { RouteRecord } from \"./framework/router.js\";\n\nexport { createNavigator, createRouteManifest, matchRoute } from \"./framework/router.js\";\nexport { defineMeekoConfig } from \"./framework/config-type.js\";\nexport type { MeekoConfig } from \"./framework/config-type.js\";\nexport type { NavigationContext, Navigator, RouteMatch, RouteRecord } from \"./framework/router.js\";\nexport { mountClientApp } from \"./framework/client.js\";\nexport type { ClientApp, ClientAppOptions, ClientComponent, ClientRoute } from \"./framework/client.js\";\nexport { definePage, PageInputError } from \"./framework/page.js\";\nexport type { PageDefinition, PageLoadContext, PageSchema } from \"./framework/page.js\";\nexport { StateStore, useState } from \"./framework/state.js\";\nexport { ClientError, createApiClient, createBrowserClientOptions, createPublicServerManifest, createRpcClient } from \"./framework/clients.js\";\nexport type { ApiClient, ApiOperation, ClientOptions, PublicApiEndpoint, PublicRpcEndpoint, PublicServerManifest, RpcClient, RpcOperation } from \"./framework/clients.js\";\nexport { createSocketClient, createSocketRegistry, useSocket, useSocketChannel } from \"./framework/socket.js\";\nexport type { SocketClient, SocketContract, SocketSchema, SocketClientOptions, SocketRegistry, SocketRegistryOptions } from \"./framework/socket.js\";\n\n/** Augmented by generated declarations in .meeko/types/routes.d.ts. */\nexport interface GeneratedRoutes {}\n\ntype RouteKey = keyof GeneratedRoutes extends never ? string : keyof GeneratedRoutes & string;\ntype RouteParams<K extends string> = K extends keyof GeneratedRoutes ? GeneratedRoutes[K] : Record<string, string>;\ntype HrefOptions<K extends string> = (keyof RouteParams<K> extends never\n ? { params?: RouteParams<K> }\n : { params: RouteParams<K> }) & {\n search?: Readonly<Record<string, string | number | boolean | null | undefined>>;\n};\ntype HrefArguments<K extends string> = K extends keyof GeneratedRoutes\n ? keyof RouteParams<K> extends never ? [options?: HrefOptions<K>] : [options: HrefOptions<K>]\n : [options?: HrefOptions<K>];\n\n/** Builds a checked URL when the project's route declarations are installed. */\nexport function href<K extends RouteKey>(routes: readonly RouteRecord[], id: K, ...[options]: HrefArguments<K>): string {\n return runtimeHref(routes, id, options as Parameters<typeof runtimeHref>[2] | undefined);\n}\n"]}
1
+ {"version":3,"file":"framework.js","sourceRoot":"","sources":["../src/framework.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAG/D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE/I,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAiB9G,gFAAgF;AAChF,MAAM,UAAU,IAAI,CAAqB,MAA8B,EAAE,EAAK,EAAE,GAAG,CAAC,OAAO,CAAmB;IAC5G,OAAO,WAAW,CAAC,MAAM,EAAE,EAAE,EAAE,OAAwD,CAAC,CAAC;AAC3F,CAAC","sourcesContent":["import { href as runtimeHref } from \"./framework/router.js\";\nimport type { RouteRecord } from \"./framework/router.js\";\n\nexport { createNavigator, createRouteManifest, matchRoute } from \"./framework/router.js\";\nexport { defineMeekoConfig } from \"./framework/config-type.js\";\nexport type { MeekoConfig } from \"./framework/config-type.js\";\nexport type { NavigationContext, Navigator, RouteMatch, RouteRecord } from \"./framework/router.js\";\nexport { mountClientApp } from \"./framework/client.js\";\nexport type { ClientApp, ClientAppOptions, ClientComponent, ClientRoute } from \"./framework/client.js\";\nexport { definePage, PageInputError } from \"./framework/page.js\";\nexport type { PageDefinition, PageLoadContext, PageSchema } from \"./framework/page.js\";\nexport { StateStore, useState } from \"./framework/state.js\";\nexport { defineRouteMiddleware } from \"./framework/middleware.js\";\nexport type { RouteMiddleware, RouteMiddlewareContext } from \"./framework/middleware.js\";\nexport { ClientError, createApiClient, createBrowserClientOptions, createPublicServerManifest, createRpcClient } from \"./framework/clients.js\";\nexport type { ApiClient, ApiOperation, ClientOptions, PublicApiEndpoint, PublicRpcEndpoint, PublicServerManifest, RpcClient, RpcOperation } from \"./framework/clients.js\";\nexport { createSocketClient, createSocketRegistry, useSocket, useSocketChannel } from \"./framework/socket.js\";\nexport type { SocketClient, SocketContract, SocketSchema, SocketClientOptions, SocketRegistry, SocketRegistryOptions } from \"./framework/socket.js\";\n\n/** Augmented by generated declarations in .meeko/types/routes.d.ts. */\nexport interface GeneratedRoutes {}\n\ntype RouteKey = keyof GeneratedRoutes extends never ? string : keyof GeneratedRoutes & string;\ntype RouteParams<K extends string> = K extends keyof GeneratedRoutes ? GeneratedRoutes[K] : Record<string, string>;\ntype HrefOptions<K extends string> = (keyof RouteParams<K> extends never\n ? { params?: never }\n : { params: RouteParams<K> }) & {\n search?: Readonly<Record<string, string | number | boolean | null | undefined>>;\n};\ntype HrefArguments<K extends string> = K extends keyof GeneratedRoutes\n ? keyof RouteParams<K> extends never ? [options?: HrefOptions<K>] : [options: HrefOptions<K>]\n : [options?: HrefOptions<K>];\n\n/** Builds a checked URL when the project's route declarations are installed. */\nexport function href<K extends RouteKey>(routes: readonly RouteRecord[], id: K, ...[options]: HrefArguments<K>): string {\n return runtimeHref(routes, id, options as Parameters<typeof runtimeHref>[2] | undefined);\n}\n"]}
package/dist/hmr.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isWritableSignal } from "./reactivity.js";
1
+ import { captureParentOwner, isWritableSignal } from "./reactivity.js";
2
2
  const enabled = new Set();
3
3
  const instances = new Map();
4
4
  /** Copia solo las señales editables de un setup, sin serializarlas. */
@@ -27,7 +27,7 @@ export function enableHotModule(id) {
27
27
  function registerHotTarget(id, key, target, props, component, snapshot, dispose) {
28
28
  if (!enabled.has(id))
29
29
  return dispose;
30
- const entry = { target, props, component: component, snapshot, dispose };
30
+ const entry = { target, props, component: component, snapshot, dispose, withParent: captureParentOwner() };
31
31
  let group = instances.get(id);
32
32
  if (!group) {
33
33
  group = new Map();
@@ -40,12 +40,17 @@ function registerHotTarget(id, key, target, props, component, snapshot, dispose)
40
40
  }
41
41
  const slot = {
42
42
  current: entry,
43
- stop: () => {
43
+ forget: () => {
44
44
  if (group.get(key) !== slot)
45
45
  return;
46
46
  group.delete(key);
47
47
  if (group.size === 0)
48
48
  instances.delete(id);
49
+ },
50
+ stop: () => {
51
+ if (group.get(key) !== slot)
52
+ return;
53
+ slot.forget();
49
54
  slot.current.dispose();
50
55
  },
51
56
  };
@@ -80,7 +85,9 @@ function replaceScope(scope, html) {
80
85
  export function replaceHotStyle(id, css) {
81
86
  if (typeof document === "undefined")
82
87
  return;
83
- let style = document.querySelector(`style[data-meeko-hot="${id}"]`);
88
+ // Module IDs are file paths and may contain CSS selector metacharacters.
89
+ let style = [...document.querySelectorAll("style[data-meeko-hot]")]
90
+ .find((candidate) => candidate.dataset.meekoHot === id);
84
91
  if (!style) {
85
92
  style = document.createElement("style");
86
93
  style.dataset.meekoHot = id;
@@ -99,26 +106,20 @@ export function replaceHotModule(id, next) {
99
106
  const entry = slot.current;
100
107
  const state = entry.snapshot();
101
108
  const previous = entry.target.kind === "root" ? entry.target.root.innerHTML : scopeRange(entry.target.scope).cloneContents();
102
- return { slot, entry, state, html: component.renderToString(entry.props, state), previous };
109
+ return { slot, entry, state, html: entry.withParent(() => component.renderToString(entry.props, state)), previous };
103
110
  });
104
- for (const { slot, entry, state, html, previous } of prepared) {
105
- entry.dispose();
106
- if (entry.target.kind === "root")
107
- entry.target.root.innerHTML = html;
108
- else
109
- replaceScope(entry.target.scope, html);
110
- try {
111
- if (entry.target.kind === "root")
112
- component.hydrate(entry.target.root, entry.props, state);
113
- else {
114
- if (!component.hydrateScope)
115
- throw new Error("Updated Meeko child component has no hydrateScope export");
116
- component.hydrateScope(entry.target.scope, entry.props, state);
111
+ const committed = [];
112
+ const rollback = ({ slot, entry, state, previous }, failures) => {
113
+ const partial = slot.current !== entry ? slot.current : undefined;
114
+ if (partial) {
115
+ try {
116
+ partial.dispose();
117
+ }
118
+ catch (cleanupError) {
119
+ failures.push(cleanupError);
117
120
  }
118
- if (slot.current === entry)
119
- throw new Error("Updated Meeko component did not register its hydrated instance");
120
121
  }
121
- catch (error) {
122
+ try {
122
123
  if (entry.target.kind === "root")
123
124
  entry.target.root.innerHTML = previous;
124
125
  else {
@@ -126,11 +127,60 @@ export function replaceHotModule(id, next) {
126
127
  range.deleteContents();
127
128
  range.insertNode(previous);
128
129
  }
130
+ if (entry.target.kind === "root") {
131
+ const { root } = entry.target;
132
+ entry.withParent(() => entry.component.hydrate(root, entry.props, state));
133
+ }
134
+ else {
135
+ const { scope } = entry.target;
136
+ entry.withParent(() => entry.component.hydrateScope?.(scope, entry.props, state));
137
+ }
138
+ if (slot.current === entry || slot.current === partial)
139
+ throw new Error("Restored Meeko component did not register its hydrated instance");
140
+ }
141
+ catch (rollbackError) {
142
+ failures.push(rollbackError);
143
+ if (slot.current !== entry && slot.current !== partial) {
144
+ try {
145
+ slot.current.dispose();
146
+ }
147
+ catch (cleanupError) {
148
+ failures.push(cleanupError);
149
+ }
150
+ }
151
+ slot.forget();
152
+ }
153
+ };
154
+ for (const item of prepared) {
155
+ const { slot, entry, state, html } = item;
156
+ try {
157
+ entry.dispose();
129
158
  if (entry.target.kind === "root")
130
- entry.component.hydrate(entry.target.root, entry.props, state);
159
+ entry.target.root.innerHTML = html;
131
160
  else
132
- entry.component.hydrateScope?.(entry.target.scope, entry.props, state);
133
- throw error;
161
+ replaceScope(entry.target.scope, html);
162
+ if (entry.target.kind === "root") {
163
+ const { root } = entry.target;
164
+ entry.withParent(() => component.hydrate(root, entry.props, state));
165
+ }
166
+ else {
167
+ if (!component.hydrateScope)
168
+ throw new Error("Updated Meeko child component has no hydrateScope export");
169
+ const { scope } = entry.target;
170
+ entry.withParent(() => component.hydrateScope(scope, entry.props, state));
171
+ }
172
+ if (slot.current === entry)
173
+ throw new Error("Updated Meeko component did not register its hydrated instance");
174
+ committed.push(item);
175
+ }
176
+ catch (error) {
177
+ const failures = [error];
178
+ rollback(item, failures);
179
+ for (const previous of committed.reverse())
180
+ rollback(previous, failures);
181
+ if (failures.length === 1)
182
+ throw error;
183
+ throw new AggregateError(failures, "Meeko hot replacement and recovery failed");
134
184
  }
135
185
  }
136
186
  replaceHotStyle(id, component.css ?? "");
package/dist/hmr.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hmr.js","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"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","sourcesContent":["import { isWritableSignal } from \"./reactivity.js\";\nimport type { DomScope } from \"./dom.js\";\n\n/** Valores de señales transferidos en memoria entre versiones de un componente. */\nexport type HotState = Record<string, unknown>;\n\n/** Contrato mínimo de un módulo Meeko reemplazable en desarrollo. */\nexport interface HotComponent<Props = unknown> {\n renderToString(props: Props, state?: HotState): string;\n hydrate(root: Element, props: Props, state?: HotState): () => void;\n hydrateScope?(scope: DomScope, props: Props, state?: HotState): () => void;\n css?: string;\n}\n\ninterface HotInstance {\n target: { kind: \"root\"; root: Element } | { kind: \"scope\"; scope: DomScope };\n props: unknown;\n component: HotComponent;\n snapshot: () => HotState;\n dispose: () => void;\n}\n\ninterface HotSlot {\n current: HotInstance;\n stop: () => void;\n}\n\nconst enabled = new Set<string>();\nconst instances = new Map<string, Map<Element | Comment, HotSlot>>();\n\n/** Copia solo las señales editables de un setup, sin serializarlas. */\nexport function snapshotSignals(bindings: Record<string, unknown>): HotState {\n const state: HotState = {};\n for (const [name, value] of Object.entries(bindings)) {\n if (isWritableSignal(value)) state[name] = value.value;\n }\n return state;\n}\n\n/** Restaura señales compatibles antes de renderizar o hidratar la nueva versión. */\nexport function restoreSignals(bindings: Record<string, unknown>, state?: HotState): void {\n if (!state) return;\n for (const [name, value] of Object.entries(state)) {\n const target = bindings[name];\n if (isWritableSignal(target)) target.value = value;\n }\n}\n\n/** Activa el seguimiento de instancias solo para módulos servidos por Vite. */\nexport function enableHotModule(id: string): void {\n enabled.add(id);\n}\n\nfunction registerHotTarget<Props>(id: string, key: Element | Comment, target: HotInstance[\"target\"], props: Props, component: HotComponent<Props>, snapshot: () => HotState, dispose: () => void): () => void {\n if (!enabled.has(id)) return dispose;\n const entry: HotInstance = { target, props, component: component as HotComponent, snapshot, dispose };\n let group = instances.get(id);\n if (!group) {\n group = new Map();\n instances.set(id, group);\n }\n const existing = group.get(key);\n if (existing) {\n existing.current = entry;\n return existing.stop;\n }\n const slot: HotSlot = {\n current: entry,\n stop: () => {\n if (group.get(key) !== slot) return;\n group.delete(key);\n if (group.size === 0) instances.delete(id);\n slot.current.dispose();\n },\n };\n group.set(key, slot);\n return slot.stop;\n}\n\n/** Registra una raíz hidratada y elimina el registro al liberarla. */\nexport function registerHotInstance<Props>(id: string, root: Element, props: Props, component: HotComponent<Props>, snapshot: () => HotState, dispose: () => void): () => void {\n return registerHotTarget(id, root, { kind: \"root\", root }, props, component, snapshot, dispose);\n}\n\n/** Registra un componente hijo delimitado por comentarios para actualizarlo sin tocar al padre. */\nexport function registerHotScope<Props>(id: string, scope: DomScope, props: Props, component: HotComponent<Props>, snapshot: () => HotState, dispose: () => void): () => void {\n if (!scope.start || !scope.end) throw new Error(\"Hot child component requires a bounded scope\");\n return registerHotTarget(id, scope.start, { kind: \"scope\", scope }, props, component, snapshot, dispose);\n}\n\nfunction scopeRange(scope: DomScope): Range {\n if (!scope.start || !scope.end) throw new Error(\"Hot child component lost its markers\");\n const range = scope.start.ownerDocument.createRange();\n range.setStartAfter(scope.start);\n range.setEndBefore(scope.end);\n return range;\n}\n\nfunction replaceScope(scope: DomScope, html: string): void {\n const range = scopeRange(scope);\n const content = range.createContextualFragment(html);\n range.deleteContents();\n range.insertNode(content);\n}\n\n/** Aplica CSS nuevo después de la hoja virtual cargada por Vite. */\nexport function replaceHotStyle(id: string, css: string): void {\n if (typeof document === \"undefined\") return;\n let style = document.querySelector<HTMLStyleElement>(`style[data-meeko-hot=\"${id}\"]`);\n if (!style) {\n style = document.createElement(\"style\");\n style.dataset.meekoHot = id;\n document.head.appendChild(style);\n }\n style.textContent = css;\n}\n\n/** Sustituye las instancias activas sin recargar la página y conserva señales editables. */\nexport function replaceHotModule(id: string, next: unknown): void {\n if (typeof next !== \"object\" || next === null || !(\"renderToString\" in next) || !(\"hydrate\" in next)\n || typeof next.renderToString !== \"function\" || typeof next.hydrate !== \"function\") {\n throw new TypeError(\"Invalid Meeko hot module\");\n }\n const component = next as HotComponent;\n const prepared = [...(instances.get(id)?.values() ?? [])].map((slot) => {\n const entry = slot.current;\n const state = entry.snapshot();\n const previous = entry.target.kind === \"root\" ? entry.target.root.innerHTML : scopeRange(entry.target.scope).cloneContents();\n return { slot, entry, state, html: component.renderToString(entry.props, state), previous };\n });\n for (const { slot, entry, state, html, previous } of prepared) {\n entry.dispose();\n if (entry.target.kind === \"root\") entry.target.root.innerHTML = html;\n else replaceScope(entry.target.scope, html);\n try {\n if (entry.target.kind === \"root\") component.hydrate(entry.target.root, entry.props, state);\n else {\n if (!component.hydrateScope) throw new Error(\"Updated Meeko child component has no hydrateScope export\");\n component.hydrateScope(entry.target.scope, entry.props, state);\n }\n if (slot.current === entry) throw new Error(\"Updated Meeko component did not register its hydrated instance\");\n } catch (error) {\n if (entry.target.kind === \"root\") entry.target.root.innerHTML = previous as string;\n else {\n const range = scopeRange(entry.target.scope);\n range.deleteContents();\n range.insertNode(previous as DocumentFragment);\n }\n if (entry.target.kind === \"root\") entry.component.hydrate(entry.target.root, entry.props, state);\n else entry.component.hydrateScope?.(entry.target.scope, entry.props, state);\n throw error;\n }\n }\n replaceHotStyle(id, component.css ?? \"\");\n}\n"]}
1
+ {"version":3,"file":"hmr.js","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"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","sourcesContent":["import { captureParentOwner, isWritableSignal } from \"./reactivity.js\";\nimport type { DomScope } from \"./dom.js\";\n\n/** Valores de señales transferidos en memoria entre versiones de un componente. */\nexport type HotState = Record<string, unknown>;\n\n/** Contrato mínimo de un módulo Meeko reemplazable en desarrollo. */\nexport interface HotComponent<Props = unknown> {\n renderToString(props: Props, state?: HotState): string;\n hydrate(root: Element, props: Props, state?: HotState): () => void;\n hydrateScope?(scope: DomScope, props: Props, state?: HotState): () => void;\n css?: string;\n}\n\ninterface HotInstance {\n target: { kind: \"root\"; root: Element } | { kind: \"scope\"; scope: DomScope };\n props: unknown;\n component: HotComponent;\n snapshot: () => HotState;\n dispose: () => void;\n withParent: <T>(work: () => T) => T;\n}\n\ninterface HotSlot {\n current: HotInstance;\n stop: () => void;\n forget: () => void;\n}\n\nconst enabled = new Set<string>();\nconst instances = new Map<string, Map<Element | Comment, HotSlot>>();\n\n/** Copia solo las señales editables de un setup, sin serializarlas. */\nexport function snapshotSignals(bindings: Record<string, unknown>): HotState {\n const state: HotState = {};\n for (const [name, value] of Object.entries(bindings)) {\n if (isWritableSignal(value)) state[name] = value.value;\n }\n return state;\n}\n\n/** Restaura señales compatibles antes de renderizar o hidratar la nueva versión. */\nexport function restoreSignals(bindings: Record<string, unknown>, state?: HotState): void {\n if (!state) return;\n for (const [name, value] of Object.entries(state)) {\n const target = bindings[name];\n if (isWritableSignal(target)) target.value = value;\n }\n}\n\n/** Activa el seguimiento de instancias solo para módulos servidos por Vite. */\nexport function enableHotModule(id: string): void {\n enabled.add(id);\n}\n\nfunction registerHotTarget<Props>(id: string, key: Element | Comment, target: HotInstance[\"target\"], props: Props, component: HotComponent<Props>, snapshot: () => HotState, dispose: () => void): () => void {\n if (!enabled.has(id)) return dispose;\n const entry: HotInstance = { target, props, component: component as HotComponent, snapshot, dispose, withParent: captureParentOwner() };\n let group = instances.get(id);\n if (!group) {\n group = new Map();\n instances.set(id, group);\n }\n const existing = group.get(key);\n if (existing) {\n existing.current = entry;\n return existing.stop;\n }\n const slot: HotSlot = {\n current: entry,\n forget: () => {\n if (group.get(key) !== slot) return;\n group.delete(key);\n if (group.size === 0) instances.delete(id);\n },\n stop: () => {\n if (group.get(key) !== slot) return;\n slot.forget();\n slot.current.dispose();\n },\n };\n group.set(key, slot);\n return slot.stop;\n}\n\n/** Registra una raíz hidratada y elimina el registro al liberarla. */\nexport function registerHotInstance<Props>(id: string, root: Element, props: Props, component: HotComponent<Props>, snapshot: () => HotState, dispose: () => void): () => void {\n return registerHotTarget(id, root, { kind: \"root\", root }, props, component, snapshot, dispose);\n}\n\n/** Registra un componente hijo delimitado por comentarios para actualizarlo sin tocar al padre. */\nexport function registerHotScope<Props>(id: string, scope: DomScope, props: Props, component: HotComponent<Props>, snapshot: () => HotState, dispose: () => void): () => void {\n if (!scope.start || !scope.end) throw new Error(\"Hot child component requires a bounded scope\");\n return registerHotTarget(id, scope.start, { kind: \"scope\", scope }, props, component, snapshot, dispose);\n}\n\nfunction scopeRange(scope: DomScope): Range {\n if (!scope.start || !scope.end) throw new Error(\"Hot child component lost its markers\");\n const range = scope.start.ownerDocument.createRange();\n range.setStartAfter(scope.start);\n range.setEndBefore(scope.end);\n return range;\n}\n\nfunction replaceScope(scope: DomScope, html: string): void {\n const range = scopeRange(scope);\n const content = range.createContextualFragment(html);\n range.deleteContents();\n range.insertNode(content);\n}\n\n/** Aplica CSS nuevo después de la hoja virtual cargada por Vite. */\nexport function replaceHotStyle(id: string, css: string): void {\n if (typeof document === \"undefined\") return;\n // Module IDs are file paths and may contain CSS selector metacharacters.\n let style = [...document.querySelectorAll<HTMLStyleElement>(\"style[data-meeko-hot]\")]\n .find((candidate) => candidate.dataset.meekoHot === id);\n if (!style) {\n style = document.createElement(\"style\");\n style.dataset.meekoHot = id;\n document.head.appendChild(style);\n }\n style.textContent = css;\n}\n\n/** Sustituye las instancias activas sin recargar la página y conserva señales editables. */\nexport function replaceHotModule(id: string, next: unknown): void {\n if (typeof next !== \"object\" || next === null || !(\"renderToString\" in next) || !(\"hydrate\" in next)\n || typeof next.renderToString !== \"function\" || typeof next.hydrate !== \"function\") {\n throw new TypeError(\"Invalid Meeko hot module\");\n }\n const component = next as HotComponent;\n const prepared = [...(instances.get(id)?.values() ?? [])].map((slot) => {\n const entry = slot.current;\n const state = entry.snapshot();\n const previous = entry.target.kind === \"root\" ? entry.target.root.innerHTML : scopeRange(entry.target.scope).cloneContents();\n return { slot, entry, state, html: entry.withParent(() => component.renderToString(entry.props, state)), previous };\n });\n type Prepared = (typeof prepared)[number];\n const committed: Prepared[] = [];\n const rollback = ({ slot, entry, state, previous }: Prepared, failures: unknown[]): void => {\n const partial = slot.current !== entry ? slot.current : undefined;\n if (partial) {\n try { partial.dispose(); }\n catch (cleanupError) { failures.push(cleanupError); }\n }\n try {\n if (entry.target.kind === \"root\") entry.target.root.innerHTML = previous as string;\n else {\n const range = scopeRange(entry.target.scope);\n range.deleteContents();\n range.insertNode(previous as DocumentFragment);\n }\n if (entry.target.kind === \"root\") {\n const { root } = entry.target;\n entry.withParent(() => entry.component.hydrate(root, entry.props, state));\n } else {\n const { scope } = entry.target;\n entry.withParent(() => entry.component.hydrateScope?.(scope, entry.props, state));\n }\n if (slot.current === entry || slot.current === partial) throw new Error(\"Restored Meeko component did not register its hydrated instance\");\n } catch (rollbackError) {\n failures.push(rollbackError);\n if (slot.current !== entry && slot.current !== partial) {\n try { slot.current.dispose(); }\n catch (cleanupError) { failures.push(cleanupError); }\n }\n slot.forget();\n }\n };\n for (const item of prepared) {\n const { slot, entry, state, html } = item;\n try {\n entry.dispose();\n if (entry.target.kind === \"root\") entry.target.root.innerHTML = html;\n else replaceScope(entry.target.scope, html);\n if (entry.target.kind === \"root\") {\n const { root } = entry.target;\n entry.withParent(() => component.hydrate(root, entry.props, state));\n }\n else {\n if (!component.hydrateScope) throw new Error(\"Updated Meeko child component has no hydrateScope export\");\n const { scope } = entry.target;\n entry.withParent(() => component.hydrateScope!(scope, entry.props, state));\n }\n if (slot.current === entry) throw new Error(\"Updated Meeko component did not register its hydrated instance\");\n committed.push(item);\n } catch (error) {\n const failures: unknown[] = [error];\n rollback(item, failures);\n for (const previous of committed.reverse()) rollback(previous, failures);\n if (failures.length === 1) throw error;\n throw new AggregateError(failures, \"Meeko hot replacement and recovery failed\");\n }\n }\n replaceHotStyle(id, component.css ?? \"\");\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { batch, computed, createContext, createRoot, effect, inject, onCleanup, provide, signal, untrack } from "./reactivity.js";
2
2
  export type { ContextKey, ReadonlySignal, Signal } from "./reactivity.js";
3
+ export { provideErrorHandler } from "./component-errors.js";
4
+ export type { ComponentErrorHandler } from "./component-errors.js";
3
5
  export { attributeValue, escapeHtml, renderAttribute, serializeJson } from "./server.js";
4
6
  export { bindAttribute, bindComponent, bindEvent, bindFor, bindIf, bindText, keyToken, renderComponent, renderFor, renderIf, withStateSnapshot } from "./dom.js";
5
7
  export type { Component, ComponentModule, ComponentRelease, DomScope } from "./dom.js";
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { batch, computed, createContext, createRoot, effect, inject, onCleanup, provide, signal, untrack } from "./reactivity.js";
2
+ export { provideErrorHandler } from "./component-errors.js";
2
3
  export { attributeValue, escapeHtml, renderAttribute, serializeJson } from "./server.js";
3
4
  export { bindAttribute, bindComponent, bindEvent, bindFor, bindIf, bindText, keyToken, renderComponent, renderFor, renderIf, withStateSnapshot } from "./dom.js";
4
5
  export { enableHotModule, registerHotInstance, registerHotScope, replaceHotModule, replaceHotStyle, restoreSignals, snapshotSignals } from "./hmr.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAElI,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAEjK,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEtJ,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC","sourcesContent":["export { batch, computed, createContext, createRoot, effect, inject, onCleanup, provide, signal, untrack } from \"./reactivity.js\";\nexport type { ContextKey, ReadonlySignal, Signal } from \"./reactivity.js\";\nexport { attributeValue, escapeHtml, renderAttribute, serializeJson } from \"./server.js\";\nexport { bindAttribute, bindComponent, bindEvent, bindFor, bindIf, bindText, keyToken, renderComponent, renderFor, renderIf, withStateSnapshot } from \"./dom.js\";\nexport type { Component, ComponentModule, ComponentRelease, DomScope } from \"./dom.js\";\nexport { enableHotModule, registerHotInstance, registerHotScope, replaceHotModule, replaceHotStyle, restoreSignals, snapshotSignals } from \"./hmr.js\";\nexport type { HotComponent, HotState } from \"./hmr.js\";\nexport { registerDevtoolsInstance } from \"./devtools.js\";\nexport type { DevtoolsEvent, DevtoolsHook, DevtoolsInstance, DevtoolsRegistration, DevtoolsSnapshot, DevtoolsValue } from \"./devtools.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAElI,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAEjK,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEtJ,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC","sourcesContent":["export { batch, computed, createContext, createRoot, effect, inject, onCleanup, provide, signal, untrack } from \"./reactivity.js\";\nexport type { ContextKey, ReadonlySignal, Signal } from \"./reactivity.js\";\nexport { provideErrorHandler } from \"./component-errors.js\";\nexport type { ComponentErrorHandler } from \"./component-errors.js\";\nexport { attributeValue, escapeHtml, renderAttribute, serializeJson } from \"./server.js\";\nexport { bindAttribute, bindComponent, bindEvent, bindFor, bindIf, bindText, keyToken, renderComponent, renderFor, renderIf, withStateSnapshot } from \"./dom.js\";\nexport type { Component, ComponentModule, ComponentRelease, DomScope } from \"./dom.js\";\nexport { enableHotModule, registerHotInstance, registerHotScope, replaceHotModule, replaceHotStyle, restoreSignals, snapshotSignals } from \"./hmr.js\";\nexport type { HotComponent, HotState } from \"./hmr.js\";\nexport { registerDevtoolsInstance } from \"./devtools.js\";\nexport type { DevtoolsEvent, DevtoolsHook, DevtoolsInstance, DevtoolsRegistration, DevtoolsSnapshot, DevtoolsValue } from \"./devtools.js\";\n"]}
package/dist/init.js CHANGED
@@ -125,7 +125,33 @@ const server = createServer(async (request, response) => {
125
125
 
126
126
  const port = Number(process.env.PORT ?? 4173);
127
127
  server.listen(port, "127.0.0.1", () => console.log("http://127.0.0.1:" + port));
128
- process.on("SIGINT", () => { server.close(); void vite?.close(); });
128
+ let shutdownPromise;
129
+ const closeServer = () => new Promise((resolveClose, rejectClose) => {
130
+ server.close((error) => error ? rejectClose(error) : resolveClose());
131
+ });
132
+ async function shutdown() {
133
+ if (shutdownPromise) return shutdownPromise;
134
+ shutdownPromise = (async () => {
135
+ const results = await Promise.allSettled([
136
+ closeServer(),
137
+ vite?.close() ?? Promise.resolve(),
138
+ ]);
139
+ const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
140
+ if (failures.length === 1) throw failures[0];
141
+ if (failures.length > 1) throw new AggregateError(failures, "Meeko server shutdown failed");
142
+ })();
143
+ return shutdownPromise;
144
+ }
145
+ const onSignal = () => {
146
+ process.off("SIGINT", onSignal);
147
+ process.off("SIGTERM", onSignal);
148
+ void shutdown().catch((error) => {
149
+ try { console.error("Meeko server shutdown failed", error); }
150
+ finally { process.exitCode = 1; }
151
+ });
152
+ };
153
+ process.once("SIGINT", onSignal);
154
+ process.once("SIGTERM", onSignal);
129
155
  `,
130
156
  "README.md": `# Meeko SSR starter
131
157