@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
|
|
@@ -66,6 +67,7 @@ export type SerializedDocumentPublishSchedule = Omit<
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
export interface ScheduledPublicationListItem extends SerializedDocumentPublishSchedule {
|
|
70
|
+
revision: number
|
|
69
71
|
collectionPath: string
|
|
70
72
|
collectionLabel: string
|
|
71
73
|
documentPath: string | null
|
|
@@ -137,49 +139,75 @@ async function scheduledPublicationRuntime(): Promise<ScheduledPublicationRuntim
|
|
|
137
139
|
|
|
138
140
|
/** Authenticated feature discovery for the admin shell and health warning. */
|
|
139
141
|
export const getScheduledPublicationRuntime = createServerFn({ method: 'GET' }).handler(
|
|
140
|
-
async () => {
|
|
142
|
+
withDocumentMutationErrors(async () => {
|
|
141
143
|
await getAdminRequestContext()
|
|
142
144
|
return scheduledPublicationRuntime()
|
|
143
|
-
}
|
|
145
|
+
})
|
|
144
146
|
)
|
|
145
147
|
|
|
146
148
|
export const scheduleCollectionDocumentPublish = createServerFn({ method: 'POST' })
|
|
147
149
|
.validator(
|
|
148
|
-
(input: {
|
|
149
|
-
|
|
150
|
+
(input: {
|
|
151
|
+
expectedRevision: number
|
|
152
|
+
collection: string
|
|
153
|
+
id: string
|
|
154
|
+
publishAt: string
|
|
155
|
+
expectedVersionId: string
|
|
156
|
+
}) => input
|
|
157
|
+
)
|
|
158
|
+
.handler(
|
|
159
|
+
withDocumentMutationErrors(async ({ data }) => {
|
|
160
|
+
assertFeatureEnabled()
|
|
161
|
+
const schedule = await getAdminBylineClient()
|
|
162
|
+
.collection(data.collection)
|
|
163
|
+
.schedulePublish(data.id, {
|
|
164
|
+
publishAt: data.publishAt,
|
|
165
|
+
expectedRevision: data.expectedRevision,
|
|
166
|
+
expectedVersionId: data.expectedVersionId,
|
|
167
|
+
})
|
|
168
|
+
return { ...serializeSchedule(schedule), revision: schedule.revision }
|
|
169
|
+
})
|
|
150
170
|
)
|
|
151
|
-
.handler(async ({ data }) => {
|
|
152
|
-
assertFeatureEnabled()
|
|
153
|
-
const schedule = await getAdminBylineClient()
|
|
154
|
-
.collection(data.collection)
|
|
155
|
-
.schedulePublish(data.id, {
|
|
156
|
-
publishAt: data.publishAt,
|
|
157
|
-
expectedVersionId: data.expectedVersionId,
|
|
158
|
-
})
|
|
159
|
-
return serializeSchedule(schedule)
|
|
160
|
-
})
|
|
161
171
|
|
|
162
172
|
export const confirmCollectionDocumentScheduledPublish = createServerFn({ method: 'POST' })
|
|
163
|
-
.validator(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
173
|
+
.validator(
|
|
174
|
+
(input: {
|
|
175
|
+
expectedRevision: number
|
|
176
|
+
collection: string
|
|
177
|
+
id: string
|
|
178
|
+
expectedVersionId: string
|
|
179
|
+
}) => input
|
|
180
|
+
)
|
|
181
|
+
.handler(
|
|
182
|
+
withDocumentMutationErrors(async ({ data }) => {
|
|
183
|
+
assertFeatureEnabled()
|
|
184
|
+
const schedule = await getAdminBylineClient()
|
|
185
|
+
.collection(data.collection)
|
|
186
|
+
.confirmScheduledPublish(data.id, {
|
|
187
|
+
expectedRevision: data.expectedRevision,
|
|
188
|
+
expectedVersionId: data.expectedVersionId,
|
|
189
|
+
})
|
|
190
|
+
return { ...serializeSchedule(schedule), revision: schedule.revision }
|
|
191
|
+
})
|
|
192
|
+
)
|
|
171
193
|
|
|
172
194
|
export const cancelCollectionDocumentScheduledPublish = createServerFn({ method: 'POST' })
|
|
173
|
-
.validator((input: { collection: string; id: string }) => input)
|
|
174
|
-
.handler(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
195
|
+
.validator((input: { expectedRevision: number; collection: string; id: string }) => input)
|
|
196
|
+
.handler(
|
|
197
|
+
withDocumentMutationErrors(async ({ data }) => {
|
|
198
|
+
assertFeatureEnabled()
|
|
199
|
+
const schedule = await getAdminBylineClient()
|
|
200
|
+
.collection(data.collection)
|
|
201
|
+
.cancelScheduledPublish(data.id, { expectedRevision: data.expectedRevision })
|
|
202
|
+
return schedule.schedule == null
|
|
203
|
+
? { status: 'not_found' as const, revision: schedule.revision }
|
|
204
|
+
: {
|
|
205
|
+
status: 'cancelled' as const,
|
|
206
|
+
schedule: serializeSchedule(schedule.schedule),
|
|
207
|
+
revision: schedule.revision,
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
)
|
|
183
211
|
|
|
184
212
|
export const listScheduledPublications = createServerFn({ method: 'GET' })
|
|
185
213
|
.validator(
|
|
@@ -190,87 +218,86 @@ export const listScheduledPublications = createServerFn({ method: 'GET' })
|
|
|
190
218
|
pageSize?: number
|
|
191
219
|
}) => input
|
|
192
220
|
)
|
|
193
|
-
.handler(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const collections = new Map(
|
|
212
|
-
core.collections.map((definition) => {
|
|
213
|
-
const record = core.getCollectionRecord(definition.path)
|
|
214
|
-
return [record.collectionId, definition] as const
|
|
221
|
+
.handler(
|
|
222
|
+
withDocumentMutationErrors(async ({ data }) => {
|
|
223
|
+
assertFeatureEnabled()
|
|
224
|
+
if (data.states?.some((state) => state !== 'armed' && state !== 'needs_reconfirm') === true) {
|
|
225
|
+
throw ERR_VALIDATION({ message: 'state must be armed or needs_reconfirm' })
|
|
226
|
+
}
|
|
227
|
+
if (data.lastAuthorizedBy != null && !UUID_RE.test(data.lastAuthorizedBy)) {
|
|
228
|
+
throw ERR_VALIDATION({ message: 'lastAuthorizedBy must be a UUID' })
|
|
229
|
+
}
|
|
230
|
+
const core = bylineCore()
|
|
231
|
+
const page = data.page ?? 1
|
|
232
|
+
const pageSize = data.pageSize ?? 25
|
|
233
|
+
const requestContext = await getAdminRequestContext()
|
|
234
|
+
const result = await listDocumentPublishSchedules(core, requestContext, {
|
|
235
|
+
states: data.states,
|
|
236
|
+
lastAuthorizedBy: data.lastAuthorizedBy,
|
|
237
|
+
page,
|
|
238
|
+
pageSize,
|
|
215
239
|
})
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
240
|
+
const collections = new Map(
|
|
241
|
+
core.collections.map((definition) => {
|
|
242
|
+
const record = core.getCollectionRecord(definition.path)
|
|
243
|
+
return [record.collectionId, definition] as const
|
|
244
|
+
})
|
|
245
|
+
)
|
|
246
|
+
// Resolve authorizer names in one query for the whole page rather than one
|
|
247
|
+
// per row. `last_authorized_by` is deliberately not a foreign key — deleting
|
|
248
|
+
// an admin account must not revoke a schedule it authorized — so an id here
|
|
249
|
+
// may simply have no user row left, and the caller falls back to the id.
|
|
250
|
+
const authorizerIds = [
|
|
251
|
+
...new Set(
|
|
252
|
+
result.schedules
|
|
253
|
+
.map((schedule) => schedule.lastAuthorizedBy)
|
|
254
|
+
.filter((id): id is string => id != null)
|
|
255
|
+
),
|
|
256
|
+
]
|
|
257
|
+
const authorizerNames = new Map<string, string>()
|
|
258
|
+
// `adminStore` is optional on core — an installation can run without the
|
|
259
|
+
// admin subsystem — so a missing store simply means no names to resolve and
|
|
260
|
+
// the column falls back to ids.
|
|
261
|
+
const adminUsers = core.adminStore?.adminUsers
|
|
262
|
+
if (authorizerIds.length > 0 && adminUsers != null) {
|
|
263
|
+
const users = await adminUsers.getByIds(authorizerIds)
|
|
264
|
+
for (const user of users) {
|
|
265
|
+
const fullName = [user.given_name, user.family_name].filter(Boolean).join(' ').trim()
|
|
266
|
+
authorizerNames.set(user.id, fullName.length > 0 ? fullName : user.email)
|
|
267
|
+
}
|
|
238
268
|
}
|
|
239
|
-
}
|
|
240
269
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
270
|
+
const schedules = await Promise.all(
|
|
271
|
+
result.schedules.map(async (schedule): Promise<ScheduledPublicationListItem> => {
|
|
272
|
+
const definition = collections.get(schedule.collectionId)
|
|
273
|
+
return {
|
|
274
|
+
...serializeSchedule(schedule),
|
|
275
|
+
collectionPath: definition?.path ?? schedule.collectionId,
|
|
276
|
+
collectionLabel:
|
|
277
|
+
definition == null
|
|
278
|
+
? schedule.collectionId
|
|
279
|
+
: isSingleton(definition)
|
|
280
|
+
? definition.label
|
|
281
|
+
: definition.labels.plural,
|
|
282
|
+
documentPath: schedule.documentPath,
|
|
283
|
+
revision: schedule.revision,
|
|
284
|
+
lastAuthorizedByName:
|
|
285
|
+
schedule.lastAuthorizedBy == null
|
|
286
|
+
? null
|
|
287
|
+
: (authorizerNames.get(schedule.lastAuthorizedBy) ?? null),
|
|
288
|
+
}
|
|
247
289
|
})
|
|
248
|
-
|
|
249
|
-
...serializeSchedule(schedule),
|
|
250
|
-
collectionPath: definition?.path ?? schedule.collectionId,
|
|
251
|
-
collectionLabel:
|
|
252
|
-
definition == null
|
|
253
|
-
? schedule.collectionId
|
|
254
|
-
: isSingleton(definition)
|
|
255
|
-
? definition.label
|
|
256
|
-
: definition.labels.plural,
|
|
257
|
-
documentPath,
|
|
258
|
-
lastAuthorizedByName:
|
|
259
|
-
schedule.lastAuthorizedBy == null
|
|
260
|
-
? null
|
|
261
|
-
: (authorizerNames.get(schedule.lastAuthorizedBy) ?? null),
|
|
262
|
-
}
|
|
263
|
-
})
|
|
264
|
-
)
|
|
290
|
+
)
|
|
265
291
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
292
|
+
return {
|
|
293
|
+
schedules,
|
|
294
|
+
meta: {
|
|
295
|
+
page,
|
|
296
|
+
pageSize,
|
|
297
|
+
total: result.total,
|
|
298
|
+
totalPages: Math.max(1, Math.ceil(result.total / pageSize)),
|
|
299
|
+
},
|
|
300
|
+
runtime: await scheduledPublicationRuntime(),
|
|
301
|
+
} satisfies ScheduledPublicationListResponse
|
|
302
|
+
})
|
|
303
|
+
)
|
|
@@ -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
|
|
@@ -23,80 +24,101 @@ import { ensureCollection } from '../../integrations/api-utils.js'
|
|
|
23
24
|
// ---------------------------------------------------------------------------
|
|
24
25
|
|
|
25
26
|
export const updateDocumentStatus = createServerFn({ method: 'POST' })
|
|
26
|
-
.validator(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
.validator(
|
|
28
|
+
(input: { expectedRevision: number; collection: string; id: string; status: string }) => input
|
|
29
|
+
)
|
|
30
|
+
.handler(
|
|
31
|
+
withDocumentMutationErrors(async ({ data: input }) => {
|
|
32
|
+
const { collection: path, id, status: nextStatus } = input
|
|
33
|
+
const logger = getLogger()
|
|
34
|
+
const config = await ensureCollection(path)
|
|
35
|
+
if (!config) {
|
|
36
|
+
throw ERR_NOT_FOUND({
|
|
37
|
+
message: 'Collection not found',
|
|
38
|
+
details: { collectionPath: path },
|
|
39
|
+
}).log(logger)
|
|
40
|
+
}
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
const serverConfig = getServerConfig()
|
|
43
|
+
const ctx: DocumentLifecycleContext = {
|
|
44
|
+
db: serverConfig.db,
|
|
45
|
+
definition: config.definition,
|
|
46
|
+
collectionId: config.collection.id,
|
|
47
|
+
collectionVersion: config.collection.version,
|
|
48
|
+
collectionPath: path,
|
|
49
|
+
logger,
|
|
50
|
+
defaultLocale: serverConfig.i18n.content.defaultLocale,
|
|
51
|
+
slugifier: serverConfig.slugifier,
|
|
52
|
+
requestContext: await getAdminRequestContext(),
|
|
53
|
+
}
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
const result = await changeDocumentStatus(ctx, {
|
|
56
|
+
documentId: id,
|
|
57
|
+
expectedRevision: input.expectedRevision,
|
|
58
|
+
nextStatus,
|
|
59
|
+
})
|
|
60
|
+
return {
|
|
61
|
+
status: 'ok' as const,
|
|
62
|
+
documentId: result.documentId,
|
|
63
|
+
revision: result.revision,
|
|
64
|
+
previousStatus: result.previousStatus,
|
|
65
|
+
newStatus: result.newStatus,
|
|
66
|
+
}
|
|
54
67
|
})
|
|
55
|
-
|
|
56
|
-
status: 'ok' as const,
|
|
57
|
-
previousStatus: result.previousStatus,
|
|
58
|
-
newStatus: result.newStatus,
|
|
59
|
-
}
|
|
60
|
-
})
|
|
68
|
+
)
|
|
61
69
|
|
|
62
70
|
// ---------------------------------------------------------------------------
|
|
63
71
|
// Unpublish document (archive the live published version)
|
|
64
72
|
// ---------------------------------------------------------------------------
|
|
65
73
|
|
|
66
74
|
export const unpublishDocument = createServerFn({ method: 'POST' })
|
|
67
|
-
.validator((input: { collection: string; id: string }) => input)
|
|
68
|
-
.handler(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
.validator((input: { expectedRevision: number; collection: string; id: string }) => input)
|
|
76
|
+
.handler(
|
|
77
|
+
withDocumentMutationErrors(async ({ data: input }) => {
|
|
78
|
+
const { collection: path, id } = input
|
|
79
|
+
const logger = getLogger()
|
|
80
|
+
const config = await ensureCollection(path)
|
|
81
|
+
if (!config) {
|
|
82
|
+
throw ERR_NOT_FOUND({
|
|
83
|
+
message: 'Collection not found',
|
|
84
|
+
details: { collectionPath: path },
|
|
85
|
+
}).log(logger)
|
|
86
|
+
}
|
|
78
87
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
const serverConfig = getServerConfig()
|
|
89
|
+
const ctx: DocumentLifecycleContext = {
|
|
90
|
+
db: serverConfig.db,
|
|
91
|
+
definition: config.definition,
|
|
92
|
+
collectionId: config.collection.id,
|
|
93
|
+
collectionVersion: config.collection.version,
|
|
94
|
+
collectionPath: path,
|
|
95
|
+
logger,
|
|
96
|
+
defaultLocale: serverConfig.i18n.content.defaultLocale,
|
|
97
|
+
slugifier: serverConfig.slugifier,
|
|
98
|
+
requestContext: await getAdminRequestContext(),
|
|
99
|
+
}
|
|
91
100
|
|
|
92
|
-
|
|
101
|
+
const result = await unpublishDocumentService(ctx, {
|
|
102
|
+
documentId: id,
|
|
103
|
+
expectedRevision: input.expectedRevision,
|
|
104
|
+
})
|
|
93
105
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
if (result.archivedCount === 0) {
|
|
107
|
+
throw ERR_NOT_FOUND({
|
|
108
|
+
message: 'No published version found for this document',
|
|
109
|
+
details: {
|
|
110
|
+
documentId: id,
|
|
111
|
+
expectedRevision: input.expectedRevision,
|
|
112
|
+
collectionPath: path,
|
|
113
|
+
},
|
|
114
|
+
}).log(logger)
|
|
115
|
+
}
|
|
100
116
|
|
|
101
|
-
|
|
102
|
-
|
|
117
|
+
return {
|
|
118
|
+
status: 'ok' as const,
|
|
119
|
+
documentId: result.documentId,
|
|
120
|
+
revision: result.revision,
|
|
121
|
+
archivedCount: result.archivedCount,
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
)
|
|
@@ -175,51 +175,109 @@ describe('admin tree mutations', () => {
|
|
|
175
175
|
expect(controller.isMoving()).toBe(false)
|
|
176
176
|
})
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
const receipt = {
|
|
179
|
+
documentId: 'doc-1',
|
|
180
|
+
revision: 2,
|
|
181
|
+
affectedDocuments: [{ documentId: 'doc-1', revision: 2 }],
|
|
182
|
+
scheduledPublicationsNeedReconfirmation: false,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
it('forwards each caller observation and requests reconciliation without retrying', async () => {
|
|
179
186
|
const placeTreeNode = vi
|
|
180
|
-
.fn()
|
|
187
|
+
.fn<Parameters<typeof placeAdminTreeNode>[0]['placeTreeNode']>()
|
|
181
188
|
.mockRejectedValueOnce(new Error('post-commit hook failed'))
|
|
182
|
-
.mockResolvedValue({ orderKey: 'key-1' })
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
placeAdminTreeNode(
|
|
189
|
+
.mockResolvedValue({ orderKey: 'key-1', ...receipt })
|
|
190
|
+
const handle = { placeTreeNode }
|
|
191
|
+
await expect(
|
|
192
|
+
placeAdminTreeNode(handle, 'doc-1', {
|
|
193
|
+
expectedRevision: 1,
|
|
186
194
|
parentDocumentId: 'parent-1',
|
|
187
195
|
beforeDocumentId: 'left-1',
|
|
188
196
|
})
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
)
|
|
197
|
+
).rejects.toThrow('post-commit hook failed')
|
|
198
|
+
expect(placeTreeNode).toHaveBeenCalledTimes(1)
|
|
199
|
+
// A separate caller action carries its newly observed receipt, never an implicit retry.
|
|
200
|
+
await expect(
|
|
201
|
+
placeAdminTreeNode(handle, 'doc-1', {
|
|
202
|
+
expectedRevision: 2,
|
|
203
|
+
parentDocumentId: 'parent-1',
|
|
204
|
+
beforeDocumentId: 'left-1',
|
|
205
|
+
})
|
|
206
|
+
).resolves.toEqual({ orderKey: 'key-1', ...receipt })
|
|
207
|
+
expect(placeTreeNode).toHaveBeenNthCalledWith(2, 'doc-1', {
|
|
208
|
+
expectedRevision: 2,
|
|
209
|
+
parentDocumentId: 'parent-1',
|
|
210
|
+
beforeDocumentId: 'left-1',
|
|
211
|
+
reconcile: true,
|
|
212
|
+
})
|
|
199
213
|
})
|
|
200
214
|
|
|
201
|
-
it('
|
|
202
|
-
const removeFromTree = vi
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
215
|
+
it('requests reconciliation for removal and preserves the caller revision', async () => {
|
|
216
|
+
const removeFromTree = vi
|
|
217
|
+
.fn<Parameters<typeof removeAdminTreeNode>[0]['removeFromTree']>()
|
|
218
|
+
.mockResolvedValue(receipt)
|
|
219
|
+
await expect(
|
|
220
|
+
removeAdminTreeNode({ removeFromTree }, 'doc-1', { expectedRevision: 1 })
|
|
221
|
+
).resolves.toEqual(receipt)
|
|
222
|
+
expect(removeFromTree).toHaveBeenCalledWith('doc-1', { expectedRevision: 1, reconcile: true })
|
|
207
223
|
})
|
|
208
224
|
|
|
209
225
|
it('preserves an explicit reconciliation override', async () => {
|
|
210
|
-
const placeTreeNode = vi
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
226
|
+
const placeTreeNode = vi
|
|
227
|
+
.fn<Parameters<typeof placeAdminTreeNode>[0]['placeTreeNode']>()
|
|
228
|
+
.mockResolvedValue({ orderKey: 'key-1', ...receipt })
|
|
229
|
+
const removeFromTree = vi
|
|
230
|
+
.fn<Parameters<typeof removeAdminTreeNode>[0]['removeFromTree']>()
|
|
231
|
+
.mockResolvedValue(receipt)
|
|
232
|
+
await placeAdminTreeNode({ placeTreeNode }, 'doc-1', {
|
|
233
|
+
expectedRevision: 1,
|
|
214
234
|
parentDocumentId: null,
|
|
215
235
|
reconcile: false,
|
|
216
236
|
})
|
|
217
|
-
await removeAdminTreeNode({ removeFromTree }
|
|
218
|
-
|
|
237
|
+
await removeAdminTreeNode({ removeFromTree }, 'doc-1', {
|
|
238
|
+
expectedRevision: 2,
|
|
239
|
+
reconcile: false,
|
|
240
|
+
})
|
|
219
241
|
expect(placeTreeNode).toHaveBeenCalledWith('doc-1', {
|
|
242
|
+
expectedRevision: 1,
|
|
220
243
|
parentDocumentId: null,
|
|
221
244
|
reconcile: false,
|
|
222
245
|
})
|
|
223
|
-
expect(removeFromTree).toHaveBeenCalledWith('doc-1', { reconcile: false })
|
|
246
|
+
expect(removeFromTree).toHaveBeenCalledWith('doc-1', { expectedRevision: 2, reconcile: false })
|
|
224
247
|
})
|
|
225
248
|
})
|
|
249
|
+
|
|
250
|
+
it('does not refresh or replace the observation after a typed stale tree move', async () => {
|
|
251
|
+
const error = {
|
|
252
|
+
code: 'ERR_DOCUMENT_STALE',
|
|
253
|
+
details: {
|
|
254
|
+
reason: 'revision_mismatch',
|
|
255
|
+
documentId: 'doc',
|
|
256
|
+
expectedRevision: 2,
|
|
257
|
+
currentRevision: 3,
|
|
258
|
+
},
|
|
259
|
+
}
|
|
260
|
+
const refresh = vi.fn()
|
|
261
|
+
const mutate = vi.fn().mockRejectedValue(error)
|
|
262
|
+
expect(await executeAdminTreeMove(mutate, refresh)).toEqual({ status: 'mutation-failed', error })
|
|
263
|
+
expect(mutate).toHaveBeenCalledTimes(1)
|
|
264
|
+
expect(refresh).not.toHaveBeenCalled()
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('retains the authorized schedule summary when refresh fails after a committed move', async () => {
|
|
268
|
+
const receipt = {
|
|
269
|
+
documentId: 'doc',
|
|
270
|
+
revision: 3,
|
|
271
|
+
affectedDocuments: [{ documentId: 'sibling', revision: 4 }],
|
|
272
|
+
scheduledPublicationsNeedReconfirmation: true,
|
|
273
|
+
}
|
|
274
|
+
const error = new Error('refresh unavailable')
|
|
275
|
+
expect(
|
|
276
|
+
await executeAdminTreeMove(
|
|
277
|
+
async () => receipt,
|
|
278
|
+
async () => {
|
|
279
|
+
throw error
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
).toEqual({ status: 'refresh-failed', error, receipt })
|
|
283
|
+
})
|