@nimbus-sh/worker 0.5.0 → 0.7.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 +2 -2
  2. package/dist/_shared/preview-host.d.ts +26 -8
  3. package/dist/_shared/preview-host.d.ts.map +1 -1
  4. package/dist/_shared/preview-host.js +51 -11
  5. package/dist/cirrus-npm-cjs.generated.d.ts +1 -1
  6. package/dist/cirrus-npm-cjs.generated.js +1 -1
  7. package/dist/facets/cirrus-real.d.ts.map +1 -1
  8. package/dist/facets/cirrus-real.js +6 -5
  9. package/dist/facets/compose.d.ts +148 -0
  10. package/dist/facets/compose.d.ts.map +1 -0
  11. package/dist/facets/compose.js +146 -0
  12. package/dist/facets/durable-images.d.ts +18 -5
  13. package/dist/facets/durable-images.d.ts.map +1 -1
  14. package/dist/facets/durable-images.js +36 -7
  15. package/dist/facets/durable-slots.d.ts +1 -1
  16. package/dist/facets/durable-slots.d.ts.map +1 -1
  17. package/dist/facets/durable-slots.js +2 -1
  18. package/dist/facets/esbuild-bundle-pool.d.ts +49 -0
  19. package/dist/facets/esbuild-bundle-pool.d.ts.map +1 -0
  20. package/dist/facets/esbuild-bundle-pool.js +122 -0
  21. package/dist/facets/exec-telemetry.d.ts +8 -0
  22. package/dist/facets/exec-telemetry.d.ts.map +1 -1
  23. package/dist/facets/manager.d.ts +337 -123
  24. package/dist/facets/manager.d.ts.map +1 -1
  25. package/dist/facets/manager.js +1100 -449
  26. package/dist/facets/process.d.ts +20 -16
  27. package/dist/facets/process.d.ts.map +1 -1
  28. package/dist/facets/process.js +59 -28
  29. package/dist/facets/real-vite-fs-shim.js +2 -2
  30. package/dist/facets/resident-identity.d.ts +23 -0
  31. package/dist/facets/resident-identity.d.ts.map +1 -0
  32. package/dist/facets/resident-identity.js +31 -0
  33. package/dist/facets/vite-dev-server.d.ts +25 -47
  34. package/dist/facets/vite-dev-server.d.ts.map +1 -1
  35. package/dist/facets/vite-dev-server.js +185 -219
  36. package/dist/facets/wasm-image-digest.d.ts +25 -0
  37. package/dist/facets/wasm-image-digest.d.ts.map +1 -0
  38. package/dist/facets/wasm-image-digest.js +25 -0
  39. package/dist/facets/wasm-swap-registry.d.ts +51 -92
  40. package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
  41. package/dist/facets/wasm-swap-registry.js +68 -227
  42. package/dist/git/commands.d.ts +0 -1
  43. package/dist/git/commands.d.ts.map +1 -1
  44. package/dist/git/commands.js +0 -3
  45. package/dist/git/network-facet.d.ts.map +1 -1
  46. package/dist/git/network-facet.js +6 -5
  47. package/dist/loaders/child-process/spawn-pool.d.ts +3 -2
  48. package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
  49. package/dist/loaders/child-process/spawn-pool.js +3 -2
  50. package/dist/loaders/generated-workers.d.ts +2 -2
  51. package/dist/loaders/generated-workers.d.ts.map +1 -1
  52. package/dist/loaders/generated-workers.js +3 -3
  53. package/dist/loaders/npm-resolve-preamble.d.ts +3 -6
  54. package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
  55. package/dist/loaders/npm-resolve-preamble.js +34 -108
  56. package/dist/node-shims-artifact.generated.js +3 -3
  57. package/dist/npm/install-batch-facet.d.ts +3 -2
  58. package/dist/npm/install-batch-facet.d.ts.map +1 -1
  59. package/dist/npm/install-batch-facet.js +45 -15
  60. package/dist/npm/installer.d.ts +22 -17
  61. package/dist/npm/installer.d.ts.map +1 -1
  62. package/dist/npm/installer.js +209 -193
  63. package/dist/npm/pre-bundle-facet.d.ts +1 -1
  64. package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
  65. package/dist/npm/pre-bundle-facet.js +1 -1
  66. package/dist/npm/resolve-facet.d.ts +6 -0
  67. package/dist/npm/resolve-facet.d.ts.map +1 -1
  68. package/dist/npm/resolve-one-facet.d.ts +21 -20
  69. package/dist/npm/resolve-one-facet.d.ts.map +1 -1
  70. package/dist/npm/resolve-one-facet.js +85 -84
  71. package/dist/npm/semver.d.ts +59 -0
  72. package/dist/npm/semver.d.ts.map +1 -0
  73. package/dist/npm/semver.js +226 -0
  74. package/dist/router/index.d.ts.map +1 -1
  75. package/dist/router/index.js +47 -10
  76. package/dist/router/public-directory.d.ts +5 -1
  77. package/dist/router/public-directory.d.ts.map +1 -1
  78. package/dist/router/public-directory.js +9 -2
  79. package/dist/router/remote-api.d.ts +18 -0
  80. package/dist/router/remote-api.d.ts.map +1 -1
  81. package/dist/router/remote-api.js +64 -5
  82. package/dist/runtime/cpython-resident.d.ts.map +1 -1
  83. package/dist/runtime/cpython-resident.js +3 -1
  84. package/dist/runtime/html-entrypoint.d.ts +7 -0
  85. package/dist/runtime/html-entrypoint.d.ts.map +1 -1
  86. package/dist/runtime/html-entrypoint.js +18 -2
  87. package/dist/runtime/node-runner.d.ts +3 -0
  88. package/dist/runtime/node-runner.d.ts.map +1 -1
  89. package/dist/runtime/node-runner.js +7 -0
  90. package/dist/runtime/node-shims.d.ts.map +1 -1
  91. package/dist/runtime/node-shims.js +305 -29
  92. package/dist/runtime/opencode-facet-runner.js +2 -2
  93. package/dist/runtime/package-manager.d.ts +44 -1
  94. package/dist/runtime/package-manager.d.ts.map +1 -1
  95. package/dist/runtime/package-manager.js +168 -3
  96. package/dist/runtime/ruby-resident.d.ts.map +1 -1
  97. package/dist/runtime/ruby-resident.js +2 -0
  98. package/dist/session/agent.d.ts.map +1 -1
  99. package/dist/session/agent.js +28 -1
  100. package/dist/session/helpers.d.ts +38 -0
  101. package/dist/session/helpers.d.ts.map +1 -1
  102. package/dist/session/helpers.js +76 -0
  103. package/dist/session/init-phases.d.ts +1 -1
  104. package/dist/session/init-phases.d.ts.map +1 -1
  105. package/dist/session/init-phases.js +2 -12
  106. package/dist/session/init.d.ts +15 -1
  107. package/dist/session/init.d.ts.map +1 -1
  108. package/dist/session/init.js +91 -603
  109. package/dist/session/keys.d.ts +4 -0
  110. package/dist/session/keys.d.ts.map +1 -1
  111. package/dist/session/keys.js +4 -0
  112. package/dist/session/nimbus-session.d.ts +90 -23
  113. package/dist/session/nimbus-session.d.ts.map +1 -1
  114. package/dist/session/nimbus-session.js +200 -110
  115. package/dist/session/npm-install-port.d.ts +16 -0
  116. package/dist/session/npm-install-port.d.ts.map +1 -0
  117. package/dist/session/npm-install-port.js +37 -0
  118. package/dist/session/port-capability.d.ts +90 -0
  119. package/dist/session/port-capability.d.ts.map +1 -1
  120. package/dist/session/port-capability.js +219 -9
  121. package/dist/session/programmatic.d.ts +125 -5
  122. package/dist/session/programmatic.d.ts.map +1 -1
  123. package/dist/session/programmatic.js +439 -89
  124. package/dist/session/routes.d.ts +39 -17
  125. package/dist/session/routes.d.ts.map +1 -1
  126. package/dist/session/routes.js +67 -131
  127. package/dist/session/rpc.d.ts +39 -16
  128. package/dist/session/rpc.d.ts.map +1 -1
  129. package/dist/session/rpc.js +137 -130
  130. package/dist/session/serving-port.d.ts +27 -0
  131. package/dist/session/serving-port.d.ts.map +1 -0
  132. package/dist/session/serving-port.js +27 -0
  133. package/dist/session/start-real-vite.d.ts +12 -0
  134. package/dist/session/start-real-vite.d.ts.map +1 -1
  135. package/dist/session/start-real-vite.js +4 -4
  136. package/dist/session/supervisor-op.d.ts +41 -4
  137. package/dist/session/supervisor-op.d.ts.map +1 -1
  138. package/dist/session/supervisor-op.js +101 -73
  139. package/dist/session/supervisor-rpc.d.ts +7 -7
  140. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  141. package/dist/session/supervisor-rpc.js +6 -3
  142. package/dist/session/vite-command.d.ts +18 -0
  143. package/dist/session/vite-command.d.ts.map +1 -0
  144. package/dist/session/vite-command.js +609 -0
  145. package/dist/session/ws.d.ts +61 -0
  146. package/dist/session/ws.d.ts.map +1 -1
  147. package/dist/session/ws.js +122 -12
  148. package/dist/shell/npm-bin-entrypoints.d.ts +0 -35
  149. package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
  150. package/dist/shell/npm-bin-entrypoints.js +12 -85
  151. package/package.json +24 -4
  152. package/public/_assets/runtime/{node-shims-9e1380d9ee430fcc.js → node-shims-8f8c64edd2a1f0f5.js} +305 -29
  153. package/public/index.html +1 -1
  154. package/scripts/bundle-facet-workers.mjs +1 -1
  155. package/scripts/bundle-npm-cjs.mjs +2 -2
  156. package/dist/facets/on-demand-bundle-gate.d.ts +0 -65
  157. package/dist/facets/on-demand-bundle-gate.d.ts.map +0 -1
  158. package/dist/facets/on-demand-bundle-gate.js +0 -125
  159. package/dist/npm/install-args.d.ts +0 -21
  160. package/dist/npm/install-args.d.ts.map +0 -1
  161. package/dist/npm/install-args.js +0 -70
  162. package/dist/npm/npm-log.d.ts +0 -31
  163. package/dist/npm/npm-log.d.ts.map +0 -1
  164. package/dist/npm/npm-log.js +0 -51
