@byline/host-tanstack-start 4.16.0 → 4.17.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 (146) hide show
  1. package/dist/admin-shell/chrome/admin-menu-drawer.js +14 -14
  2. package/dist/admin-shell/chrome/dashboard.js +41 -5
  3. package/dist/admin-shell/chrome/dashboard.test.d.ts +8 -0
  4. package/dist/admin-shell/collections/api.d.ts +2 -2
  5. package/dist/admin-shell/collections/create.d.ts +2 -2
  6. package/dist/admin-shell/collections/create.js +1 -0
  7. package/dist/admin-shell/collections/edit.d.ts +2 -2
  8. package/dist/admin-shell/collections/edit.js +1 -0
  9. package/dist/admin-shell/collections/history.d.ts +4 -12
  10. package/dist/admin-shell/collections/history.js +112 -303
  11. package/dist/admin-shell/collections/reindex-button.d.ts +1 -1
  12. package/dist/admin-shell/collections/reindex-button.js +5 -1
  13. package/dist/admin-shell/collections/restore-version-modal.d.ts +4 -4
  14. package/dist/admin-shell/collections/restore-version-modal.js +8 -24
  15. package/dist/admin-shell/collections/version-history.d.ts +72 -0
  16. package/dist/admin-shell/collections/version-history.js +259 -0
  17. package/dist/admin-shell/singletons/history.d.ts +19 -0
  18. package/dist/admin-shell/singletons/history.js +170 -0
  19. package/dist/admin-shell/singletons/history.test.d.ts +8 -0
  20. package/dist/admin-shell/singletons/preview-link.d.ts +12 -0
  21. package/dist/admin-shell/singletons/preview-link.js +54 -0
  22. package/dist/admin-shell/singletons/view-menu.d.ts +13 -0
  23. package/dist/admin-shell/singletons/view-menu.js +116 -0
  24. package/dist/admin-shell/singletons/view.d.ts +18 -0
  25. package/dist/admin-shell/singletons/view.js +259 -0
  26. package/dist/admin-shell/singletons/view.test.d.ts +8 -0
  27. package/dist/integrations/api-utils.d.ts +18 -4
  28. package/dist/integrations/api-utils.js +10 -3
  29. package/dist/routes/create-admin-dashboard-route.js +2 -2
  30. package/dist/routes/create-collection-api-route.js +3 -3
  31. package/dist/routes/create-collection-create-route.js +4 -3
  32. package/dist/routes/create-collection-edit-route.js +4 -3
  33. package/dist/routes/create-collection-history-route.js +4 -3
  34. package/dist/routes/create-collection-list-route.js +4 -3
  35. package/dist/routes/create-singleton-history-route.d.ts +8 -0
  36. package/dist/routes/create-singleton-history-route.js +99 -0
  37. package/dist/routes/create-singleton-route.d.ts +35 -0
  38. package/dist/routes/create-singleton-route.js +130 -0
  39. package/dist/routes/get-multi-collection-definition.d.ts +10 -0
  40. package/dist/routes/get-multi-collection-definition.js +6 -0
  41. package/dist/routes/get-singleton-definition.d.ts +10 -0
  42. package/dist/routes/get-singleton-definition.js +6 -0
  43. package/dist/routes/index.d.ts +2 -0
  44. package/dist/routes/index.js +2 -0
  45. package/dist/server-fns/actors.d.ts +16 -0
  46. package/dist/server-fns/actors.js +25 -0
  47. package/dist/server-fns/admin-document-presentation.d.ts +30 -0
  48. package/dist/server-fns/admin-document-presentation.js +32 -0
  49. package/dist/server-fns/collections/actors.d.ts +1 -10
  50. package/dist/server-fns/collections/actors.js +1 -25
  51. package/dist/server-fns/collections/get.js +9 -26
  52. package/dist/server-fns/collections/reorder.js +1 -1
  53. package/dist/server-fns/collections/scheduled-publication.js +2 -2
  54. package/dist/server-fns/collections/upload.js +3 -3
  55. package/dist/server-fns/collections/utils.d.ts +1 -7
  56. package/dist/server-fns/collections/utils.js +1 -4
  57. package/dist/server-fns/serialise.d.ts +14 -0
  58. package/dist/server-fns/serialise.js +4 -0
  59. package/dist/server-fns/singleton-document-read.d.ts +20 -0
  60. package/dist/server-fns/singleton-document-read.js +10 -0
  61. package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +12 -0
  62. package/dist/server-fns/singletons/cancel-scheduled-publish.js +7 -0
  63. package/dist/server-fns/singletons/change-status.d.ts +14 -0
  64. package/dist/server-fns/singletons/change-status.js +7 -0
  65. package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +14 -0
  66. package/dist/server-fns/singletons/confirm-scheduled-publish.js +9 -0
  67. package/dist/server-fns/singletons/copy-to-locale.d.ts +18 -0
  68. package/dist/server-fns/singletons/copy-to-locale.js +11 -0
  69. package/dist/server-fns/singletons/find-by-version.d.ts +16 -0
  70. package/dist/server-fns/singletons/find-by-version.js +9 -0
  71. package/dist/server-fns/singletons/get-scheduled-publish.d.ts +12 -0
  72. package/dist/server-fns/singletons/get-scheduled-publish.js +7 -0
  73. package/dist/server-fns/singletons/get.d.ts +24 -0
  74. package/dist/server-fns/singletons/get.js +6 -0
  75. package/dist/server-fns/singletons/history.d.ts +31 -0
  76. package/dist/server-fns/singletons/history.js +22 -0
  77. package/dist/server-fns/singletons/index.d.ts +19 -0
  78. package/dist/server-fns/singletons/index.js +12 -0
  79. package/dist/server-fns/singletons/restore-version.d.ts +14 -0
  80. package/dist/server-fns/singletons/restore-version.js +7 -0
  81. package/dist/server-fns/singletons/schedule-publish.d.ts +16 -0
  82. package/dist/server-fns/singletons/schedule-publish.js +10 -0
  83. package/dist/server-fns/singletons/unpublish.d.ts +12 -0
  84. package/dist/server-fns/singletons/unpublish.js +7 -0
  85. package/dist/server-fns/singletons/update.d.ts +14 -0
  86. package/dist/server-fns/singletons/update.js +14 -0
  87. package/dist/vitest-jsdom.test.d.ts +8 -0
  88. package/package.json +24 -18
  89. package/src/admin-shell/chrome/admin-menu-drawer.tsx +13 -13
  90. package/src/admin-shell/chrome/dashboard.test.tsx +199 -0
  91. package/src/admin-shell/chrome/dashboard.tsx +43 -9
  92. package/src/admin-shell/collections/api.tsx +2 -2
  93. package/src/admin-shell/collections/create.tsx +6 -2
  94. package/src/admin-shell/collections/edit.tsx +6 -2
  95. package/src/admin-shell/collections/history.module.css +1 -1
  96. package/src/admin-shell/collections/history.tsx +162 -442
  97. package/src/admin-shell/collections/reindex-button.tsx +4 -2
  98. package/src/admin-shell/collections/restore-version-modal.tsx +19 -25
  99. package/src/admin-shell/collections/version-history.tsx +349 -0
  100. package/src/admin-shell/singletons/history.test.tsx +407 -0
  101. package/src/admin-shell/singletons/history.tsx +185 -0
  102. package/src/admin-shell/singletons/preview-link.tsx +75 -0
  103. package/src/admin-shell/singletons/view-menu.tsx +136 -0
  104. package/src/admin-shell/singletons/view.test.tsx +400 -0
  105. package/src/admin-shell/singletons/view.tsx +351 -0
  106. package/src/integrations/api-utils.ts +34 -9
  107. package/src/routes/create-admin-dashboard-route.test.node.ts +70 -0
  108. package/src/routes/create-admin-dashboard-route.tsx +10 -2
  109. package/src/routes/create-collection-api-route.tsx +4 -4
  110. package/src/routes/create-collection-create-route.tsx +5 -8
  111. package/src/routes/create-collection-edit-route.tsx +5 -4
  112. package/src/routes/create-collection-history-route.tsx +5 -8
  113. package/src/routes/create-collection-list-route.tsx +5 -8
  114. package/src/routes/create-singleton-history-route.test.node.ts +148 -0
  115. package/src/routes/create-singleton-history-route.tsx +116 -0
  116. package/src/routes/create-singleton-route.test.node.ts +172 -0
  117. package/src/routes/create-singleton-route.tsx +176 -0
  118. package/src/routes/get-multi-collection-definition.ts +16 -0
  119. package/src/routes/get-singleton-definition.ts +16 -0
  120. package/src/routes/index.ts +2 -0
  121. package/src/server-fns/actors.ts +43 -0
  122. package/src/server-fns/admin-document-presentation.ts +78 -0
  123. package/src/server-fns/collections/actors.ts +1 -52
  124. package/src/server-fns/collections/get.ts +10 -49
  125. package/src/server-fns/collections/reorder.ts +1 -1
  126. package/src/server-fns/collections/scheduled-publication.ts +7 -1
  127. package/src/server-fns/collections/upload-singleton.test.node.ts +164 -0
  128. package/src/server-fns/collections/upload.ts +7 -3
  129. package/src/server-fns/collections/utils.ts +1 -9
  130. package/src/server-fns/serialise.ts +17 -0
  131. package/src/server-fns/singleton-document-read.ts +32 -0
  132. package/src/server-fns/singletons/cancel-scheduled-publish.ts +19 -0
  133. package/src/server-fns/singletons/change-status.ts +19 -0
  134. package/src/server-fns/singletons/confirm-scheduled-publish.ts +23 -0
  135. package/src/server-fns/singletons/copy-to-locale.ts +32 -0
  136. package/src/server-fns/singletons/find-by-version.ts +23 -0
  137. package/src/server-fns/singletons/get-scheduled-publish.ts +19 -0
  138. package/src/server-fns/singletons/get.ts +32 -0
  139. package/src/server-fns/singletons/history.ts +41 -0
  140. package/src/server-fns/singletons/index.ts +20 -0
  141. package/src/server-fns/singletons/restore-version.ts +19 -0
  142. package/src/server-fns/singletons/schedule-publish.ts +24 -0
  143. package/src/server-fns/singletons/singletons.test.node.ts +298 -0
  144. package/src/server-fns/singletons/unpublish.ts +19 -0
  145. package/src/server-fns/singletons/update.ts +31 -0
  146. package/src/vitest-jsdom.test.tsx +27 -0
