@byline/host-tanstack-start 4.18.0 → 5.0.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/collections/create.js +22 -5
- package/dist/admin-shell/collections/edit.d.ts +1 -1
- package/dist/admin-shell/collections/edit.js +222 -34
- package/dist/admin-shell/collections/history.js +4 -2
- package/dist/admin-shell/collections/list.js +30 -6
- package/dist/admin-shell/collections/restore-version-modal.d.ts +3 -1
- package/dist/admin-shell/collections/restore-version-modal.js +23 -6
- package/dist/admin-shell/collections/save-navigation.test.d.ts +8 -0
- package/dist/admin-shell/collections/tree-list.js +16 -6
- package/dist/admin-shell/collections/version-history.d.ts +2 -1
- package/dist/admin-shell/collections/version-history.js +11 -2
- package/dist/admin-shell/document-mutation-notice.d.ts +6 -0
- package/dist/admin-shell/document-mutation-notice.js +53 -0
- package/dist/admin-shell/document-mutation-state.d.ts +22 -0
- package/dist/admin-shell/document-mutation-state.js +71 -0
- package/dist/admin-shell/document-mutation-state.test.d.ts +1 -0
- package/dist/admin-shell/scheduled-publications/list.js +15 -5
- package/dist/admin-shell/singletons/history.js +4 -2
- package/dist/admin-shell/singletons/view.d.ts +1 -1
- package/dist/admin-shell/singletons/view.js +108 -23
- package/dist/integrations/byline-field-services.js +2 -9
- package/dist/routes/create-collection-edit-route.js +1 -1
- package/dist/routes/create-collection-list-route.d.ts +0 -7
- package/dist/routes/create-collection-list-route.js +7 -9
- package/dist/routes/create-singleton-route.d.ts +2 -0
- package/dist/routes/create-singleton-route.js +4 -3
- package/dist/server-fns/collections/copy-to-locale.d.ts +4 -8
- package/dist/server-fns/collections/copy-to-locale.js +17 -8
- package/dist/server-fns/collections/create.d.ts +2 -8
- package/dist/server-fns/collections/create.js +20 -10
- package/dist/server-fns/collections/delete-locale.d.ts +4 -8
- package/dist/server-fns/collections/delete-locale.js +15 -6
- package/dist/server-fns/collections/delete-outcome.d.ts +7 -3
- package/dist/server-fns/collections/delete-outcome.js +4 -0
- package/dist/server-fns/collections/delete.d.ts +2 -7
- package/dist/server-fns/collections/delete.js +5 -3
- package/dist/server-fns/collections/duplicate.d.ts +4 -8
- package/dist/server-fns/collections/duplicate.js +14 -5
- package/dist/server-fns/collections/get.d.ts +1 -7
- package/dist/server-fns/collections/get.js +5 -4
- package/dist/server-fns/collections/index.d.ts +1 -0
- package/dist/server-fns/collections/index.js +1 -0
- package/dist/server-fns/collections/list.d.ts +0 -7
- package/dist/server-fns/collections/list.js +11 -3
- package/dist/server-fns/collections/reorder.d.ts +7 -8
- package/dist/server-fns/collections/reorder.js +20 -76
- package/dist/server-fns/collections/restore-version.d.ts +4 -8
- package/dist/server-fns/collections/restore-version.js +22 -12
- package/dist/server-fns/collections/save-outcome.d.ts +24 -0
- package/dist/server-fns/collections/save-outcome.js +19 -0
- package/dist/server-fns/collections/scheduled-publication.d.ts +49 -9
- package/dist/server-fns/collections/scheduled-publication.js +31 -21
- package/dist/server-fns/collections/status.d.ts +8 -7
- package/dist/server-fns/collections/status.js +13 -5
- package/dist/server-fns/collections/tree-mutation.d.ts +7 -6
- package/dist/server-fns/collections/tree-mutation.js +17 -5
- package/dist/server-fns/collections/tree.d.ts +14 -8
- package/dist/server-fns/collections/tree.js +21 -17
- package/dist/server-fns/collections/update.d.ts +14 -11
- package/dist/server-fns/collections/update.js +48 -24
- package/dist/server-fns/collections/upload.d.ts +1 -0
- package/dist/server-fns/collections/upload.js +3 -2
- package/dist/server-fns/document-mutation-errors.d.ts +5 -0
- package/dist/server-fns/document-mutation-errors.js +50 -0
- package/dist/server-fns/editable-revision.d.ts +9 -0
- package/dist/server-fns/editable-revision.js +11 -0
- package/dist/server-fns/singleton-document-read.d.ts +1 -1
- package/dist/server-fns/singleton-document-read.js +7 -3
- package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +6 -8
- package/dist/server-fns/singletons/cancel-scheduled-publish.js +4 -1
- package/dist/server-fns/singletons/change-status.d.ts +2 -7
- package/dist/server-fns/singletons/change-status.js +4 -1
- package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +5 -8
- package/dist/server-fns/singletons/confirm-scheduled-publish.js +4 -2
- package/dist/server-fns/singletons/copy-to-locale.d.ts +2 -7
- package/dist/server-fns/singletons/copy-to-locale.js +16 -5
- package/dist/server-fns/singletons/get.d.ts +4 -4
- package/dist/server-fns/singletons/restore-version.d.ts +2 -7
- package/dist/server-fns/singletons/restore-version.js +13 -1
- package/dist/server-fns/singletons/schedule-publish.d.ts +5 -8
- package/dist/server-fns/singletons/schedule-publish.js +4 -2
- package/dist/server-fns/singletons/unpublish.d.ts +2 -7
- package/dist/server-fns/singletons/unpublish.js +4 -1
- package/dist/server-fns/singletons/update.d.ts +4 -11
- package/dist/server-fns/singletons/update.js +16 -8
- package/dist/server-fns/structural-receipt.d.ts +4 -0
- package/dist/server-fns/structural-receipt.js +31 -0
- package/package.json +11 -11
- package/src/admin-shell/collections/create.tsx +22 -3
- package/src/admin-shell/collections/edit.tsx +233 -61
- package/src/admin-shell/collections/history.tsx +11 -2
- package/src/admin-shell/collections/list.tsx +22 -6
- package/src/admin-shell/collections/restore-version-modal.tsx +23 -5
- package/src/admin-shell/collections/save-navigation.test.tsx +532 -0
- package/src/admin-shell/collections/tree-list.tsx +16 -6
- package/src/admin-shell/collections/version-history.tsx +13 -3
- package/src/admin-shell/document-mutation-notice.tsx +45 -0
- package/src/admin-shell/document-mutation-state.test.tsx +146 -0
- package/src/admin-shell/document-mutation-state.ts +95 -0
- package/src/admin-shell/scheduled-publications/list.tsx +16 -6
- package/src/admin-shell/singletons/history.test.tsx +46 -1
- package/src/admin-shell/singletons/history.tsx +10 -2
- package/src/admin-shell/singletons/view.test.tsx +86 -18
- package/src/admin-shell/singletons/view.tsx +145 -45
- package/src/integrations/byline-field-services.ts +2 -3
- package/src/routes/create-collection-edit-route.tsx +1 -0
- package/src/routes/create-collection-list-route.tsx +13 -2
- package/src/routes/create-singleton-route.test.node.ts +10 -2
- package/src/routes/create-singleton-route.tsx +6 -4
- package/src/server-fns/collections/copy-to-locale.ts +48 -29
- package/src/server-fns/collections/create.ts +56 -44
- package/src/server-fns/collections/delete-locale.ts +48 -25
- package/src/server-fns/collections/delete-outcome.test.node.ts +16 -0
- package/src/server-fns/collections/delete-outcome.ts +27 -16
- package/src/server-fns/collections/delete.ts +44 -38
- package/src/server-fns/collections/duplicate.ts +45 -25
- package/src/server-fns/collections/get.ts +8 -4
- package/src/server-fns/collections/index.ts +1 -0
- package/src/server-fns/collections/list.ts +8 -3
- package/src/server-fns/collections/reorder.ts +32 -124
- package/src/server-fns/collections/restore-version.ts +51 -36
- package/src/server-fns/collections/revision-input.test.node.ts +108 -0
- package/src/server-fns/collections/save-outcome.test.node.ts +80 -0
- package/src/server-fns/collections/save-outcome.ts +57 -0
- package/src/server-fns/collections/scheduled-publication.ts +138 -111
- package/src/server-fns/collections/status.ts +86 -64
- package/src/server-fns/collections/tree-mutation.test.node.ts +87 -29
- package/src/server-fns/collections/tree-mutation.ts +28 -11
- package/src/server-fns/collections/tree.ts +150 -122
- package/src/server-fns/collections/update.ts +110 -71
- package/src/server-fns/collections/upload-singleton.test.node.ts +3 -7
- package/src/server-fns/collections/upload.ts +81 -77
- package/src/server-fns/document-mutation-errors.ts +67 -0
- package/src/server-fns/document-mutation-transport.test.node.ts +211 -0
- package/src/server-fns/editable-revision.test.node.ts +23 -0
- package/src/server-fns/editable-revision.ts +18 -0
- package/src/server-fns/singleton-document-read.ts +7 -3
- package/src/server-fns/singletons/cancel-scheduled-publish.ts +10 -3
- package/src/server-fns/singletons/change-status.ts +10 -3
- package/src/server-fns/singletons/confirm-scheduled-publish.ts +12 -6
- package/src/server-fns/singletons/copy-to-locale.ts +20 -8
- package/src/server-fns/singletons/get.ts +3 -3
- package/src/server-fns/singletons/restore-version.ts +17 -3
- package/src/server-fns/singletons/schedule-publish.ts +18 -7
- package/src/server-fns/singletons/singletons.test.node.ts +96 -24
- package/src/server-fns/singletons/unpublish.ts +10 -3
- package/src/server-fns/singletons/update.ts +21 -9
- package/src/server-fns/structural-receipt.ts +41 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
1
2
|
/**
|
|
2
3
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
4
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -10,23 +11,34 @@ import { createServerFn } from '@tanstack/react-start'
|
|
|
10
11
|
|
|
11
12
|
import { getAdminBylineClient } from '@byline/client/server'
|
|
12
13
|
|
|
14
|
+
import { toCommittedDocumentHookFailureResponse } from '../collections/save-outcome.js'
|
|
13
15
|
import { serialise } from '../serialise.js'
|
|
14
16
|
|
|
15
17
|
export const copySingletonToLocale = createServerFn({ method: 'POST' })
|
|
16
18
|
.validator(
|
|
17
19
|
(input: {
|
|
20
|
+
expectedRevision: number
|
|
18
21
|
singleton: string
|
|
19
22
|
sourceLocale: string
|
|
20
23
|
targetLocale: string
|
|
21
24
|
overwrite?: boolean
|
|
22
25
|
}) => input
|
|
23
26
|
)
|
|
24
|
-
.handler(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
.handler(
|
|
28
|
+
withDocumentMutationErrors(async ({ data }) => {
|
|
29
|
+
try {
|
|
30
|
+
return serialise(
|
|
31
|
+
await getAdminBylineClient().singleton(data.singleton).copyToLocale({
|
|
32
|
+
expectedRevision: data.expectedRevision,
|
|
33
|
+
sourceLocale: data.sourceLocale,
|
|
34
|
+
targetLocale: data.targetLocale,
|
|
35
|
+
overwrite: data.overwrite,
|
|
36
|
+
})
|
|
37
|
+
)
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const committedFailure = toCommittedDocumentHookFailureResponse(error)
|
|
40
|
+
if (committedFailure != null) return committedFailure
|
|
41
|
+
throw error
|
|
42
|
+
}
|
|
43
|
+
})
|
|
32
44
|
)
|
|
@@ -23,9 +23,9 @@ export interface GetSingletonInput {
|
|
|
23
23
|
* - this shared host layer owns lenient reads, raw missing-locale values,
|
|
24
24
|
* relation-summary population, transport serialization, and preservation of
|
|
25
25
|
* restore warnings and locale metadata;
|
|
26
|
-
* - the route loader composes published-version
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* - the route loader composes the informational published-version badge, feature
|
|
27
|
+
* enablement, capability, and schedule aliases. Schedule state itself comes
|
|
28
|
+
* from the same editable snapshot as the source document.
|
|
29
29
|
*/
|
|
30
30
|
export const getSingleton = createServerFn({ method: 'GET' })
|
|
31
31
|
.validator((input: GetSingletonInput) => input)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
1
2
|
/**
|
|
2
3
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
4
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -10,10 +11,23 @@ import { createServerFn } from '@tanstack/react-start'
|
|
|
10
11
|
|
|
11
12
|
import { getAdminBylineClient } from '@byline/client/server'
|
|
12
13
|
|
|
14
|
+
import { toCommittedDocumentHookFailureResponse } from '../collections/save-outcome.js'
|
|
13
15
|
import { serialise } from '../serialise.js'
|
|
14
16
|
|
|
15
17
|
export const restoreSingletonVersion = createServerFn({ method: 'POST' })
|
|
16
|
-
.validator((input: { singleton: string; versionId: string }) => input)
|
|
17
|
-
.handler(
|
|
18
|
-
|
|
18
|
+
.validator((input: { expectedRevision: number; singleton: string; versionId: string }) => input)
|
|
19
|
+
.handler(
|
|
20
|
+
withDocumentMutationErrors(async ({ data }) => {
|
|
21
|
+
try {
|
|
22
|
+
return serialise(
|
|
23
|
+
await getAdminBylineClient()
|
|
24
|
+
.singleton(data.singleton)
|
|
25
|
+
.restoreVersion(data.versionId, { expectedRevision: data.expectedRevision })
|
|
26
|
+
)
|
|
27
|
+
} catch (error) {
|
|
28
|
+
const committedFailure = toCommittedDocumentHookFailureResponse(error)
|
|
29
|
+
if (committedFailure != null) return committedFailure
|
|
30
|
+
throw error
|
|
31
|
+
}
|
|
32
|
+
})
|
|
19
33
|
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
1
2
|
/**
|
|
2
3
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
4
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -13,12 +14,22 @@ import { getAdminBylineClient } from '@byline/client/server'
|
|
|
13
14
|
import { serialise } from '../serialise.js'
|
|
14
15
|
|
|
15
16
|
export const scheduleSingletonPublish = createServerFn({ method: 'POST' })
|
|
16
|
-
.validator(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
.validator(
|
|
18
|
+
(input: {
|
|
19
|
+
expectedRevision: number
|
|
20
|
+
singleton: string
|
|
21
|
+
publishAt: string
|
|
22
|
+
expectedVersionId: string
|
|
23
|
+
}) => input
|
|
24
|
+
)
|
|
25
|
+
.handler(
|
|
26
|
+
withDocumentMutationErrors(async ({ data }) =>
|
|
27
|
+
serialise(
|
|
28
|
+
await getAdminBylineClient().singleton(data.singleton).schedulePublish({
|
|
29
|
+
publishAt: data.publishAt,
|
|
30
|
+
expectedRevision: data.expectedRevision,
|
|
31
|
+
expectedVersionId: data.expectedVersionId,
|
|
32
|
+
})
|
|
33
|
+
)
|
|
23
34
|
)
|
|
24
35
|
)
|
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import {
|
|
10
|
+
ERR_DOCUMENT_HOOK_COMMITTED,
|
|
11
|
+
ERR_LOCK_CONFLICT,
|
|
12
|
+
ErrorCodes,
|
|
13
|
+
getLockConflictDetails,
|
|
14
|
+
} from '@byline/core'
|
|
9
15
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
10
16
|
|
|
11
17
|
const mocks = vi.hoisted(() => ({
|
|
@@ -56,7 +62,7 @@ const invoke = (serverFunction: unknown, data: Record<string, any>) =>
|
|
|
56
62
|
(serverFunction as ServerFunction)({ data })
|
|
57
63
|
|
|
58
64
|
const handle = {
|
|
59
|
-
|
|
65
|
+
getForEdit: vi.fn(),
|
|
60
66
|
update: vi.fn(),
|
|
61
67
|
changeStatus: vi.fn(),
|
|
62
68
|
unpublish: vi.fn(),
|
|
@@ -83,14 +89,13 @@ describe('singleton server functions', () => {
|
|
|
83
89
|
const cases = [
|
|
84
90
|
{
|
|
85
91
|
serverFunction: getSingleton,
|
|
86
|
-
method: '
|
|
92
|
+
method: 'getForEdit',
|
|
87
93
|
input: { singleton: 'site-settings', locale: 'fr' },
|
|
88
94
|
args: [
|
|
89
95
|
{
|
|
90
96
|
locale: 'fr',
|
|
91
97
|
populate: undefined,
|
|
92
98
|
depth: undefined,
|
|
93
|
-
status: 'any',
|
|
94
99
|
onMissingLocale: 'empty',
|
|
95
100
|
lenient: true,
|
|
96
101
|
},
|
|
@@ -104,23 +109,23 @@ describe('singleton server functions', () => {
|
|
|
104
109
|
singleton: 'site-settings',
|
|
105
110
|
data: { title: 'Changed' },
|
|
106
111
|
locale: 'fr',
|
|
107
|
-
|
|
112
|
+
expectedRevision: 7,
|
|
108
113
|
},
|
|
109
|
-
args: [{ title: 'Changed' }, { locale: 'fr',
|
|
114
|
+
args: [{ title: 'Changed' }, { locale: 'fr', expectedRevision: 7 }],
|
|
110
115
|
result: { versionId: 'version-2' },
|
|
111
116
|
},
|
|
112
117
|
{
|
|
113
118
|
serverFunction: changeSingletonStatus,
|
|
114
119
|
method: 'changeStatus',
|
|
115
|
-
input: { singleton: 'site-settings', status: 'published' },
|
|
116
|
-
args: ['published'],
|
|
120
|
+
input: { singleton: 'site-settings', status: 'published', expectedRevision: 7 },
|
|
121
|
+
args: ['published', { expectedRevision: 7 }],
|
|
117
122
|
result: { newStatus: 'published' },
|
|
118
123
|
},
|
|
119
124
|
{
|
|
120
125
|
serverFunction: unpublishSingleton,
|
|
121
126
|
method: 'unpublish',
|
|
122
|
-
input: { singleton: 'site-settings' },
|
|
123
|
-
args: [],
|
|
127
|
+
input: { singleton: 'site-settings', expectedRevision: 7 },
|
|
128
|
+
args: [{ expectedRevision: 7 }],
|
|
124
129
|
result: { archivedCount: 1 },
|
|
125
130
|
},
|
|
126
131
|
{
|
|
@@ -129,11 +134,13 @@ describe('singleton server functions', () => {
|
|
|
129
134
|
input: {
|
|
130
135
|
singleton: 'site-settings',
|
|
131
136
|
publishAt: '2026-09-01T10:00:00Z',
|
|
137
|
+
expectedRevision: 7,
|
|
132
138
|
expectedVersionId: 'version-2',
|
|
133
139
|
},
|
|
134
140
|
args: [
|
|
135
141
|
{
|
|
136
142
|
publishAt: '2026-09-01T10:00:00Z',
|
|
143
|
+
expectedRevision: 7,
|
|
137
144
|
expectedVersionId: 'version-2',
|
|
138
145
|
},
|
|
139
146
|
],
|
|
@@ -142,15 +149,15 @@ describe('singleton server functions', () => {
|
|
|
142
149
|
{
|
|
143
150
|
serverFunction: confirmSingletonScheduledPublish,
|
|
144
151
|
method: 'confirmScheduledPublish',
|
|
145
|
-
input: { singleton: 'site-settings', expectedVersionId: 'version-3' },
|
|
146
|
-
args: [{ expectedVersionId: 'version-3' }],
|
|
152
|
+
input: { singleton: 'site-settings', expectedRevision: 7, expectedVersionId: 'version-3' },
|
|
153
|
+
args: [{ expectedRevision: 7, expectedVersionId: 'version-3' }],
|
|
147
154
|
result: { state: 'armed' },
|
|
148
155
|
},
|
|
149
156
|
{
|
|
150
157
|
serverFunction: cancelSingletonScheduledPublish,
|
|
151
158
|
method: 'cancelScheduledPublish',
|
|
152
|
-
input: { singleton: 'site-settings' },
|
|
153
|
-
args: [],
|
|
159
|
+
input: { singleton: 'site-settings', expectedRevision: 7 },
|
|
160
|
+
args: [{ expectedRevision: 7 }],
|
|
154
161
|
result: { state: 'cancelled' },
|
|
155
162
|
},
|
|
156
163
|
{
|
|
@@ -184,8 +191,8 @@ describe('singleton server functions', () => {
|
|
|
184
191
|
{
|
|
185
192
|
serverFunction: restoreSingletonVersion,
|
|
186
193
|
method: 'restoreVersion',
|
|
187
|
-
input: { singleton: 'site-settings', versionId: 'version-1' },
|
|
188
|
-
args: ['version-1'],
|
|
194
|
+
input: { singleton: 'site-settings', versionId: 'version-1', expectedRevision: 7 },
|
|
195
|
+
args: ['version-1', { expectedRevision: 7 }],
|
|
189
196
|
result: { versionId: 'version-4' },
|
|
190
197
|
},
|
|
191
198
|
{
|
|
@@ -193,18 +200,23 @@ describe('singleton server functions', () => {
|
|
|
193
200
|
method: 'copyToLocale',
|
|
194
201
|
input: {
|
|
195
202
|
singleton: 'site-settings',
|
|
203
|
+
expectedRevision: 7,
|
|
196
204
|
sourceLocale: 'en',
|
|
197
205
|
targetLocale: 'fr',
|
|
198
206
|
overwrite: true,
|
|
199
207
|
},
|
|
200
|
-
args: [{ sourceLocale: 'en', targetLocale: 'fr', overwrite: true }],
|
|
208
|
+
args: [{ expectedRevision: 7, sourceLocale: 'en', targetLocale: 'fr', overwrite: true }],
|
|
201
209
|
result: { versionId: 'version-5' },
|
|
202
210
|
},
|
|
203
211
|
] as const
|
|
204
212
|
|
|
205
213
|
for (const testCase of cases) {
|
|
206
214
|
const method = handle[testCase.method]
|
|
207
|
-
method.mockResolvedValueOnce(
|
|
215
|
+
method.mockResolvedValueOnce(
|
|
216
|
+
testCase.method === 'getForEdit'
|
|
217
|
+
? { state: 'document', document: testCase.result }
|
|
218
|
+
: testCase.result
|
|
219
|
+
)
|
|
208
220
|
|
|
209
221
|
const result = await invoke(testCase.serverFunction, testCase.input)
|
|
210
222
|
|
|
@@ -218,24 +230,59 @@ describe('singleton server functions', () => {
|
|
|
218
230
|
}
|
|
219
231
|
})
|
|
220
232
|
|
|
221
|
-
it('forwards the
|
|
233
|
+
it('forwards the observed revision to update unchanged', async () => {
|
|
222
234
|
handle.update.mockResolvedValueOnce({ versionId: 'version-next' })
|
|
223
235
|
|
|
224
236
|
await invoke(updateSingleton, {
|
|
225
237
|
singleton: 'site-settings',
|
|
226
238
|
data: { title: 'Changed' },
|
|
227
|
-
|
|
239
|
+
expectedRevision: 7,
|
|
228
240
|
})
|
|
229
241
|
|
|
230
242
|
expect(handle.update).toHaveBeenCalledWith(
|
|
231
243
|
{ title: 'Changed' },
|
|
232
|
-
{ locale: undefined,
|
|
244
|
+
{ locale: undefined, expectedRevision: 7 }
|
|
233
245
|
)
|
|
234
246
|
})
|
|
235
247
|
|
|
248
|
+
it.each([
|
|
249
|
+
['update', updateSingleton, { singleton: 'site-settings', data: { title: 'Changed' } }],
|
|
250
|
+
[
|
|
251
|
+
'copyToLocale',
|
|
252
|
+
copySingletonToLocale,
|
|
253
|
+
{ singleton: 'site-settings', sourceLocale: 'en', targetLocale: 'fr' },
|
|
254
|
+
],
|
|
255
|
+
[
|
|
256
|
+
'restoreVersion',
|
|
257
|
+
restoreSingletonVersion,
|
|
258
|
+
{ singleton: 'site-settings', versionId: 'version-1' },
|
|
259
|
+
],
|
|
260
|
+
] as const)('returns a committed hook outcome for %s', async (method, serverFunction, input) => {
|
|
261
|
+
handle[method].mockRejectedValueOnce(
|
|
262
|
+
ERR_DOCUMENT_HOOK_COMMITTED({
|
|
263
|
+
message: 'write committed',
|
|
264
|
+
details: {
|
|
265
|
+
phase: 'afterSave',
|
|
266
|
+
documentId: 'doc-settings',
|
|
267
|
+
documentVersionId: 'version-next',
|
|
268
|
+
revision: 8,
|
|
269
|
+
sideEffectCode: ErrorCodes.UNHANDLED,
|
|
270
|
+
},
|
|
271
|
+
})
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
await expect(invoke(serverFunction, input)).resolves.toEqual({
|
|
275
|
+
status: 'committed-hook-failed',
|
|
276
|
+
documentId: 'doc-settings',
|
|
277
|
+
documentVersionId: 'version-next',
|
|
278
|
+
revision: 8,
|
|
279
|
+
sideEffectFailure: { phase: 'afterSave', code: ErrorCodes.UNHANDLED },
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
236
283
|
it('preserves authentication failures from the handle', async () => {
|
|
237
284
|
const error = { code: 'ERR_UNAUTHENTICATED', message: 'sign in required' }
|
|
238
|
-
handle.
|
|
285
|
+
handle.getForEdit.mockRejectedValueOnce(error)
|
|
239
286
|
|
|
240
287
|
await expect(invoke(getSingleton, { singleton: 'site-settings' })).rejects.toBe(error)
|
|
241
288
|
})
|
|
@@ -267,9 +314,34 @@ describe('singleton server functions', () => {
|
|
|
267
314
|
expect(mocks.resolveActorLabels).toHaveBeenCalledWith([])
|
|
268
315
|
})
|
|
269
316
|
|
|
317
|
+
it('preserves confirmed lock failures as safe wire data through the host', async () => {
|
|
318
|
+
const conflict = ERR_LOCK_CONFLICT({
|
|
319
|
+
message: 'private diagnostic',
|
|
320
|
+
cause: new Error('driver SQL'),
|
|
321
|
+
})
|
|
322
|
+
handle.update.mockRejectedValueOnce(conflict)
|
|
323
|
+
let received: unknown
|
|
324
|
+
try {
|
|
325
|
+
await invoke(updateSingleton, {
|
|
326
|
+
singleton: 'site-settings',
|
|
327
|
+
data: { title: 'Changed' },
|
|
328
|
+
expectedRevision: 6,
|
|
329
|
+
})
|
|
330
|
+
} catch (error) {
|
|
331
|
+
received = error
|
|
332
|
+
}
|
|
333
|
+
expect(received).not.toHaveProperty('cause')
|
|
334
|
+
expect(getLockConflictDetails(JSON.parse(JSON.stringify(received)))).toEqual({
|
|
335
|
+
reason: 'lock_conflict',
|
|
336
|
+
rolledBack: true,
|
|
337
|
+
retryable: true,
|
|
338
|
+
})
|
|
339
|
+
expect(handle.update).toHaveBeenCalledTimes(1)
|
|
340
|
+
})
|
|
341
|
+
|
|
270
342
|
it('keeps missing-slot and stale-write failures distinguishable', async () => {
|
|
271
343
|
const missing = { code: 'ERR_NOT_FOUND', message: 'slot is empty' }
|
|
272
|
-
const stale = { code: '
|
|
344
|
+
const stale = { code: 'ERR_DOCUMENT_STALE', message: 'document changed' }
|
|
273
345
|
handle.changeStatus.mockRejectedValueOnce(missing)
|
|
274
346
|
handle.update.mockRejectedValueOnce(stale)
|
|
275
347
|
|
|
@@ -280,9 +352,9 @@ describe('singleton server functions', () => {
|
|
|
280
352
|
invoke(updateSingleton, {
|
|
281
353
|
singleton: 'site-settings',
|
|
282
354
|
data: { title: 'Changed' },
|
|
283
|
-
|
|
355
|
+
expectedRevision: 6,
|
|
284
356
|
})
|
|
285
|
-
).rejects.toMatchObject({ code: '
|
|
357
|
+
).rejects.toMatchObject({ code: 'ERR_DOCUMENT_STALE' })
|
|
286
358
|
})
|
|
287
359
|
|
|
288
360
|
it('serialises schedule dates at the transport boundary', async () => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
1
2
|
/**
|
|
2
3
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
4
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -13,7 +14,13 @@ import { getAdminBylineClient } from '@byline/client/server'
|
|
|
13
14
|
import { serialise } from '../serialise.js'
|
|
14
15
|
|
|
15
16
|
export const unpublishSingleton = createServerFn({ method: 'POST' })
|
|
16
|
-
.validator((input: { singleton: string }) => input)
|
|
17
|
-
.handler(
|
|
18
|
-
|
|
17
|
+
.validator((input: { expectedRevision: number; singleton: string }) => input)
|
|
18
|
+
.handler(
|
|
19
|
+
withDocumentMutationErrors(async ({ data }) =>
|
|
20
|
+
serialise(
|
|
21
|
+
await getAdminBylineClient()
|
|
22
|
+
.singleton(data.singleton)
|
|
23
|
+
.unpublish({ expectedRevision: data.expectedRevision })
|
|
24
|
+
)
|
|
25
|
+
)
|
|
19
26
|
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
1
2
|
/**
|
|
2
3
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
4
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -9,23 +10,34 @@
|
|
|
9
10
|
import { createServerFn } from '@tanstack/react-start'
|
|
10
11
|
|
|
11
12
|
import { getAdminBylineClient } from '@byline/client/server'
|
|
13
|
+
import type { SingletonSavePrecondition } from '@byline/core'
|
|
12
14
|
|
|
15
|
+
import { toCommittedDocumentHookFailureResponse } from '../collections/save-outcome.js'
|
|
13
16
|
import { serialise } from '../serialise.js'
|
|
14
17
|
|
|
15
|
-
export
|
|
18
|
+
export type UpdateSingletonInput = SingletonSavePrecondition & {
|
|
16
19
|
singleton: string
|
|
17
20
|
data: Record<string, any>
|
|
18
21
|
locale?: string
|
|
19
|
-
expectedVersionId?: string
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export const updateSingleton = createServerFn({ method: 'POST' })
|
|
23
25
|
.validator((input: UpdateSingletonInput) => input)
|
|
24
|
-
.handler(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
.handler(
|
|
27
|
+
withDocumentMutationErrors(async ({ data }) => {
|
|
28
|
+
const { singleton, data: fields, locale, ...precondition } = data
|
|
29
|
+
try {
|
|
30
|
+
const result = await getAdminBylineClient()
|
|
31
|
+
.singleton(singleton)
|
|
32
|
+
.update(fields, {
|
|
33
|
+
locale,
|
|
34
|
+
...precondition,
|
|
35
|
+
})
|
|
36
|
+
return serialise(result)
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const committedFailure = toCommittedDocumentHookFailureResponse(error)
|
|
39
|
+
if (committedFailure != null) return committedFailure
|
|
40
|
+
throw error
|
|
41
|
+
}
|
|
29
42
|
})
|
|
30
|
-
|
|
31
|
-
})
|
|
43
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ErrorCodes, isDocumentRevision, type StructuralMutationReceipt } from '@byline/core'
|
|
2
|
+
|
|
3
|
+
/** Copy only the authorized receipt fields emitted by the lifecycle service. */
|
|
4
|
+
export function structuralReceipt(value: unknown): StructuralMutationReceipt | null {
|
|
5
|
+
if (typeof value !== 'object' || value === null) return null
|
|
6
|
+
const documentId = Reflect.get(value, 'documentId')
|
|
7
|
+
const revision = Reflect.get(value, 'revision')
|
|
8
|
+
const suspended = Reflect.get(value, 'scheduledPublicationsNeedReconfirmation')
|
|
9
|
+
const affected = Reflect.get(value, 'affectedDocuments')
|
|
10
|
+
if (
|
|
11
|
+
typeof documentId !== 'string' ||
|
|
12
|
+
!isDocumentRevision(revision) ||
|
|
13
|
+
typeof suspended !== 'boolean' ||
|
|
14
|
+
!Array.isArray(affected)
|
|
15
|
+
)
|
|
16
|
+
return null
|
|
17
|
+
const affectedDocuments = []
|
|
18
|
+
for (const row of affected) {
|
|
19
|
+
if (typeof row !== 'object' || row === null) return null
|
|
20
|
+
const id = Reflect.get(row, 'documentId')
|
|
21
|
+
const token = Reflect.get(row, 'revision')
|
|
22
|
+
if (typeof id !== 'string' || !isDocumentRevision(token)) return null
|
|
23
|
+
affectedDocuments.push({ documentId: id, revision: token })
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
documentId,
|
|
27
|
+
revision,
|
|
28
|
+
affectedDocuments,
|
|
29
|
+
scheduledPublicationsNeedReconfirmation: suspended,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function committedTreeReceipt(error: unknown): StructuralMutationReceipt | null {
|
|
34
|
+
if (
|
|
35
|
+
typeof error !== 'object' ||
|
|
36
|
+
error === null ||
|
|
37
|
+
Reflect.get(error, 'code') !== ErrorCodes.TREE_HOOK_COMMITTED
|
|
38
|
+
)
|
|
39
|
+
return null
|
|
40
|
+
return structuralReceipt(Reflect.get(error, 'details'))
|
|
41
|
+
}
|