@byline/host-tanstack-start 3.21.0 → 4.1.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 (236) hide show
  1. package/dist/admin-shell/admin-activity/list.js +15 -5
  2. package/dist/admin-shell/admin-roles/delete.js +2 -1
  3. package/dist/admin-shell/admin-roles/list.js +2 -1
  4. package/dist/admin-shell/admin-users/delete.js +2 -1
  5. package/dist/admin-shell/admin-users/list.js +4 -7
  6. package/dist/admin-shell/chrome/admin-app-bar.js +2 -1
  7. package/dist/admin-shell/chrome/admin-menu-drawer.js +9 -8
  8. package/dist/admin-shell/chrome/admin-menu-provider.js +2 -1
  9. package/dist/admin-shell/chrome/dashboard.js +4 -3
  10. package/dist/admin-shell/chrome/sign-in-page.d.ts +2 -2
  11. package/dist/admin-shell/chrome/sign-in-page.js +2 -2
  12. package/dist/admin-shell/collections/create.js +4 -3
  13. package/dist/admin-shell/collections/document-history.js +6 -1
  14. package/dist/admin-shell/collections/edit.js +18 -14
  15. package/dist/admin-shell/collections/history.js +5 -4
  16. package/dist/admin-shell/collections/list.js +8 -7
  17. package/dist/admin-shell/collections/restore-version-modal.js +2 -1
  18. package/dist/admin-shell/collections/tree-list.js +53 -17
  19. package/dist/admin-shell/collections/view-menu.js +9 -8
  20. package/dist/i18n/resolve-locale.js +1 -1
  21. package/dist/index.d.ts +5 -6
  22. package/dist/integrations/abilities.js +8 -7
  23. package/dist/integrations/host-bridge.d.ts +13 -0
  24. package/dist/integrations/host-bridge.js +22 -0
  25. package/dist/integrations/serve-uploads.d.ts +1 -0
  26. package/dist/integrations/serve-uploads.js +1 -0
  27. package/dist/integrations/start-errors.js +2 -1
  28. package/dist/routes/admin-path.d.ts +22 -0
  29. package/dist/routes/admin-path.js +35 -0
  30. package/dist/routes/create-admin-account-route.js +3 -2
  31. package/dist/routes/create-admin-activity-route.js +3 -2
  32. package/dist/routes/create-admin-dashboard-route.js +2 -1
  33. package/dist/routes/create-admin-layout-route.d.ts +1 -1
  34. package/dist/routes/create-admin-layout-route.js +3 -1
  35. package/dist/routes/create-admin-permissions-route.js +3 -2
  36. package/dist/routes/create-admin-role-edit-route.js +4 -3
  37. package/dist/routes/create-admin-roles-list-route.js +3 -2
  38. package/dist/routes/create-admin-user-edit-route.js +4 -3
  39. package/dist/routes/create-admin-users-list-route.js +3 -2
  40. package/dist/routes/create-collection-api-route.js +5 -4
  41. package/dist/routes/create-collection-create-route.js +4 -3
  42. package/dist/routes/create-collection-edit-route.js +4 -3
  43. package/dist/routes/create-collection-history-route.js +5 -4
  44. package/dist/routes/create-collection-list-route.js +3 -2
  45. package/dist/routes/create-sign-in-route.js +3 -2
  46. package/dist/routes/index.d.ts +3 -0
  47. package/dist/routes/index.js +2 -0
  48. package/dist/routes/sign-in-path.d.ts +13 -0
  49. package/dist/routes/sign-in-path.js +19 -0
  50. package/dist/server-fns/admin-account/change-password.js +1 -1
  51. package/dist/server-fns/admin-account/get.js +1 -1
  52. package/dist/server-fns/admin-account/update.js +1 -1
  53. package/dist/server-fns/admin-activity/get.js +1 -1
  54. package/dist/server-fns/admin-permissions/get-role-abilities.js +1 -1
  55. package/dist/server-fns/admin-permissions/list-registered.js +1 -1
  56. package/dist/server-fns/admin-permissions/set-role-abilities.js +1 -1
  57. package/dist/server-fns/admin-permissions/who-has.js +1 -1
  58. package/dist/server-fns/admin-roles/create.js +1 -1
  59. package/dist/server-fns/admin-roles/delete.js +1 -1
  60. package/dist/server-fns/admin-roles/get.js +1 -1
  61. package/dist/server-fns/admin-roles/list.js +1 -1
  62. package/dist/server-fns/admin-roles/reorder.js +1 -1
  63. package/dist/server-fns/admin-roles/update.js +1 -1
  64. package/dist/server-fns/admin-users/create.js +1 -1
  65. package/dist/server-fns/admin-users/delete.js +1 -1
  66. package/dist/server-fns/admin-users/disable.js +1 -1
  67. package/dist/server-fns/admin-users/enable.js +1 -1
  68. package/dist/server-fns/admin-users/get-user-roles.js +1 -1
  69. package/dist/server-fns/admin-users/get.js +1 -1
  70. package/dist/server-fns/admin-users/list.js +1 -1
  71. package/dist/server-fns/admin-users/set-password.js +1 -1
  72. package/dist/server-fns/admin-users/set-user-roles.js +1 -1
  73. package/dist/server-fns/admin-users/update.js +1 -1
  74. package/dist/server-fns/ai/execute.js +1 -1
  75. package/dist/server-fns/auth/current-user.js +1 -1
  76. package/dist/server-fns/auth/sign-in.js +1 -1
  77. package/dist/server-fns/auth/sign-out.js +1 -2
  78. package/dist/server-fns/collections/audit.js +1 -1
  79. package/dist/server-fns/collections/copy-to-locale.js +1 -1
  80. package/dist/server-fns/collections/create.js +1 -1
  81. package/dist/server-fns/collections/delete-locale.js +1 -1
  82. package/dist/server-fns/collections/delete-outcome.d.ts +30 -0
  83. package/dist/server-fns/collections/delete-outcome.js +34 -0
  84. package/dist/server-fns/collections/delete.d.ts +1 -4
  85. package/dist/server-fns/collections/delete.js +3 -5
  86. package/dist/server-fns/collections/duplicate.js +1 -1
  87. package/dist/server-fns/collections/get.js +1 -1
  88. package/dist/server-fns/collections/history.js +1 -1
  89. package/dist/server-fns/collections/index.d.ts +1 -0
  90. package/dist/server-fns/collections/index.js +1 -0
  91. package/dist/server-fns/collections/list.js +1 -1
  92. package/dist/server-fns/collections/reindex.js +1 -1
  93. package/dist/server-fns/collections/reorder.js +1 -1
  94. package/dist/server-fns/collections/restore-version.js +1 -1
  95. package/dist/server-fns/collections/stats.js +1 -1
  96. package/dist/server-fns/collections/status.js +1 -1
  97. package/dist/server-fns/collections/tree-mutation.d.ts +46 -0
  98. package/dist/server-fns/collections/tree-mutation.js +109 -0
  99. package/dist/server-fns/collections/tree-read.d.ts +6 -0
  100. package/dist/server-fns/collections/tree-read.js +15 -0
  101. package/dist/server-fns/collections/tree.d.ts +5 -4
  102. package/dist/server-fns/collections/tree.js +16 -13
  103. package/dist/server-fns/collections/update.js +3 -2
  104. package/dist/server-fns/collections/upload.js +1 -1
  105. package/dist/server-fns/i18n/set-locale.js +1 -1
  106. package/dist/server-fns/preview/disable.js +1 -1
  107. package/dist/server-fns/preview/enable.js +1 -2
  108. package/dist/server-fns/preview/state.js +1 -1
  109. package/dist/vite/client-hook-boundary.d.ts +19 -0
  110. package/dist/vite/client-hook-boundary.js +20 -0
  111. package/dist/{integrations/byline-public-client.d.ts → vite/index.d.ts} +1 -2
  112. package/dist/vite/index.js +1 -0
  113. package/package.json +15 -14
  114. package/src/admin-shell/admin-activity/list.tsx +13 -3
  115. package/src/admin-shell/admin-roles/delete.tsx +2 -1
  116. package/src/admin-shell/admin-roles/list.tsx +2 -1
  117. package/src/admin-shell/admin-users/delete.tsx +3 -2
  118. package/src/admin-shell/admin-users/list.tsx +9 -8
  119. package/src/admin-shell/chrome/admin-app-bar.tsx +2 -1
  120. package/src/admin-shell/chrome/admin-menu-drawer.tsx +13 -8
  121. package/src/admin-shell/chrome/admin-menu-provider.tsx +2 -1
  122. package/src/admin-shell/chrome/dashboard.tsx +4 -3
  123. package/src/admin-shell/chrome/sign-in-page.tsx +3 -3
  124. package/src/admin-shell/collections/create.tsx +4 -3
  125. package/src/admin-shell/collections/document-history.tsx +5 -0
  126. package/src/admin-shell/collections/edit.tsx +20 -13
  127. package/src/admin-shell/collections/history.tsx +6 -5
  128. package/src/admin-shell/collections/list.tsx +8 -7
  129. package/src/admin-shell/collections/restore-version-modal.tsx +2 -1
  130. package/src/admin-shell/collections/tree-list.tsx +78 -22
  131. package/src/admin-shell/collections/view-menu.tsx +9 -8
  132. package/src/i18n/resolve-locale.ts +1 -1
  133. package/src/index.ts +5 -6
  134. package/src/integrations/abilities.tsx +14 -4
  135. package/src/integrations/host-bridge.ts +56 -0
  136. package/src/integrations/serve-uploads.ts +6 -0
  137. package/src/integrations/start-errors.test.node.ts +22 -0
  138. package/src/integrations/start-errors.ts +1 -0
  139. package/src/routes/admin-path.test.node.ts +129 -0
  140. package/src/routes/admin-path.ts +66 -0
  141. package/src/routes/auth-routes.test.node.ts +39 -0
  142. package/src/routes/create-admin-account-route.tsx +3 -2
  143. package/src/routes/create-admin-activity-route.tsx +3 -2
  144. package/src/routes/create-admin-dashboard-route.tsx +2 -1
  145. package/src/routes/create-admin-layout-route.tsx +4 -4
  146. package/src/routes/create-admin-permissions-route.tsx +3 -2
  147. package/src/routes/create-admin-role-edit-route.tsx +7 -3
  148. package/src/routes/create-admin-roles-list-route.tsx +3 -2
  149. package/src/routes/create-admin-user-edit-route.tsx +7 -3
  150. package/src/routes/create-admin-users-list-route.tsx +3 -2
  151. package/src/routes/create-collection-api-route.tsx +8 -4
  152. package/src/routes/create-collection-create-route.tsx +7 -3
  153. package/src/routes/create-collection-edit-route.tsx +7 -3
  154. package/src/routes/create-collection-history-route.tsx +8 -4
  155. package/src/routes/create-collection-list-route.tsx +3 -2
  156. package/src/routes/create-sign-in-route.tsx +9 -3
  157. package/src/routes/index.ts +11 -0
  158. package/src/routes/sign-in-path.test.node.ts +79 -0
  159. package/src/routes/sign-in-path.ts +36 -0
  160. package/src/server-fns/admin-account/change-password.ts +1 -1
  161. package/src/server-fns/admin-account/get.ts +1 -1
  162. package/src/server-fns/admin-account/update.ts +1 -1
  163. package/src/server-fns/admin-activity/get.ts +1 -1
  164. package/src/server-fns/admin-permissions/get-role-abilities.ts +1 -1
  165. package/src/server-fns/admin-permissions/list-registered.ts +1 -1
  166. package/src/server-fns/admin-permissions/set-role-abilities.ts +1 -1
  167. package/src/server-fns/admin-permissions/who-has.ts +1 -1
  168. package/src/server-fns/admin-roles/create.ts +1 -1
  169. package/src/server-fns/admin-roles/delete.ts +1 -1
  170. package/src/server-fns/admin-roles/get.ts +1 -1
  171. package/src/server-fns/admin-roles/list.ts +1 -1
  172. package/src/server-fns/admin-roles/reorder.ts +1 -1
  173. package/src/server-fns/admin-roles/update.ts +1 -1
  174. package/src/server-fns/admin-users/create.ts +1 -1
  175. package/src/server-fns/admin-users/delete.ts +1 -1
  176. package/src/server-fns/admin-users/disable.ts +1 -1
  177. package/src/server-fns/admin-users/enable.ts +1 -1
  178. package/src/server-fns/admin-users/get-user-roles.ts +1 -1
  179. package/src/server-fns/admin-users/get.ts +1 -1
  180. package/src/server-fns/admin-users/list.ts +1 -1
  181. package/src/server-fns/admin-users/set-password.ts +1 -1
  182. package/src/server-fns/admin-users/set-user-roles.ts +1 -1
  183. package/src/server-fns/admin-users/update.ts +1 -1
  184. package/src/server-fns/ai/execute.ts +1 -2
  185. package/src/server-fns/auth/current-user.ts +1 -1
  186. package/src/server-fns/auth/sign-in.ts +1 -1
  187. package/src/server-fns/auth/sign-out.ts +5 -3
  188. package/src/server-fns/collections/audit.ts +1 -1
  189. package/src/server-fns/collections/copy-to-locale.ts +1 -1
  190. package/src/server-fns/collections/create.ts +1 -1
  191. package/src/server-fns/collections/delete-locale.ts +1 -1
  192. package/src/server-fns/collections/delete-outcome.test.node.ts +72 -0
  193. package/src/server-fns/collections/delete-outcome.ts +80 -0
  194. package/src/server-fns/collections/delete.ts +3 -2
  195. package/src/server-fns/collections/duplicate.ts +1 -1
  196. package/src/server-fns/collections/get.ts +3 -1
  197. package/src/server-fns/collections/history.ts +1 -1
  198. package/src/server-fns/collections/index.ts +1 -0
  199. package/src/server-fns/collections/list.ts +1 -1
  200. package/src/server-fns/collections/reindex.ts +1 -1
  201. package/src/server-fns/collections/reorder.ts +1 -1
  202. package/src/server-fns/collections/restore-version.ts +1 -1
  203. package/src/server-fns/collections/stats.ts +2 -1
  204. package/src/server-fns/collections/status.ts +1 -1
  205. package/src/server-fns/collections/tree-mutation.test.node.ts +225 -0
  206. package/src/server-fns/collections/tree-mutation.ts +165 -0
  207. package/src/server-fns/collections/tree-read.test.node.ts +46 -0
  208. package/src/server-fns/collections/tree-read.ts +26 -0
  209. package/src/server-fns/collections/tree.ts +27 -11
  210. package/src/server-fns/collections/update.ts +4 -1
  211. package/src/server-fns/collections/upload.ts +1 -1
  212. package/src/server-fns/i18n/set-locale.ts +1 -1
  213. package/src/server-fns/preview/disable.ts +1 -1
  214. package/src/server-fns/preview/enable.ts +1 -2
  215. package/src/server-fns/preview/state.ts +1 -1
  216. package/src/vite/client-hook-boundary.test.node.ts +50 -0
  217. package/src/vite/client-hook-boundary.ts +45 -0
  218. package/src/vite/index.ts +9 -0
  219. package/dist/auth/auth-context.d.ts +0 -37
  220. package/dist/auth/auth-context.js +0 -57
  221. package/dist/auth/auth-cookies.d.ts +0 -34
  222. package/dist/auth/auth-cookies.js +0 -46
  223. package/dist/auth/preview-cookies.d.ts +0 -19
  224. package/dist/auth/preview-cookies.js +0 -26
  225. package/dist/integrations/byline-client.d.ts +0 -24
  226. package/dist/integrations/byline-client.js +0 -25
  227. package/dist/integrations/byline-public-client.js +0 -15
  228. package/dist/integrations/byline-viewer-client.d.ts +0 -20
  229. package/dist/integrations/byline-viewer-client.js +0 -39
  230. package/src/auth/auth-context.test.node.ts +0 -215
  231. package/src/auth/auth-context.ts +0 -124
  232. package/src/auth/auth-cookies.ts +0 -113
  233. package/src/auth/preview-cookies.ts +0 -73
  234. package/src/integrations/byline-client.ts +0 -66
  235. package/src/integrations/byline-public-client.ts +0 -53
  236. package/src/integrations/byline-viewer-client.ts +0 -99
