@loutrejs/loutre 0.4.1 → 0.6.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 (164) hide show
  1. package/README.md +63 -32
  2. package/dist/adapters/aws-lambda.d.ts +5 -3
  3. package/dist/adapters/aws-lambda.js +53 -23
  4. package/dist/adapters/bun.d.ts +7 -4
  5. package/dist/adapters/bun.js +52 -42
  6. package/dist/adapters/cloudflare-workers.d.ts +5 -3
  7. package/dist/adapters/cloudflare-workers.js +23 -28
  8. package/dist/adapters/deno.d.ts +7 -4
  9. package/dist/adapters/deno.js +94 -57
  10. package/dist/adapters/electron.d.ts +13 -5
  11. package/dist/adapters/electron.js +76 -28
  12. package/dist/application/definition.d.ts +31 -0
  13. package/dist/application/definition.js +16 -0
  14. package/dist/application/index.d.ts +4 -63
  15. package/dist/application/index.js +2 -11
  16. package/dist/application/kernel-internal.d.ts +2 -0
  17. package/dist/application/kernel-internal.js +10 -0
  18. package/dist/application/kernel.d.ts +21 -0
  19. package/dist/application/kernel.js +65 -0
  20. package/dist/core/diagnostic.d.ts +9 -0
  21. package/dist/core/diagnostic.js +9 -0
  22. package/dist/core/extension.d.ts +107 -0
  23. package/dist/core/extension.js +73 -0
  24. package/dist/core/generic-layer.d.ts +26 -0
  25. package/dist/core/generic-layer.js +116 -0
  26. package/dist/core/index.d.ts +31 -15
  27. package/dist/core/index.js +15 -15
  28. package/dist/core/injection.d.ts +4 -13
  29. package/dist/core/injection.js +63 -0
  30. package/dist/core/model/context.d.ts +18 -0
  31. package/dist/core/model/context.js +34 -0
  32. package/dist/core/model/lifecycle.d.ts +5 -0
  33. package/dist/core/model/lifecycle.js +44 -0
  34. package/dist/core/model/modules.d.ts +2 -0
  35. package/dist/core/model/modules.js +101 -0
  36. package/dist/core/model/providers.d.ts +4 -0
  37. package/dist/core/model/providers.js +74 -0
  38. package/dist/core/model/snapshot.d.ts +6 -0
  39. package/dist/core/model/snapshot.js +31 -0
  40. package/dist/core/model/types.d.ts +78 -0
  41. package/dist/core/model/validation.d.ts +4 -0
  42. package/dist/core/model/validation.js +40 -0
  43. package/dist/core/model/visibility.d.ts +4 -0
  44. package/dist/core/model/visibility.js +20 -0
  45. package/dist/core/model.d.ts +10 -0
  46. package/dist/core/model.js +346 -0
  47. package/dist/core/module.d.ts +8 -13
  48. package/dist/core/source-location.d.ts +8 -0
  49. package/dist/core/source-location.js +31 -0
  50. package/dist/core/token-internal.d.ts +3 -0
  51. package/dist/core/token-internal.js +4 -0
  52. package/dist/core/type.d.ts +7 -0
  53. package/dist/core/type.js +4 -0
  54. package/dist/generated/version.d.ts +1 -1
  55. package/dist/generated/version.js +1 -1
  56. package/dist/graph/index.d.ts +2 -3
  57. package/dist/graph/index.js +1 -3
  58. package/dist/graph/model.d.ts +34 -0
  59. package/dist/graph/model.js +127 -0
  60. package/dist/http/auth.d.ts +46 -0
  61. package/dist/http/auth.js +96 -0
  62. package/dist/http/client.d.ts +28 -31
  63. package/dist/http/client.js +60 -41
  64. package/dist/http/cors-internal.d.ts +29 -0
  65. package/dist/http/cors-internal.js +160 -0
  66. package/dist/http/cors.d.ts +5 -8
  67. package/dist/http/cors.js +7 -165
  68. package/dist/http/extension.d.ts +348 -0
  69. package/dist/http/extension.js +947 -0
  70. package/dist/http/index.d.ts +9 -7
  71. package/dist/http/index.js +5 -7
  72. package/dist/{application → http}/openapi.d.ts +12 -3
  73. package/dist/{application → http}/openapi.js +164 -101
  74. package/dist/http/path.d.ts +2 -39
  75. package/dist/http/path.js +7 -2
  76. package/dist/http/runtime/request.d.ts +17 -0
  77. package/dist/http/runtime/request.js +109 -0
  78. package/dist/http/runtime/response.d.ts +18 -0
  79. package/dist/http/runtime/response.js +108 -0
  80. package/dist/index.d.ts +4 -0
  81. package/dist/index.js +2 -0
  82. package/dist/message-port/extension.d.ts +66 -0
  83. package/dist/message-port/extension.js +395 -0
  84. package/dist/message-port/index.d.ts +4 -73
  85. package/dist/message-port/index.js +2 -189
  86. package/dist/message-port/transport.d.ts +11 -0
  87. package/dist/message-port/transport.js +45 -0
  88. package/dist/runtime/async-iterator-lifecycle.d.ts +12 -0
  89. package/dist/runtime/async-iterator-lifecycle.js +95 -0
  90. package/dist/runtime/capability.d.ts +11 -11
  91. package/dist/runtime/capability.js +3 -3
  92. package/dist/runtime/di.d.ts +2 -22
  93. package/dist/runtime/di.js +8 -285
  94. package/dist/runtime/execution-tracker.d.ts +8 -0
  95. package/dist/runtime/execution-tracker.js +54 -0
  96. package/dist/runtime/index.d.ts +15 -9
  97. package/dist/runtime/index.js +9 -9
  98. package/dist/runtime/ingress-gate.d.ts +7 -0
  99. package/dist/runtime/ingress-gate.js +33 -0
  100. package/dist/runtime/kernel.d.ts +23 -0
  101. package/dist/runtime/kernel.js +354 -0
  102. package/dist/runtime/logger.d.ts +2 -0
  103. package/dist/runtime/logger.js +2 -0
  104. package/dist/runtime/node-capabilities.d.ts +1 -1
  105. package/dist/runtime/node-capabilities.js +1 -1
  106. package/dist/runtime/server-port.d.ts +2 -0
  107. package/dist/runtime/server-port.js +11 -0
  108. package/dist/tasks/extension.d.ts +144 -0
  109. package/dist/tasks/extension.js +499 -0
  110. package/dist/tasks/index.d.ts +2 -0
  111. package/dist/tasks/index.js +1 -0
  112. package/dist/tasks/runtime/cron.d.ts +6 -0
  113. package/dist/tasks/runtime/cron.js +89 -0
  114. package/dist/websocket/errors.d.ts +9 -0
  115. package/dist/websocket/errors.js +18 -0
  116. package/dist/websocket/extension.d.ts +154 -0
  117. package/dist/websocket/extension.js +524 -0
  118. package/dist/websocket/index.d.ts +3 -0
  119. package/dist/websocket/index.js +2 -0
  120. package/package.json +12 -16
  121. package/dist/application/binding.d.ts +0 -33
  122. package/dist/application/binding.js +0 -295
  123. package/dist/application/host.d.ts +0 -8
  124. package/dist/application/host.js +0 -14
  125. package/dist/application/queue.d.ts +0 -3
  126. package/dist/application/queue.js +0 -4
  127. package/dist/application/scheduler.d.ts +0 -2
  128. package/dist/application/scheduler.js +0 -45
  129. package/dist/core/context-key.d.ts +0 -14
  130. package/dist/core/context-key.js +0 -14
  131. package/dist/core/contract.d.ts +0 -94
  132. package/dist/core/contract.js +0 -79
  133. package/dist/core/execution.d.ts +0 -78
  134. package/dist/core/execution.js +0 -61
  135. package/dist/core/implementation.d.ts +0 -34
  136. package/dist/core/implementation.js +0 -30
  137. package/dist/core/layer.d.ts +0 -109
  138. package/dist/core/layer.js +0 -52
  139. package/dist/graph/compiler.d.ts +0 -4
  140. package/dist/graph/compiler.js +0 -228
  141. package/dist/graph/graph.d.ts +0 -17
  142. package/dist/graph/graph.js +0 -1103
  143. package/dist/graph/ir.d.ts +0 -175
  144. package/dist/http/application.d.ts +0 -18
  145. package/dist/http/application.js +0 -506
  146. package/dist/http/basic-auth.d.ts +0 -37
  147. package/dist/http/basic-auth.js +0 -67
  148. package/dist/http/definitions.d.ts +0 -225
  149. package/dist/http/definitions.js +0 -69
  150. package/dist/http/hosted-protocol.d.ts +0 -4
  151. package/dist/http/hosted-protocol.js +0 -4
  152. package/dist/http/params.d.ts +0 -2
  153. package/dist/http/params.js +0 -19
  154. package/dist/http/request-body.d.ts +0 -4
  155. package/dist/http/request-body.js +0 -6
  156. package/dist/http/validate.d.ts +0 -20
  157. package/dist/http/validate.js +0 -6
  158. package/dist/message-port/environment.d.ts +0 -2
  159. package/dist/message-port/environment.js +0 -4
  160. package/dist/runtime/application.d.ts +0 -38
  161. package/dist/runtime/application.js +0 -339
  162. package/dist/runtime/pipeline.d.ts +0 -11
  163. package/dist/runtime/pipeline.js +0 -161
  164. /package/dist/{graph/ir.js → core/model/types.js} +0 -0
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @loutrejs/loutre
2
2
 
