@nifrajs/web 5.6.0 → 5.8.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 (129) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +21 -11
  3. package/dist/build.d.ts +1 -1
  4. package/dist/build.d.ts.map +1 -1
  5. package/dist/build.js +1 -0
  6. package/dist/build.js.map +1 -1
  7. package/dist/client.d.ts +1 -0
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +39 -16
  10. package/dist/client.js.map +1 -1
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/internal/codegen.d.ts.map +1 -1
  16. package/dist/internal/codegen.js +8 -2
  17. package/dist/internal/codegen.js.map +1 -1
  18. package/dist/internal/identity-epoch.d.ts +2 -2
  19. package/dist/internal/identity-epoch.d.ts.map +1 -1
  20. package/dist/internal/identity-epoch.js +15 -14
  21. package/dist/internal/identity-epoch.js.map +1 -1
  22. package/dist/internal/live-contract.d.ts +5 -0
  23. package/dist/internal/live-contract.d.ts.map +1 -1
  24. package/dist/internal/live-contract.js.map +1 -1
  25. package/dist/internal/page-execution.d.ts +2 -0
  26. package/dist/internal/page-execution.d.ts.map +1 -1
  27. package/dist/internal/page-execution.js +22 -5
  28. package/dist/internal/page-execution.js.map +1 -1
  29. package/dist/internal/parity.d.ts +3 -0
  30. package/dist/internal/parity.d.ts.map +1 -1
  31. package/dist/internal/parity.js +28 -5
  32. package/dist/internal/parity.js.map +1 -1
  33. package/dist/internal/ppr/runtime.d.ts +1 -1
  34. package/dist/internal/ppr/runtime.d.ts.map +1 -1
  35. package/dist/internal/ppr/runtime.js +4 -1
  36. package/dist/internal/ppr/runtime.js.map +1 -1
  37. package/dist/internal/ppr/server.d.ts.map +1 -1
  38. package/dist/internal/ppr/server.js +4 -2
  39. package/dist/internal/ppr/server.js.map +1 -1
  40. package/dist/internal/ppr/shell-store.d.ts +1 -0
  41. package/dist/internal/ppr/shell-store.d.ts.map +1 -1
  42. package/dist/internal/ppr/shell-store.js +17 -1
  43. package/dist/internal/ppr/shell-store.js.map +1 -1
  44. package/dist/internal/present.d.ts +11 -0
  45. package/dist/internal/present.d.ts.map +1 -0
  46. package/dist/internal/present.js +13 -0
  47. package/dist/internal/present.js.map +1 -0
  48. package/dist/internal/render-document.d.ts +6 -63
  49. package/dist/internal/render-document.d.ts.map +1 -1
  50. package/dist/internal/render-document.js +3 -50
  51. package/dist/internal/render-document.js.map +1 -1
  52. package/dist/internal/status-page.d.ts +68 -0
  53. package/dist/internal/status-page.d.ts.map +1 -0
  54. package/dist/internal/status-page.js +66 -0
  55. package/dist/internal/status-page.js.map +1 -0
  56. package/dist/internal/web-app.d.ts +3 -2
  57. package/dist/internal/web-app.d.ts.map +1 -1
  58. package/dist/internal/web-app.js +1 -0
  59. package/dist/internal/web-app.js.map +1 -1
  60. package/dist/keep-alive.d.ts.map +1 -1
  61. package/dist/keep-alive.js +46 -13
  62. package/dist/keep-alive.js.map +1 -1
  63. package/dist/live/bus.d.ts +16 -2
  64. package/dist/live/bus.d.ts.map +1 -1
  65. package/dist/live/bus.js +23 -0
  66. package/dist/live/bus.js.map +1 -1
  67. package/dist/live/hub.d.ts +4 -0
  68. package/dist/live/hub.d.ts.map +1 -1
  69. package/dist/live/hub.js +5 -0
  70. package/dist/live/hub.js.map +1 -1
  71. package/dist/live/keyring.d.ts +5 -0
  72. package/dist/live/keyring.d.ts.map +1 -1
  73. package/dist/live/keyring.js +6 -1
  74. package/dist/live/keyring.js.map +1 -1
  75. package/dist/live-client.d.ts.map +1 -1
  76. package/dist/live-client.js +45 -7
  77. package/dist/live-client.js.map +1 -1
  78. package/dist/live-redis.d.ts +14 -2
  79. package/dist/live-redis.d.ts.map +1 -1
  80. package/dist/live-redis.js +16 -5
  81. package/dist/live-redis.js.map +1 -1
  82. package/dist/live.d.ts +57 -1
  83. package/dist/live.d.ts.map +1 -1
  84. package/dist/live.js +119 -16
  85. package/dist/live.js.map +1 -1
  86. package/dist/render-seam.d.ts +2 -0
  87. package/dist/render-seam.d.ts.map +1 -1
  88. package/dist/render-seam.js +2 -0
  89. package/dist/render-seam.js.map +1 -1
  90. package/dist/router.d.ts.map +1 -1
  91. package/dist/router.js +49 -37
  92. package/dist/router.js.map +1 -1
  93. package/dist/server-components/definition.d.ts.map +1 -1
  94. package/dist/server-components/definition.js +2 -1
  95. package/dist/server-components/definition.js.map +1 -1
  96. package/dist/server-components/runtime.d.ts.map +1 -1
  97. package/dist/server-components/runtime.js +2 -1
  98. package/dist/server-components/runtime.js.map +1 -1
  99. package/dist/server-components/vite.d.ts.map +1 -1
  100. package/dist/server-components/vite.js +26 -9
  101. package/dist/server-components/vite.js.map +1 -1
  102. package/package.json +3 -3
  103. package/src/build.ts +1 -0
  104. package/src/client.ts +42 -19
  105. package/src/index.ts +1 -4
  106. package/src/internal/codegen.ts +8 -1
  107. package/src/internal/identity-epoch.ts +20 -16
  108. package/src/internal/live-contract.ts +5 -0
  109. package/src/internal/page-execution.ts +29 -8
  110. package/src/internal/parity.ts +35 -6
  111. package/src/internal/ppr/runtime.ts +4 -1
  112. package/src/internal/ppr/server.ts +4 -2
  113. package/src/internal/ppr/shell-store.ts +14 -1
  114. package/src/internal/present.ts +20 -0
  115. package/src/internal/render-document.ts +10 -92
  116. package/src/internal/status-page.ts +95 -0
  117. package/src/internal/web-app.ts +4 -2
  118. package/src/keep-alive.ts +55 -16
  119. package/src/live/bus.ts +29 -2
  120. package/src/live/hub.ts +5 -0
  121. package/src/live/keyring.ts +6 -1
  122. package/src/live-client.ts +45 -7
  123. package/src/live-redis.ts +31 -6
  124. package/src/live.ts +178 -17
  125. package/src/render-seam.ts +2 -0
  126. package/src/router.ts +47 -38
  127. package/src/server-components/definition.ts +2 -1
  128. package/src/server-components/runtime.ts +2 -1
  129. package/src/server-components/vite.ts +33 -14
