@cloudflare/containers 0.0.27 → 0.0.29

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.
package/README.md CHANGED
@@ -19,12 +19,13 @@ npm install @cloudflare/containers
19
19
  ## Basic Example
20
20
 
21
21
  ```typescript
22
- import { Container, getRandom } from '@cloudflare/containers';
22
+ import { Container, getContainer, getRandom } from '@cloudflare/containers';
23
23
 
24
24
  export class MyContainer extends Container {
25
25
  // Configure default port for the container
26
26
  defaultPort = 8080;
27
- sleepAfter = "1m";
27
+ // After 1 minute of no new activity, shutdown the container
28
+ sleepAfter = '1m';
28
29
  }
29
30
 
30
31
  export default {
@@ -34,18 +35,15 @@ export default {
34
35
  // If you want to route requests to a specific container,
35
36
  // pass a unique container identifier to .get()
36
37
 
37
- if (pathname.startsWith("/specific/")) {
38
+ if (pathname.startsWith('/specific/')) {
38
39
  // In this case, each unique pathname will spawn a new container
39
- let id = env.MY_CONTAINER.idFromName(pathname);
40
- let stub = env.MY_CONTAINER.get(id);
41
- return await stub.fetch(request);
40
+ const container = env.MY_CONTAINER.getByName(pathname);
41
+ return await container.fetch(request);
42
42
  }
43
43
 
44
- // (Note: getRandom is a temporary method until built-in autoscaling an
45
- // load balancing are added)
46
-
47
- // If you want to route to one of many containers (in this case 5),
48
- // use the getRandom helper
44
+ // Note: this is a temporary method until built-in autoscaling and load balancing are added.
45
+ // If you want to route to one of many containers (in this case 5), use the getRandom helper.
46
+ // This load balances incoming requests across these container instances.
49
47
  let container = await getRandom(env.MY_CONTAINER, 5);
50
48
  return await container.fetch(request);
51
49
  },
@@ -54,69 +52,216 @@ export default {
54
52
 
55
53
  ## API Reference
56
54
 
57
- ### Container Class
55
+ ### `Container` Class
58
56
 
59
- The main class that extends a container-enbled Durable Object to provide additional container-specific functionality.
57
+ The `Container` class that extends a container-enbled Durable Object to provide additional container-specific functionality.
60
58
 
61
59
  #### Properties
62
60
 
63
- - `defaultPort?`: Optional default port to use when communicating with the container. If not set, you must specify port in `containerFetch` calls, or use `switchPort`.
64
- - `requiredPorts?`: Array of ports that should be checked for availability during container startup. Used by startAndWaitForPorts when no specific ports are provided.
65
- - `sleepAfter`: How long to keep the container alive without activity (format: number for seconds, or string like "5m", "30s", "1h")
66
- - `env`: Environment variables to pass to the container (Record<string, string>)
67
- - `entrypoint?`: Custom entrypoint to override container default (string[])
68
- - `enableInternet`: Whether to enable internet access for the container (boolean, default: true)
69
- - Lifecycle methods: `onStart`, `onStop`, `onError`, `onActivityExpired`
61
+ - `defaultPort?`
70
62
 
71
- #### Constructor Options
63
+ Optional default port to use when communicating with the container. If this is not set, or you want to target a specific port on your container, you can specify the port with `fetch(switchPort(req, 8080))` or `containerFetch(req, 8080)`.
72
64
 
73
- ```typescript
74
- constructor(ctx: any, env: Env, options?: {
75
- defaultPort?: number; // Override default port
76
- sleepAfter?: string | number; // Override sleep timeout
77
- env?: Record<string, string>; // Environment variables to pass to the container
78
- entrypoint?: string[]; // Custom entrypoint to override container default
79
- enableInternet?: boolean; // Whether to enable internet access for the container
80
- })
81
- ```
65
+ - `requiredPorts?`
66
+
67
+ Array of ports that should be checked for availability during container startup. Used by `startAndWaitForPorts` when no specific ports are provided.
68
+
69
+ - `sleepAfter`
70
+
71
+ How long to keep the container alive without activity (format: number for seconds, or string like "5m", "30s", "1h").
72
+
73
+ Defaults to "10m", meaning that after the Container class Durable Object receives no requests for 10 minutes, it will shut down the container.
74
+
75
+ The following properties are used to set defaults when starting the container, but can be overriden on a per-instance basis by passing in values to `startAndWaitForPorts()` or `start()`.
76
+
77
+ - `env?: Record<string, string>`
78
+
79
+ Environment variables to pass to the container when starting up.
80
+
81
+ - `entrypoint?: string[]`
82
+
83
+ Specify an entrypoint to override image default.
84
+
85
+ - `enableInternet: boolean`
86
+
87
+ Whether to enable internet access for the container.
88
+
89
+ Defaults to `true`.
82
90
 
83
91
  #### Methods
84
92
 
85
- ##### Lifecycle Methods
86
- All lifecycle methods can be implemented as async if needed.
93
+ ##### Lifecycle Hooks
94
+
95
+ These lifecycle methods are automatically called when the container state transitions. Override these methods to use these hooks.
96
+
97
+ See [this example](#http-example-with-lifecycle-hooks).
98
+
99
+ - `onStart()`
100
+
101
+ Called when container starts successfully.
87
102
 
88
- - `onStart()`: Called when container starts successfully - override to add custom behavior
89
- - `onStop()`: Called when container shuts down - override to add custom behavior
90
- - `onError(error)`: Called when container encounters an error - override to add custom behavior
91
- - `onActivityExpired()`: Called when the activity is expired - override to add custom behavior, like communicating with the container to see if it should be shutdown.
103
+ - called when states transition from `stopped` -> `running`, `running` -> `healthy`
92
104
 
93
- By default, it calls `ctx.container.destroy()`.
94
- If you don't stop the container here, the activity tracker will be renewed, and this lifecycle hook will be called again when the timer re-expires.
105
+ - `onStop()`
106
+
107
+ Called when container shuts down.
108
+
109
+ - `onError(error)`
110
+
111
+ Called when container encounters an error, and by default logs and throws the error.
112
+
113
+ - `onActivityExpired()`
114
+
115
+ Called when the activity is expired. The container will run continue to run for some time after the last activity - this length of time is configured by `sleepAfter`.
116
+ By default, this stops the container with a `SIGTERM`, but you can override this behaviour, as with other lifecycle hooks. However, if you don't stop the container here, the activity tracker will be renewed, and this lifecycle hook will be called again when the timer re-expires.
95
117
 
96
118
  ##### Container Methods
97
119
 
98
- - `fetch(request)`: Default handler to forward HTTP requests to the container. Can be overridden.
99
- - `containerFetch(...)`: Sends an HTTP request to the container. Supports both standard fetch API signatures:
120
+ - `fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>`
121
+
122
+ Forwards HTTP requests to the container.
123
+
124
+ If you want to target a specific port on the container, rather than the default port, you should use `switchPort` like so:
125
+
126
+ ```typescript
127
+ const container = env.MY_CONTAINER.getByName('id');
128
+ await container.fetch(switchPort(request, 8080));
129
+ ```
130
+
131
+ Make sure you provide a port with switchPort or specify a port with the `defaultPort` property.
132
+
133
+ You must use `fetch` rather than `containerFetch` if you want to forward websockets.
134
+
135
+ Note that when you call any of the fetch functions, the activity will be automatically renewed (sleepAfter time starts after last activity), and the container will be started if not already running.
136
+
137
+ - `containerFetch(...)`
138
+
139
+ Note: `containerFetch` does not work with websockets.
140
+
141
+ Sends an HTTP request to the container. Supports both standard fetch API signatures:
142
+
100
143
  - `containerFetch(request, port?)`: Traditional signature with Request object
101
144
  - `containerFetch(url, init?, port?)`: Standard fetch-like signature with URL string/object and RequestInit options
102
- Either port parameter or defaultPort must be specified.
103
- When you call any of the fetch functions, the activity will be automatically renewed, and if the container will be started if not already running.
104
- **Do not use 'containerFetch' when trying to send a Request object with a websocket, until [this issue is addressed](https://github.com/cloudflare/workerd/issues/2319).
105
- You can overcome this limitation by doing:
106
- `container.fetch(switchPort(request, port))`
107
-
108
- - `start()`: Starts the container if it's not running and sets up monitoring, without waiting for any ports to be ready.
109
- - `startAndWaitForPorts(ports?, maxTries?)`: Starts the container using `start()` and then waits for specified ports to be ready. If no ports are specified, uses `requiredPorts` or `defaultPort`. If no ports can be determined, just starts the container without port checks.
110
- - `stop(signal = SIGTERM)`: Sends the specified signal to the container.
111
- - `destroy()`: Forcefully destroys the container.
112
- - `getState()`: Get the current container state.
113
- - `renewActivityTimeout()`: Manually renews the container activity timeout (extends container lifetime).
114
- - `stopDueToInactivity()`: Called automatically when the container times out due to inactivity.
115
- - `alarm()`: Default alarm handler. It's in charge of renewing the container activity and keeping the durable object alive. You can override `alarm()`, but because its functionality is currently vital to managing the container lifecycle, we recommend calling `schedule` to schedule tasks instead.
145
+
146
+ - `startAndWaitForPorts(args: StartAndWaitForPortsOptions): Promise<void>`
147
+
148
+ Starts the container and then waits for specified ports to be ready. Prioritises `ports` passed in to the function, then `requiredPorts` if set, then `defaultPort`.
149
+
150
+ ```typescript
151
+ interface StartAndWaitForPortsOptions {
152
+ startOptions?: {
153
+ /** Environment variables to pass to the container */
154
+ envVars?: Record<string, string>;
155
+ /** Custom entrypoint to override container default */
156
+ entrypoint?: string[];
157
+ /** Whether to enable internet access for the container */
158
+ enableInternet?: boolean;
159
+ };
160
+ /** Ports to check */
161
+ ports?: number | number[];
162
+ cancellationOptions?: {
163
+ /** Abort signal to cancel start and port checking */
164
+ abort?: AbortSignal;
165
+ /** Max time to wait for container to start, in milliseconds */
166
+ instanceGetTimeoutMS?: number;
167
+ /** Max time to wait for ports to be ready, in milliseconds */
168
+ portReadyTimeoutMS?: number;
169
+ /** Polling interval for checking container has started or ports are ready, in milliseconds */
170
+ waitInterval?: number;
171
+ };
172
+ }
173
+ ```
174
+
175
+ - `start(startOptions?: ContainerStartConfigOptions, waitOptions?: WaitOptions)`
176
+
177
+ Starts the container, without waiting for any ports to be ready.
178
+
179
+ You might want to use this instead of `startAndWaitForPorts` if you want to:
180
+
181
+ - Start a container without blocking until a port is available
182
+ - Initialize a container that doesn't expose ports
183
+ - Perform custom port availability checks separately
184
+
185
+ Options:
186
+
187
+ ```typescript
188
+ interface ContainerStartConfigOptions {
189
+ /** Environment variables to pass to the container */
190
+ envVars?: Record<string, string>;
191
+ /** Custom entrypoint to override container default */
192
+ entrypoint?: string[];
193
+ /** Whether to enable internet access for the container */
194
+ enableInternet?: boolean;
195
+ }
196
+
197
+ interface WaitOptions {
198
+ /** The port number to check for readiness */
199
+ portToCheck: number;
200
+ /** Optional AbortSignal, use this to abort waiting for ports */
201
+ signal?: AbortSignal;
202
+ /** Number of attempts to wait for port to be ready */
203
+ retries?: number;
204
+ /** Time to wait in between polling port for readiness, in milliseconds */
205
+ waitInterval?: number;
206
+ }
207
+ ```
208
+
209
+ - `stop(signal = SIGTERM): Promise<void>`
210
+
211
+ Sends the specified signal to the container. Triggers `onStop`.
212
+
213
+ - `destroy(): Promise<void>`
214
+
215
+ Forcefully destroys the container (sends `SIGKILL`). Triggers `onStop`.
216
+
217
+ - `getState(): Promise<State>`
218
+
219
+ Get the current container state.
220
+
221
+ ```typescript
222
+ type State = {
223
+ lastChange: number;
224
+ } & (
225
+ | {
226
+ // 'running' means that the container is trying to start and is transitioning to a healthy status.
227
+ // onStop might be triggered if there is an exit code, and it will transition to 'stopped'.
228
+ status: 'running' | 'stopping' | 'stopped' | 'healthy';
229
+ }
230
+ | {
231
+ status: 'stopped_with_code';
232
+ exitCode?: number;
233
+ }
234
+ );
235
+ ```
236
+
237
+ - `renewActivityTimeout()`
238
+
239
+ Manually renews the container activity timeout (extends container lifetime).
240
+
241
+ - `schedule<T = string>(when: Date | number, callback: string, payload?: T): Promise<Schedule<T>>`
242
+
243
+ Options:
244
+
245
+ - `when`: When to execute the task (Date object or number of seconds delay)
246
+ - `callback`: Name of the function to call as a string
247
+ - `payload`: Data to pass to the callback
248
+
249
+ Instead of using the default alarm handler, use `schedule()` instead. The default alarm handler is in charge of renewing the container activity and keeping the durable object alive. You can override `alarm()`, but because its functionality is currently vital to managing the container lifecycle, we recommend calling `schedule` to schedule tasks instead.
116
250
 
117
251
  ### Utility Functions
118
252
 
119
- - `getRandom(binding, instances?)`: Load balances requests across multiple container instances
253
+ - `getRandom(binding, instances?: number)`
254
+
255
+ Get a random container instances across N instances. This is useful for load balancing.
256
+ Returns a stub for the container.
257
+ See [example](#using-load-balancing).
258
+
259
+ - `getContainer(binding, name?: string)`
260
+ Helper to get a particular container instance stub.
261
+
262
+ e.g. `const container = getContainer(env.CONTAINER, "unique-id")`
263
+
264
+ If no name is provided, "cf-singleton-container" is used.
120
265
 
121
266
  ## Examples
122
267
 
@@ -131,7 +276,7 @@ export class MyContainer extends Container {
131
276
 
132
277
  // Set how long the container should stay active without requests
133
278
  // Supported formats: "10m" (minutes), "30s" (seconds), "1h" (hours), or a number (seconds)
134
- sleepAfter = "10m";
279
+ sleepAfter = '10m';
135
280
 
136
281
  // Lifecycle method called when container starts
137
282
  override onStart(): void {
@@ -153,9 +298,7 @@ export class MyContainer extends Container {
153
298
 
154
299
  // Lifecycle method when the container class considers the activity to be expired
155
300
  override onActivityExpired() {
156
- console.log(
157
- 'Container activity expired'
158
- );
301
+ console.log('Container activity expired');
159
302
  await this.destroy();
160
303
  }
161
304
 
@@ -168,14 +311,6 @@ export class MyContainer extends Container {
168
311
  console.log('Container activity timeout extended');
169
312
  }
170
313
 
171
- // Handle incoming requests
172
- async fetch(request: Request): Promise<Response> {
173
-
174
- // Default implementation forwards requests to the container
175
- // This will automatically renew the activity timeout
176
- return await this.containerFetch(request);
177
- }
178
-
179
314
  // Additional methods can be implemented as needed
180
315
  }
181
316
  ```
