@byline/host-tanstack-start 3.9.0 → 3.10.1

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 (97) hide show
  1. package/dist/admin-shell/collections/document-history.d.ts +53 -0
  2. package/dist/admin-shell/collections/document-history.js +103 -0
  3. package/dist/admin-shell/collections/document-history.module.js +9 -0
  4. package/dist/admin-shell/collections/document-history_module.css +28 -0
  5. package/dist/admin-shell/collections/history.d.ts +8 -1
  6. package/dist/admin-shell/collections/history.js +73 -29
  7. package/dist/admin-shell/collections/history.module.js +1 -0
  8. package/dist/admin-shell/collections/history_module.css +4 -0
  9. package/dist/routes/create-collection-history-route.js +23 -6
  10. package/dist/server-fns/admin-account/change-password.js +1 -1
  11. package/dist/server-fns/admin-account/get.js +1 -1
  12. package/dist/server-fns/admin-account/update.js +1 -1
  13. package/dist/server-fns/admin-permissions/get-role-abilities.js +1 -1
  14. package/dist/server-fns/admin-permissions/set-role-abilities.js +1 -1
  15. package/dist/server-fns/admin-permissions/who-has.js +1 -1
  16. package/dist/server-fns/admin-roles/create.js +1 -1
  17. package/dist/server-fns/admin-roles/delete.js +1 -1
  18. package/dist/server-fns/admin-roles/get.js +1 -1
  19. package/dist/server-fns/admin-roles/reorder.js +1 -1
  20. package/dist/server-fns/admin-roles/update.js +1 -1
  21. package/dist/server-fns/admin-users/create.js +1 -1
  22. package/dist/server-fns/admin-users/delete.js +1 -1
  23. package/dist/server-fns/admin-users/disable.js +1 -1
  24. package/dist/server-fns/admin-users/enable.js +1 -1
  25. package/dist/server-fns/admin-users/get-user-roles.js +1 -1
  26. package/dist/server-fns/admin-users/get.js +1 -1
  27. package/dist/server-fns/admin-users/list.js +1 -1
  28. package/dist/server-fns/admin-users/set-password.js +1 -1
  29. package/dist/server-fns/admin-users/set-user-roles.js +1 -1
  30. package/dist/server-fns/admin-users/update.js +1 -1
  31. package/dist/server-fns/ai/execute.js +1 -1
  32. package/dist/server-fns/auth/sign-in.js +1 -1
  33. package/dist/server-fns/collections/audit.d.ts +50 -0
  34. package/dist/server-fns/collections/audit.js +40 -0
  35. package/dist/server-fns/collections/copy-to-locale.js +1 -1
  36. package/dist/server-fns/collections/create.js +1 -1
  37. package/dist/server-fns/collections/delete-locale.js +1 -1
  38. package/dist/server-fns/collections/delete.js +1 -1
  39. package/dist/server-fns/collections/duplicate.js +1 -1
  40. package/dist/server-fns/collections/get.js +2 -2
  41. package/dist/server-fns/collections/history.js +1 -1
  42. package/dist/server-fns/collections/index.d.ts +1 -0
  43. package/dist/server-fns/collections/index.js +1 -0
  44. package/dist/server-fns/collections/list.js +1 -1
  45. package/dist/server-fns/collections/reorder.js +1 -1
  46. package/dist/server-fns/collections/restore-version.js +1 -1
  47. package/dist/server-fns/collections/stats.js +1 -1
  48. package/dist/server-fns/collections/status.js +2 -2
  49. package/dist/server-fns/collections/update.js +2 -2
  50. package/dist/server-fns/collections/upload.js +1 -1
  51. package/dist/server-fns/i18n/set-locale.js +1 -1
  52. package/package.json +10 -10
  53. package/src/admin-shell/collections/document-history.module.css +46 -0
  54. package/src/admin-shell/collections/document-history.tsx +156 -0
  55. package/src/admin-shell/collections/history.module.css +6 -0
  56. package/src/admin-shell/collections/history.tsx +331 -281
  57. package/src/routes/create-collection-history-route.tsx +25 -3
  58. package/src/server-fns/admin-account/change-password.ts +1 -1
  59. package/src/server-fns/admin-account/get.ts +1 -1
  60. package/src/server-fns/admin-account/update.ts +1 -1
  61. package/src/server-fns/admin-permissions/get-role-abilities.ts +1 -1
  62. package/src/server-fns/admin-permissions/set-role-abilities.ts +1 -1
  63. package/src/server-fns/admin-permissions/who-has.ts +1 -1
  64. package/src/server-fns/admin-roles/create.ts +1 -1
  65. package/src/server-fns/admin-roles/delete.ts +1 -1
  66. package/src/server-fns/admin-roles/get.ts +1 -1
  67. package/src/server-fns/admin-roles/reorder.ts +1 -1
  68. package/src/server-fns/admin-roles/update.ts +1 -1
  69. package/src/server-fns/admin-users/create.ts +1 -1
  70. package/src/server-fns/admin-users/delete.ts +1 -1
  71. package/src/server-fns/admin-users/disable.ts +1 -1
  72. package/src/server-fns/admin-users/enable.ts +1 -1
  73. package/src/server-fns/admin-users/get-user-roles.ts +1 -1
  74. package/src/server-fns/admin-users/get.ts +1 -1
  75. package/src/server-fns/admin-users/list.ts +1 -1
  76. package/src/server-fns/admin-users/set-password.ts +1 -1
  77. package/src/server-fns/admin-users/set-user-roles.ts +1 -1
  78. package/src/server-fns/admin-users/update.ts +1 -1
  79. package/src/server-fns/ai/execute.ts +1 -1
  80. package/src/server-fns/auth/sign-in.ts +1 -1
  81. package/src/server-fns/collections/audit.ts +95 -0
  82. package/src/server-fns/collections/copy-to-locale.ts +1 -1
  83. package/src/server-fns/collections/create.ts +1 -1
  84. package/src/server-fns/collections/delete-locale.ts +1 -1
  85. package/src/server-fns/collections/delete.ts +1 -1
  86. package/src/server-fns/collections/duplicate.ts +1 -1
  87. package/src/server-fns/collections/get.ts +2 -2
  88. package/src/server-fns/collections/history.ts +1 -1
  89. package/src/server-fns/collections/index.ts +1 -0
  90. package/src/server-fns/collections/list.ts +1 -1
  91. package/src/server-fns/collections/reorder.ts +1 -1
  92. package/src/server-fns/collections/restore-version.ts +1 -1
  93. package/src/server-fns/collections/stats.ts +1 -1
  94. package/src/server-fns/collections/status.ts +2 -2
  95. package/src/server-fns/collections/update.ts +2 -2
  96. package/src/server-fns/collections/upload.ts +1 -1
  97. package/src/server-fns/i18n/set-locale.ts +1 -1