package/src/router.ts CHANGED
@@ -10,6 +10,7 @@ import type { StandardSchemaV1 } from "@nifrajs/core/server"
10
10
  import type { BoundaryDescriptor, BoundaryState, BoundaryStates } from "./boundary.ts"
11
11
  import { parseNdjsonData } from "./deferred.ts"
12
12
  import { epochOf, IDENTITY_CHANNEL, onIdentityChange } from "./identity.ts"
13
+ import { present } from "./internal/present.ts"
13
14
  import { parsePageData } from "./internal/revive-dates.ts"
14
15
  import { appUrl, requestPattern } from "./internal/route-path.ts"
15
16
  import { shareData } from "./internal/share-data.ts"
@@ -1361,11 +1362,14 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
1361
1362
  searchClientKeys[state.routeId] ?? [],
1362
1363
  )
1363
1364
  ) {
1364
- ++generation // supersede any in-flight navigation so its late result is dropped
1365
+ const mine = ++generation // supersede any in-flight navigation so its late result is dropped
1365
1366
  navAbort?.abort()
1366
- cachePut(path, dataFields(state))
1367
- state = { ...state, path, pending: false, pendingPath: undefined }
1368
- emit()
1367
+ await present(() => {
1368
+ if (mine !== generation) return
1369
+ cachePut(path, dataFields(state))
1370
+ state = { ...state, path, pending: false, pendingPath: undefined }
1371
+ emit()
1372
+ })
1369
1373
  return
1370
1374
  }
