@loutrejs/loutre 0.9.2 → 0.10.1

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 (145) hide show
  1. package/dist/adapters/aws-lambda.d.ts +1 -1
  2. package/dist/adapters/aws-lambda.js +8 -2
  3. package/dist/adapters/bun.d.ts +3 -2
  4. package/dist/adapters/bun.js +12 -6
  5. package/dist/adapters/cloudflare-workers.d.ts +1 -1
  6. package/dist/adapters/cloudflare-workers.js +2 -1
  7. package/dist/adapters/electron.d.ts +1 -1
  8. package/dist/adapters/electron.js +8 -51
  9. package/dist/adapters/server-transports.js +2 -2
  10. package/dist/adapters/websocket-native.js +4 -1
  11. package/dist/application/definition.d.ts +4 -7
  12. package/dist/application/definition.js +3 -1
  13. package/dist/application/kernel.d.ts +0 -2
  14. package/dist/application/kernel.js +1 -3
  15. package/dist/core/args.d.ts +1 -0
  16. package/dist/core/args.js +2 -1
  17. package/dist/core/env.d.ts +1 -0
  18. package/dist/core/env.js +2 -1
  19. package/dist/core/extension.d.ts +23 -13
  20. package/dist/core/extension.js +3 -1
  21. package/dist/core/generic-layer.d.ts +9 -2
  22. package/dist/core/generic-layer.js +2 -1
  23. package/dist/core/identity.d.ts +18 -0
  24. package/dist/core/identity.js +5 -0
  25. package/dist/core/index.d.ts +13 -5
  26. package/dist/core/index.js +7 -1
  27. package/dist/core/injection.d.ts +2 -1
  28. package/dist/core/instrumentation.d.ts +7 -5
  29. package/dist/core/lifecycle.d.ts +4 -0
  30. package/dist/core/logger-identity.d.ts +4 -0
  31. package/dist/core/logger-identity.js +9 -0
  32. package/dist/core/logger-internal.d.ts +5 -0
  33. package/dist/core/logger-internal.js +27 -0
  34. package/dist/core/logger-module.d.ts +5 -0
  35. package/dist/core/logger-module.js +8 -0
  36. package/dist/core/logger.d.ts +42 -0
  37. package/dist/core/logger.js +221 -0
  38. package/dist/core/model/context.d.ts +6 -3
  39. package/dist/core/model/context.js +17 -17
  40. package/dist/core/model/identity.d.ts +19 -0
  41. package/dist/core/model/identity.js +8 -0
  42. package/dist/core/model/lifecycle.d.ts +3 -2
  43. package/dist/core/model/lifecycle.js +7 -3
  44. package/dist/core/model/logger.d.ts +3 -0
  45. package/dist/core/model/logger.js +59 -0
  46. package/dist/core/model/modules.d.ts +4 -1
  47. package/dist/core/model/modules.js +6 -4
  48. package/dist/core/model/providers.d.ts +1 -1
  49. package/dist/core/model/providers.js +8 -8
  50. package/dist/core/model/types.d.ts +25 -18
  51. package/dist/core/model/validation.js +3 -2
  52. package/dist/core/model/visibility.js +7 -2
  53. package/dist/core/model.js +15 -7
  54. package/dist/core/module.d.ts +7 -7
  55. package/dist/core/module.js +0 -3
  56. package/dist/core/runtime-input.d.ts +2 -1
  57. package/dist/core/source-location.d.ts +14 -2
  58. package/dist/core/source-location.js +8 -3
  59. package/dist/core/token-internal.js +3 -1
  60. package/dist/core/token.d.ts +4 -2
  61. package/dist/core/token.js +3 -0
  62. package/dist/core/units.d.ts +22 -0
  63. package/dist/core/units.js +14 -0
  64. package/dist/devtools/capsule.d.ts +17 -13
  65. package/dist/devtools/capsule.js +14 -10
  66. package/dist/devtools/decoder.d.ts +15 -0
  67. package/dist/devtools/decoder.js +179 -0
  68. package/dist/devtools/identity-internal.d.ts +6 -0
  69. package/dist/devtools/identity-internal.js +5 -0
  70. package/dist/devtools/identity.d.ts +21 -0
  71. package/dist/devtools/identity.js +7 -0
  72. package/dist/devtools/index.d.ts +3 -0
  73. package/dist/devtools/index.js +2 -0
  74. package/dist/devtools/inspector.d.ts +18 -16
  75. package/dist/devtools/observer.d.ts +32 -28
  76. package/dist/devtools/runtime.js +34 -30
  77. package/dist/devtools/values.js +3 -5
  78. package/dist/generated/version.d.ts +1 -1
  79. package/dist/generated/version.js +1 -1
  80. package/dist/graph/model.d.ts +25 -14
  81. package/dist/http/auth.d.ts +4 -2
  82. package/dist/http/client.d.ts +6 -3
  83. package/dist/http/client.js +8 -9
  84. package/dist/http/cors-internal.d.ts +7 -5
  85. package/dist/http/cors-internal.js +8 -6
  86. package/dist/http/extension.d.ts +60 -21
  87. package/dist/http/extension.js +91 -20
  88. package/dist/http/identity.d.ts +20 -0
  89. package/dist/http/identity.js +10 -0
  90. package/dist/http/index.d.ts +6 -2
  91. package/dist/http/index.js +3 -1
  92. package/dist/http/openapi-identity.d.ts +9 -0
  93. package/dist/http/openapi-identity.js +4 -0
  94. package/dist/http/openapi.d.ts +1 -1
  95. package/dist/http/openapi.js +10 -7
  96. package/dist/http/path.d.ts +8 -5
  97. package/dist/http/route-validation.js +2 -1
  98. package/dist/http/runtime/request.d.ts +3 -2
  99. package/dist/http/runtime/request.js +1 -4
  100. package/dist/http/websocket-upgrade.d.ts +2 -0
  101. package/dist/http/websocket-upgrade.js +4 -0
  102. package/dist/internal/brand.d.ts +8 -0
  103. package/dist/internal/brand.js +1 -0
  104. package/dist/message-port/extension.d.ts +4 -2
  105. package/dist/message-port/extension.js +15 -8
  106. package/dist/message-port/identity.d.ts +9 -0
  107. package/dist/message-port/identity.js +16 -0
  108. package/dist/message-port/index.d.ts +2 -0
  109. package/dist/message-port/index.js +1 -0
  110. package/dist/message-port/transport.d.ts +4 -2
  111. package/dist/message-port/transport.js +6 -1
  112. package/dist/runtime/async-iterator-lifecycle.d.ts +2 -1
  113. package/dist/runtime/async-iterator-lifecycle.js +2 -1
  114. package/dist/runtime/capability.d.ts +6 -2
  115. package/dist/runtime/capability.js +3 -1
  116. package/dist/runtime/di.d.ts +1 -3
  117. package/dist/runtime/di.js +62 -35
  118. package/dist/runtime/error.d.ts +3 -1
  119. package/dist/runtime/index.d.ts +6 -2
  120. package/dist/runtime/index.js +3 -1
  121. package/dist/runtime/kernel.d.ts +0 -2
  122. package/dist/runtime/kernel.js +68 -30
  123. package/dist/runtime/logger-source.d.ts +4 -0
  124. package/dist/runtime/logger-source.js +47 -0
  125. package/dist/runtime/logger.d.ts +2 -38
  126. package/dist/runtime/logger.js +1 -209
  127. package/dist/runtime/node-capabilities.d.ts +1 -1
  128. package/dist/runtime/node-capabilities.js +2 -1
  129. package/dist/runtime/server-port.d.ts +5 -1
  130. package/dist/runtime/server-port.js +2 -1
  131. package/dist/runtime/shutdown-hooks.d.ts +1 -1
  132. package/dist/runtime/shutdown-hooks.js +5 -4
  133. package/dist/tasks/extension.d.ts +18 -16
  134. package/dist/tasks/extension.js +28 -23
  135. package/dist/tasks/identity.d.ts +9 -0
  136. package/dist/tasks/identity.js +4 -0
  137. package/dist/tasks/index.d.ts +1 -0
  138. package/dist/tasks/runtime/cron.d.ts +6 -2
  139. package/dist/tasks/runtime/cron.js +9 -0
  140. package/dist/websocket/extension.d.ts +19 -9
  141. package/dist/websocket/extension.js +103 -30
  142. package/dist/websocket/identity.d.ts +9 -0
  143. package/dist/websocket/identity.js +4 -0
  144. package/dist/websocket/index.d.ts +1 -0
  145. package/package.json +1 -1