3
- LoutreのCore packageです。Application Definition、Contract、DI、Pipeline、Task、Trigger、Environment、Arguments、Runtime bindingなど、Loutre Applicationを構成するpublic APIを提供します。
3
+ LoutreのApplication Graph Kernelです。Application Definition / Model、Module、DI、Environment、Arguments、Lifecycle、Runtime Capability、generic Layer、Kernel bootstrapを提供します。
4
4
 
5
- > Loutreは現在v0.xです。Public APIには破壊的変更が入る可能性があります。
5
+ HTTP、Task、MessagePort、WebSocketは同じpackageのsubpathからExecution Extensionとして提供します。
6
6
 
7
7
  ## Install
8
8
 
@@ -10,53 +10,84 @@ LoutreのCore packageです。Application Definition、Contract、DI、Pipeline
10
10
  npm install @loutrejs/loutre
11
11
  ```
12
12
 
13
- 新しいApplicationを始める場合はinitializerも利用できます。
13
+ 新しいApplicationはinitializerから作成できます。
14
14
 
15
15
  ```sh
16
16
  npm create loutre@latest my-app
17
17
  ```
18
18
 
19
- ## Application model
19
+ ## Application Model
20
20
 
21
- LoutreはHTTP endpointだけを中心にせず、Applicationが実行する処理を同じmodelへ載せます。
21
+ Applicationの構成は`defineApplication()`でcanonical `Application Model`へcompileされます。
22
22
 
23
- - **Contract / Implementation** — protocolごとのinterfaceと実装
24
- - **Module / DI** — dependencyとApplication構成
25
- - **Pipeline / Layer** — requestやexecution contextの処理
26
- - **Task / Trigger / Queue** — 明示実行、schedule、queue consumer
27
- - **Environment / Arguments** — Hostから渡されるtyped input
28
- - **Application Graph** — Application構造をRuntimeとToolingで共有
23
+ ```ts
24
+ import { defineApplication, defineModule } from '@loutrejs/loutre'
25
+ import { http } from '@loutrejs/loutre/http'
26
+ import { z } from 'zod'
27
+
28
+ const HealthContract = http.contract({
29
+ health: {
30
+ method: 'GET',
31
+ path: '/health',
32
+ responses: {
33
+ ok: { status: 200, body: z.string() },
34
+ },
35
+ },
36
+ })
37
+
38
+ const HealthHttp = http.implementation({
39
+ name: 'HealthHttp',
40
+ contract: HealthContract,
41
+ factory: () => ({
42
+ health: (ctx) => ctx.response.ok({ body: 'ok' }),
43
+ }),
44
+ })
45
+
46
+ const AppModule = defineModule(() => ({
47
+ executions: [HealthHttp],
48
+ }))
49
+
50
+ export default defineApplication({ modules: [AppModule()] })
51
+ ```
29
52
 
30
- ## Example
53
+ ## Kernel bootstrap
54
+
55
+ portableなApplicationはroot packageから起動できます。
31
56
 
32
57
  ```ts
