@byline/host-tanstack-start 4.15.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.
- package/dist/admin-shell/chrome/admin-menu-drawer.d.ts +2 -1
- package/dist/admin-shell/chrome/admin-menu-drawer.js +17 -5
- package/dist/admin-shell/chrome/dashboard.js +41 -5
- package/dist/admin-shell/chrome/dashboard.test.d.ts +8 -0
- package/dist/admin-shell/collections/api.d.ts +2 -2
- package/dist/admin-shell/collections/create.d.ts +2 -2
- package/dist/admin-shell/collections/create.js +1 -0
- package/dist/admin-shell/collections/edit.d.ts +2 -2
- package/dist/admin-shell/collections/edit.js +1 -0
- package/dist/admin-shell/collections/history.d.ts +4 -12
- package/dist/admin-shell/collections/history.js +112 -303
- package/dist/admin-shell/collections/reindex-button.d.ts +1 -1
- package/dist/admin-shell/collections/reindex-button.js +5 -1
- package/dist/admin-shell/collections/restore-version-modal.d.ts +4 -4
- package/dist/admin-shell/collections/restore-version-modal.js +8 -24
- package/dist/admin-shell/collections/version-history.d.ts +72 -0
- package/dist/admin-shell/collections/version-history.js +259 -0
- package/dist/admin-shell/singletons/history.d.ts +19 -0
- package/dist/admin-shell/singletons/history.js +170 -0
- package/dist/admin-shell/singletons/history.test.d.ts +8 -0
- package/dist/admin-shell/singletons/preview-link.d.ts +12 -0
- package/dist/admin-shell/singletons/preview-link.js +54 -0
- package/dist/admin-shell/singletons/view-menu.d.ts +13 -0
- package/dist/admin-shell/singletons/view-menu.js +116 -0
- package/dist/admin-shell/singletons/view.d.ts +18 -0
- package/dist/admin-shell/singletons/view.js +259 -0
- package/dist/admin-shell/singletons/view.test.d.ts +8 -0
- package/dist/integrations/analytics-agent.d.ts +20 -0
- package/dist/integrations/analytics-agent.js +57 -0
- package/dist/integrations/analytics-events.d.ts +51 -0
- package/dist/integrations/analytics-events.js +129 -0
- package/dist/integrations/api-utils.d.ts +18 -4
- package/dist/integrations/api-utils.js +10 -3
- package/dist/integrations/serve-analytics-agent.d.ts +8 -0
- package/dist/integrations/serve-analytics-agent.js +11 -0
- package/dist/routes/analytics-range.d.ts +12 -0
- package/dist/routes/analytics-range.js +35 -0
- package/dist/routes/create-admin-analytics-route.d.ts +8 -0
- package/dist/routes/create-admin-analytics-route.js +108 -0
- package/dist/routes/create-admin-dashboard-route.js +2 -2
- package/dist/routes/create-admin-layout-route.js +10 -4
- package/dist/routes/create-collection-api-route.js +3 -3
- package/dist/routes/create-collection-create-route.js +4 -3
- package/dist/routes/create-collection-edit-route.js +4 -3
- package/dist/routes/create-collection-history-route.js +4 -3
- package/dist/routes/create-collection-list-route.js +4 -3
- package/dist/routes/create-singleton-history-route.d.ts +8 -0
- package/dist/routes/create-singleton-history-route.js +99 -0
- package/dist/routes/create-singleton-route.d.ts +35 -0
- package/dist/routes/create-singleton-route.js +130 -0
- package/dist/routes/get-multi-collection-definition.d.ts +10 -0
- package/dist/routes/get-multi-collection-definition.js +6 -0
- package/dist/routes/get-singleton-definition.d.ts +10 -0
- package/dist/routes/get-singleton-definition.js +6 -0
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.js +3 -0
- package/dist/server-fns/actors.d.ts +16 -0
- package/dist/server-fns/actors.js +25 -0
- package/dist/server-fns/admin-document-presentation.d.ts +30 -0
- package/dist/server-fns/admin-document-presentation.js +32 -0
- package/dist/server-fns/analytics/index.d.ts +9 -0
- package/dist/server-fns/analytics/index.js +2 -0
- package/dist/server-fns/analytics/maintenance.d.ts +22 -0
- package/dist/server-fns/analytics/maintenance.js +42 -0
- package/dist/server-fns/analytics/queries.d.ts +25 -0
- package/dist/server-fns/analytics/queries.js +54 -0
- package/dist/server-fns/collections/actors.d.ts +1 -10
- package/dist/server-fns/collections/actors.js +1 -25
- package/dist/server-fns/collections/get.js +9 -26
- package/dist/server-fns/collections/reorder.js +1 -1
- package/dist/server-fns/collections/scheduled-publication.js +2 -2
- package/dist/server-fns/collections/upload.js +3 -3
- package/dist/server-fns/collections/utils.d.ts +1 -7
- package/dist/server-fns/collections/utils.js +1 -4
- package/dist/server-fns/serialise.d.ts +14 -0
- package/dist/server-fns/serialise.js +4 -0
- package/dist/server-fns/singleton-document-read.d.ts +20 -0
- package/dist/server-fns/singleton-document-read.js +10 -0
- package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +12 -0
- package/dist/server-fns/singletons/cancel-scheduled-publish.js +7 -0
- package/dist/server-fns/singletons/change-status.d.ts +14 -0
- package/dist/server-fns/singletons/change-status.js +7 -0
- package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +14 -0
- package/dist/server-fns/singletons/confirm-scheduled-publish.js +9 -0
- package/dist/server-fns/singletons/copy-to-locale.d.ts +18 -0
- package/dist/server-fns/singletons/copy-to-locale.js +11 -0
- package/dist/server-fns/singletons/find-by-version.d.ts +16 -0
- package/dist/server-fns/singletons/find-by-version.js +9 -0
- package/dist/server-fns/singletons/get-scheduled-publish.d.ts +12 -0
- package/dist/server-fns/singletons/get-scheduled-publish.js +7 -0
- package/dist/server-fns/singletons/get.d.ts +24 -0
- package/dist/server-fns/singletons/get.js +6 -0
- package/dist/server-fns/singletons/history.d.ts +31 -0
- package/dist/server-fns/singletons/history.js +22 -0
- package/dist/server-fns/singletons/index.d.ts +19 -0
- package/dist/server-fns/singletons/index.js +12 -0
- package/dist/server-fns/singletons/restore-version.d.ts +14 -0
- package/dist/server-fns/singletons/restore-version.js +7 -0
- package/dist/server-fns/singletons/schedule-publish.d.ts +16 -0
- package/dist/server-fns/singletons/schedule-publish.js +10 -0
- package/dist/server-fns/singletons/unpublish.d.ts +12 -0
- package/dist/server-fns/singletons/unpublish.js +7 -0
- package/dist/server-fns/singletons/update.d.ts +14 -0
- package/dist/server-fns/singletons/update.js +14 -0
- package/dist/vite/analytics-dev.d.ts +22 -0
- package/dist/vite/analytics-dev.js +28 -0
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.js +1 -0
- package/dist/vitest-jsdom.test.d.ts +8 -0
- package/package.json +28 -16
- package/src/admin-shell/chrome/admin-menu-drawer.tsx +19 -5
- package/src/admin-shell/chrome/dashboard.test.tsx +199 -0
- package/src/admin-shell/chrome/dashboard.tsx +43 -9
- package/src/admin-shell/collections/api.tsx +2 -2
- package/src/admin-shell/collections/create.tsx +6 -2
- package/src/admin-shell/collections/edit.tsx +6 -2
- package/src/admin-shell/collections/history.module.css +1 -1
- package/src/admin-shell/collections/history.tsx +162 -442
- package/src/admin-shell/collections/reindex-button.tsx +4 -2
- package/src/admin-shell/collections/restore-version-modal.tsx +19 -25
- package/src/admin-shell/collections/version-history.tsx +349 -0
- package/src/admin-shell/singletons/history.test.tsx +407 -0
- package/src/admin-shell/singletons/history.tsx +185 -0
- package/src/admin-shell/singletons/preview-link.tsx +75 -0
- package/src/admin-shell/singletons/view-menu.tsx +136 -0
- package/src/admin-shell/singletons/view.test.tsx +400 -0
- package/src/admin-shell/singletons/view.tsx +351 -0
- package/src/integrations/analytics-agent.tsx +91 -0
- package/src/integrations/analytics-events.test.node.ts +245 -0
- package/src/integrations/analytics-events.ts +224 -0
- package/src/integrations/api-utils.ts +34 -9
- package/src/integrations/serve-analytics-agent.test.node.ts +20 -0
- package/src/integrations/serve-analytics-agent.ts +19 -0
- package/src/routes/analytics-range.ts +55 -0
- package/src/routes/create-admin-analytics-route.test.node.ts +46 -0
- package/src/routes/create-admin-analytics-route.tsx +109 -0
- package/src/routes/create-admin-dashboard-route.test.node.ts +70 -0
- package/src/routes/create-admin-dashboard-route.tsx +10 -2
- package/src/routes/create-admin-layout-route.tsx +15 -8
- package/src/routes/create-collection-api-route.tsx +4 -4
- package/src/routes/create-collection-create-route.tsx +5 -8
- package/src/routes/create-collection-edit-route.tsx +5 -4
- package/src/routes/create-collection-history-route.tsx +5 -8
- package/src/routes/create-collection-list-route.tsx +5 -8
- package/src/routes/create-singleton-history-route.test.node.ts +148 -0
- package/src/routes/create-singleton-history-route.tsx +116 -0
- package/src/routes/create-singleton-route.test.node.ts +172 -0
- package/src/routes/create-singleton-route.tsx +176 -0
- package/src/routes/get-multi-collection-definition.ts +16 -0
- package/src/routes/get-singleton-definition.ts +16 -0
- package/src/routes/index.ts +3 -0
- package/src/server-fns/actors.ts +43 -0
- package/src/server-fns/admin-document-presentation.ts +78 -0
- package/src/server-fns/analytics/index.ts +23 -0
- package/src/server-fns/analytics/maintenance.ts +54 -0
- package/src/server-fns/analytics/queries.ts +86 -0
- package/src/server-fns/collections/actors.ts +1 -52
- package/src/server-fns/collections/get.ts +10 -49
- package/src/server-fns/collections/reorder.ts +1 -1
- package/src/server-fns/collections/scheduled-publication.ts +7 -1
- package/src/server-fns/collections/upload-singleton.test.node.ts +164 -0
- package/src/server-fns/collections/upload.ts +7 -3
- package/src/server-fns/collections/utils.ts +1 -9
- package/src/server-fns/serialise.ts +17 -0
- package/src/server-fns/singleton-document-read.ts +32 -0
- package/src/server-fns/singletons/cancel-scheduled-publish.ts +19 -0
- package/src/server-fns/singletons/change-status.ts +19 -0
- package/src/server-fns/singletons/confirm-scheduled-publish.ts +23 -0
- package/src/server-fns/singletons/copy-to-locale.ts +32 -0
- package/src/server-fns/singletons/find-by-version.ts +23 -0
- package/src/server-fns/singletons/get-scheduled-publish.ts +19 -0
- package/src/server-fns/singletons/get.ts +32 -0
- package/src/server-fns/singletons/history.ts +41 -0
- package/src/server-fns/singletons/index.ts +20 -0
- package/src/server-fns/singletons/restore-version.ts +19 -0
- package/src/server-fns/singletons/schedule-publish.ts +24 -0
- package/src/server-fns/singletons/singletons.test.node.ts +298 -0
- package/src/server-fns/singletons/unpublish.ts +19 -0
- package/src/server-fns/singletons/update.ts +31 -0
- package/src/vite/analytics-dev.test.node.ts +126 -0
- package/src/vite/analytics-dev.ts +56 -0
- package/src/vite/index.ts +1 -0
- package/src/vitest-jsdom.test.tsx +27 -0
|
@@ -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,126 @@
|
|
|
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 type { IncomingMessage, ServerResponse } from 'node:http'
|
|
10
|
+
|
|
11
|
+
import { ANALYTICS_AGENT_SOURCE } from '@byline/analytics-agent/source'
|
|
12
|
+
import type { Plugin } from 'vite'
|
|
13
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
14
|
+
|
|
15
|
+
import { bylineAnalyticsDev } from './analytics-dev.js'
|
|
16
|
+
|
|
17
|
+
type Middleware = (
|
|
18
|
+
request: IncomingMessage,
|
|
19
|
+
response: ServerResponse,
|
|
20
|
+
next: (error?: unknown) => void
|
|
21
|
+
) => void | Promise<void>
|
|
22
|
+
|
|
23
|
+
describe('bylineAnalyticsDev', () => {
|
|
24
|
+
it('directly serves browser script requests before Vite can transform them', async () => {
|
|
25
|
+
const handler = captureMiddleware(bylineAnalyticsDev())
|
|
26
|
+
const request = requestStub('GET', '/b.js?version=current', {
|
|
27
|
+
accept: '*/*',
|
|
28
|
+
'sec-fetch-dest': 'script',
|
|
29
|
+
})
|
|
30
|
+
const { response, headers, end } = responseStub()
|
|
31
|
+
const next = vi.fn()
|
|
32
|
+
|
|
33
|
+
await handler(request, response, next)
|
|
34
|
+
|
|
35
|
+
expect(response.statusCode).toBe(200)
|
|
36
|
+
expect(headers).toEqual({
|
|
37
|
+
'cache-control': 'public, max-age=3600, must-revalidate',
|
|
38
|
+
'content-type': 'text/javascript; charset=utf-8',
|
|
39
|
+
'x-content-type-options': 'nosniff',
|
|
40
|
+
})
|
|
41
|
+
expect(end).toHaveBeenCalledWith(ANALYTICS_AGENT_SOURCE)
|
|
42
|
+
expect(next).not.toHaveBeenCalled()
|
|
43
|
+
expect(request.headers).toEqual({
|
|
44
|
+
accept: '*/*',
|
|
45
|
+
'sec-fetch-dest': 'script',
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('continues unrelated requests through the Vite middleware chain', async () => {
|
|
50
|
+
const handler = captureMiddleware(bylineAnalyticsDev())
|
|
51
|
+
const { response, end } = responseStub()
|
|
52
|
+
const next = vi.fn()
|
|
53
|
+
|
|
54
|
+
await handler(requestStub('GET', '/about'), response, next)
|
|
55
|
+
await handler(requestStub('POST', '/api/events'), response, next)
|
|
56
|
+
|
|
57
|
+
expect(next).toHaveBeenCalledTimes(2)
|
|
58
|
+
expect(end).not.toHaveBeenCalled()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('supports a custom public agent path', async () => {
|
|
62
|
+
const handler = captureMiddleware(bylineAnalyticsDev({ agentPath: '/first-party.js' }))
|
|
63
|
+
const defaultResponse = responseStub()
|
|
64
|
+
const customResponse = responseStub()
|
|
65
|
+
const next = vi.fn()
|
|
66
|
+
|
|
67
|
+
await handler(requestStub('GET', '/b.js'), defaultResponse.response, next)
|
|
68
|
+
await handler(requestStub('GET', '/first-party.js'), customResponse.response, next)
|
|
69
|
+
|
|
70
|
+
expect(next).toHaveBeenCalledOnce()
|
|
71
|
+
expect(defaultResponse.end).not.toHaveBeenCalled()
|
|
72
|
+
expect(customResponse.end).toHaveBeenCalledWith(ANALYTICS_AGENT_SOURCE)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
function captureMiddleware(plugin: Plugin): Middleware {
|
|
77
|
+
const handlers: Middleware[] = []
|
|
78
|
+
const configureServer = plugin.configureServer
|
|
79
|
+
|
|
80
|
+
expect(typeof configureServer).toBe('function')
|
|
81
|
+
if (typeof configureServer !== 'function') {
|
|
82
|
+
throw new Error('Expected a Vite configureServer hook')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
configureServer.call(
|
|
86
|
+
{} as never,
|
|
87
|
+
{
|
|
88
|
+
middlewares: {
|
|
89
|
+
use(handler: Middleware) {
|
|
90
|
+
handlers.push(handler)
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
} as never
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const handler = handlers[0]
|
|
97
|
+
expect(handler).toBeDefined()
|
|
98
|
+
if (handler == null) throw new Error('Expected a Vite middleware')
|
|
99
|
+
return handler
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function requestStub(
|
|
103
|
+
method: string,
|
|
104
|
+
url: string,
|
|
105
|
+
headers: IncomingMessage['headers'] = {}
|
|
106
|
+
): IncomingMessage {
|
|
107
|
+
return { method, url, headers } as IncomingMessage
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function responseStub(): {
|
|
111
|
+
response: ServerResponse
|
|
112
|
+
headers: Record<string, string | number | readonly string[]>
|
|
113
|
+
end: ReturnType<typeof vi.fn>
|
|
114
|
+
} {
|
|
115
|
+
const headers: Record<string, string | number | readonly string[]> = {}
|
|
116
|
+
const end = vi.fn()
|
|
117
|
+
const response = {
|
|
118
|
+
statusCode: 0,
|
|
119
|
+
setHeader(name: string, value: string | number | readonly string[]) {
|
|
120
|
+
headers[name] = value
|
|
121
|
+
return response
|
|
122
|
+
},
|
|
123
|
+
end,
|
|
124
|
+
} as unknown as ServerResponse
|
|
125
|
+
return { response, headers, end }
|
|
126
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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 type { Plugin } from 'vite'
|
|
10
|
+
|
|
11
|
+
import { serveAnalyticsAgent } from '../integrations/serve-analytics-agent.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Complete the first-party analytics transport during Vite development.
|
|
15
|
+
*
|
|
16
|
+
* Browser script requests carry `Sec-Fetch-Dest: script`, which Vite routes
|
|
17
|
+
* into its module pipeline before TanStack Start can serve the agent route.
|
|
18
|
+
* This plugin serves the agent directly. The application event route remains
|
|
19
|
+
* responsible for its development identity because POST server routes can
|
|
20
|
+
* bypass Vite's ordinary middleware chain. Production continues to use the
|
|
21
|
+
* application-owned agent route.
|
|
22
|
+
*/
|
|
23
|
+
export function bylineAnalyticsDev(
|
|
24
|
+
options: {
|
|
25
|
+
/** Public path that serves the standalone agent. */
|
|
26
|
+
agentPath?: string
|
|
27
|
+
} = {}
|
|
28
|
+
): Plugin {
|
|
29
|
+
const agentPath = options.agentPath ?? '/b.js'
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
name: 'byline:analytics-dev',
|
|
33
|
+
apply: 'serve',
|
|
34
|
+
configureServer(server) {
|
|
35
|
+
server.middlewares.use(async (request, response, next) => {
|
|
36
|
+
const pathname = request.url?.split('?', 1)[0]
|
|
37
|
+
|
|
38
|
+
if (request.method === 'GET' && pathname === agentPath) {
|
|
39
|
+
try {
|
|
40
|
+
const agentResponse = serveAnalyticsAgent()
|
|
41
|
+
response.statusCode = agentResponse.status
|
|
42
|
+
agentResponse.headers.forEach((value, name) => {
|
|
43
|
+
response.setHeader(name, value)
|
|
44
|
+
})
|
|
45
|
+
response.end(await agentResponse.text())
|
|
46
|
+
} catch (error) {
|
|
47
|
+
next(error)
|
|
48
|
+
}
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
next()
|
|
53
|
+
})
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/vite/index.ts
CHANGED
|
@@ -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
|
+
})
|