@ultimat3/http 19.1.1 → 19.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/http",
3
- "version": "19.1.1",
3
+ "version": "19.1.3",
4
4
  "description": "Owned request lifecycle over Bun.serve: router, ordered pipeline, problem+json errors",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,9 +31,9 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@ultimat3/core": "19.1.1",
35
- "@ultimat3/i18n": "19.1.1",
36
- "@ultimat3/schema": "19.1.1",
37
- "@ultimat3/time": "19.1.1"
34
+ "@ultimat3/core": "19.1.3",
35
+ "@ultimat3/i18n": "19.1.3",
36
+ "@ultimat3/schema": "19.1.3",
37
+ "@ultimat3/time": "19.1.3"
38
38
  }
39
39
  }
package/src/errors.ts CHANGED
@@ -240,7 +240,11 @@ export const buildSkew = (clientBuildId: string, serverBuildId: string): HttpErr
240
240
  new HttpError({
241
241
  code: 'X_BUILD_SKEW',
242
242
  cause: `client sent build ${clientBuildId}, server is running ${serverBuildId}`,
243
- fix: 'reload the page — the service worker will fetch the new build manifest',
243
+ // Not "reload": a reload re-enters the same service worker, which stamps the same stale
244
+ // id and earns this same refusal. The worker heals itself on this response (it needs the
245
+ // `x-ultimate-build` header the `context` stage stamps beside this throw); a client with
246
+ // no worker only ever sees this once, on the request that carried a stale id by hand.
247
+ fix: 'the service worker recovers on this response; a stale tab that cannot, clears its registration',
244
248
  });
245
249
 
246
250
  export const serverNotStarted = (member: string): HttpError =>
package/src/stages.ts CHANGED
@@ -160,6 +160,11 @@ export const stageRunners = (input: StageRunnersInput): Record<StageName, StageR
160
160
  if (answered !== undefined) return answered;
161
161
 
162
162
  ctx.clientBuildId = request.header(config.buildIdHeader);
163
+ // Stamped BEFORE the assertion, so the refusal carries it too. A skew answer that
164
+ // withholds the server's own id tells a client it is stale without telling it what to
165
+ // become — and the one caller that can act on that, the service worker holding the
166
+ // stale id, then has no way to tell this 409 from any other.
167
+ if (config.buildId !== null) ctx.headers.set(config.buildIdHeader, config.buildId);
163
168
  request.assertBuild();
164
169
 
165
170
  const pathname = stripBasePath(ctx.url.pathname, config.basePath);