1371
1375
  const mine = ++generation
@@ -1378,46 +1382,51 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
1378
1382
  const [to, target, loaded] = await loadFollowing(mine, path, matched, ac.signal, true, true)
1379
1383
  if (mine !== generation) return // a newer navigation superseded this one - drop the stale result
1380
1384
  cachePut(to, loaded) // keep the keyed cache coherent with what we publish
1381
- // The page's own URL is never intercepted: navigating there (Back out of a boundary, a link
1382
- // to itself) shows the page, even when a boundary's pattern matches it.
1383
- const pagePath = pagePathOf(state)
1384
- const interception =
1385
- loaded.terminalRouteId === undefined && pathnameOf(to) !== pathnameOf(pagePath)
1386
- ? interceptionFor(state.routeId, to, target)
1387
- : undefined
1388
- if (interception !== undefined) {
1389
- // The target's ordinary data request has already completed (including its server authz and
1390
- // optional client wrapper). Only the presentation route is retained; the current page stays
1391
- // mounted underneath the named slot. A target that returned a terminal status never reaches
1392
- // this branch, so an unauthorized/missing target cannot become a modal.
1385
+ // The swap can wait a frame for the presenter, and a newer navigation started meanwhile owns it.
1386
+ await present(() => {
1387
+ if (mine !== generation) return
1388
+ // The page's own URL is never intercepted: navigating there (Back out of a boundary, a link
1389
+ // to itself) shows the page, even when a boundary's pattern matches it.
1390
+ const pagePath = pagePathOf(state)
1391
+ const interception =
1392
+ loaded.terminalRouteId === undefined && pathnameOf(to) !== pathnameOf(pagePath)
1393
+ ? interceptionFor(state.routeId, to, target)
1394
+ : undefined
1395
+ if (interception !== undefined) {
1396
+ // The target's ordinary data request has already completed (including its server authz and
1397
+ // optional client wrapper). Only the presentation route is retained; the current page stays
1398
+ // mounted underneath the named slot. A target that returned a terminal status never reaches
1399
+ // this branch, so an unauthorized/missing target cannot become a modal.
1400
+ state = {
1401
+ ...state,
1402
+ path: to,
1403
+ boundaries: holdTarget(state.boundaries, interception, loaded),
1404
+ intercepted: { boundary: interception.name, pagePath },
1405
+ pending: false,
1406
+ pendingPath: undefined,
1407
+ }
1408
+ emit()
1409
+ return
1410
+ }
1393
1411
  state = {
1394
- ...state,
1412
+ routeId: loaded.terminalRouteId ?? target.routeId,
1413
+ params: loaded.terminalRouteId === undefined ? target.params : {},
1395
1414
  path: to,
1396
- boundaries: holdTarget(state.boundaries, interception, loaded),
1397
- intercepted: { boundary: interception.name, pagePath },
1415
+ ...dataFields(loaded),
1416
+ actionData: undefined, // a fresh navigation has no action result
1398
1417
  pending: false,
1399
- pendingPath: undefined,
1400
1418
  }
1419
+ shownPage = loaded
1401
1420
  emit()
1402
- return
1403
- }
1404
- state = {
1405
- routeId: loaded.terminalRouteId ?? target.routeId,
1406
- params: loaded.terminalRouteId === undefined ? target.params : {},
1407
- path: to,
1408
- ...dataFields(loaded),
1409
- actionData: undefined, // a fresh navigation has no action result
1410
- pending: false,
1411
- }
1412
- shownPage = loaded
1413
- emit()
1421
+ })
1414
1422
  } catch (err) {
1415
- // Clear our pending flag + target (only if still current) and rethrow - the caller decides how
1416
- // to recover (the history layer falls back to a full-page navigation).
1417
- if (mine === generation) {
1418
- state = { ...state, pending: false, pendingPath: undefined }
1419
- emit()
1420
- }
1423
+ // A newer navigation or submit owns the page, and aborting this one's request is how it took
1424
+ // over: as with a submit, the superseded navigation settles quietly.
1425
+ if (mine !== generation) return
1426
+ // Clear our pending flag + target and rethrow - the caller decides how to recover (the history
1427
+ // layer falls back to a full-page navigation).
1428
+ state = { ...state, pending: false, pendingPath: undefined }
1429
+ emit()
1421
1430
  throw err
1422
1431
  }