33
- import { defineApplication, task } from '@loutrejs/loutre'
58
+ import { bootstrapApplication } from '@loutrejs/loutre'
59
+ import { bindHttpServer } from '@loutrejs/loutre/http'
34
60
 
35
- export const hello = task<void, string>({
36
- name: 'hello',
37
- factory: () => () => 'Hello, Loutre!',
61
+ const app = await bootstrapApplication({
62
+ application,
63
+ capabilities: [bindHttpServer({ runtime: 'test' })],
38
64
  })
39
65
 
40
- export default defineApplication({
41
- modules: [],
42
- tasks: [hello],
43
- })
66
+ const response = await app.http.fetch(new Request('http://localhost/health'))
44
67
  ```
45
68
 
69
+ ExtensionがApplication Modelに存在するときだけ、そのExtensionのHost APIがApplicationへ合成されます。
70
+
46
71
  ## Entry points
47
72
 
48
- 主なsubpath exportは次のとおりです。
49
-
50
- | Entry point | Role |
51
- | ------------------------------- | ------------------------ |
52
- | `@loutrejs/loutre/http` | HTTP Contract / Pipeline |
53
- | `@loutrejs/loutre/host` | Application bootstrap |
54
- | `@loutrejs/loutre/binding` | Host / Queue binding |
55
- | `@loutrejs/loutre/runtime` | Runtime capability |
56
- | `@loutrejs/loutre/graph` | Application Graph |
57
- | `@loutrejs/loutre/openapi` | OpenAPI generation |
58
- | `@loutrejs/loutre/presentation` | startup presentation |
59
- | `@loutrejs/loutre/message-port` | MessagePort protocol |
73
+ | Entry point | Role |
74
+ | ------------------------------- | --------------------------------------------- |
75
+ | `@loutrejs/loutre` | Application Graph Kernel / Kernel bootstrap |
76
+ | `@loutrejs/loutre/http` | HTTP Execution Extension |
77
+ | `@loutrejs/loutre/tasks` | Task / Trigger / Queue Execution Extension |
78
+ | `@loutrejs/loutre/message-port` | MessagePort Execution Extension |
79
+ | `@loutrejs/loutre/websocket` | WebSocket Execution Extension |
80
+ | `@loutrejs/loutre/graph` | Application Model graph projection |
81
+ | `@loutrejs/loutre/runtime` | Runtime capability / Kernel runtime primitive |
82
+ | `@loutrejs/loutre/http/openapi` | OpenAPI projection |
83
+ | `@loutrejs/loutre/presentation` | startup presentation |
84
+ | `@loutrejs/loutre/runtime/*` | portable Runtime adapter |
85
+
86
+ Node.js listener ownership is provided by `@loutrejs/node`.
87
+
88
+ ## Architecture invariant
89
+
90
+ Core never interprets HTTP, Task, MessagePort, or WebSocket identity. Execution Extensions compile their definitions into the single Application Model and own their runtime semantics, Host API, and Graph projection.
60
91
 
61
92
  ## Documentation
62
93
 
@@ -1,9 +1,11 @@
1
- import { type ApplicationDefinition, type BootstrapArguments, type HasHttp } from '../application/index.js';
2
- type IsAny<TValue> = 0 extends 1 & TValue ? true : false;
3
- type HttpApplication<TDefinition extends ApplicationDefinition> = IsAny<TDefinition> extends true ? TDefinition : HasHttp<TDefinition> extends true ? TDefinition : never;
1
+ import { type ApplicationDefinition, type BootstrapArguments, type RequireApplicationExtension } from '../application/index.js';
2
+ import type { RuntimeCapabilityBinding } from '../core/index.js';
3
+ import { httpExecutionExtension } from '../http/index.js';
4
+ type HttpApplication<TDefinition extends ApplicationDefinition> = RequireApplicationExtension<TDefinition, typeof httpExecutionExtension>;
4
5
  export type AwsLambdaBindBaseOptions<TDefinition extends ApplicationDefinition> = {
5
6
  readonly application: HttpApplication<TDefinition>;
6
7
  readonly environment?: unknown;
8
+ readonly capabilities?: readonly RuntimeCapabilityBinding[];
7
9
  } & BootstrapArguments<TDefinition>;
8
10
  export type AwsLambdaBindOptions<TDefinition extends ApplicationDefinition> = AwsLambdaBindBaseOptions<TDefinition> & {
9
11
  readonly response?: 'buffered';
@@ -1,4 +1,6 @@
1
- import { binding, } from '../application/index.js';
1
+ import { createKernelApplication, } from '../application/index.js';
2
+ import { bindApplicationCapability } from '../application/kernel-internal.js';
3
+ import { httpExecutionExtension } from '../http/index.js';
2
4
  import { assertRuntimeEngine } from '../runtime/engine.js';
3
5
  export const awsLambdaRuntime = {
4
6
  runtime: 'aws-lambda',
@@ -14,18 +16,27 @@ export const awsLambdaRuntime = {
14
16
  };
15
17
  function bind(options) {
16
18
  assertRuntimeEngine('aws-lambda');
17
- const invocation = binding.invocation({
19
+ if (options.application.model.extensions.get(httpExecutionExtension) ===
20
+ undefined) {
21
+ throw new Error('LUTRE_RUNTIME_HTTP_REQUIRED: awsLambdaRuntime.bind() requires the HTTP Execution Extension.');
22
+ }
23
+ const application = createKernelApplication({
24
+ ...options,
18
25
  application: options.application,
26
+ capabilities: [
27
+ bindApplicationCapability(options.application.model, 'http.server', {
28
+ runtime: 'aws-lambda',
29
+ }),
30
+ ...(options.capabilities ?? []),
31
+ ],
19
32
  environment: 'environment' in options ? options.environment : process.env,
20
- ...('arguments' in options ? { arguments: options.arguments } : {}),
21
33
  });
22
- const http = 'http' in invocation
23
- ? invocation.http
24
- : undefined;
25
- if (!http) {
26
- void invocation.application.close();
27
- throw new Error('LUTRE_RUNTIME_HTTP_REQUIRED: awsLambdaRuntime.bind() requires an HTTP-capable Application.');
28
- }
34
+ const http = {
35
+ initialize: async () => {
36
+ await application.init();
37
+ },
38
+ fetch: (request) => application.http.fetch(request),
39
+ };
29
40
  if (options.response === 'streaming') {
30
41
  const handler = createAwsLambdaStreamingHttpDriver(http);
31
42
  const aws = awsLambdaGlobal();
@@ -36,9 +47,9 @@ function bind(options) {
36
47
  function createAwsLambdaHttpDriver(application) {
37
48
  let initialization;
38
49
  return async (event) => {
39
- initialization ??= application.initialize();
50
+ initialization ??= application.initialize?.() ?? Promise.resolve();
40
51
  await initialization;
41
- const response = await application.handle(toRequest(event));
52
+ const response = await application.fetch(toRequest(event));
42
53
  const metadata = responseMetadata(response);
43
54
  return {
44
55
  statusCode: response.status,
@@ -51,9 +62,9 @@ function createAwsLambdaHttpDriver(application) {
51
62
  function createAwsLambdaStreamingHttpDriver(application) {
52
63
  let initialization;
53
64
  return async (event, output) => {
54
- initialization ??= application.initialize();
65
+ initialization ??= application.initialize?.() ?? Promise.resolve();
55
66
  await initialization;
56
- const response = await application.handle(toRequest(event));
67
+ const response = await application.fetch(toRequest(event));
57
68
  const metadata = responseMetadata(response);
58
69
  const aws = awsLambdaGlobal();
59
70
  const outputMetadata = {
@@ -73,15 +84,34 @@ function createAwsLambdaStreamingHttpDriver(application) {
73
84
  });
74
85
  }
75
86
  const reader = response.body?.getReader();
76
- if (reader) {
77
- while (true) {
78
- const chunk = await reader.read();
79
- if (chunk.done)
80
- break;
81
- if (!stream.write(chunk.value) && stream.once) {
82
- await new Promise((resolve) => {
83
- stream.once?.('drain', resolve);
84
- });
87
+ let completed = false;
88
+ let failure;
89
+ try {
90
+ if (reader) {
91
+ while (true) {
92
+ const chunk = await reader.read();
93
+ if (chunk.done)
94
+ break;
95
+ if (!stream.write(chunk.value) && stream.once) {
96
+ await new Promise((resolve) => {
97
+ stream.once?.('drain', resolve);
98
+ });
99
+ }
100
+ }
101
+ }
102
+ completed = true;
103
+ }
104
+ catch (error) {
105
+ failure = error;
106
+ throw error;
107
+ }
108
+ finally {
109
+ if (reader && !completed) {
110
+ try {
111
+ await reader.cancel(failure);
112
+ }
113
+ catch {
114
+ // cancel失敗でresponse pumpの元errorを上書きしない。
85
115
  }
86
116
  }
87
117
  }
@@ -1,9 +1,12 @@
1
- import { type ApplicationDefinition, type BootstrapArguments, type HasHttp, type HostBindingApplication } from '../application/index.js';
2
- type IsAny<TValue> = 0 extends 1 & TValue ? true : false;
3
- type HttpApplication<TDefinition extends ApplicationDefinition> = IsAny<TDefinition> extends true ? TDefinition : HasHttp<TDefinition> extends true ? TDefinition : never;
1
+ import { type ApplicationDefinition, type BootstrapArguments, type KernelHostedApplication, type RequireApplicationExtension } from '../application/index.js';
2
+ import type { RuntimeCapabilityBinding } from '../core/index.js';
3
+ import { httpExecutionExtension } from '../http/index.js';
4
+ type HttpApplication<TDefinition extends ApplicationDefinition> = RequireApplicationExtension<TDefinition, typeof httpExecutionExtension>;
4
5
  export type BunCreateOptions<TDefinition extends ApplicationDefinition> = {
5
6
  readonly application: HttpApplication<TDefinition>;
6
7
  readonly environment?: unknown;
8
+ readonly capabilities?: readonly RuntimeCapabilityBinding[];
9
+ readonly forceShutdownTimeoutMs?: number;
7
10
  } & BootstrapArguments<TDefinition>;
8
11
  export interface BunServeOptions {
9
12
  readonly port?: number;
@@ -13,7 +16,7 @@ export interface BunServeOptions {
13
16
  export interface BunListenerHandle {
14
17
  readonly port: number;
15
18
  }
16
- export type BunRuntimeApplication<TDefinition extends ApplicationDefinition = ApplicationDefinition> = HostBindingApplication<TDefinition> & {
19
+ export type BunRuntimeApplication<TDefinition extends ApplicationDefinition = ApplicationDefinition> = KernelHostedApplication<TDefinition> & {
17
20
  serve(options?: BunServeOptions): Promise<BunListenerHandle>;
18
21
  };
19
22
  export declare const bunRuntime: {
@@ -1,7 +1,10 @@
1
- import { binding, } from '../application/index.js';
1
+ import { createKernelApplication, } from '../application/index.js';
2
+ import { bindApplicationCapability } from '../application/kernel-internal.js';
3
+ import { httpExecutionExtension } from '../http/index.js';
2
4
  import { LOUTRE_VERSION, detectPresentationTerminal, startStartupPresentation, } from '../presentation.js';
3
5
  import { assertRuntimeEngine } from '../runtime/engine.js';
4
6
  import { serverUrl } from '../runtime/server-url.js';
7
+ import { canRetryOnNextPort, initialServerPort, } from '../runtime/server-port.js';
5
8
  export const bunRuntime = {
6
9
  runtime: 'bun',
7
10
  capabilities: new Set([
@@ -26,46 +29,64 @@ async function create(options) {
26
29
  terminal: detectPresentationTerminal(process.stdout, environment),
27
30
  write: (value) => console.log(value),
28
31
  });
29
- const host = binding.host({
32
+ if (options.application.model.extensions.get(httpExecutionExtension) ===
33
+ undefined) {
34
+ throw new Error('LUTRE_RUNTIME_HTTP_REQUIRED: bunRuntime.create() requires the HTTP Execution Extension.');
35
+ }
36
+ const hosted = createKernelApplication({
37
+ ...options,
30
38
  application: options.application,
39
+ capabilities: [
40
+ bindApplicationCapability(options.application.model, 'http.server', {
41
+ runtime: 'bun',
42
+ }),
43
+ ...(options.capabilities ?? []),
44
+ ],
31
45
  environment: 'environment' in options ? options.environment : environment,
32
- ...('arguments' in options ? { arguments: options.arguments } : {}),
33
46
  });
34
- const http = 'http' in host ? host.http : undefined;
35
- if (!http) {
36
- await host.application.close();
37
- throw new Error('LUTRE_RUNTIME_HTTP_REQUIRED: bunRuntime.create() requires an HTTP-capable Application.');
38
- }
39
- await host.application.init();
40
- const application = host.application;
41
- const closeApplication = host.application.close.bind(host.application);
47
+ await hosted.init();
48
+ const application = hosted;
49
+ const http = hosted
50
+ .http;
51
+ const closeApplication = application.close.bind(application);
42
52
  let server;
43
53
  let removeShutdownHooks;
44
54
  let serving = false;
45
55
  let closed = false;
46
- const close = async (signal) => {
56
+ let closingPromise;
57
+ const close = (signal) => {
47
58
  if (closed)
48
- return;
49
- closed = true;
50
- removeShutdownHooks?.();
51
- removeShutdownHooks = undefined;
52
- const errors = [];
53
- if (server) {
59
+ return Promise.resolve();
60
+ if (closingPromise)
61
+ return closingPromise;
62
+ closingPromise = (async () => {
63
+ removeShutdownHooks?.();
64
+ removeShutdownHooks = undefined;
65
+ const errors = [];
66
+ if (server) {
67
+ try {
68
+ await server.stop(true);
69
+ server = undefined;
70
+ }
71
+ catch (error) {
72
+ errors.push(error);
73
+ }
74
+ }
54
75
  try {
55
- await server.stop(true);
76
+ await closeApplication(signal);
56
77
  }
57
78
  catch (error) {
58
79
  errors.push(error);
59
80
  }
60
- }
61
- try {
62
- await closeApplication(signal);
63
- }
64
- catch (error) {
65
- errors.push(error);
66
- }
67
- if (errors.length > 0)
68
- throw new AggregateError(errors, 'Bun runtime shutdown failed');
81
+ if (errors.length > 0) {
82
+ throw new AggregateError(errors, 'Bun runtime shutdown failed');
83
+ }
84
+ closed = true;
85
+ })().catch((error) => {
86
+ closingPromise = undefined;
87
+ throw error;
88
+ });
89
+ return closingPromise;
69
90
  };
70
91
  const serve = async (serveOptions = {}) => {
71
92
  if (closed) {
@@ -80,10 +101,8 @@ async function create(options) {
80
101
  }
81
102
  serving = true;
82
103
  try {
83
- if ('triggers' in application)
84
- await application.triggers.start();
85
104
  const requestedPort = serveOptions.port;
86
- let port = requestedPort ?? 3000;
105
+ let port = initialServerPort(requestedPort);
87
106
  while (true) {
88
107
  try {
89
108
  server = bun.serve({
@@ -139,21 +158,12 @@ function registerBunShutdownHooks(close) {
139
158
  }
140
159
  return remove;
141
160
  }
142
- function canRetryOnNextPort(error, port) {
143
- return port < 65_535 && isAddressInUseError(error);
144
- }
145
- function isAddressInUseError(error) {
146
- return (typeof error === 'object' &&
147
- error !== null &&
148
- 'code' in error &&
149
- error.code === 'EADDRINUSE');
150
- }
151
161
  function createBunFetchDriver(application) {
152
162
  let initialization;
153
163
  return async (request) => {
154
- initialization ??= application.initialize();
164
+ initialization ??= application.initialize?.() ?? Promise.resolve();
155
165
  await initialization;
156
- return application.handle(request);
166
+ return application.fetch(request);
157
167
  };
158
168
  }
159
169
  function bunGlobal() {
@@ -1,8 +1,10 @@
1
- import { type ApplicationDefinition, type BootstrapArguments, type HasHttp } from '../application/index.js';
2
- type IsAny<TValue> = 0 extends 1 & TValue ? true : false;
3
- type HttpApplication<TDefinition extends ApplicationDefinition> = IsAny<TDefinition> extends true ? TDefinition : HasHttp<TDefinition> extends true ? TDefinition : never;
1
+ import { type ApplicationDefinition, type BootstrapArguments, type RequireApplicationExtension } from '../application/index.js';
2
+ import type { RuntimeCapabilityBinding } from '../core/index.js';
3
+ import { httpExecutionExtension } from '../http/index.js';
4
+ type HttpApplication<TDefinition extends ApplicationDefinition> = RequireApplicationExtension<TDefinition, typeof httpExecutionExtension>;
4
5
  export type CloudflareWorkersBindOptions<TDefinition extends ApplicationDefinition> = {
5
6
  readonly application: HttpApplication<TDefinition>;
7
+ readonly capabilities?: readonly RuntimeCapabilityBinding[];
6
8
  } & BootstrapArguments<TDefinition>;
7
9
  export interface CloudflareWorkersBinding {
8
10
  fetch(request: Request, environment?: unknown, executionContext?: unknown): Promise<Response>;
@@ -1,4 +1,6 @@
1
- import { binding, } from '../application/index.js';
1
+ import { createKernelApplication, } from '../application/index.js';
2
+ import { bindApplicationCapability } from '../application/kernel-internal.js';
3
+ import { httpExecutionExtension } from '../http/index.js';
2
4
  import { assertRuntimeEngine } from '../runtime/engine.js';
3
5
  export const cloudflareWorkersRuntime = {
4
6
  runtime: 'cloudflare-workers',
@@ -17,41 +19,34 @@ export const cloudflareWorkersRuntime = {
17
19
  };
18
20
  function bind(options) {
19
21
  assertRuntimeEngine('cloudflare-workers');
20
- let invocation;
21
- let fetch;
22
- const resolve = (environment) => {
23
- if (invocation && fetch)
24
- return { invocation, fetch };
25
- invocation = binding.invocation({
22
+ if (options.application.model.extensions.get(httpExecutionExtension) ===
23
+ undefined) {
24
+ throw new Error('LUTRE_RUNTIME_HTTP_REQUIRED: cloudflareWorkersRuntime.bind() requires the HTTP Execution Extension.');
25
+ }
26
+ let application;
27
+ let initialization;
28
+ const resolve = async (environment) => {
29
+ application ??= createKernelApplication({
30
+ ...options,
26
31
  application: options.application,
32
+ capabilities: [
33
+ bindApplicationCapability(options.application.model, 'http.server', {
34
+ runtime: 'cloudflare-workers',
35
+ }),
36
+ ...(options.capabilities ?? []),
37
+ ],
27
38
  environment,
28
- ...('arguments' in options ? { arguments: options.arguments } : {}),
29
39
  });
30
- const http = 'http' in invocation
31
- ? invocation.http
32
- : undefined;
33
- if (!http) {
34
- void invocation.application.close();
35
- throw new Error('LUTRE_RUNTIME_HTTP_REQUIRED: cloudflareWorkersRuntime.bind() requires an HTTP-capable Application.');
36
- }
37
- fetch = createCloudflareWorkersFetchDriver(http);
38
- return { invocation, fetch };
40
+ initialization ??= application.init();
41
+ await initialization;
42
+ return application.http;
39
43
  };
40
44
  return {
41
45
  async fetch(request, environment) {
42
- const resolved = resolve(environment);
43
- return resolved.fetch(request);
46
+ return (await resolve(environment)).fetch(request);
44
47
  },
45
48
  async close(signal) {
46
- await invocation?.application.close(signal);
49
+ await application?.close(signal);
47
50
  },
48
51
  };
49
52
  }
50
- function createCloudflareWorkersFetchDriver(application) {
51
- let initialization;
52
- return async (request) => {
53
- initialization ??= application.initialize();
54
- await initialization;
55
- return application.handle(request);
56
- };
57
- }
@@ -1,9 +1,12 @@
1
- import { type ApplicationDefinition, type BootstrapArguments, type HasHttp, type HostBindingApplication } from '../application/index.js';
2
- type IsAny<TValue> = 0 extends 1 & TValue ? true : false;
3
- type HttpApplication<TDefinition extends ApplicationDefinition> = IsAny<TDefinition> extends true ? TDefinition : HasHttp<TDefinition> extends true ? TDefinition : never;
1
+ import { type ApplicationDefinition, type BootstrapArguments, type KernelHostedApplication, type RequireApplicationExtension } from '../application/index.js';
2
+ import type { RuntimeCapabilityBinding } from '../core/index.js';
3
+ import { httpExecutionExtension } from '../http/index.js';
4
+ type HttpApplication<TDefinition extends ApplicationDefinition> = RequireApplicationExtension<TDefinition, typeof httpExecutionExtension>;
4
5
  export type DenoRuntimeOptions<TDefinition extends ApplicationDefinition> = {
5
6
  readonly application: HttpApplication<TDefinition>;
6
7
  readonly environment?: unknown;
8
+ readonly capabilities?: readonly RuntimeCapabilityBinding[];
9
+ readonly forceShutdownTimeoutMs?: number;
7
10
  } & BootstrapArguments<TDefinition>;
8
11
  export type DenoCreateOptions<TDefinition extends ApplicationDefinition> = DenoRuntimeOptions<TDefinition>;
9
12
  export interface DenoServeOptions {
@@ -18,7 +21,7 @@ export interface DenoBinding {
18
21
  export interface DenoListenerHandle {
19
22
  readonly port: number;
20
23
  }
21
- export type DenoRuntimeApplication<TDefinition extends ApplicationDefinition = ApplicationDefinition> = HostBindingApplication<TDefinition> & {
24
+ export type DenoRuntimeApplication<TDefinition extends ApplicationDefinition = ApplicationDefinition> = KernelHostedApplication<TDefinition> & {
22
25
  serve(options?: DenoServeOptions): Promise<DenoListenerHandle>;
23
26
  };
24
27
  export declare const denoRuntime: {