@@ -184,18 +319,16 @@ export class MyContainer extends Container {
184
319
 
185
320
  The Container class automatically supports proxying WebSocket connections to your container. WebSocket connections are bi-directionally proxied, with messages forwarded in both directions. The Container also automatically renews the activity timeout when WebSocket messages are sent or received.
186
321
 
187
- You can call the `containerFetch` method directly to establish WebSocket connections:
188
-
189
322
  ```typescript
190
323
  // Connect to a WebSocket on port 9000
191
- const response = await container.containerFetch(request, 9000);
324
+ const response = await container.fetch(switchPort(request, 9000));
192
325
  ```
193
326
 
194
- By default `fetch` also will do this by calling `containerFetch`.
327
+ Note websockets are not supported with `containerFetch`.
195
328
 
196
329
  ### Container Configuration Example
197
330
 
198
- You can configure how the container starts by setting the instance properties for environment variables, entrypoint, and network access:
331
+ You can configure defaults for how the container starts by setting the instance properties for environment variables, entrypoint, and network access:
199
332
 
200
333
  ```typescript
201
334
  import { Container } from '@cloudflare/containers';
@@ -205,13 +338,13 @@ export class ConfiguredContainer extends Container {
205
338
  defaultPort = 9000;
206
339
 
207
340
  // Set the timeout for sleeping the container after inactivity
208
- sleepAfter = "2h";
341
+ sleepAfter = '2h';
209
342
 
210
343
  // Environment variables to pass to the container
211
344
  envVars = {
212
345
  NODE_ENV: 'production',
213
346
  LOG_LEVEL: 'info',
214
- APP_PORT: '9000'
347
+ APP_PORT: '9000',
215
348
  };
216
349
 
217
350
  // Custom entrypoint to run in the container
@@ -225,6 +358,8 @@ export class ConfiguredContainer extends Container {
225
358
  }
226
359
  ```
227
360
 
361
+ You can also set these on a per-instance basis with `start` or `startAnbdWaitForPorts`
362
+
228
363
  ### Multiple Ports and Custom Routing
229
364
 
230
365
  You can create a container that doesn't use a default port and instead routes traffic to different ports based on request path or other factors:
@@ -249,18 +384,16 @@ export class MultiPortContainer extends Container {
249
384
  if (url.pathname.startsWith('/api')) {
250
385
  // API server runs on port 3000
251
386
  return await this.containerFetch(request, 3000);
252
- }
253
- else if (url.pathname.startsWith('/admin')) {
387
+ } else if (url.pathname.startsWith('/admin')) {
254
388
  // Admin interface runs on port 8080
255
389
  return await this.containerFetch(request, 8080);
256
- }
257
- else {
390
+ } else {
258
391
  // Public website runs on port 80
259
392
  return await this.containerFetch(request, 80);
260
393
  }
261
394
  } catch (error) {
262
395
  return new Response(`Error: ${error instanceof Error ? error.message : String(error)}`, {
263
- status: 500
396
+ status: 500,
264
397
  });
265
398
  }
266
399
  }