@@ -16,10 +16,12 @@
16
16
 
17
17
  import { createServerFn } from '@tanstack/react-start'
18
18
 
19
- import { ERR_NOT_FOUND, getLogger } from '@byline/core'
19
+ import { getAdminBylineClient } from '@byline/client/server'
20
+ import { createReadContext, ERR_NOT_FOUND, getLogger } from '@byline/core'
20
21
 
21
22
  import { ensureCollection } from '../../integrations/api-utils.js'
22
- import { getAdminBylineClient } from '../../integrations/byline-client.js'
23
+ import { placeAdminTreeNode, removeAdminTreeNode } from './tree-mutation.js'
24
+ import { getAdminTreeParent, getAdminUnplacedTreeDocuments } from './tree-read.js'
23
25
 
24
26
  // ---------------------------------------------------------------------------
25
27
  // Place / move a node within the tree (place / reorder / re-parent)
@@ -33,6 +35,7 @@ export const placeTreeNode = createServerFn({ method: 'POST' })
33
35
  parentDocumentId: string | null
34
36
  beforeDocumentId?: string | null
35
37
  afterDocumentId?: string | null
38
+ reconcile?: boolean
36
39
  }) => input
37
40
  )
38
41
  .handler(async ({ data }) => {
@@ -45,10 +48,11 @@ export const placeTreeNode = createServerFn({ method: 'POST' })
45
48
  }).log(getLogger())