@@ -55,24 +55,42 @@ declare const PortVisibilitySchema: z.ZodEnum<{
55
55
  public: "public";
56
56
  }>;
57
57
  export type PortVisibility = z.infer<typeof PortVisibilitySchema>;
58
+ /**
59
+ * The optional name alias a reservation may carry: one DNS label, so it can
60
+ * stand where the port stands in a preview host (`<name>--<sid>`). Never
61
+ * purely numeric — a numeric middle label IS a port. No `--`: that is the
62
+ * host-label separator. No 24 lowercase hex: that is ambiguous with a
63
+ * capability label, so parsing could mistake a name for a bearer.
64
+ */
65
+ export declare const APP_NAME_RE: RegExp;
66
+ export declare function isValidAppName(name: string): boolean;
58
67
  export declare const PortRecordSchema: z.ZodObject<{
68
+ kind: z.ZodDefault<z.ZodEnum<{
69
+ explicit: "explicit";
70
+ derived: "derived";
71
+ }>>;
59
72
  capability: z.ZodNullable<z.ZodString>;
60
73
  owner: z.ZodNullable<z.ZodString>;
61
74
  visibility: z.ZodOptional<z.ZodEnum<{
62
75
  scoped: "scoped";
63
76
  public: "public";
64
77
  }>>;
78
+ name: z.ZodOptional<z.ZodString>;
65
79
  }, z.core.$strip>;