1423
1432
  },
@@ -1,7 +1,8 @@
1
1
  import { narrowCapabilityContext } from "@nifrajs/core/capabilities"
2
2
  import { validateStandard } from "@nifrajs/core/schema"
3
3
  import { outputGuard, runGuard } from "../internal/output-guard.ts"
4
- import { isControlFlow, isStatusSignal } from "../internal/render-document.ts"
4
+ import { isControlFlow } from "../internal/render-document.ts"
5
+ import { isStatusSignal } from "../internal/status-page.ts"
5
6
  import { ServerComponentError } from "./error.ts"
6
7
  import { assertComponentSchema, componentValue } from "./schema.ts"
7
8
  import type {
@@ -1,5 +1,6 @@
1
1
  import type { CapabilityDefinition } from "@nifrajs/core/capabilities"
2
- import { isResponseResult, isStatusSignal, STATUS_SIGNAL } from "../internal/render-document.ts"
2
+ import { isResponseResult } from "../internal/render-document.ts"
3
+ import { isStatusSignal, STATUS_SIGNAL } from "../internal/status-page.ts"
3
4
  import {
4
5
  authorizeServerComponent,
5
6
  isServerComponent,
@@ -25,6 +25,17 @@ const reactRuntime = "\0nifra-component-react-runtime"
25
25
  // Vite uses forward slashes for module IDs even when the filesystem uses backslashes.
26
26
  const bare = (id: string): string => (id.split("?")[0] ?? id).replaceAll("\\", "/")
27
27
 
28
+ // Blanked with spaces rather than cut, so later offsets still point at the source.
29
+ function withoutUseClient(source: string, path: string): string {
30
+ for (const statement of sourceFile(source, path).statements) {
31
+ if (!ts.isExpressionStatement(statement) || !ts.isStringLiteral(statement.expression)) break
32
+ if (statement.expression.text !== "use client") continue
33
+ const start = statement.getStart()
34
+ return `${source.slice(0, start)}${" ".repeat(statement.end - start)}${source.slice(statement.end)}`
35
+ }
36
+ return source
37
+ }
38
+
28
39
  export interface ServerComponentsPluginOptions {
29
40
  readonly root: string
30
41
  readonly adapter: "react" | "vue" | "svelte" | "solid" | "preact"
@@ -224,23 +235,31 @@ export function serverComponentsPlugin(
224
235
  return null
225
236
  },
226
237
  transform(source, id) {
227
- if (id.includes(nativeQuery) || internal.has(bare(id))) return null
228
238
  const path = bare(id)
229
239
  let code = source
230
- if (componentFile.test(path)) {
231
- code = compileComponentModule(source, path, options.root, this.environment.name).code
232
- componentModules.add(path)
240
+ if (!id.includes(nativeQuery) && !internal.has(path)) {
241
+ if (componentFile.test(path)) {
242
+ code = compileComponentModule(source, path, options.root, this.environment.name).code
243
+ componentModules.add(path)
244
+ }
245
+ if (islandFile.test(path))
246
+ code = compileComponentModule(
247
+ source,
248
+ path,
249
+ options.root,
250
+ this.environment.name,
251
+ "island",
252
+ ).code
253
+ if (this.environment.name === "rsc" && options.adapter === "react")
254
+ code = guardCreateElementCalls(code, path)
233
255
  }
234
- if (islandFile.test(path))
235
- code = compileComponentModule(
236
- source,
237
- path,
238
- options.root,
239
- this.environment.name,
240
- "island",
241
- ).code
242
- if (this.environment.name === "rsc" && options.adapter === "react")
243
- code = guardCreateElementCalls(code, path)
256
+ // Only the rsc build acts on "use client"; the client and ssr bundles would warn about it.
257
+ if (
258
+ this.environment.name !== "rsc" &&
259
+ options.adapter === "react" &&
260
+ code.includes("use client")
261
+ )
262
+ code = withoutUseClient(code, path)
244
263
  return code === source ? null : { code, map: null }
245
264
  },
246
265
  }