@@ -0,0 +1,23 @@
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 { getAdminBylineClient } from '@byline/client/server'
12
+
13
+ import { serialise } from '../serialise.js'
14
+
15
+ export const findSingletonByVersion = createServerFn({ method: 'GET' })
16
+ .validator((input: { singleton: string; versionId: string; locale?: string }) => input)
17
+ .handler(async ({ data }) =>
18
+ serialise(
19
+ await getAdminBylineClient()
20
+ .singleton(data.singleton)
21
+ .findByVersion(data.versionId, { locale: data.locale ?? 'all' })
22
+ )
23
+ )
@@ -0,0 +1,19 @@
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 { getAdminBylineClient } from '@byline/client/server'
12
+
13
+ import { serialise } from '../serialise.js'
14
+
15
+ export const getSingletonScheduledPublish = createServerFn({ method: 'GET' })
16
+ .validator((input: { singleton: string }) => input)
17
+ .handler(async ({ data }) =>
18
+ serialise(await getAdminBylineClient().singleton(data.singleton).getScheduledPublish())
19
+ )
@@ -0,0 +1,32 @@
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 { readSingletonDocument } from '../singleton-document-read.js'
12
+
13
+ export interface GetSingletonInput {
14
+ singleton: string
15
+ locale?: string
16
+ depth?: number
17
+ populateRelations?: boolean
18
+ }
19
+
20
+ /**
21
+ * Task 4 singleton-loader contract:
22
+ *
23
+ * - this shared host layer owns lenient reads, raw missing-locale values,
24
+ * relation-summary population, transport serialization, and preservation of
25
+ * restore warnings and locale metadata;
26
+ * - the route loader composes published-version metadata plus scheduled-
27
+ * publication enablement, capability, and schedule aliases because those
28
+ * require host-only database and actor context beyond the singleton handle.
29
+ */
30
+ export const getSingleton = createServerFn({ method: 'GET' })
31
+ .validator((input: GetSingletonInput) => input)
32
+ .handler(async ({ data }) => readSingletonDocument(data))
@@ -0,0 +1,41 @@
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 { getAdminBylineClient } from '@byline/client/server'
12
+
13
+ import { type ActorLabelMap, resolveActorLabels } from '../actors.js'
14
+ import { serialise } from '../serialise.js'
15
+
16
+ export interface SingletonHistorySearchParams {
17
+ page?: number
18
+ page_size?: number
19
+ order?: string
20
+ desc?: boolean
21
+ locale?: string
22
+ }
23
+
24
+ export const getSingletonHistory = createServerFn({ method: 'GET' })
25
+ .validator((input: { singleton: string; params?: SingletonHistorySearchParams }) => input)
26
+ .handler(async ({ data }) => {
27
+ const params = data.params ?? {}
28
+ const result = await getAdminBylineClient()
29
+ .singleton(data.singleton)
30
+ .history({
31
+ locale: params.locale ?? 'en',
32
+ page: params.page,
33
+ pageSize: params.page_size,
34
+ order: params.order,
35
+ desc: params.desc,
36
+ })
37
+ const actors: ActorLabelMap = await resolveActorLabels(
38
+ result.docs.map((item) => item.createdBy)
39
+ )
40
+ return { ...serialise(result), actors }
41
+ })
@@ -0,0 +1,20 @@
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 * from './cancel-scheduled-publish.js'
10
+ export * from './change-status.js'
11
+ export * from './confirm-scheduled-publish.js'
12
+ export * from './copy-to-locale.js'
13
+ export * from './find-by-version.js'
14
+ export * from './get.js'
15
+ export * from './get-scheduled-publish.js'
16
+ export * from './history.js'
17
+ export * from './restore-version.js'
18
+ export * from './schedule-publish.js'
19
+ export * from './unpublish.js'
20
+ export * from './update.js'
@@ -0,0 +1,19 @@
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 { getAdminBylineClient } from '@byline/client/server'
12
+
13
+ import { serialise } from '../serialise.js'
14
+
15
+ export const restoreSingletonVersion = createServerFn({ method: 'POST' })
16
+ .validator((input: { singleton: string; versionId: string }) => input)
17
+ .handler(async ({ data }) =>
18
+ serialise(await getAdminBylineClient().singleton(data.singleton).restoreVersion(data.versionId))
19
+ )
@@ -0,0 +1,24 @@
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 { getAdminBylineClient } from '@byline/client/server'
12
+
13
+ import { serialise } from '../serialise.js'
14
+
15
+ export const scheduleSingletonPublish = createServerFn({ method: 'POST' })
16
+ .validator((input: { singleton: string; publishAt: string; expectedVersionId: string }) => input)
17
+ .handler(async ({ data }) =>
18
+ serialise(
19
+ await getAdminBylineClient().singleton(data.singleton).schedulePublish({
20
+ publishAt: data.publishAt,
21
+ expectedVersionId: data.expectedVersionId,
22
+ })
23
+ )
24
+ )
@@ -0,0 +1,298 @@
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 { beforeEach, describe, expect, it, vi } from 'vitest'
10
+
11
+ const mocks = vi.hoisted(() => ({
12
+ getAdminBylineClient: vi.fn(),
13
+ resolveActorLabels: vi.fn(),
14
+ }))
15
+
16
+ vi.mock('@tanstack/react-start', () => ({
17
+ createServerFn: () => {
18
+ let validate = (input: unknown) => input
19
+ const chain = {
20
+ validator(validator: (input: unknown) => unknown) {
21
+ validate = validator
22
+ return chain
23
+ },
24
+ handler(handler: (options: { data: any }) => Promise<unknown>) {
25
+ return async (options: { data: unknown }) => handler({ data: validate(options.data) })
26
+ },
27
+ }
28
+ return chain
29
+ },
30
+ }))
31
+
32
+ vi.mock('@byline/client/server', () => ({
33
+ getAdminBylineClient: mocks.getAdminBylineClient,
34
+ }))
35
+
36
+ vi.mock('../actors.js', () => ({
37
+ resolveActorLabels: mocks.resolveActorLabels,
38
+ }))
39
+
40
+ import { cancelSingletonScheduledPublish } from './cancel-scheduled-publish.js'
41
+ import { changeSingletonStatus } from './change-status.js'
42
+ import { confirmSingletonScheduledPublish } from './confirm-scheduled-publish.js'
43
+ import { copySingletonToLocale } from './copy-to-locale.js'
44
+ import { findSingletonByVersion } from './find-by-version.js'
45
+ import { getSingleton } from './get.js'
46
+ import { getSingletonScheduledPublish } from './get-scheduled-publish.js'
47
+ import { getSingletonHistory } from './history.js'
48
+ import { restoreSingletonVersion } from './restore-version.js'
49
+ import { scheduleSingletonPublish } from './schedule-publish.js'
50
+ import { unpublishSingleton } from './unpublish.js'
51
+ import { updateSingleton } from './update.js'
52
+
53
+ type ServerFunction = (options: { data: any }) => Promise<any>
54
+
55
+ const invoke = (serverFunction: unknown, data: Record<string, any>) =>
56
+ (serverFunction as ServerFunction)({ data })
57
+
58
+ const handle = {
59
+ get: vi.fn(),
60
+ update: vi.fn(),
61
+ changeStatus: vi.fn(),
62
+ unpublish: vi.fn(),
63
+ schedulePublish: vi.fn(),
64
+ confirmScheduledPublish: vi.fn(),
65
+ cancelScheduledPublish: vi.fn(),
66
+ getScheduledPublish: vi.fn(),
67
+ history: vi.fn(),
68
+ findByVersion: vi.fn(),
69
+ restoreVersion: vi.fn(),
70
+ copyToLocale: vi.fn(),
71
+ }
72
+
73
+ const client = { singleton: vi.fn(() => handle) }
74
+
75
+ describe('singleton server functions', () => {
76
+ beforeEach(() => {
77
+ vi.clearAllMocks()
78
+ mocks.getAdminBylineClient.mockReturnValue(client)
79
+ mocks.resolveActorLabels.mockResolvedValue({ actor: { label: 'Ada Editor' } })
80
+ })
81
+
82
+ it('delegates every operation to the singleton handle for the requested path', async () => {
83
+ const cases = [
84
+ {
85
+ serverFunction: getSingleton,
86
+ method: 'get',
87
+ input: { singleton: 'site-settings', locale: 'fr' },
88
+ args: [
89
+ {
90
+ locale: 'fr',
91
+ populate: undefined,
92
+ depth: undefined,
93
+ status: 'any',
94
+ onMissingLocale: 'empty',
95
+ lenient: true,
96
+ },
97
+ ],
98
+ result: { title: 'Site' },
99
+ },
100
+ {
101
+ serverFunction: updateSingleton,
102
+ method: 'update',
103
+ input: {
104
+ singleton: 'site-settings',
105
+ data: { title: 'Changed' },
106
+ locale: 'fr',
107
+ expectedVersionId: 'version-1',
108
+ },
109
+ args: [{ title: 'Changed' }, { locale: 'fr', expectedVersionId: 'version-1' }],
110
+ result: { versionId: 'version-2' },
111
+ },
112
+ {
113
+ serverFunction: changeSingletonStatus,
114
+ method: 'changeStatus',
115
+ input: { singleton: 'site-settings', status: 'published' },
116
+ args: ['published'],
117
+ result: { newStatus: 'published' },
118
+ },
119
+ {
120
+ serverFunction: unpublishSingleton,
121
+ method: 'unpublish',
122
+ input: { singleton: 'site-settings' },
123
+ args: [],
124
+ result: { archivedCount: 1 },
125
+ },
126
+ {
127
+ serverFunction: scheduleSingletonPublish,
128
+ method: 'schedulePublish',
129
+ input: {
130
+ singleton: 'site-settings',
131
+ publishAt: '2026-09-01T10:00:00Z',
132
+ expectedVersionId: 'version-2',
133
+ },
134
+ args: [
135
+ {
136
+ publishAt: '2026-09-01T10:00:00Z',
137
+ expectedVersionId: 'version-2',
138
+ },
139
+ ],
140
+ result: { state: 'armed' },
141
+ },
142
+ {
143
+ serverFunction: confirmSingletonScheduledPublish,
144
+ method: 'confirmScheduledPublish',
145
+ input: { singleton: 'site-settings', expectedVersionId: 'version-3' },
146
+ args: [{ expectedVersionId: 'version-3' }],
147
+ result: { state: 'armed' },
148
+ },
149
+ {
150
+ serverFunction: cancelSingletonScheduledPublish,
151
+ method: 'cancelScheduledPublish',
152
+ input: { singleton: 'site-settings' },
153
+ args: [],
154
+ result: { state: 'cancelled' },
155
+ },
156
+ {
157
+ serverFunction: getSingletonScheduledPublish,
158
+ method: 'getScheduledPublish',
159
+ input: { singleton: 'site-settings' },
160
+ args: [],
161
+ result: { state: 'armed' },
162
+ },
163
+ {
164
+ serverFunction: getSingletonHistory,
165
+ method: 'history',
166
+ input: {
167
+ singleton: 'site-settings',
168
+ params: { locale: 'all', page: 2, page_size: 10, order: 'created_at', desc: true },
169
+ },
170
+ args: [{ locale: 'all', page: 2, pageSize: 10, order: 'created_at', desc: true }],
171
+ result: {
172
+ docs: [{ createdBy: 'actor', versionId: 'version-1' }],
173
+ meta: { total: 1, page: 2, pageSize: 10, totalPages: 1 },
174
+ },
175
+ actors: true,
176
+ },
177
+ {
178
+ serverFunction: findSingletonByVersion,
179
+ method: 'findByVersion',
180
+ input: { singleton: 'site-settings', versionId: 'version-1', locale: 'fr' },
181
+ args: ['version-1', { locale: 'fr' }],
182
+ result: { title: 'Historical' },
183
+ },
184
+ {
185
+ serverFunction: restoreSingletonVersion,
186
+ method: 'restoreVersion',
187
+ input: { singleton: 'site-settings', versionId: 'version-1' },
188
+ args: ['version-1'],
189
+ result: { versionId: 'version-4' },
190
+ },
191
+ {
192
+ serverFunction: copySingletonToLocale,
193
+ method: 'copyToLocale',
194
+ input: {
195
+ singleton: 'site-settings',
196
+ sourceLocale: 'en',
197
+ targetLocale: 'fr',
198
+ overwrite: true,
199
+ },
200
+ args: [{ sourceLocale: 'en', targetLocale: 'fr', overwrite: true }],
201
+ result: { versionId: 'version-5' },
202
+ },
203
+ ] as const
204
+
205
+ for (const testCase of cases) {
206
+ const method = handle[testCase.method]
207
+ method.mockResolvedValueOnce(testCase.result)
208
+
209
+ const result = await invoke(testCase.serverFunction, testCase.input)
210
+
211
+ expect(client.singleton).toHaveBeenLastCalledWith('site-settings')
212
+ expect(method).toHaveBeenCalledWith(...testCase.args)
213
+ expect(result).toEqual(
214
+ testCase.actors
215
+ ? { ...testCase.result, actors: { actor: { label: 'Ada Editor' } } }
216
+ : testCase.result
217
+ )
218
+ }
219
+ })
220
+
221
+ it('forwards the optimistic version to update unchanged', async () => {
222
+ handle.update.mockResolvedValueOnce({ versionId: 'version-next' })
223
+
224
+ await invoke(updateSingleton, {
225
+ singleton: 'site-settings',
226
+ data: { title: 'Changed' },
227
+ expectedVersionId: 'version-current',
228
+ })
229
+
230
+ expect(handle.update).toHaveBeenCalledWith(
231
+ { title: 'Changed' },
232
+ { locale: undefined, expectedVersionId: 'version-current' }
233
+ )
234
+ })
235
+
236
+ it('preserves authentication failures from the handle', async () => {
237
+ const error = { code: 'ERR_UNAUTHENTICATED', message: 'sign in required' }
238
+ handle.get.mockRejectedValueOnce(error)
239
+
240
+ await expect(invoke(getSingleton, { singleton: 'site-settings' })).rejects.toBe(error)
241
+ })
242
+
243
+ it('preserves the requested empty history envelope from an unmaterialised slot', async () => {
244
+ handle.history.mockResolvedValueOnce({
245
+ docs: [],
246
+ meta: { total: 0, page: 4, pageSize: 30, totalPages: 0 },
247
+ })
248
+ mocks.resolveActorLabels.mockResolvedValueOnce({})
249
+
250
+ await expect(
251
+ invoke(getSingletonHistory, {
252
+ singleton: 'site-settings',
253
+ params: { page: 4, page_size: 30 },
254
+ })
255
+ ).resolves.toEqual({
256
+ docs: [],
257
+ meta: { total: 0, page: 4, pageSize: 30, totalPages: 0 },
258
+ actors: {},
259
+ })
260
+ expect(handle.history).toHaveBeenCalledWith({
261
+ locale: 'en',
262
+ page: 4,
263
+ pageSize: 30,
264
+ order: undefined,
265
+ desc: undefined,
266
+ })
267
+ expect(mocks.resolveActorLabels).toHaveBeenCalledWith([])
268
+ })
269
+
270
+ it('keeps missing-slot and stale-write failures distinguishable', async () => {
271
+ const missing = { code: 'ERR_NOT_FOUND', message: 'slot is empty' }
272
+ const stale = { code: 'ERR_CONFLICT', message: 'version changed' }
273
+ handle.changeStatus.mockRejectedValueOnce(missing)
274
+ handle.update.mockRejectedValueOnce(stale)
275
+
276
+ await expect(
277
+ invoke(changeSingletonStatus, { singleton: 'site-settings', status: 'published' })
278
+ ).rejects.toMatchObject({ code: 'ERR_NOT_FOUND' })
279
+ await expect(
280
+ invoke(updateSingleton, {
281
+ singleton: 'site-settings',
282
+ data: { title: 'Changed' },
283
+ expectedVersionId: 'stale',
284
+ })
285
+ ).rejects.toMatchObject({ code: 'ERR_CONFLICT' })
286
+ })
287
+
288
+ it('serialises schedule dates at the transport boundary', async () => {
289
+ handle.getScheduledPublish.mockResolvedValueOnce({
290
+ state: 'armed',
291
+ publishAt: new Date('2026-09-01T10:00:00Z'),
292
+ })
293
+
294
+ await expect(
295
+ invoke(getSingletonScheduledPublish, { singleton: 'site-settings' })
296
+ ).resolves.toMatchObject({ publishAt: '2026-09-01T10:00:00.000Z' })
297
+ })
298
+ })
@@ -0,0 +1,19 @@
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 { getAdminBylineClient } from '@byline/client/server'
12
+
13
+ import { serialise } from '../serialise.js'
14
+
15
+ export const unpublishSingleton = createServerFn({ method: 'POST' })
16
+ .validator((input: { singleton: string }) => input)
17
+ .handler(async ({ data }) =>
18
+ serialise(await getAdminBylineClient().singleton(data.singleton).unpublish())
19
+ )
@@ -0,0 +1,31 @@
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 { getAdminBylineClient } from '@byline/client/server'
12
+
13
+ import { serialise } from '../serialise.js'
14
+
15
+ export interface UpdateSingletonInput {
16
+ singleton: string
17
+ data: Record<string, any>
18
+ locale?: string
19
+ expectedVersionId?: string
20
+ }
21
+
22
+ export const updateSingleton = createServerFn({ method: 'POST' })
23
+ .validator((input: UpdateSingletonInput) => input)
24
+ .handler(async ({ data }) => {
25
+ const { singleton, data: fields, locale, expectedVersionId } = data
26
+ const result = await getAdminBylineClient().singleton(singleton).update(fields, {
27
+ locale,
28
+ expectedVersionId,
29
+ })
30
+ return serialise(result)
31
+ })
@@ -0,0 +1,27 @@
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 { act } from 'react'
10
+
11
+ import { createRoot } from 'react-dom/client'
12
+ import { expect, it } from 'vitest'
13
+
14
+ it('collects and renders React component tests in jsdom mode', () => {
15
+ const reactGlobals = globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
16
+ reactGlobals.IS_REACT_ACT_ENVIRONMENT = true
17
+ const container = document.createElement('div')
18
+ document.body.append(container)
19
+ const root = createRoot(container)
20
+
21
+ act(() => root.render(<span data-testid="probe">jsdom ready</span>))
22
+ expect(container.querySelector('[data-testid="probe"]')?.textContent).toBe('jsdom ready')
23
+
24
+ act(() => root.unmount())
25
+ container.remove()
26
+ delete reactGlobals.IS_REACT_ACT_ENVIRONMENT
27
+ })