@@ -21,6 +21,7 @@ import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs
21
21
  import { HistoryView } from '../admin-shell/collections/history.js'
22
22
  import {
23
23
  getCollectionDocument,
24
+ getCollectionDocumentAuditLog,
24
25
  getCollectionDocumentHistory,
25
26
  } from '../server-fns/collections/index.js'
26
27
  import type { ContentLocaleOption } from '../admin-shell/collections/view-menu.js'
@@ -31,8 +32,17 @@ const searchSchema = z.object({
31
32
  order: z.string().optional(),
32
33
  desc: z.coerce.boolean().optional(),
33
34
  locale: z.string().optional(),
35
+ // Which sub-view of the history page is active (docs/AUDIT.md — Workstream
36
+ // 3). 'versions' is the content version stream; 'document' is the
37
+ // document-grain audit log. Absent → 'versions'.
38
+ tab: z.enum(['versions', 'document']).optional(),
34
39
  })
35
40
 
41
+ // The per-document audit log is small and bounded (path / locale / status
42
+ // changes + the deletion event), so v1 fetches a single generous page rather
43
+ // than wiring a second, tab-specific pager into the shared history route.
44
+ const AUDIT_LOG_PAGE_SIZE = 100
45
+
36
46
  interface CollectionHistoryOpts {
37
47
  contentLocales: ReadonlyArray<ContentLocaleOption>
38
48
  defaultContentLocale: string
@@ -67,7 +77,7 @@ export function createCollectionHistoryRoute(path: string, opts: CollectionHisto
67
77
  throw notFound()
68
78
  }
69
79
 
70
- const [history, currentDocument] = await Promise.all([
80
+ const [history, currentDocument, auditLog] = await Promise.all([
71
81
  getCollectionDocumentHistory({
72
82
  data: {
73
83
  collection: params.collection,
@@ -84,15 +94,26 @@ export function createCollectionHistoryRoute(path: string, opts: CollectionHisto
84
94
  // Fetch the current document with the same locale (or 'all') so diffs
85
95
  // compare the same shape as what the user is viewing.
86
96
  getCollectionDocument(params.collection, params.id, deps.locale ?? 'all'),
97
+ // Document-grain audit log for the "Document history" tab (W3). Fetched
98
+ // in parallel and unconditionally — it's cheap, and the active tab is a
99
+ // pure render concern read from the URL, so switching tabs never
100
+ // refetches.
101
+ getCollectionDocumentAuditLog({
102
+ data: {
103
+ collection: params.collection,
104
+ id: params.id,
105
+ params: { page: 1, page_size: AUDIT_LOG_PAGE_SIZE },
106
+ },
107
+ }),
87
108
  ])
88
109
 
89
- return { history, currentDocument }
110
+ return { history, currentDocument, auditLog }
90
111
  },
91
112
  staleTime: 0,
92
113
  gcTime: 0,
93
114
  shouldReload: true,
94
115
  component: function CollectionHistoryComponent() {
95
- const { history, currentDocument } = Route.useLoaderData()
116
+ const { history, currentDocument, auditLog } = Route.useLoaderData()
96
117
  const { collection } = Route.useParams() as { collection: string; id: string }
97
118
  const collectionDef = getCollectionDefinition(collection) as CollectionDefinition
98
119
  const adminConfig = getCollectionAdminConfig(collection)
@@ -119,6 +140,7 @@ export function createCollectionHistoryRoute(path: string, opts: CollectionHisto
119
140
  workflowStatuses={getWorkflowStatuses(collectionDef)}
120
141
  adminConfig={adminConfig ?? undefined}
121
142
  data={history}
143
+ auditLog={auditLog}
122
144
  currentDocument={currentDocument as Record<string, unknown> | null}
123
145
  contentLocales={opts.contentLocales}
124
146
  defaultContentLocale={opts.defaultContentLocale}
@@ -20,7 +20,7 @@ export interface ChangeAccountPasswordInput {
20
20
  }
21
21
 
22
22
  export const changeAccountPassword = createServerFn({ method: 'POST' })
23
- .inputValidator((input: ChangeAccountPasswordInput) => input)
23
+ .validator((input: ChangeAccountPasswordInput) => input)
24
24
  .handler(async ({ data }): Promise<AccountResponse> => {
25
25
  const context = await getAdminRequestContext()
26
26
  return changeAccountPasswordCommand(context, data, { store: bylineCore().adminStore! })
@@ -20,7 +20,7 @@ import { bylineCore } from '../../integrations/byline-core.js'
20
20
  * doesn't carry.
21
21
  */
22
22
  export const getAccount = createServerFn({ method: 'GET' })
23
- .inputValidator((input?: Record<string, never>) => input ?? {})
23
+ .validator((input?: Record<string, never>) => input ?? {})
24
24
  .handler(async ({ data }): Promise<AccountResponse> => {
25
25
  const context = await getAdminRequestContext()
26
26
  return getAccountCommand(context, data, { store: bylineCore().adminStore! })
@@ -24,7 +24,7 @@ export interface UpdateAccountInput {
24
24
  }
25
25
 
26
26
  export const updateAccount = createServerFn({ method: 'POST' })
27
- .inputValidator((input: UpdateAccountInput) => input)
27
+ .validator((input: UpdateAccountInput) => input)
28
28
  .handler(async ({ data }): Promise<AccountResponse> => {
29
29
  const context = await getAdminRequestContext()
30
30
  return updateAccountCommand(context, data, { store: bylineCore().adminStore! })
@@ -17,7 +17,7 @@ import { getAdminRequestContext } from '../../auth/auth-context.js'
17
17
  import { bylineCore } from '../../integrations/byline-core.js'
18
18
 
19
19
  export const getRoleAbilities = createServerFn({ method: 'GET' })
20
- .inputValidator((input: { id: string }) => input)
20
+ .validator((input: { id: string }) => input)
21
21
  .handler(async ({ data }): Promise<GetRoleAbilitiesResponse> => {
22
22
  const context = await getAdminRequestContext()
23
23
  return getRoleAbilitiesCommand(context, data, {
@@ -22,7 +22,7 @@ export interface SetRoleAbilitiesInput {
22
22
  }
23
23
 
24
24
  export const setRoleAbilities = createServerFn({ method: 'POST' })
25
- .inputValidator((input: SetRoleAbilitiesInput) => input)
25
+ .validator((input: SetRoleAbilitiesInput) => input)
26
26
  .handler(async ({ data }): Promise<SetRoleAbilitiesResponse> => {
27
27
  const context = await getAdminRequestContext()
28
28
  return setRoleAbilitiesCommand(context, data, {
@@ -14,7 +14,7 @@ import { getAdminRequestContext } from '../../auth/auth-context.js'
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
15
 
16
16
  export const whoHasAbility = createServerFn({ method: 'GET' })
17
- .inputValidator((input: { ability: string }) => input)
17
+ .validator((input: { ability: string }) => input)
18
18
  .handler(async ({ data }): Promise<WhoHasAbilityResponse> => {
19
19
  const context = await getAdminRequestContext()
20
20
  return whoHasAbilityCommand(context, data, {
@@ -21,7 +21,7 @@ export interface CreateAdminRoleInput {
21
21
  }
22
22
 
23
23
  export const createAdminRole = createServerFn({ method: 'POST' })
24
- .inputValidator((input: CreateAdminRoleInput) => input)
24
+ .validator((input: CreateAdminRoleInput) => input)
25
25
  .handler(async ({ data }): Promise<AdminRoleResponse> => {
26
26
  const context = await getAdminRequestContext()
27
27
  return createAdminRoleCommand(context, data, { store: bylineCore().adminStore! })
@@ -18,7 +18,7 @@ import { bylineCore } from '../../integrations/byline-core.js'
18
18
  // modules each declare their own identical `OkResponse` today, and the
19
19
  // root `@byline/admin` barrel can only re-export one of them.
20
20
  export const deleteAdminRole = createServerFn({ method: 'POST' })
21
- .inputValidator((input: { id: string; vid: number }) => input)
21
+ .validator((input: { id: string; vid: number }) => input)
22
22
  .handler(async ({ data }): Promise<{ ok: true }> => {
23
23
  const context = await getAdminRequestContext()
24
24
  return deleteAdminRoleCommand(context, data, { store: bylineCore().adminStore! })
@@ -14,7 +14,7 @@ import { getAdminRequestContext } from '../../auth/auth-context.js'
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
15
 
16
16
  export const getAdminRole = createServerFn({ method: 'GET' })
17
- .inputValidator((input: { id: string }) => input)
17
+ .validator((input: { id: string }) => input)
18
18
  .handler(async ({ data }): Promise<AdminRoleResponse> => {
19
19
  const context = await getAdminRequestContext()
20
20
  return getAdminRoleCommand(context, data, { store: bylineCore().adminStore! })
@@ -14,7 +14,7 @@ import { getAdminRequestContext } from '../../auth/auth-context.js'
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
15
 
16
16
  export const reorderAdminRoles = createServerFn({ method: 'POST' })
17
- .inputValidator((input: { ids: string[] }) => input)
17
+ .validator((input: { ids: string[] }) => input)
18
18
  .handler(async ({ data }): Promise<{ ok: true }> => {
19
19
  const context = await getAdminRequestContext()
20
20
  return reorderAdminRolesCommand(context, data, { store: bylineCore().adminStore! })
@@ -24,7 +24,7 @@ export interface UpdateAdminRoleInput {
24
24
  }
25
25
 
26
26
  export const updateAdminRole = createServerFn({ method: 'POST' })
27
- .inputValidator((input: UpdateAdminRoleInput) => input)
27
+ .validator((input: UpdateAdminRoleInput) => input)
28
28
  .handler(async ({ data }): Promise<AdminRoleResponse> => {
29
29
  const context = await getAdminRequestContext()
30
30
  return updateAdminRoleCommand(context, data, { store: bylineCore().adminStore! })
@@ -25,7 +25,7 @@ export interface CreateAdminUserInput {
25
25
  }
26
26
 
27
27
  export const createAdminUser = createServerFn({ method: 'POST' })
28
- .inputValidator((input: CreateAdminUserInput) => input)
28
+ .validator((input: CreateAdminUserInput) => input)
29
29
  .handler(async ({ data }): Promise<AdminUserResponse> => {
30
30
  const context = await getAdminRequestContext()
31
31
  return createAdminUserCommand(context, data, { store: bylineCore().adminStore! })
@@ -14,7 +14,7 @@ import { getAdminRequestContext } from '../../auth/auth-context.js'
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
15
 
16
16
  export const deleteAdminUser = createServerFn({ method: 'POST' })
17
- .inputValidator((input: { id: string; vid: number }) => input)
17
+ .validator((input: { id: string; vid: number }) => input)
18
18
  .handler(async ({ data }): Promise<OkResponse> => {
19
19
  const context = await getAdminRequestContext()
20
20
  return deleteAdminUserCommand(context, data, { store: bylineCore().adminStore! })
@@ -17,7 +17,7 @@ import { bylineCore } from '../../integrations/byline-core.js'
17
17
  // detail-view enable/disable control they will be wired into. Keep until
18
18
  // that control lands; not dead code.
19
19
  export const disableAdminUser = createServerFn({ method: 'POST' })
20
- .inputValidator((input: { id: string }) => input)
20
+ .validator((input: { id: string }) => input)
21
21
  .handler(async ({ data }): Promise<OkResponse> => {
22
22
  const context = await getAdminRequestContext()
23
23
  return disableAdminUserCommand(context, data, { store: bylineCore().adminStore! })
@@ -17,7 +17,7 @@ import { bylineCore } from '../../integrations/byline-core.js'
17
17
  // detail-view enable/disable control they will be wired into. Keep until
18
18
  // that control lands; not dead code.
19
19
  export const enableAdminUser = createServerFn({ method: 'POST' })
20
- .inputValidator((input: { id: string }) => input)
20
+ .validator((input: { id: string }) => input)
21
21
  .handler(async ({ data }): Promise<OkResponse> => {
22
22
  const context = await getAdminRequestContext()
23
23
  return enableAdminUserCommand(context, data, { store: bylineCore().adminStore! })
@@ -14,7 +14,7 @@ import { getAdminRequestContext } from '../../auth/auth-context.js'
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
15
 
16
16
  export const getUserRoles = createServerFn({ method: 'GET' })
17
- .inputValidator((input: { userId: string }) => input)
17
+ .validator((input: { userId: string }) => input)
18
18
  .handler(async ({ data }): Promise<UserRolesResponse> => {
19
19
  const context = await getAdminRequestContext()
20
20
  return getRolesForUserCommand(context, data, { store: bylineCore().adminStore! })
@@ -14,7 +14,7 @@ import { getAdminRequestContext } from '../../auth/auth-context.js'
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
15
 
16
16
  export const getAdminUser = createServerFn({ method: 'GET' })
17
- .inputValidator((input: { id: string }) => input)
17
+ .validator((input: { id: string }) => input)
18
18
  .handler(async ({ data }): Promise<AdminUserResponse> => {
19
19
  const context = await getAdminRequestContext()
20
20
  return getAdminUserCommand(context, data, { store: bylineCore().adminStore! })
@@ -33,7 +33,7 @@ export interface ListAdminUsersInput {
33
33
  * TanStack Start transport layer for the client to branch on.
34
34
  */
35
35
  export const listAdminUsers = createServerFn({ method: 'GET' })
36
- .inputValidator((input: ListAdminUsersInput) => input ?? {})
36
+ .validator((input: ListAdminUsersInput) => input ?? {})
37
37
  .handler(async ({ data }): Promise<AdminUserListResponse> => {
38
38
  const context = await getAdminRequestContext()
39
39
  return listAdminUsersCommand(context, data, { store: bylineCore().adminStore! })
@@ -20,7 +20,7 @@ export interface SetAdminUserPasswordInput {
20
20
  }
21
21
 
22
22
  export const setAdminUserPassword = createServerFn({ method: 'POST' })
23
- .inputValidator((input: SetAdminUserPasswordInput) => input)
23
+ .validator((input: SetAdminUserPasswordInput) => input)
24
24
  .handler(async ({ data }): Promise<AdminUserResponse> => {
25
25
  const context = await getAdminRequestContext()
26
26
  return setAdminUserPasswordCommand(context, data, { store: bylineCore().adminStore! })
@@ -19,7 +19,7 @@ export interface SetUserRolesInput {
19
19
  }
20
20
 
21
21
  export const setUserRoles = createServerFn({ method: 'POST' })
22
- .inputValidator((input: SetUserRolesInput) => input)
22
+ .validator((input: SetUserRolesInput) => input)
23
23
  .handler(async ({ data }): Promise<UserRolesResponse> => {
24
24
  const context = await getAdminRequestContext()
25
25
  return setRolesForUserCommand(context, data, { store: bylineCore().adminStore! })
@@ -27,7 +27,7 @@ export interface UpdateAdminUserInput {
27
27
  }
28
28
 
29
29
  export const updateAdminUser = createServerFn({ method: 'POST' })
30
- .inputValidator((input: UpdateAdminUserInput) => input)
30
+ .validator((input: UpdateAdminUserInput) => input)
31
31
  .handler(async ({ data }): Promise<AdminUserResponse> => {
32
32
  const context = await getAdminRequestContext()
33
33
  return updateAdminUserCommand(context, data, { store: bylineCore().adminStore! })
@@ -38,7 +38,7 @@ type ExecuteAiInstructionInput = {
38
38
  }
39
39
 
40
40
  export const executeAiInstruction = createServerFn({ method: 'POST' })
41
- .inputValidator((input: ExecuteAiInstructionInput) => input)
41
+ .validator((input: ExecuteAiInstructionInput) => input)
42
42
  .handler(async ({ data }): Promise<Response> => {
43
43
  // Throws ERR_UNAUTHENTICATED if there is no admin session.
44
44
  await getAdminRequestContext()
@@ -36,7 +36,7 @@ export interface SignInResult {
36
36
  }
37
37
 
38
38
  export const adminSignIn = createServerFn({ method: 'POST' })
39
- .inputValidator((input: SignInInput) => {
39
+ .validator((input: SignInInput) => {
40
40
  if (typeof input?.email !== 'string' || input.email.length === 0) {
41
41
  throw new Error('email is required')
42
42
  }
@@ -0,0 +1,95 @@
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
+ import { createServerFn } from '@tanstack/react-start'
10
+
11
+ import { ERR_NOT_FOUND, getLogger } from '@byline/core'
12
+
13
+ import { ensureCollection } from '../../integrations/api-utils.js'
14
+ import { getAdminBylineClient } from '../../integrations/byline-client.js'
15
+ import { type ActorLabelMap, resolveActorLabels } from './actors.js'
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Shared param types
19
+ // ---------------------------------------------------------------------------
20
+
21
+ export interface AuditLogSearchParams {
22
+ page?: number
23
+ page_size?: number
24
+ }
25
+
26
+ /**
27
+ * Audit entry as it crosses the server-fn boundary. `occurredAt` is an ISO
28
+ * string (Date doesn't survive serialization) and `before` / `after` are
29
+ * narrowed from the storage layer's `unknown` jsonb to the concrete shapes the
30
+ * shipped actions actually carry (path/status strings, the available-locales
31
+ * array, or null for the deletion event) — `unknown` is not a serializable
32
+ * type the TanStack server-fn validator accepts.
33
+ */
34
+ export interface AuditLogEntryDto {
35
+ id: string
36
+ documentId: string | null
37
+ collectionId: string | null
38
+ actorId: string | null
39
+ actorRealm: string
40
+ action: string
41
+ field: string | null
42
+ before: string | string[] | null
43
+ after: string | string[] | null
44
+ occurredAt: string
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Get document-grain audit log (docs/AUDIT.md — Workstream 3)
49
+ // ---------------------------------------------------------------------------
50
+
51
+ export const getCollectionDocumentAuditLog = createServerFn({ method: 'GET' })
52
+ .validator((input: { collection: string; id: string; params?: AuditLogSearchParams }) => input)
53
+ .handler(async ({ data }) => {
54
+ const { collection: path, id, params } = data
55
+ const config = await ensureCollection(path)
56
+ if (!config) {
57
+ throw ERR_NOT_FOUND({
58
+ message: 'Collection not found',
59
+ details: { collectionPath: path },
60
+ }).log(getLogger())
61
+ }
62
+
63
+ // Routes through CollectionHandle.auditLog so the document's own read gate
64
+ // (`beforeRead` via `findById`) is applied — identical to the history
65
+ // server fn. An actor whose predicate excludes the document gets an empty
66
+ // log rather than leaked change metadata.
67
+ const result = await getAdminBylineClient().collection(path).auditLog(id, {
68
+ page: params?.page,
69
+ pageSize: params?.page_size,
70
+ })
71
+
72
+ // Acting-user labels for the audit list (docs/AUDIT.md — W3). Resolved
73
+ // here, in the admin realm, from each entry's raw `actorId`; the UI joins
74
+ // by id. System/tooling rows (NULL actorId) and deleted users are absent
75
+ // from the map — the UI renders the corresponding tombstone label.
76
+ const actors: ActorLabelMap = await resolveActorLabels(result.entries.map((e) => e.actorId))
77
+
78
+ // Map to the serializable DTO: ISO-string the timestamp and narrow the
79
+ // jsonb before/after off `unknown` so the value survives the TanStack
80
+ // server-fn boundary.
81
+ const entries: AuditLogEntryDto[] = result.entries.map((e) => ({
82
+ id: e.id,
83
+ documentId: e.documentId,
84
+ collectionId: e.collectionId,
85
+ actorId: e.actorId,
86
+ actorRealm: e.actorRealm,
87
+ action: e.action,
88
+ field: e.field,
89
+ before: e.before as string | string[] | null,
90
+ after: e.after as string | string[] | null,
91
+ occurredAt: e.occurredAt instanceof Date ? e.occurredAt.toISOString() : String(e.occurredAt),
92
+ }))
93
+
94
+ return { entries, meta: result.meta, actors }
95
+ })
@@ -29,7 +29,7 @@ import { ensureCollection } from '../../integrations/api-utils.js'
29
29
  * failures propagate to TanStack Start's transport layer.
30
30
  */
31
31
  export const copyDocumentToLocale = createServerFn({ method: 'POST' })
32
- .inputValidator(
32
+ .validator(
33
33
  (input: {
34
34
  collection: string
35
35
  id: string
@@ -20,7 +20,7 @@ import { ensureCollection } from '../../integrations/api-utils.js'
20
20
  // ---------------------------------------------------------------------------
21
21
 
22
22
  export const createCollectionDocument = createServerFn({ method: 'POST' })
23
- .inputValidator(
23
+ .validator(
24
24
  (input: {
25
25
  collection: string
26
26
  data: any
@@ -30,7 +30,7 @@ import { ensureCollection } from '../../integrations/api-utils.js'
30
30
  * transport layer.
31
31
  */
32
32
  export const deleteDocumentLocale = createServerFn({ method: 'POST' })
33
- .inputValidator((input: { collection: string; id: string; locale: string }) => input)
33
+ .validator((input: { collection: string; id: string; locale: string }) => input)
34
34
  .handler(async ({ data: input }): Promise<DeleteLocaleResult> => {
35
35
  const { collection: path, id, locale } = input
36
36
  const logger = getLogger()
@@ -20,7 +20,7 @@ import { ensureCollection } from '../../integrations/api-utils.js'
20
20
  // ---------------------------------------------------------------------------
21
21
 
22
22
  export const deleteDocument = createServerFn({ method: 'POST' })
23
- .inputValidator((input: { collection: string; id: string }) => input)
23
+ .validator((input: { collection: string; id: string }) => input)
24
24
  .handler(async ({ data: input }) => {
25
25
  const { collection: path, id } = input
26
26
  const logger = getLogger()
@@ -31,7 +31,7 @@ import { ensureCollection } from '../../integrations/api-utils.js'
31
31
  * TanStack Start's transport layer for the client to branch on.
32
32
  */
33
33
  export const duplicateCollectionDocument = createServerFn({ method: 'POST' })
34
- .inputValidator((input: { collection: string; id: string }) => input)
34
+ .validator((input: { collection: string; id: string }) => input)
35
35
  .handler(async ({ data: input }): Promise<DuplicateDocumentResult> => {
36
36
  const { collection: path, id: sourceDocumentId } = input
37
37
  const logger = getLogger()
@@ -30,7 +30,7 @@ import { serialise } from './utils'
30
30
  // ---------------------------------------------------------------------------
31
31
 
32
32
  const getDocumentFn = createServerFn({ method: 'GET' })
33
- .inputValidator(
33
+ .validator(
34
34
  (input: {
35
35
  collection: string
36
36
  id: string
@@ -183,7 +183,7 @@ const getDocumentFn = createServerFn({ method: 'GET' })
183
183
  // ---------------------------------------------------------------------------
184
184
 
185
185
  const getDocumentByVersionFn = createServerFn({ method: 'GET' })
186
- .inputValidator((input: { collection: string; versionId: string; locale?: string }) => input)
186
+ .validator((input: { collection: string; versionId: string; locale?: string }) => input)
187
187
  .handler(async ({ data }) => {
188
188
  const { collection: path, versionId, locale } = data
189
189
  const resolvedLocale = locale ?? 'all'
@@ -32,7 +32,7 @@ export interface HistorySearchParams {
32
32
  // ---------------------------------------------------------------------------
33
33
 
34
34
  export const getCollectionDocumentHistory = createServerFn({ method: 'GET' })
35
- .inputValidator((input: { collection: string; id: string; params: HistorySearchParams }) => input)
35
+ .validator((input: { collection: string; id: string; params: HistorySearchParams }) => input)
36
36
  .handler(async ({ data }) => {
37
37
  const { collection: path, id, params } = data
38
38
  const config = await ensureCollection(path)
@@ -5,6 +5,7 @@
5
5
  * function and exports a clean public API.
6
6
  */
7
7
 
8
+ export * from './audit'
8
9
  export * from './copy-to-locale'
9
10
  export * from './create'
10
11
  export * from './delete'
@@ -40,7 +40,7 @@ export interface CollectionSearchParams {
40
40
  // ---------------------------------------------------------------------------
41
41
 
42
42
  export const getCollectionDocuments = createServerFn({ method: 'GET' })
43
- .inputValidator((input: { collection: string; params: CollectionSearchParams }) => input)
43
+ .validator((input: { collection: string; params: CollectionSearchParams }) => input)
44
44
  .handler(async ({ data }) => {
45
45
  const { collection: path, params } = data
46
46
  const config = await ensureCollection(path)
@@ -38,7 +38,7 @@ import { ensureCollection } from '../../integrations/api-utils.js'
38
38
  // ---------------------------------------------------------------------------
39
39
 
40
40
  export const reorderCollectionDocument = createServerFn({ method: 'POST' })
41
- .inputValidator(
41
+ .validator(
42
42
  (input: {
43
43
  collection: string
44
44
  documentId: string
@@ -20,7 +20,7 @@ import { ensureCollection } from '../../integrations/api-utils.js'
20
20
  // ---------------------------------------------------------------------------
21
21
 
22
22
  export const restoreDocumentVersion = createServerFn({ method: 'POST' })
23
- .inputValidator((input: { collection: string; id: string; versionId: string }) => input)
23
+ .validator((input: { collection: string; id: string; versionId: string }) => input)
24
24
  .handler(async ({ data: input }) => {
25
25
  const { collection: path, id, versionId } = input
26
26
  const logger = getLogger()
@@ -25,7 +25,7 @@ export interface CollectionStatusCount {
25
25
  // ---------------------------------------------------------------------------
26
26
 
27
27
  const getCollectionStatsFn = createServerFn({ method: 'GET' })
28
- .inputValidator((input: { collection: string }) => input)
28
+ .validator((input: { collection: string }) => input)
29
29
  .handler(async ({ data }) => {
30
30
  const config = await ensureCollection(data.collection)
31
31
  if (!config) return { stats: [] as CollectionStatusCount[] }
@@ -23,7 +23,7 @@ import { ensureCollection } from '../../integrations/api-utils.js'
23
23
  // ---------------------------------------------------------------------------
24
24
 
25
25
  export const updateDocumentStatus = createServerFn({ method: 'POST' })
26
- .inputValidator((input: { collection: string; id: string; status: string }) => input)
26
+ .validator((input: { collection: string; id: string; status: string }) => input)
27
27
  .handler(async ({ data: input }) => {
28
28
  const { collection: path, id, status: nextStatus } = input
29
29
  const logger = getLogger()
@@ -64,7 +64,7 @@ export const updateDocumentStatus = createServerFn({ method: 'POST' })
64
64
  // ---------------------------------------------------------------------------
65
65
 
66
66
  export const unpublishDocument = createServerFn({ method: 'POST' })
67
- .inputValidator((input: { collection: string; id: string }) => input)
67
+ .validator((input: { collection: string; id: string }) => input)
68
68
  .handler(async ({ data: input }) => {
69
69
  const { collection: path, id } = input
70
70
  const logger = getLogger()
@@ -26,7 +26,7 @@ import { ensureCollection } from '../../integrations/api-utils.js'
26
26
  // ---------------------------------------------------------------------------
27
27
 
28
28
  export const updateCollectionDocumentWithPatches = createServerFn({ method: 'POST' })
29
- .inputValidator(
29
+ .validator(
30
30
  (input: {
31
31
  collection: string
32
32
  id: string
@@ -78,7 +78,7 @@ export const updateCollectionDocumentWithPatches = createServerFn({ method: 'POS
78
78
  // ---------------------------------------------------------------------------
79
79
 
80
80
  export const updateCollectionDocumentSystemFields = createServerFn({ method: 'POST' })
81
- .inputValidator(
81
+ .validator(
82
82
  (input: {
83
83
  collection: string
84
84
  id: string
@@ -147,7 +147,7 @@ function resolveUploadFieldName(
147
147
  * without depending on TanStack Start server-function transport details.
148
148
  */
149
149
  export const uploadCollectionField = createServerFn({ method: 'POST' })
150
- .inputValidator(parseUploadFormData)
150
+ .validator(parseUploadFormData)
151
151
  .handler(async ({ data }) => {
152
152
  const { collectionPath, shouldCreateDocument, fieldName, file, fields } = data
153
153
  const logger = getLogger()
@@ -54,7 +54,7 @@ export interface SetInterfaceLocaleResult {
54
54
  }
55
55
 
56
56
  export const setInterfaceLocaleFn = createServerFn({ method: 'POST' })
57
- .inputValidator((input: SetInterfaceLocaleInput) => input)
57
+ .validator((input: SetInterfaceLocaleInput) => input)
58
58
  .handler(async ({ data }): Promise<SetInterfaceLocaleResult> => {
59
59
  const core = bylineCore()
60
60
  const locales = core.config.i18n.interface.locales