66
80
  export interface PortExposure {
67
81
  readonly capability: string;
68
82
  readonly owner: string | null;
69
83
  readonly visibility: PortVisibility;
84
+ readonly name?: string;
70
85
  }
71
86
  /** The per-port record as stored: a bare reservation has no capability yet. */
72
87
  export interface PortReservation {
88
+ readonly kind: 'explicit' | 'derived';
73
89
  readonly owner: string | null;
74
90
  readonly capability: string | null;
75
91
  readonly visibility: PortVisibility;
92
+ /** The reservation's name alias, when one was given at expose time. */
93
+ readonly name?: string;
76
94
  }
77
95
  export declare function portRecordKey(port: number): string;
78
96
  /** Read the raw per-port record: a reservation, an exposure, or nothing. */
@@ -81,6 +99,24 @@ export declare function readPortReservation(ctx: {
81
99
  }, port: number): Promise<PortReservation | null>;
82
100
  /** Read retained exposure metadata without starting a session or restoring a listener. */
83
101
  export declare function readPortExposure(ctx: PortCapabilityHost['ctx'], port: number): Promise<PortExposure | null>;
102
+ /** Every stored port record, keyed by port — the scan `apps.list` and the name lookups read. */
103
+ export declare function listPortReservations(ctx: {
104
+ storage: PortReservationTransaction;
105
+ }): Promise<Map<number, PortReservation>>;
106
+ /** The port a name alias resolves to inside this session, or null. */
107
+ export declare function readPortReservationByName(ctx: {
108
+ storage: PortReservationTransaction;
109
+ }, name: string): Promise<{
110
+ port: number;
111
+ reservation: PortReservation;
112
+ } | null>;
113
+ /** The port an owner holds, or null when the owner has no reservation. */
114
+ export declare function readPortReservationByOwner(ctx: {
115
+ storage: PortReservationTransaction;
116
+ }, owner: string): Promise<{
117
+ port: number;
118
+ reservation: PortReservation;
119
+ } | null>;
84
120
  /**
85
121
  * Hold a port for `owner` across instances. The owner's existing port is
86
122
  * answered again; otherwise the preferred port is claimed, or the lowest
@@ -97,9 +133,12 @@ export declare function readPortExposure(ctx: PortCapabilityHost['ctx'], port: n
97
133
  export declare function reservePort(ctx: PortReservationHost['ctx'], input: {
98
134
  owner: string;
99
135
  preferredPort?: number;
136
+ kind?: 'explicit' | 'derived';
100
137
  occupiedPorts: ReadonlySet<number>;
101
138
  capability?: string;
102
139
  visibility?: PortVisibility;
140
+ /** A name alias for the reservation — unique per session, DNS-label-safe. */
141
+ name?: string;
103
142
  }): Promise<number>;