@@ -283,21 +416,22 @@ export class FetchStyleContainer extends Container {
283
416
  const response = await this.containerFetch('/api/data', {
284
417
  method: 'POST',
285
418
  headers: {
286
- 'Content-Type': 'application/json'
419
+ 'Content-Type': 'application/json',
287
420
  },
288
- body: JSON.stringify({ query: 'example' })
421
+ body: JSON.stringify({ query: 'example' }),
289
422
  });
290
423
 
291
424
  // You can also specify a port with this syntax
292
- const adminResponse = await this.containerFetch('https://example.com/admin',
425
+ const adminResponse = await this.containerFetch(
426
+ 'https://example.com/admin',
293
427
  { method: 'GET' },
294
- 3000 // port
428
+ 3000 // port
295
429
  );
296
430
 
297
431
  return response;
298
432
  } catch (error) {
299
433
  return new Response(`Error: ${error instanceof Error ? error.message : String(error)}`, {
300
- status: 500
434
+ status: 500,
301
435
  });
302
436
  }
303
437
  }
@@ -316,7 +450,7 @@ export class TimeoutContainer extends Container {
316
450
  defaultPort = 8080;
317
451
 
318
452
  // Set timeout to 30 minutes of inactivity
319
- sleepAfter = "30m"; // Supports "30s", "5m", "1h" formats, or a number in seconds
453
+ sleepAfter = '30m'; // Supports "30s", "5m", "1h" formats, or a number in seconds
320
454
 
321
455
  // Custom method that will extend the container's lifetime
322
456
  async performBackgroundTask(data: any): Promise<void> {
@@ -337,11 +471,14 @@ export class TimeoutContainer extends Container {
337
471
  if (url.pathname === '/task') {
338
472
  await this.performBackgroundTask();
339
473
 
340
- return new Response(JSON.stringify({
341
- success: true,
342
- message: 'Background task executed',
343
- nextStop: `Container will shut down after ${this.sleepAfter} of inactivity`
344
- }), { headers: { 'Content-Type': 'application/json' } });
474
+ return new Response(
475
+ JSON.stringify({
476
+ success: true,
477
+ message: 'Background task executed',
478
+ nextStop: `Container will shut down after ${this.sleepAfter} of inactivity`,
479
+ }),
480
+ { headers: { 'Content-Type': 'application/json' } }
481
+ );
345
482
  }
346
483
 
347
484
  // For all other requests, forward to the container
@@ -354,7 +491,7 @@ export class TimeoutContainer extends Container {
354
491
  ### Using Load Balancing
355
492
 
356
493
  This package includes a `getRandom` helper which routes requests to one of N instances.
357
- In the future, this will be automatically handled with smart by Cloudflare Containers
494
+ In the future, this will be automatically handled with smart by Cloudflare Containers
358
495
  with autoscaling set to true, but is not yet implemented.
359
496
 
360
497
  ```typescript
@@ -382,16 +519,6 @@ export default {
382
519
  }
383
520
 
384
521
  return new Response('Not found', { status: 404 });
385
- }
522
+ },
386
523
  };
387
524
  ```
388
-
389
- ### Using getContainer
390
-
391
- This package includes a `getContainer` helper which returns a container instance
392
- stub.
393
-
394
- The first argument is the Container's Durable Object namespace. The second argument is
395
- optional and is a "name" for the Durable Object. This will be used to generate an ID,
396
- then return a specific Container instance (Durable Object instance). If no second argument
397
- is given, the name "cf-singleton-container" is used.