@@ -1,6 +1,10 @@
1
- import { type ExecutionDefinition, type ExecutionContextView, type ExecutionExtensionDrainContext, type RuntimeCapabilityBinding, type SchemaOutput, type StandardSchemaV1 } from '../core/index.js';
1
+ import { type WebSocketSessionId, type WebSocketProcedureName, type NegotiatedSubprotocol } from './identity.js';
2
+ import { type HttpPathTemplate, type HttpNormalizedPath } from '../http/identity.js';
3
+ import type { ParsedHttpPath } from '../http/path.js';
4
+ import type { Descriptor } from '../internal/brand.js';
5
+ import { type ExecutionDefinition, type ExecutionContextView, type ExecutionExtensionDrainContext, type LayerState, type RuntimeCapabilityBinding, type SchemaOutput, type StandardSchemaV1 } from '../core/index.js';
2
6
  import type { HttpRequestHeadDefinition } from '../http/request-head.js';
3
- import { type HttpPathSegment } from '../http/index.js';
7
+ import { type HttpRawRouteDefinition } from '../http/index.js';
4
8
  export type WebSocketDataMessage = {
5
9
  readonly type: 'text';
6
10
  readonly data: string;
@@ -29,8 +33,8 @@ export interface WebSocketUpgradeOptions {
29
33
  readonly protocol?: string;
30
34
  }
31
35
  export interface WebSocketSession {
32
- readonly id: string;
33
- readonly protocol: string;
36
+ readonly id: WebSocketSessionId;
37
+ readonly protocol: NegotiatedSubprotocol;
34
38
  }
35
39
  export interface WebSocketServerDriver {
36
40
  readonly runtime: string;
@@ -57,6 +61,7 @@ export interface WebSocketBranchDefinition {
57
61
  }
58
62
  export interface WebSocketHandshakeDefinition extends HttpRequestHeadDefinition {
59
63
  readonly protocols?: readonly string[];
64
+ readonly middlewares?: HttpRawRouteDefinition['middlewares'];
60
65
  }
61
66
  export interface WebSocketRouteDefinition {
62
67
  readonly path: string;
@@ -64,7 +69,7 @@ export interface WebSocketRouteDefinition {
64
69
  readonly messages?: WebSocketMessageCodec;
65
70
  }
66
71
  export type WebSocketRouteTree = Readonly<Record<string, WebSocketRouteDefinition | WebSocketBranchDefinition>>;
67
- export interface WebSocketContract<TRoutes extends WebSocketRouteTree = WebSocketRouteTree> {
72
+ export interface WebSocketContract<TRoutes extends WebSocketRouteTree = WebSocketRouteTree> extends Descriptor<'websocket-contract'> {
68
73
  readonly kind: 'websocket-contract';
69
74
  readonly routes: TRoutes;
70
75
  }
@@ -95,6 +100,11 @@ type OpeningRequestInput<TRoute extends WebSocketRouteDefinition> = {
95
100
  };
96
101
  export type WebSocketHandlerContext<TRoute extends WebSocketRouteDefinition = WebSocketRouteDefinition> = {
97
102
  readonly request: Request;
103
+ readonly state: Readonly<TRoute extends {
104
+ readonly handshake: {
105
+ readonly middlewares: infer TLayers extends NonNullable<WebSocketHandshakeDefinition['middlewares']>;
106
+ };
107
+ } ? LayerState<TLayers> : {}>;
98
108
  readonly session: WebSocketSession;
99
109
  readonly protocol: string;
100
110
  readonly execution: ExecutionContextView;
@@ -116,10 +126,10 @@ export interface WebSocketImplementationDefinition<TContract extends WebSocketCo
116
126
  readonly factory: () => WebSocketHandlers<TContract>;
117
127
  }
118
128
  interface CompiledWebSocketRoute {
119
- readonly name: string;
120
- readonly path: string;
121
- readonly normalizedPath: string;
122
- readonly segments: readonly HttpPathSegment[];
129
+ readonly name: WebSocketProcedureName;
130
+ readonly path: HttpPathTemplate;
131
+ readonly normalizedPath: HttpNormalizedPath;
132
+ readonly segments: ParsedHttpPath;
123
133
  readonly handshake?: WebSocketHandshakeDefinition;
124
134
  readonly messages?: WebSocketMessageCodec;
125
135
  }
@@ -1,4 +1,8 @@
1
- import { collectInjectedDependencies, defineExecution, defineExecutionExtension, runtimeCapability, runInInjectionContext, validateSchema, } from '../core/index.js';
1
+ import { dependencyConsumerId } from '../core/identity.js';
2
+ import { milliseconds } from '../core/units.js';
3
+ import { createWebSocketSessionId, webSocketProcedureName, negotiatedSubprotocol, observedCloseCode, } from './identity.js';
4
+ import { requestPathname, } from '../http/identity.js';
5
+ import { collectInjectedDependencies, getSourceLocation, composeLayers, defineExecution, defineExecutionExtension, runtimeCapability, runInInjectionContext, validateSchema, } from '../core/index.js';
2
6
  import { IngressGate } from '../runtime/ingress-gate.js';
3
7
  import { WebSocketConnectionNotOpenError, WebSocketMessageDecodeError, WebSocketMessageEncodeError, } from './errors.js';
4
8
  import { compareHttpPathSpecificity, HttpPathDecodeError, matchHttpPath, normalizeHttpPath, parseHttpPath, } from '../http/index.js';
@@ -8,20 +12,37 @@ export const websocketExtension = defineExecutionExtension({
8
12
  abiVersion: '2',
9
13
  name: 'loutre:websocket',
10
14
  compile(definition, context) {
15
+ const id = definition.name ||
16
+ `${context.moduleId}.websocket.${context.definitionIndex}`;
17
+ const routes = compileRouteTree(definition.contract.routes);
18
+ const dependencies = new Set(collectInjectedDependencies({
19
+ kind: 'execution',
20
+ id: dependencyConsumerId(`websocket:${id}`),
21
+ name: id,
22
+ }, () => definition.factory()));
23
+ for (const route of routes) {
24
+ route.handshake?.middlewares?.forEach((middleware, index) => {
25
+ for (const dependency of collectInjectedDependencies({
26
+ kind: 'layer',
27
+ id: dependencyConsumerId(`websocket:${id}:${route.name}:${index}`),
28
+ name: middleware.name,
29
+ }, () => middleware.factory()))
30
+ dependencies.add(dependency);
31
+ });
32
+ }
11
33
  return {
12
34
  kind: 'execution',
13
- id: definition.name ||
14
- `${context.moduleId}.websocket.${context.definitionIndex}`,
35
+ id,
15
36
  executionKind: 'websocket.session',
16
- dependencies: collectInjectedDependencies({
17
- kind: 'execution',
18
- id: `websocket:${definition.name || `${context.moduleId}.websocket.${context.definitionIndex}`}`,
19
- name: definition.name ||
20
- `${context.moduleId}.websocket.${context.definitionIndex}`,
21
- }, () => definition.factory()),
22
- capabilities: [WEBSOCKET_SERVER],
37
+ dependencies: [...dependencies],
38
+ capabilities: [
39
+ ...new Set([
40
+ WEBSOCKET_SERVER,
41
+ ...routes.flatMap((route) => route.handshake?.middlewares?.flatMap((middleware) => middleware.capabilities) ?? []),
42
+ ]),
43
+ ],
23
44
  compiled: Object.freeze({
24
- routes: Object.freeze(compileRouteTree(definition.contract.routes)),
45
+ routes: Object.freeze(routes),
25
46
  factory: definition.factory,
26
47
  }),
27
48
  };
@@ -50,6 +71,17 @@ export const websocketExtension = defineExecutionExtension({
50
71
  routes: execution.compiled.routes.map((route) => ({
51
72
  name: route.name,
52
73
  path: route.path,
74
+ ...(route.handshake?.middlewares?.length
75
+ ? {
76
+ middlewares: route.handshake.middlewares.map((middleware) => ({
77
+ name: middleware.name,
78
+ capabilities: middleware.capabilities.map((capability) => capability.id),
79
+ ...(getSourceLocation(middleware) === undefined
80
+ ? {}
81
+ : { source: getSourceLocation(middleware) }),
82
+ })),
83
+ }
84
+ : {}),
53
85
  ...(route.messages === undefined
54
86
  ? {}
55
87
  : {
@@ -69,7 +101,10 @@ export const websocketExtension = defineExecutionExtension({
69
101
  },
70
102
  });
71
103
  export function defineWebSocketContract(routes) {
72
- return Object.freeze({ kind: 'websocket-contract', routes });
104
+ return Object.freeze({
105
+ kind: 'websocket-contract',
106
+ routes,
107
+ });
73
108
  }
74
109
  export function defineWebSocketImplementation(definition) {
75
110
  return defineExecution(websocketExtension, {
@@ -104,7 +139,7 @@ export const websocket = Object.freeze({
104
139
  function compileRouteTree(tree, parentPath = '', prefix = '', routeNames = new Set()) {
105
140
  return Object.entries(tree).flatMap(([name, node]) => {
106
141
  if ('request' in node || 'responses' in node)
107
- throw new TypeError('WebSocket v2ではhandshakeを使用してください。');
142
+ throw new TypeError('Use handshake for WebSocket v2 routes.');
108
143
  if (isBranch(node)) {
109
144
  const branchPath = node.path ?? '';
110
145
  if (branchPath !== '')
@@ -115,14 +150,14 @@ function compileRouteTree(tree, parentPath = '', prefix = '', routeNames = new S
115
150
  const path = joinPath(parentPath, node.path);
116
151
  const segments = parseHttpPath(path);
117
152
  assertExactPathParams(path, segments, node.handshake?.params);
118
- const routeName = prefix ? `${prefix}.${name}` : name;
153
+ const routeName = webSocketProcedureName(prefix ? `${prefix}.${name}` : name);
119
154
  if (routeNames.has(routeName))
120
155
  throw new TypeError(`Duplicate nested WebSocket route name: ${routeName}`);
121
156
  routeNames.add(routeName);
122
157
  return [
123
158
  Object.freeze({
124
159
  name: routeName,
125
- path,
160
+ path: path,
126
161
  normalizedPath: normalizeHttpPath(segments),
127
162
  segments,
128
163
  ...(node.handshake === undefined
@@ -150,6 +185,9 @@ function assertExactPathParams(path, segments, params) {
150
185
  function snapshotHandshake(handshake) {
151
186
  return Object.freeze({
152
187
  ...handshake,
188
+ ...(handshake.middlewares === undefined
189
+ ? {}
190
+ : { middlewares: Object.freeze([...handshake.middlewares]) }),
153
191
  ...(handshake.params === undefined
154
192
  ? {}
155
193
  : { params: Object.freeze({ ...handshake.params }) }),
@@ -179,15 +217,16 @@ function createWebSocketRuntime(bindings, driver, applicationRuntime) {
179
217
  handlers.set(execution.id, runInInjectionContext({
180
218
  consumer: {
181
219
  kind: 'execution',
182
- id: `websocket:${execution.id}`,
220
+ id: dependencyConsumerId(`websocket:${execution.id}`),
183
221
  name: execution.id,
184
222
  },
185
223
  resolve: (token, consumer) => resolve(token, { consumer }),
186
224
  }, () => execution.compiled.factory()));
187
225
  }
188
226
  const routes = bindings
189
- .flatMap(({ execution }) => execution.compiled.routes.map((route) => ({
227
+ .flatMap(({ execution, resolve }) => execution.compiled.routes.map((route) => ({
190
228
  executionId: execution.id,
229
+ resolve,
191
230
  route,
192
231
  })))
193
232
  .toSorted((left, right) => compareHttpPathSpecificity(left.route.segments, right.route.segments));
@@ -196,6 +235,12 @@ function createWebSocketRuntime(bindings, driver, applicationRuntime) {
196
235
  if (state !== 'running') {
197
236
  return Response.json({ error: 'Service Unavailable' }, { status: 503 });
198
237
  }
238
+ if (request.method !== 'GET') {
239
+ return new Response('WebSocket handshake requires GET.', {
240
+ status: 405,
241
+ headers: { allow: 'GET' },
242
+ });
243
+ }
199
244
  let match;
200
245
  try {
201
246
  match = findRoute(routes, request);
@@ -225,7 +270,7 @@ function createWebSocketRuntime(bindings, driver, applicationRuntime) {
225
270
  .filter(Boolean);
226
271
  const protocol = match.route.handshake?.protocols?.find((value) => offered.includes(value));
227
272
  if (match.route.handshake?.protocols?.length && protocol === undefined) {
228
- return new Response('WebSocket subprotocolが一致しません。', {
273
+ return new Response('WebSocket subprotocol mismatch.', {
229
274
  status: 400,
230
275
  });
231
276
  }
@@ -247,16 +292,42 @@ function createWebSocketRuntime(bindings, driver, applicationRuntime) {
247
292
  throw error;
248
293
  }
249
294
  let upgraded;
295
+ let handshakeState = {};
296
+ const signal = AbortSignal.any([request.signal, lease.signal]);
250
297
  try {
251
- upgraded = await driver.upgrade(request, protocol === undefined ? {} : { protocol });
298
+ const dispatch = () => composeLayers({
299
+ context: { request, input: { ...input, body: undefined }, signal },
300
+ layers: match.route.handshake?.middlewares ?? [],
301
+ resolve: match.resolve,
302
+ terminal: async ({ state: middlewareState }) => {
303
+ signal.throwIfAborted();
304
+ handshakeState = middlewareState;
305
+ upgraded = await driver.upgrade(request, protocol === undefined ? {} : { protocol });
306
+ return upgraded.response;
307
+ },
308
+ });
309
+ const response = await (lease.run ? lease.run(dispatch) : dispatch());
310
+ if (!(response instanceof Response))
311
+ throw new TypeError('WebSocket handshake middleware must return a Response.');
312
+ signal.throwIfAborted();
313
+ if (!upgraded) {
314
+ lease.complete();
315
+ completePendingIngress();
316
+ return response;
317
+ }
252
318
  }
253
319
  catch (error) {
254
- lease.fail?.(error);
255
- lease.complete();
256
- completePendingIngress();
320
+ try {
321
+ await upgraded?.connection.terminate();
322
+ }
323
+ finally {
324
+ lease.fail?.(error);
325
+ lease.complete();
326
+ completePendingIngress();
327
+ }
257
328
  throw error;
258
329
  }
259
- const session = createSession(upgraded.connection, request, applicationRuntime, match.route, input, lease, handlers.get(match.executionId)?.[match.route.name], lease.run);
330
+ const session = createSession(upgraded.connection, request, applicationRuntime, match.route, input, handshakeState, lease, handlers.get(match.executionId)?.[match.route.name], lease.run);
260
331
  sessions.add(session.active);
261
332
  void session.completion.then(() => sessions.delete(session.active), () => sessions.delete(session.active));
262
333
  completePendingIngress();
@@ -306,10 +377,11 @@ function createWebSocketRuntime(bindings, driver, applicationRuntime) {
306
377
  function findRoute(routes, request) {
307
378
  const url = new URL(request.url);
308
379
  for (const candidate of routes) {
309
- const params = matchHttpPath(candidate.route.segments, url.pathname);
380
+ const params = matchHttpPath(candidate.route.segments, requestPathname(url.pathname));
310
381
  if (params) {
311
382
  return {
312
383
  executionId: candidate.executionId,
384
+ resolve: candidate.resolve,
313
385
  route: candidate.route,
314
386
  params,
315
387
  };
@@ -347,10 +419,10 @@ function decodeQuery(searchParams) {
347
419
  }
348
420
  return query;
349
421
  }
350
- function createSession(connection, request, applicationRuntime, route, input, lease, handler, runInExecution) {
422
+ function createSession(connection, request, applicationRuntime, route, input, handshakeState, lease, handler, runInExecution) {
351
423
  const session = Object.freeze({
352
- id: `ws_${crypto.randomUUID()}`,
353
- protocol: connection.protocol,
424
+ id: createWebSocketSessionId(),
425
+ protocol: negotiatedSubprotocol(connection.protocol),
354
426
  });
355
427
  lease.annotate?.({
356
428
  'websocket.session.id': session.id,
@@ -408,6 +480,7 @@ function createSession(connection, request, applicationRuntime, route, input, le
408
480
  const messages = decodeMessages(connection.messages, route.messages);
409
481
  const context = {
410
482
  request,
483
+ state: handshakeState,
411
484
  session,
412
485
  protocol: session.protocol,
413
486
  execution: {
@@ -537,13 +610,13 @@ async function encodeMessage(codec, value) {
537
610
  }
538
611
  function normalizeCloseInfo(info) {
539
612
  return {
540
- code: Number.isInteger(info.code) ? info.code : 1006,
613
+ code: observedCloseCode(Number.isInteger(info.code) ? info.code : 1006),
541
614
  reason: info.reason ?? '',
542
615
  wasClean: info.wasClean === true,
543
616
  };
544
617
  }
545
618
  function webSocketGracefulShutdownBudget(timeoutMs) {
546
619
  if (timeoutMs <= 0)
547
- return 0;
548
- return Math.min(5_000, Math.max(0, Math.floor(timeoutMs * 0.8)));
620
+ return milliseconds(0);
621
+ return milliseconds(Math.min(5_000, Math.max(0, Math.floor(timeoutMs * 0.8))));
549
622
  }
@@ -0,0 +1,9 @@
1
+ import type { Brand } from '../internal/brand.js';
2
+ export type WebSocketSessionId = Brand<string, 'websocket.session-id'>;
3
+ export type WebSocketProcedureName = Brand<string, 'websocket.procedure'>;
4
+ export type NegotiatedSubprotocol = Brand<string, 'websocket.negotiated-subprotocol'>;
5
+ export type ObservedCloseCode = Brand<number, 'websocket.observed-close-code'>;
6
+ export declare const createWebSocketSessionId: () => WebSocketSessionId;
7
+ export declare const webSocketProcedureName: (value: string) => WebSocketProcedureName;
8
+ export declare const negotiatedSubprotocol: (value: string) => NegotiatedSubprotocol;
9
+ export declare const observedCloseCode: (value: number) => ObservedCloseCode;
@@ -0,0 +1,4 @@
1
+ export const createWebSocketSessionId = () => `ws_${crypto.randomUUID()}`;
2
+ export const webSocketProcedureName = (value) => value;
3
+ export const negotiatedSubprotocol = (value) => value;
4
+ export const observedCloseCode = (value) => value;
@@ -3,3 +3,4 @@ export { WebSocketConnectionNotOpenError, WebSocketMessageDecodeError, WebSocket
3
3
  export type { WebSocketBranchDefinition, WebSocketHandshakeDefinition, WebSocketSession, WebSocketUpgradeOptions, WebSocketCloseInfo, WebSocketCodecKind, WebSocketConnectionDriver, WebSocketContract, WebSocketDataMessage, WebSocketExecutionDefinition, WebSocketExtensionRuntime, WebSocketHandlerContext, WebSocketHandlers, WebSocketHostApi, WebSocketImplementationDefinition, WebSocketIncomingMessage, WebSocketMessageCodec, WebSocketRouteDefinition, WebSocketRouteTree, WebSocketServerDriver, WebSocketUpgradeResult, } from './extension.js';
4
4
  export { createEventWebSocketConnection, createWebSocketDriverChannel, } from './driver.js';
5
5
  export type { EventWebSocket, WebSocketDriverChannel } from './driver.js';
6
+ export type { WebSocketSessionId, WebSocketProcedureName, NegotiatedSubprotocol, ObservedCloseCode, } from './identity.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loutrejs/loutre",
3
- "version": "0.9.2",
3
+ "version": "0.10.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "files": [