104
143
  /** End an owner's hold on a port. Another owner's record is left alone and refused. */
105
144
  export declare function releasePortReservation(ctx: PortReservationHost['ctx'], input: {
@@ -120,11 +159,62 @@ export declare function restorePortCapability(self: PortCapabilityHost, port: nu
120
159
  */
121
160
  export declare function restoreReservedPortCapability(self: PortCapabilityHost, port: number, owner: string): Promise<string | null>;
122
161
  export declare function persistPortCapability(self: PortCapabilityHost, port: number, capability: string): Promise<void>;
162
+ /**
163
+ * Replace the port's capability with a freshly minted one, in place: owner,
164
+ * visibility and name stay, every URL built on the old value stops
165
+ * resolving. Answers the new capability, or null when no record exists —
166
+ * there is nothing to rotate on a port nobody has reserved or exposed.
167
+ */
168
+ export declare function rotatePortCapability(self: PortCapabilityHost, port: number, capability: string): Promise<string | null>;
123
169
  /**
124
170
  * Retire the durable capability for a port. Called before every registration
125
171
  * that is not a restore, so a token handed out for the previous occupant of a
126
172
  * port cannot reach the next one. An owner's reservation survives it.
127
173
  */
128
174
  export declare function clearPortCapability(self: PortCapabilityHost, port: number): Promise<void>;
175
+ /** The host surface `routeToSessionPort` actually reads — the narrowest shape that serves every port-addressed route. */
176
+ export interface SessionPortHost {
177
+ ctx: DurableObjectState;
178
+ portRegistry: PortRegistry;
179
+ ensureDurableAppOnPort?: (port: number) => Promise<'absent' | 'started' | 'failed'>;
180
+ _viteShimPort?: number | null;
181
+ viteDevServer?: {
182
+ isRunning: boolean;
183
+ handleRequest(request: Request, innerPath: string, mountBase: string): Promise<Response>;
184
+ } | null;
185
+ cirrusReal?: {
186
+ isRunning: boolean;
187
+ } | null;
188
+ restorePersistedDevServer?: (onlyPort?: number) => Promise<void>;
189
+ acceptCirrusHmrWs?: (request: Request) => Response;
190
+ }
191
+ export declare function normalizeForwardedHttpPath(path: string): string;
192
+ /** True if `innerPath` targets the cirrus-real HMR socket, under any mount base. */
193
+ export declare function isCirrusHmrPath(innerPath: string): boolean;
194
+ /**
195
+ * Re-adopt a preview capability the embedder already holds, after a restore
196
+ * re-registered the port under a freshly minted one.
197
+ */
198
+ export declare function readoptCapability(self: PortCapabilityHost, port: number, capability: string | null): Promise<void>;
199
+ /**
200
+ * Route a request to whatever is listening on a session port.
201
+ *
202
+ * The one implementation behind every port-addressed surface: `/port/<n>/`,
203
+ * `/preview/?port=N`, the `<port>--<sid>` preview hostname (which the router
204
+ * forwards as `/port/<n>/`), and the embedder's capability route. They
205
+ * differ only in how the port and the inner path are spelled, so they must
206
+ * not differ in what answers.
207
+ *
208
+ * `mountBase` is the public URL prefix the served app is mounted at for THIS
209
+ * request — '' for a root-mounted `<port>--<sid>` host, '/s/<sid>/preview' for
210
+ * the preview path. The in-process Cirrus dev server rewrites base-relative
211
+ * URLs (module URLs, <base href>, BASE_URL, router basename), so it is handed
212
+ * the base directly: the generic port proxy strips the Nimbus base header at
213
+ * the untrusted-code boundary and cannot carry it, and a plain user server on
214
+ * any other port is mounted at root and needs no rewriting.
215
+ */
216
+ export declare function routeToSessionPort(self: SessionPortHost, port: number, request: Request, innerPath: string, mountBase: string, capability?: string): Promise<Response>;
217
+ /** Route a capability-authenticated embedder request to a guest HTTP server. */
218
+ export declare function routeCapabilityPort(self: SessionPortHost, port: number, capability: string, request: Request, innerPath: string): Promise<Response>;
129
219
  export {};
130
220
  //# sourceMappingURL=port-capability.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"port-capability.d.ts","sourceRoot":"","sources":["../../src/session/port-capability.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"port-capability.d.ts","sourceRoot":"","sources":["../../src/session/port-capability.ts"],"names":[],"mappings":"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"}
@@ -23,15 +23,31 @@
23
23
  * keeps the owner's reservation; only `releasePortReservation` ends it.
24
24
  */
25
25
  import { z } from 'zod/v4';
26
+ import { PUBLIC_BEARER_HEADER, renderSessionStatusPage } from '../_shared/session-router.js';
26
27
  import { PORT_CAPABILITY_KEY_PREFIX } from './keys.js';
27
28
  /** The shape `createPortCapability` mints: 12 random bytes, hex. */
28
29
  const PortCapabilitySchema = z.string().regex(/^[a-f0-9]{24}$/);
29
30
  /** 'scoped' is the default: capability-checked but never the public bearer. */
30
31
  const PortVisibilitySchema = z.enum(['scoped', 'public']);
32
+ /**
33
+ * The optional name alias a reservation may carry: one DNS label, so it can
34
+ * stand where the port stands in a preview host (`<name>--<sid>`). Never
35
+ * purely numeric — a numeric middle label IS a port. No `--`: that is the
36
+ * host-label separator. No 24 lowercase hex: that is ambiguous with a
37
+ * capability label, so parsing could mistake a name for a bearer.
38
+ */
39
+ export const APP_NAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
40
+ export function isValidAppName(name) {
41
+ return APP_NAME_RE.test(name) && !name.includes('--')
42
+ && !/^\d+$/.test(name) && !/^[a-f0-9]{24}$/.test(name);
43
+ }
44
+ const AppNameSchema = z.string().refine(isValidAppName, 'app name must be a DNS label: no --, not numeric, not 24 lowercase hex');
31
45
  export const PortRecordSchema = z.object({
46
+ kind: z.enum(['explicit', 'derived']).default('explicit'),
32
47
  capability: PortCapabilitySchema.nullable(),
33
48
  owner: z.string().nullable(),
34
49
  visibility: PortVisibilitySchema.optional(),
50
+ name: AppNameSchema.optional(),
35
51
  });
36
52
  const RESERVATION_FIRST_PORT = 20000;
37
53
  const RESERVATION_LAST_PORT = 65535;
@@ -52,9 +68,43 @@ export async function readPortReservation(ctx, port) {
52
68
  export async function readPortExposure(ctx, port) {
53
69
  const stored = await readPortReservation(ctx, port);
54
70
  return stored !== null && stored.capability !== null
55
- ? { capability: stored.capability, owner: stored.owner, visibility: stored.visibility }
71
+ ? {
72
+ capability: stored.capability, owner: stored.owner, visibility: stored.visibility,
73
+ ...(stored.name !== undefined ? { name: stored.name } : {}),
74
+ }
56
75
  : null;
57
76
  }
77
+ /** Every stored port record, keyed by port — the scan `apps.list` and the name lookups read. */
78
+ export async function listPortReservations(ctx) {
79
+ const rows = await ctx.storage.list({ prefix: PORT_CAPABILITY_KEY_PREFIX });
80
+ const out = new Map();
81
+ for (const [key, value] of rows) {
82
+ const port = Number(key.slice(PORT_CAPABILITY_KEY_PREFIX.length));
83
+ const parsed = PortRecordSchema.safeParse(value);
84
+ if (!Number.isInteger(port) || !parsed.success)
85
+ continue;
86
+ out.set(port, { ...parsed.data, visibility: parsed.data.visibility ?? 'scoped' });
87
+ }
88
+ return out;
89
+ }
90
+ /** The port a name alias resolves to inside this session, or null. */
91
+ export async function readPortReservationByName(ctx, name) {
92
+ if (!isValidAppName(name))
93
+ return null;
94
+ for (const [port, reservation] of await listPortReservations(ctx)) {
95
+ if (reservation.name === name)
96
+ return { port, reservation };
97
+ }
98
+ return null;
99
+ }
100
+ /** The port an owner holds, or null when the owner has no reservation. */
101
+ export async function readPortReservationByOwner(ctx, owner) {
102
+ for (const [port, reservation] of await listPortReservations(ctx)) {
103
+ if (reservation.owner === owner)
104
+ return { port, reservation };
105
+ }
106
+ return null;
107
+ }
58
108
  /**
59
109
  * Hold a port for `owner` across instances. The owner's existing port is
60
110
  * answered again; otherwise the preferred port is claimed, or the lowest
@@ -69,32 +119,52 @@ export async function readPortExposure(ctx, port) {
69
119
  * capability is never rotated, a URL already handed out stays good.
70
120
  */
71
121
  export async function reservePort(ctx, input) {
122
+ if (input.name !== undefined && !isValidAppName(input.name)) {
123
+ throw conflict(`'${input.name}' is not a valid app name (one DNS label, not numeric, not 24 hex)`);
124
+ }
72
125
  const claim = async (txn) => {
73
126
  const records = await txn.list({ prefix: PORT_CAPABILITY_KEY_PREFIX });
74
127
  const taken = new Set(input.occupiedPorts);
128
+ const unowned = new Map();
75
129
  let held = null;
76
130
  for (const [storedKey, value] of records) {
77
131
  const port = Number(storedKey.slice(PORT_CAPABILITY_KEY_PREFIX.length));
78
132
  if (!Number.isInteger(port))
79
133
  continue;
80
- taken.add(port);
81
134
  const parsed = PortRecordSchema.safeParse(value);
82
- if (parsed.success && parsed.data.owner === input.owner) {
135
+ // listPorts may already have persisted the live capability without an
136
+ // owner. That is an exposure to adopt, not somebody else's reservation.
137
+ if (parsed.success && parsed.data.owner === null)
138
+ unowned.set(port, parsed.data);
139
+ else
140
+ taken.add(port);
141
+ if (!parsed.success)
142
+ continue;
143
+ if (parsed.data.owner === input.owner) {
83
144
  held = { port, stored: parsed.data };
84
145
  }
146
+ else if (input.name !== undefined && parsed.data.name === input.name
147
+ && !(parsed.data.owner === null && port === input.preferredPort)) {
148
+ throw conflict(`name '${input.name}' is already taken by another app on port ${port}`);
149
+ }
85
150
  }
86
151
  const preferred = input.preferredPort;
87
152
  if (held !== null) {
88
153
  if (preferred !== undefined && preferred !== held.port) {
89
154
  throw conflict(`owner already holds port ${held.port}, cannot reserve ${preferred}`);
90
155
  }
91
- // Upgrade in place: adopt a caller-supplied capability the row lacks
92
- // and/or apply the caller's visibility — never rotate an existing one.
156
+ // Upgrade in place: adopt a caller-supplied capability the row lacks,
157
+ // apply the caller's visibility and/or name — never rotate an existing
158
+ // capability.
93
159
  const stored = held.stored;
94
160
  const capability = stored?.capability ?? input.capability ?? null;
95
161
  const visibility = input.visibility ?? stored?.visibility ?? 'scoped';
96
- if (stored === null || capability !== stored.capability || visibility !== (stored.visibility ?? 'scoped')) {
97
- await txn.put(portRecordKey(held.port), { owner: input.owner, capability, visibility });
162
+ const name = input.name ?? stored?.name;
163
+ if (stored === null || capability !== stored.capability
164
+ || visibility !== (stored.visibility ?? 'scoped') || name !== stored.name) {
165
+ await txn.put(portRecordKey(held.port), {
166
+ owner: input.owner, kind: stored?.kind ?? input.kind ?? 'explicit', capability, visibility, ...(name !== undefined ? { name } : {}),
167
+ });
98
168
  }
99
169
  return held.port;
100
170
  }
@@ -114,10 +184,14 @@ export async function reservePort(ctx, input) {
114
184
  if (port > RESERVATION_LAST_PORT)
115
185
  throw conflict('no free port left to reserve');
116
186
  }
187
+ const exposure = unowned.get(port);
188
+ const name = input.name ?? exposure?.name;
117
189
  await txn.put(portRecordKey(port), {
118
190
  owner: input.owner,
119
- capability: input.capability ?? null,
120
- visibility: input.visibility ?? 'scoped',
191
+ kind: input.kind ?? 'explicit',
192
+ capability: exposure?.capability ?? input.capability ?? null,
193
+ visibility: input.visibility ?? exposure?.visibility ?? 'scoped',
194
+ ...(name !== undefined ? { name } : {}),
121
195
  });
122
196
  return port;
123
197
  };
@@ -178,12 +252,37 @@ export async function persistPortCapability(self, port, capability) {
178
252
  await txn.put(portRecordKey(port), {
179
253
  capability: PortCapabilitySchema.parse(capability),
180
254
  owner: parsed.success ? parsed.data.owner : null,
255
+ kind: parsed.success ? parsed.data.kind : 'explicit',
181
256
  ...(parsed.success && parsed.data.visibility !== undefined
182
257
  ? { visibility: parsed.data.visibility }
183
258
  : {}),
259
+ ...(parsed.success && parsed.data.name !== undefined
260
+ ? { name: parsed.data.name }
261
+ : {}),
184
262
  });
185
263
  });
186
264
  }
265
+ /**
266
+ * Replace the port's capability with a freshly minted one, in place: owner,
267
+ * visibility and name stay, every URL built on the old value stops
268
+ * resolving. Answers the new capability, or null when no record exists —
269
+ * there is nothing to rotate on a port nobody has reserved or exposed.
270
+ */
271
+ export async function rotatePortCapability(self, port, capability) {
272
+ const rotated = await self.ctx.storage.transaction(async (txn) => {
273
+ const parsed = PortRecordSchema.safeParse(await txn.get(portRecordKey(port)));
274
+ if (!parsed.success)
275
+ return null;
276
+ await txn.put(portRecordKey(port), {
277
+ ...parsed.data,
278
+ capability: PortCapabilitySchema.parse(capability),
279
+ });
280
+ return capability;
281
+ });
282
+ // Registry adoption belongs to the caller, after a fresh serving-owner
283
+ // check: the storage await above may have allowed a new listener to bind.
284
+ return rotated;
285
+ }
187
286
  /**
188
287
  * Retire the durable capability for a port. Called before every registration
189
288
  * that is not a restore, so a token handed out for the previous occupant of a
@@ -196,11 +295,122 @@ export async function clearPortCapability(self, port) {
196
295
  if (record !== null && record.owner !== null) {
197
296
  await txn.put(portRecordKey(port), {
198
297
  owner: record.owner,
298
+ kind: record.kind,
199
299
  capability: null,
200
300
  ...(record.visibility !== undefined ? { visibility: record.visibility } : {}),
301
+ ...(record.name !== undefined ? { name: record.name } : {}),
201
302
  });
202
303
  return;
203
304
  }
204
305
  await txn.delete(portRecordKey(port));
205
306
  });
206
307
  }
308
+ export function normalizeForwardedHttpPath(path) {
309
+ const text = path || '/';
310
+ return '/' + text.replace(/^\/+/, '');
311
+ }
312
+ /** True if `innerPath` targets the cirrus-real HMR socket, under any mount base. */
313
+ export function isCirrusHmrPath(innerPath) {
314
+ const p = innerPath.split('?')[0];
315
+ return p === '/__nimbus_hmr' || p.endsWith('/__nimbus_hmr');
316
+ }
317
+ /** The "durable application is (re)starting" 503 page — self-refreshing. */
318
+ function renderPortStartingHtml(port) {
319
+ return renderSessionStatusPage({
320
+ title: 'Starting',
321
+ heading: 'Starting&hellip;',
322
+ body: `The application on port <code>${port}</code> is restarting.<br>This page refreshes itself.`,
323
+ metaRefreshSeconds: 3,
324
+ });
325
+ }
326
+ /**
327
+ * Re-adopt a preview capability the embedder already holds, after a restore
328
+ * re-registered the port under a freshly minted one.
329
+ */
330
+ export async function readoptCapability(self, port, capability) {
331
+ if (!capability)
332
+ return;
333
+ if (self.portRegistry.restoreCapability(port, capability)) {
334
+ await persistPortCapability(self, port, capability);
335
+ }
336
+ }
337
+ /**
338
+ * Route a request to whatever is listening on a session port.
339
+ *
340
+ * The one implementation behind every port-addressed surface: `/port/<n>/`,
341
+ * `/preview/?port=N`, the `<port>--<sid>` preview hostname (which the router
342
+ * forwards as `/port/<n>/`), and the embedder's capability route. They
343
+ * differ only in how the port and the inner path are spelled, so they must
344
+ * not differ in what answers.
345
+ *
346
+ * `mountBase` is the public URL prefix the served app is mounted at for THIS
347
+ * request — '' for a root-mounted `<port>--<sid>` host, '/s/<sid>/preview' for
348
+ * the preview path. The in-process Cirrus dev server rewrites base-relative
349
+ * URLs (module URLs, <base href>, BASE_URL, router basename), so it is handed
350
+ * the base directly: the generic port proxy strips the Nimbus base header at
351
+ * the untrusted-code boundary and cannot carry it, and a plain user server on
352
+ * any other port is mounted at root and needs no rewriting.
353
+ */
354
+ export async function routeToSessionPort(self, port, request, innerPath, mountBase, capability) {
355
+ await self.restorePersistedDevServer?.(port);
356
+ // The durable seam: a request on a port nothing is serving may be a
357
+ // durable application a reset left dead — the alarm pump would re-drive
358
+ // it eventually, but a URL in someone's hands cannot wait for eventually.
359
+ // 'absent' falls through to the honest 502 unchanged; 'started' continues
360
+ // to normal routing with the port freshly bound; 'failed' is mid-launch
361
+ // or a lost boot, so the page re-asks on its own timer.
362
+ if (self.ensureDurableAppOnPort !== undefined) {
363
+ const durable = await self.ensureDurableAppOnPort(port);
364
+ if (durable === 'failed') {
365
+ return new Response(renderPortStartingHtml(port), {
366
+ status: 503,
367
+ headers: {
368
+ 'Content-Type': 'text/html; charset=utf-8',
369
+ 'Cache-Control': 'no-store',
370
+ 'Retry-After': '3',
371
+ },
372
+ });
373
+ }
374
+ }
375
+ if (capability !== undefined) {
376
+ // A rebuilt supervisor holds a capability nobody was handed; the durable
377
+ // one is the value in circulation, so it wins before the check.
378
+ await restorePortCapability(self, port);
379
+ if (!self.portRegistry.hasCapability(port, capability)) {
380
+ // 404, not 403: a wrong capability must not confirm that the port is
381
+ // listening at all.
382
+ return new Response('Not found', { status: 404 });
383
+ }
384
+ // The public bearer form arrived without a session attach: the
385
+ // capability is real, but it authorises this route only for a port the
386
+ // owner deliberately exposed — the stored visibility is the gate.
387
+ if (request.headers.get(PUBLIC_BEARER_HEADER) !== null) {
388
+ const reservation = await readPortReservation(self.ctx, port);
389
+ if (reservation === null || reservation.visibility !== 'public') {
390
+ return new Response('Not found', { status: 404 });
391
+ }
392
+ }
393
+ }
394
+ if (port === self._viteShimPort) {
395
+ // The preview HMR WebSocket can't cross the port-registry RPC, so it is
396
+ // accepted in-DO for both dev servers — the same handling `/preview/` uses.
397
+ if (self.cirrusReal?.isRunning && self.acceptCirrusHmrWs && isCirrusHmrPath(innerPath)) {
398
+ return self.acceptCirrusHmrWs(request);
399
+ }
400
+ // The Cirrus dev server rewrites base-relative URLs per request and the
401
+ // generic proxy strips the base header, so hand it the request directly.
402
+ if (self.viteDevServer?.isRunning) {
403
+ return self.viteDevServer.handleRequest(request, innerPath, mountBase);
404
+ }
405
+ }
406
+ const proxied = capability === undefined
407
+ ? await self.portRegistry.routeRequest(port, request, innerPath)
408
+ : await self.portRegistry.routeCapabilityRequest(port, capability, request, innerPath);
409
+ if (proxied)
410
+ return proxied;
411
+ return new Response(`No process listening on port ${port}`, { status: 502 });
412
+ }
413
+ /** Route a capability-authenticated embedder request to a guest HTTP server. */
414
+ export function routeCapabilityPort(self, port, capability, request, innerPath) {
415
+ return routeToSessionPort(self, port, request, normalizeForwardedHttpPath(innerPath), '', capability);
416
+ }