46
49
  }
47
50
  const handle = getAdminBylineClient().collection(path)
48
- const result = await handle.placeTreeNode(documentId, {
51
+ const result = await placeAdminTreeNode(handle, documentId, {
49
52
  parentDocumentId: data.parentDocumentId,
50
53
  beforeDocumentId: data.beforeDocumentId ?? null,
51
54
  afterDocumentId: data.afterDocumentId ?? null,
55
+ reconcile: data.reconcile,
52
56
  })
53
57
  return { status: 'ok' as const, orderKey: result.orderKey }
54
58
  })
@@ -58,7 +62,7 @@ export const placeTreeNode = createServerFn({ method: 'POST' })
58
62
  // ---------------------------------------------------------------------------
59
63
 
60
64
  export const removeFromTree = createServerFn({ method: 'POST' })
61
- .validator((input: { collection: string; documentId: string }) => input)
65
+ .validator((input: { collection: string; documentId: string; reconcile?: boolean }) => input)
62
66
  .handler(async ({ data }) => {
63
67
  const { collection: path, documentId } = data
64
68
  const config = await ensureCollection(path)
@@ -68,7 +72,9 @@ export const removeFromTree = createServerFn({ method: 'POST' })
68
72
  details: { collectionPath: path },
69
73
  }).log(getLogger())
70
74
  }
71
- await getAdminBylineClient().collection(path).removeFromTree(documentId)
75
+ await removeAdminTreeNode(getAdminBylineClient().collection(path), documentId, {
76
+ reconcile: data.reconcile,
77
+ })
72
78
  return { status: 'ok' as const }
73
79
  })
74
80
 
@@ -118,7 +124,7 @@ export const getTreeParent = createServerFn({ method: 'GET' })
118
124
  details: { collectionPath: path },
119
125
  }).log(getLogger())
120
126
  }
121
- return getAdminBylineClient().collection(path).getTreeParent(documentId)
127
+ return getAdminTreeParent(getAdminBylineClient().collection(path), documentId)
122
128
  })
123
129
 
124
130
  // ---------------------------------------------------------------------------
@@ -154,8 +160,19 @@ export const getCollectionTree = createServerFn({ method: 'GET' })
154
160
  }).log(getLogger())
155
161
  }
156
162
  const handle = getAdminBylineClient().collection(path)
157
-
158
- const forest = await handle.getSubtree({ status: 'any', locale: data.locale })
163
+ // Shared deliberately across the structural and unplaced reads: the
164
+ // beforeRead predicate cache is per-ReadContext, so sharing gives both
165
+ // halves one predicate snapshot — a time-dependent predicate (embargo
166
+ // cutoff) evaluated twice could otherwise disagree between the placed
167
+ // forest and the unplaced list. The request-memoized context factory
168
+ // keeps this sharing safe (one request authority per request).
169
+ const readContext = createReadContext()
170
+
171
+ const forest = await handle.getSubtree({
172
+ status: 'any',
173
+ locale: data.locale,
174
+ _readContext: readContext,
175
+ })
159
176
  const rows: CollectionTreeRow[] = []
160
177
  const placed = new Set<string>()
161
178
  const walk = (nodes: typeof forest, depth: number, parentId: string | null): void => {
@@ -180,9 +197,8 @@ export const getCollectionTree = createServerFn({ method: 'GET' })
180
197
 
181
198
  // Surface documents not yet in the tree (e.g. freshly created) so they
182
199
  // remain reachable. Trees are small by design, so a single wide read is fine.
183
- const all = await handle.find({ status: 'any', pageSize: 1000, _bypassBeforeRead: true })
184
- for (const doc of all.docs) {
185
- if (placed.has(doc.id)) continue
200
+ const unplaced = await getAdminUnplacedTreeDocuments(handle, placed, readContext, data.locale)
201
+ for (const doc of unplaced) {
186
202
  rows.push({
187
203
  id: doc.id,
188
204
  parentId: null,
@@ -8,12 +8,12 @@
8
8
 
9
9
  import { createServerFn } from '@tanstack/react-start'
10
10
 
11
+ import { getAdminRequestContext } from '@byline/client/server'
11
12
  import { ERR_NOT_FOUND, getLogger, getServerConfig } from '@byline/core'
12
13
  import type { DocumentPatch } from '@byline/core/patches'
13
14
  import type { DocumentLifecycleContext } from '@byline/core/services'
14
15
  import { updateDocumentSystemFields, updateDocumentWithPatches } from '@byline/core/services'
15
16
 
16
- import { getAdminRequestContext } from '../../auth/auth-context.js'
17
17
  import { ensureCollection } from '../../integrations/api-utils.js'
18
18
 
19
19
  // ---------------------------------------------------------------------------
@@ -118,6 +118,9 @@ export const updateCollectionDocumentSystemFields = createServerFn({ method: 'PO
118
118
  locale: locale ?? serverConfig.i18n.content.defaultLocale,
119
119
  path: explicitPath,
120
120
  availableLocales,
121
+ // A failed after-hook happens after commit. Retrying this same admin save
122
+ // must re-run cache/search reconciliation when DB values are now current.
123
+ reconcile: true,
121
124
  })
122
125
 
123
126
  return { status: 'ok' as const }
@@ -1,5 +1,6 @@
1
1
  import { createServerFn } from '@tanstack/react-start'
2
2
 
3
+ import { getAdminRequestContext } from '@byline/client/server'
3
4
  import type {
4
5
  CollectionDefinition,
5
6
  FieldUploadContext,
@@ -12,7 +13,6 @@ import { extractImageMeta, generateImageVariants, isBypassMimeType } from '@byli
12
13
  import { getLogger, withLogContext } from '@byline/core/logger'
13
14
  import { uploadField as coreUploadField } from '@byline/core/services'
14
15
 
15
- import { getAdminRequestContext } from '../../auth/auth-context.js'
16
16
  import { ensureCollection } from '../../integrations/api-utils.js'
17
17
 
18
18
  /**
@@ -30,8 +30,8 @@ import { createServerFn } from '@tanstack/react-start'
30
30
  import type { AccountResponse } from '@byline/admin/admin-account'
31
31
  import { setPreferredLocaleCommand } from '@byline/admin/admin-account'
32
32
  import { AuthError, AuthErrorCodes } from '@byline/auth'
33
+ import { getAdminRequestContext } from '@byline/client/server'
33
34
 
34
- import { getAdminRequestContext } from '../../auth/auth-context.js'
35
35
  import { clearAdminLocaleCookie, setAdminLocaleCookie } from '../../i18n/locale-cookie.js'
36
36
  import { bylineCore } from '../../integrations/byline-core.js'
37
37
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { createServerFn } from '@tanstack/react-start'
18
18
 
19
- import { clearPreviewCookie } from '../../auth/preview-cookies.js'
19
+ import { clearPreviewCookie } from '@byline/client/server'
20
20
 
21
21
  export const disablePreviewModeFn = createServerFn({ method: 'POST' }).handler(async () => {
22
22
  clearPreviewCookie()
@@ -17,8 +17,7 @@
17
17
 
18
18
  import { createServerFn } from '@tanstack/react-start'
19
19
 
20
- import { getAdminRequestContext } from '../../auth/auth-context.js'
21
- import { setPreviewCookie } from '../../auth/preview-cookies.js'
20
+ import { getAdminRequestContext, setPreviewCookie } from '@byline/client/server'
22
21
 
23
22
  export const enablePreviewModeFn = createServerFn({ method: 'POST' }).handler(async () => {
24
23
  await getAdminRequestContext()
@@ -23,7 +23,7 @@
23
23
 
24
24
  import { createServerFn } from '@tanstack/react-start'
25
25
 
26
- import { readPreviewCookie } from '../../auth/preview-cookies.js'
26
+ import { readPreviewCookie } from '@byline/client/server'
27
27
 
28
28
  export const getPreviewStateFn = createServerFn({ method: 'GET' }).handler(async () => {
29
29
  return { preview: readPreviewCookie() }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Unit coverage for the client-bundle guard's module classifier. Production
11
+ * application builds exercise the Vite plugin itself; these focused cases pin
12
+ * path normalization and the supported server-only filename conventions.
13
+ */
14
+ import { describe, expect, it } from 'vitest'
15
+
16
+ import { findServerHookModules } from './client-hook-boundary.js'
17
+
18
+ describe('client hook boundary', () => {
19
+ it('finds collection hook implementations and their shared server graph', () => {
20
+ expect(
21
+ findServerHookModules([
22
+ '/project/byline/collections/docs/hooks.ts',
23
+ '/project/byline/collections/content-types/publications/file-hooks.ts',
24
+ '/project/byline/collections/content-types/publications/cover-hooks.tsx',
25
+ '/project/byline/collections/server-hooks.ts?import',
26
+ String.raw`C:\project\byline\collections\create-revalidation-lifecycle-hooks.ts`,
27
+ '/project/byline/collections/events/hooks.mts',
28
+ '/project/byline/collections/run-webhook-side-effects.ts?worker',
29
+ '/project/byline/collections/docs/schema.ts',
30
+ ])
31
+ ).toEqual([
32
+ '/project/byline/collections/docs/hooks.ts',
33
+ '/project/byline/collections/content-types/publications/file-hooks.ts',
34
+ '/project/byline/collections/content-types/publications/cover-hooks.tsx',
35
+ '/project/byline/collections/server-hooks.ts?import',
36
+ String.raw`C:\project\byline\collections\create-revalidation-lifecycle-hooks.ts`,
37
+ '/project/byline/collections/events/hooks.mts',
38
+ '/project/byline/collections/run-webhook-side-effects.ts?worker',
39
+ ])
40
+ })
41
+
42
+ it('allows schemas and unrelated client modules', () => {
43
+ expect(
44
+ findServerHookModules([
45
+ '/project/byline/collections/docs/schema.ts',
46
+ '/project/src/ui/byline/render-blocks.tsx',
47
+ ])
48
+ ).toEqual([])
49
+ })
50
+ })
@@ -0,0 +1,45 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Production client-bundle guard for server-only Byline lifecycle modules.
11
+ *
12
+ * Collection schemas are isomorphic, but registered lifecycle hooks may
13
+ * import Node APIs, caches, storage SDKs, and other server-only dependencies.
14
+ * This Vite plugin inspects emitted client chunks and fails the build if a
15
+ * hook implementation, server hook registry, or shared lifecycle helper
16
+ * becomes reachable from the browser bundle.
17
+ */
18
+ import type { Plugin } from 'vite'
19
+
20
+ const SERVER_HOOK_MODULE =
21
+ /\/byline\/collections\/(?:.+\/)?(?:hooks|[^/]+-hooks|[^/]+-lifecycle-hooks|[^/]+-side-effects)\.[cm]?[jt]sx?$/
22
+
23
+ export function findServerHookModules(moduleIds: Iterable<string>): string[] {
24
+ return [...moduleIds].filter((moduleId) => {
25
+ const normalized = moduleId.replaceAll('\\', '/').split('?', 1)[0]
26
+ return SERVER_HOOK_MODULE.test(normalized)
27
+ })
28
+ }
29
+
30
+ export function bylineClientHookBoundary(): Plugin {
31
+ return {
32
+ name: 'byline:client-hook-boundary',
33
+ generateBundle(_options, bundle) {
34
+ if (this.environment?.name !== 'client') return
35
+ const leaked = findServerHookModules(
36
+ Object.values(bundle).flatMap((output) =>
37
+ output.type === 'chunk' ? Object.keys(output.modules) : []
38
+ )
39
+ )
40
+ if (leaked.length > 0) {
41
+ this.error(`server lifecycle modules entered the client bundle:\n${leaked.join('\n')}`)
42
+ }
43
+ },
44
+ }
45
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ export { bylineClientHookBoundary } from './client-hook-boundary.js'
@@ -1,37 +0,0 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
- /**
9
- * Request-scoped auth context for admin server functions.
10
- *
11
- * Reads the session cookies, verifies the access token, transparently
12
- * refreshes when needed, and returns a `RequestContext` carrying the
13
- * authenticated `AdminAuth`. Every admin server fn calls this as its
14
- * first step — it is the single point where the admin transport boundary
15
- * meets the actor/ability machinery.
16
- *
17
- * Flow, in order:
18
- *
19
- * 1. Read the access cookie.
20
- * 2. If present, try `sessionProvider.verifyAccessToken`. On success,
21
- * return the context — no DB write, no cookie churn.
22
- * 3. On verify failure (or missing access cookie), read the refresh
23
- * cookie. If present, call `sessionProvider.refreshSession` — this
24
- * rotates the refresh token atomically and issues a new access
25
- * token. Write both fresh cookies to the response.
26
- * 4. Verify the new access token (populate the actor) and return the
27
- * context.
28
- * 5. Any failure along the way clears both cookies so the browser
29
- * stops sending a session the server has already rejected, and
30
- * throws `ERR_UNAUTHENTICATED`.
31
- *
32
- * Burns a refresh-token rotation only when the access token actually
33
- * fails verification — not on every request. The "one session" UX is
34
- * the consequence of this helper working invisibly behind each call.
35
- */
36
- import { type RequestContext } from '@byline/auth';
37
- export declare function getAdminRequestContext(): Promise<RequestContext>;
@@ -1,57 +0,0 @@
1
- import { ERR_UNAUTHENTICATED } from "@byline/auth";
2
- import { getServerConfig } from "@byline/core";
3
- import { v7 } from "uuid";
4
- import { clearSessionCookies, readAccessTokenCookie, readRefreshTokenCookie, setSessionCookies } from "./auth-cookies.js";
5
- function requireSessionProvider() {
6
- const provider = getServerConfig().sessionProvider;
7
- if (!provider) throw new Error("No sessionProvider configured on ServerConfig. Construct a JwtSessionProvider in your server config and pass it to initBylineCore().");
8
- return provider;
9
- }
10
- async function getAdminRequestContext() {
11
- const provider = requireSessionProvider();
12
- const accessToken = readAccessTokenCookie();
13
- if (accessToken) try {
14
- const { actor } = await provider.verifyAccessToken(accessToken);
15
- return {
16
- actor,
17
- requestId: v7(),
18
- readMode: 'any'
19
- };
20
- } catch {}
21
- const refreshToken = readRefreshTokenCookie();
22
- if (!refreshToken) {
23
- if (accessToken) clearSessionCookies();
24
- throw ERR_UNAUTHENTICATED({
25
- message: 'no admin session'
26
- });
27
- }
28
- let refreshed;
29
- try {
30
- refreshed = await provider.refreshSession({
31
- refreshToken
32
- });
33
- } catch (err) {
34
- clearSessionCookies();
35
- throw ERR_UNAUTHENTICATED({
36
- message: 'admin session could not be refreshed',
37
- cause: err
38
- });
39
- }
40
- setSessionCookies(refreshed);
41
- let verified;
42
- try {
43
- verified = await provider.verifyAccessToken(refreshed.accessToken);
44
- } catch (err) {
45
- clearSessionCookies();
46
- throw ERR_UNAUTHENTICATED({
47
- message: 'refreshed access token did not verify',
48
- cause: err
49
- });
50
- }
51
- return {
52
- actor: verified.actor,
53
- requestId: v7(),
54
- readMode: 'any'
55
- };
56
- }
57
- export { getAdminRequestContext };
@@ -1,34 +0,0 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
- export declare const ACCESS_TOKEN_COOKIE = "byline_access_token";
9
- export declare const REFRESH_TOKEN_COOKIE = "byline_refresh_token";
10
- /** Read the access-token cookie. Returns undefined when not present. */
11
- export declare function readAccessTokenCookie(): string | undefined;
12
- /** Read the refresh-token cookie. Returns undefined when not present. */
13
- export declare function readRefreshTokenCookie(): string | undefined;
14
- export interface SessionCookieTokens {
15
- accessToken: string;
16
- refreshToken: string;
17
- accessTokenExpiresAt: Date;
18
- refreshTokenExpiresAt: Date;
19
- }
20
- /**
21
- * Write both access and refresh cookies. Called after sign-in and after
22
- * every transparent refresh in `getAdminRequestContext()`.
23
- *
24
- * `maxAge` is derived from each token's own expiry claim so the browser
25
- * drops the cookies at the same moment the server would reject them —
26
- * saves round trips when the refresh token has fully expired.
27
- */
28
- export declare function setSessionCookies(tokens: SessionCookieTokens): void;
29
- /**
30
- * Clear both session cookies. Called on sign-out and on any auth failure
31
- * during `getAdminRequestContext()` (ensures the browser does not keep
32
- * trying a token that the server has already rejected).
33
- */
34
- export declare function clearSessionCookies(): void;
@@ -1,46 +0,0 @@
1
- import { getCookie, setCookie } from "@tanstack/react-start/server";
2
- const ACCESS_TOKEN_COOKIE = 'byline_access_token';
3
- const REFRESH_TOKEN_COOKIE = 'byline_refresh_token';
4
- const IS_PROD = 'production' === process.env.NODE_ENV;
5
- function readAccessTokenCookie() {
6
- return getCookie(ACCESS_TOKEN_COOKIE);
7
- }
8
- function readRefreshTokenCookie() {
9
- return getCookie(REFRESH_TOKEN_COOKIE);
10
- }
11
- function setSessionCookies(tokens) {
12
- const now = Date.now();
13
- const accessMaxAgeSeconds = Math.max(0, Math.floor((tokens.accessTokenExpiresAt.getTime() - now) / 1000));
14
- const refreshMaxAgeSeconds = Math.max(0, Math.floor((tokens.refreshTokenExpiresAt.getTime() - now) / 1000));
15
- setCookie(ACCESS_TOKEN_COOKIE, tokens.accessToken, {
16
- httpOnly: true,
17
- sameSite: 'lax',
18
- secure: IS_PROD,
19
- path: '/',
20
- maxAge: accessMaxAgeSeconds
21
- });
22
- setCookie(REFRESH_TOKEN_COOKIE, tokens.refreshToken, {
23
- httpOnly: true,
24
- sameSite: 'lax',
25
- secure: IS_PROD,
26
- path: '/',
27
- maxAge: refreshMaxAgeSeconds
28
- });
29
- }
30
- function clearSessionCookies() {
31
- setCookie(ACCESS_TOKEN_COOKIE, '', {
32
- httpOnly: true,
33
- sameSite: 'lax',
34
- secure: IS_PROD,
35
- path: '/',
36
- maxAge: 0
37
- });
38
- setCookie(REFRESH_TOKEN_COOKIE, '', {
39
- httpOnly: true,
40
- sameSite: 'lax',
41
- secure: IS_PROD,
42
- path: '/',
43
- maxAge: 0
44
- });
45
- }
46
- export { ACCESS_TOKEN_COOKIE, REFRESH_TOKEN_COOKIE, clearSessionCookies, readAccessTokenCookie, readRefreshTokenCookie, setSessionCookies };
@@ -1,19 +0,0 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
- export declare const PREVIEW_COOKIE = "byline_preview";
9
- /** True iff the preview cookie is currently set on the request. */
10
- export declare function readPreviewCookie(): boolean;
11
- /**
12
- * Write the preview cookie. Callers should ensure the request is from a
13
- * valid admin session before invoking — the cookie itself is just a flag
14
- * and carries no proof of authorisation. See `enablePreviewModeFn` for
15
- * the canonical entry point.
16
- */
17
- export declare function setPreviewCookie(): void;
18
- /** Clear the preview cookie. Safe to call from any context. */
19
- export declare function clearPreviewCookie(): void;
@@ -1,26 +0,0 @@
1
- import { getCookie, setCookie } from "@tanstack/react-start/server";
2
- const PREVIEW_COOKIE = 'byline_preview';
3
- const PREVIEW_MAX_AGE_SECONDS = 86400;
4
- const IS_PROD = 'production' === process.env.NODE_ENV;
5
- function readPreviewCookie() {
6
- return '1' === getCookie(PREVIEW_COOKIE);
7
- }
8
- function setPreviewCookie() {
9
- setCookie(PREVIEW_COOKIE, '1', {
10
- httpOnly: true,
11
- sameSite: 'lax',
12
- secure: IS_PROD,
13
- path: '/',
14
- maxAge: PREVIEW_MAX_AGE_SECONDS
15
- });
16
- }
17
- function clearPreviewCookie() {
18
- setCookie(PREVIEW_COOKIE, '', {
19
- httpOnly: true,
20
- sameSite: 'lax',
21
- secure: IS_PROD,
22
- path: '/',
23
- maxAge: 0
24
- });
25
- }
26
- export { PREVIEW_COOKIE, clearPreviewCookie, readPreviewCookie, setPreviewCookie };
@@ -1,24 +0,0 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
- import { type BylineClient } from '@byline/client';
9
- export declare function getAdminBylineClient(): BylineClient;
10
- /**
11
- * A `BylineClient` bound to an explicit super-admin context — for
12
- * **system / background** work that is not scoped to an HTTP request:
13
- * lifecycle-hook search indexing, maintenance scripts, seeds, migrations.
14
- *
15
- * Unlike {@link getAdminBylineClient}, this does **not** read session
16
- * cookies, so it works outside the TanStack Start server runtime (a bare
17
- * `tsx` script, a seed, a test). Reaching for the request-scoped client in
18
- * a lifecycle hook couples that hook to the live server and throws
19
- * `No StartEvent found in AsyncLocalStorage` from any out-of-band write
20
- * path. Indexing is maintenance, not a user action — it reads the published
21
- * view and bypasses `beforeRead` — so the super-admin context is both
22
- * correct and runtime-agnostic. The context is auditable by its stable id.
23
- */
24
- export declare function getSystemBylineClient(): BylineClient;
@@ -1,25 +0,0 @@
1
- import { createSuperAdminContext } from "@byline/auth";
2
- import { createBylineClient } from "@byline/client";
3
- import { getServerConfig } from "@byline/core";
4
- import { getAdminRequestContext } from "../auth/auth-context.js";
5
- let cachedClient;
6
- function getAdminBylineClient() {
7
- if (cachedClient) return cachedClient;
8
- cachedClient = createBylineClient({
9
- config: getServerConfig(),
10
- requestContext: getAdminRequestContext
11
- });
12
- return cachedClient;
13
- }
14
- let cachedSystemClient;
15
- function getSystemBylineClient() {
16
- if (cachedSystemClient) return cachedSystemClient;
17
- cachedSystemClient = createBylineClient({
18
- config: getServerConfig(),
19
- requestContext: createSuperAdminContext({
20
- id: 'byline-system-client'
21
- })
22
- });
23
- return cachedSystemClient;
24
- }
25
- export { getAdminBylineClient, getSystemBylineClient };
@@ -1,15 +0,0 @@
1
- import { createRequestContext } from "@byline/auth";
2
- import { createBylineClient } from "@byline/client";
3
- import { getServerConfig } from "@byline/core";
4
- let cachedClient;
5
- function getPublicBylineClient() {
6
- if (cachedClient) return cachedClient;
7
- cachedClient = createBylineClient({
8
- config: getServerConfig(),
9
- requestContext: ()=>createRequestContext({
10
- readMode: 'published'
11
- })
12
- });
13
- return cachedClient;
14
- }
15
- export { getPublicBylineClient };
@@ -1,20 +0,0 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
- import { type BylineClient } from '@byline/client';
9
- export declare function getViewerBylineClient(): BylineClient;
10
- /**
11
- * Resolve whether the current request should surface non-published
12
- * versions. True iff the preview cookie is set **and** a valid admin
13
- * session resolves. Server fns call this once and pass the result to
14
- * `status: preview ? 'any' : 'published'` on the SDK read.
15
- *
16
- * Defensive by design: the cookie alone is not enough. A signed-out
17
- * browser carrying an old preview cookie still gets `false` here, which
18
- * keeps stray query strings or shared links from leaking drafts.
19
- */
20
- export declare function isPreviewActive(): Promise<boolean>;
@@ -1,39 +0,0 @@
1
- import { createRequestContext } from "@byline/auth";
2
- import { createBylineClient } from "@byline/client";
3
- import { getServerConfig } from "@byline/core";
4
- import { getAdminRequestContext } from "../auth/auth-context.js";
5
- import { readPreviewCookie } from "../auth/preview-cookies.js";
6
- let cachedClient;
7
- function getViewerBylineClient() {
8
- if (cachedClient) return cachedClient;
9
- cachedClient = createBylineClient({
10
- config: getServerConfig(),
11
- requestContext: async ()=>{
12
- if (!readPreviewCookie()) return createRequestContext({
13
- readMode: 'published'
14
- });
15
- try {
16
- const ctx = await getAdminRequestContext();
17
- return {
18
- ...ctx,
19
- readMode: 'any'
20
- };
21
- } catch {
22
- return createRequestContext({
23
- readMode: 'published'
24
- });
25
- }
26
- }
27
- });
28
- return cachedClient;
29
- }
30
- async function isPreviewActive() {
31
- if (!readPreviewCookie()) return false;
32
- try {
33
- await getAdminRequestContext();
34
- return true;
35
- } catch {
36
- return false;
37
- }
38
- }
39
- export { getViewerBylineClient, isPreviewActive };