@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.
Files changed (148) hide show
  1. package/dist/admin-shell/collections/create.js +22 -5
  2. package/dist/admin-shell/collections/edit.d.ts +1 -1
  3. package/dist/admin-shell/collections/edit.js +222 -34
  4. package/dist/admin-shell/collections/history.js +4 -2
  5. package/dist/admin-shell/collections/list.js +30 -6
  6. package/dist/admin-shell/collections/restore-version-modal.d.ts +3 -1
  7. package/dist/admin-shell/collections/restore-version-modal.js +23 -6
  8. package/dist/admin-shell/collections/save-navigation.test.d.ts +8 -0
  9. package/dist/admin-shell/collections/tree-list.js +16 -6
  10. package/dist/admin-shell/collections/version-history.d.ts +2 -1
  11. package/dist/admin-shell/collections/version-history.js +11 -2
  12. package/dist/admin-shell/document-mutation-notice.d.ts +6 -0
  13. package/dist/admin-shell/document-mutation-notice.js +53 -0
  14. package/dist/admin-shell/document-mutation-state.d.ts +22 -0
  15. package/dist/admin-shell/document-mutation-state.js +71 -0
  16. package/dist/admin-shell/document-mutation-state.test.d.ts +1 -0
  17. package/dist/admin-shell/scheduled-publications/list.js +15 -5
  18. package/dist/admin-shell/singletons/history.js +4 -2
  19. package/dist/admin-shell/singletons/view.d.ts +1 -1
  20. package/dist/admin-shell/singletons/view.js +108 -23
  21. package/dist/integrations/byline-field-services.js +2 -9
  22. package/dist/routes/create-collection-edit-route.js +1 -1
  23. package/dist/routes/create-collection-list-route.d.ts +0 -7
  24. package/dist/routes/create-collection-list-route.js +7 -9
  25. package/dist/routes/create-singleton-route.d.ts +2 -0
  26. package/dist/routes/create-singleton-route.js +4 -3
  27. package/dist/server-fns/collections/copy-to-locale.d.ts +4 -8
  28. package/dist/server-fns/collections/copy-to-locale.js +17 -8
  29. package/dist/server-fns/collections/create.d.ts +2 -8
  30. package/dist/server-fns/collections/create.js +20 -10
  31. package/dist/server-fns/collections/delete-locale.d.ts +4 -8
  32. package/dist/server-fns/collections/delete-locale.js +15 -6
  33. package/dist/server-fns/collections/delete-outcome.d.ts +7 -3
  34. package/dist/server-fns/collections/delete-outcome.js +4 -0
  35. package/dist/server-fns/collections/delete.d.ts +2 -7
  36. package/dist/server-fns/collections/delete.js +5 -3
  37. package/dist/server-fns/collections/duplicate.d.ts +4 -8
  38. package/dist/server-fns/collections/duplicate.js +14 -5
  39. package/dist/server-fns/collections/get.d.ts +1 -7
  40. package/dist/server-fns/collections/get.js +5 -4
  41. package/dist/server-fns/collections/index.d.ts +1 -0
  42. package/dist/server-fns/collections/index.js +1 -0
  43. package/dist/server-fns/collections/list.d.ts +0 -7
  44. package/dist/server-fns/collections/list.js +11 -3
  45. package/dist/server-fns/collections/reorder.d.ts +7 -8
  46. package/dist/server-fns/collections/reorder.js +20 -76
  47. package/dist/server-fns/collections/restore-version.d.ts +4 -8
  48. package/dist/server-fns/collections/restore-version.js +22 -12
  49. package/dist/server-fns/collections/save-outcome.d.ts +24 -0
  50. package/dist/server-fns/collections/save-outcome.js +19 -0
  51. package/dist/server-fns/collections/scheduled-publication.d.ts +49 -9
  52. package/dist/server-fns/collections/scheduled-publication.js +31 -21
  53. package/dist/server-fns/collections/status.d.ts +8 -7
  54. package/dist/server-fns/collections/status.js +13 -5
  55. package/dist/server-fns/collections/tree-mutation.d.ts +7 -6
  56. package/dist/server-fns/collections/tree-mutation.js +17 -5
  57. package/dist/server-fns/collections/tree.d.ts +14 -8
  58. package/dist/server-fns/collections/tree.js +21 -17
  59. package/dist/server-fns/collections/update.d.ts +14 -11
  60. package/dist/server-fns/collections/update.js +48 -24
  61. package/dist/server-fns/collections/upload.d.ts +1 -0
  62. package/dist/server-fns/collections/upload.js +3 -2
  63. package/dist/server-fns/document-mutation-errors.d.ts +5 -0
  64. package/dist/server-fns/document-mutation-errors.js +50 -0
  65. package/dist/server-fns/editable-revision.d.ts +9 -0
  66. package/dist/server-fns/editable-revision.js +11 -0
  67. package/dist/server-fns/singleton-document-read.d.ts +1 -1
  68. package/dist/server-fns/singleton-document-read.js +7 -3
  69. package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +6 -8
  70. package/dist/server-fns/singletons/cancel-scheduled-publish.js +4 -1
  71. package/dist/server-fns/singletons/change-status.d.ts +2 -7
  72. package/dist/server-fns/singletons/change-status.js +4 -1
  73. package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +5 -8
  74. package/dist/server-fns/singletons/confirm-scheduled-publish.js +4 -2
  75. package/dist/server-fns/singletons/copy-to-locale.d.ts +2 -7
  76. package/dist/server-fns/singletons/copy-to-locale.js +16 -5
  77. package/dist/server-fns/singletons/get.d.ts +4 -4
  78. package/dist/server-fns/singletons/restore-version.d.ts +2 -7
  79. package/dist/server-fns/singletons/restore-version.js +13 -1
  80. package/dist/server-fns/singletons/schedule-publish.d.ts +5 -8
  81. package/dist/server-fns/singletons/schedule-publish.js +4 -2
  82. package/dist/server-fns/singletons/unpublish.d.ts +2 -7
  83. package/dist/server-fns/singletons/unpublish.js +4 -1
  84. package/dist/server-fns/singletons/update.d.ts +4 -11
  85. package/dist/server-fns/singletons/update.js +16 -8
  86. package/dist/server-fns/structural-receipt.d.ts +4 -0
  87. package/dist/server-fns/structural-receipt.js +31 -0
  88. package/package.json +11 -11
  89. package/src/admin-shell/collections/create.tsx +22 -3
  90. package/src/admin-shell/collections/edit.tsx +233 -61
  91. package/src/admin-shell/collections/history.tsx +11 -2
  92. package/src/admin-shell/collections/list.tsx +22 -6
  93. package/src/admin-shell/collections/restore-version-modal.tsx +23 -5
  94. package/src/admin-shell/collections/save-navigation.test.tsx +532 -0
  95. package/src/admin-shell/collections/tree-list.tsx +16 -6
  96. package/src/admin-shell/collections/version-history.tsx +13 -3
  97. package/src/admin-shell/document-mutation-notice.tsx +45 -0
  98. package/src/admin-shell/document-mutation-state.test.tsx +146 -0
  99. package/src/admin-shell/document-mutation-state.ts +95 -0
  100. package/src/admin-shell/scheduled-publications/list.tsx +16 -6
  101. package/src/admin-shell/singletons/history.test.tsx +46 -1
  102. package/src/admin-shell/singletons/history.tsx +10 -2
  103. package/src/admin-shell/singletons/view.test.tsx +86 -18
  104. package/src/admin-shell/singletons/view.tsx +145 -45
  105. package/src/integrations/byline-field-services.ts +2 -3
  106. package/src/routes/create-collection-edit-route.tsx +1 -0
  107. package/src/routes/create-collection-list-route.tsx +13 -2
  108. package/src/routes/create-singleton-route.test.node.ts +10 -2
  109. package/src/routes/create-singleton-route.tsx +6 -4
  110. package/src/server-fns/collections/copy-to-locale.ts +48 -29
  111. package/src/server-fns/collections/create.ts +56 -44
  112. package/src/server-fns/collections/delete-locale.ts +48 -25
  113. package/src/server-fns/collections/delete-outcome.test.node.ts +16 -0
  114. package/src/server-fns/collections/delete-outcome.ts +27 -16
  115. package/src/server-fns/collections/delete.ts +44 -38
  116. package/src/server-fns/collections/duplicate.ts +45 -25
  117. package/src/server-fns/collections/get.ts +8 -4
  118. package/src/server-fns/collections/index.ts +1 -0
  119. package/src/server-fns/collections/list.ts +8 -3
  120. package/src/server-fns/collections/reorder.ts +32 -124
  121. package/src/server-fns/collections/restore-version.ts +51 -36
  122. package/src/server-fns/collections/revision-input.test.node.ts +108 -0
  123. package/src/server-fns/collections/save-outcome.test.node.ts +80 -0
  124. package/src/server-fns/collections/save-outcome.ts +57 -0
  125. package/src/server-fns/collections/scheduled-publication.ts +138 -111
  126. package/src/server-fns/collections/status.ts +86 -64
  127. package/src/server-fns/collections/tree-mutation.test.node.ts +87 -29
  128. package/src/server-fns/collections/tree-mutation.ts +28 -11
  129. package/src/server-fns/collections/tree.ts +150 -122
  130. package/src/server-fns/collections/update.ts +110 -71
  131. package/src/server-fns/collections/upload-singleton.test.node.ts +3 -7
  132. package/src/server-fns/collections/upload.ts +81 -77
  133. package/src/server-fns/document-mutation-errors.ts +67 -0
  134. package/src/server-fns/document-mutation-transport.test.node.ts +211 -0
  135. package/src/server-fns/editable-revision.test.node.ts +23 -0
  136. package/src/server-fns/editable-revision.ts +18 -0
  137. package/src/server-fns/singleton-document-read.ts +7 -3
  138. package/src/server-fns/singletons/cancel-scheduled-publish.ts +10 -3
  139. package/src/server-fns/singletons/change-status.ts +10 -3
  140. package/src/server-fns/singletons/confirm-scheduled-publish.ts +12 -6
  141. package/src/server-fns/singletons/copy-to-locale.ts +20 -8
  142. package/src/server-fns/singletons/get.ts +3 -3
  143. package/src/server-fns/singletons/restore-version.ts +17 -3
  144. package/src/server-fns/singletons/schedule-publish.ts +18 -7
  145. package/src/server-fns/singletons/singletons.test.node.ts +96 -24
  146. package/src/server-fns/singletons/unpublish.ts +10 -3
  147. package/src/server-fns/singletons/update.ts +21 -9
  148. 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
@@ -14,6 +15,10 @@ import type { CopyToLocaleResult, DocumentLifecycleContext } from '@byline/core/
14
15
  import { copyToLocale } from '@byline/core/services'
15
16
 
16
17
  import { ensureCollection } from '../../integrations/api-utils.js'
18
+ import {
19
+ type CollectionDocumentCommittedHookFailureResponse,
20
+ toCommittedDocumentHookFailureResponse,
21
+ } from './save-outcome.js'
17
22
 
18
23
  // ---------------------------------------------------------------------------
19
24
  // Copy document content from one locale into another
@@ -31,6 +36,7 @@ import { ensureCollection } from '../../integrations/api-utils.js'
31
36
  export const copyDocumentToLocale = createServerFn({ method: 'POST' })
32
37
  .validator(
33
38
  (input: {
39
+ expectedRevision: number
34
40
  collection: string
35
41
  id: string
36
42
  sourceLocale: string
@@ -38,34 +44,47 @@ export const copyDocumentToLocale = createServerFn({ method: 'POST' })
38
44
  overwrite: boolean
39
45
  }) => input
40
46
  )
41
- .handler(async ({ data: input }): Promise<CopyToLocaleResult> => {
42
- const { collection: path, id, sourceLocale, targetLocale, overwrite } = input
43
- const logger = getLogger()
44
- const config = await ensureCollection(path)
45
- if (!config) {
46
- throw ERR_NOT_FOUND({
47
- message: 'Collection not found',
48
- details: { collectionPath: path },
49
- }).log(logger)
50
- }
47
+ .handler(
48
+ withDocumentMutationErrors(
49
+ async ({
50
+ data: input,
51
+ }): Promise<CopyToLocaleResult | CollectionDocumentCommittedHookFailureResponse> => {
52
+ const { collection: path, id, sourceLocale, targetLocale, overwrite } = input
53
+ const logger = getLogger()
54
+ const config = await ensureCollection(path)
55
+ if (!config) {
56
+ throw ERR_NOT_FOUND({
57
+ message: 'Collection not found',
58
+ details: { collectionPath: path },
59
+ }).log(logger)
60
+ }
51
61
 
52
- const serverConfig = getServerConfig()
53
- const ctx: DocumentLifecycleContext = {
54
- db: serverConfig.db,
55
- definition: config.definition,
56
- collectionId: config.collection.id,
57
- collectionVersion: config.collection.version,
58
- collectionPath: path,
59
- logger,
60
- defaultLocale: serverConfig.i18n.content.defaultLocale,
61
- slugifier: serverConfig.slugifier,
62
- requestContext: await getAdminRequestContext(),
63
- }
62
+ const serverConfig = getServerConfig()
63
+ const ctx: DocumentLifecycleContext = {
64
+ db: serverConfig.db,
65
+ definition: config.definition,
66
+ collectionId: config.collection.id,
67
+ collectionVersion: config.collection.version,
68
+ collectionPath: path,
69
+ logger,
70
+ defaultLocale: serverConfig.i18n.content.defaultLocale,
71
+ slugifier: serverConfig.slugifier,
72
+ requestContext: await getAdminRequestContext(),
73
+ }
64
74
 
65
- return copyToLocale(ctx, {
66
- documentId: id,
67
- sourceLocale,
68
- targetLocale,
69
- overwrite,
70
- })
71
- })
75
+ try {
76
+ return await copyToLocale(ctx, {
77
+ documentId: id,
78
+ expectedRevision: input.expectedRevision,
79
+ sourceLocale,
80
+ targetLocale,
81
+ overwrite,
82
+ })
83
+ } catch (error) {
84
+ const committedFailure = toCommittedDocumentHookFailureResponse(error)
85
+ if (committedFailure != null) return committedFailure
86
+ throw error
87
+ }
88
+ }
89
+ )
90
+ )
@@ -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,11 @@ import { createServerFn } from '@tanstack/react-start'
10
11
 
11
12
  import { getAdminRequestContext } from '@byline/client/server'
12
13
  import { ERR_NOT_FOUND, getLogger, getServerConfig } from '@byline/core'
13
- import type { DocumentLifecycleContext } from '@byline/core/services'
14
+ import type { CreateDocumentResult, DocumentLifecycleContext } from '@byline/core/services'
14
15
  import { createDocument } from '@byline/core/services'
15
16
 
16
17
  import { ensureCollection } from '../../integrations/api-utils.js'
18
+ import { toCommittedDocumentHookFailureResponse } from './save-outcome.js'
17
19
 
18
20
  // ---------------------------------------------------------------------------
19
21
  // Create document
@@ -29,49 +31,59 @@ export const createCollectionDocument = createServerFn({ method: 'POST' })
29
31
  availableLocales?: string[]
30
32
  }) => input
31
33
  )
32
- .handler(async ({ data: input }) => {
33
- const {
34
- collection: path,
35
- data: documentData,
36
- locale,
37
- path: explicitPath,
38
- availableLocales,
39
- } = input
40
- const logger = getLogger()
41
- const config = await ensureCollection(path)
42
- if (!config) {
43
- throw ERR_NOT_FOUND({
44
- message: 'Collection not found',
45
- details: { collectionPath: path },
46
- }).log(logger)
47
- }
34
+ .handler(
35
+ withDocumentMutationErrors(async ({ data: input }) => {
36
+ const {
37
+ collection: path,
38
+ data: documentData,
39
+ locale,
40
+ path: explicitPath,
41
+ availableLocales,
42
+ } = input
43
+ const logger = getLogger()
44
+ const config = await ensureCollection(path)
45
+ if (!config) {
46
+ throw ERR_NOT_FOUND({
47
+ message: 'Collection not found',
48
+ details: { collectionPath: path },
49
+ }).log(logger)
50
+ }
48
51
 
49
- const serverConfig = getServerConfig()
50
- const ctx: DocumentLifecycleContext = {
51
- db: serverConfig.db,
52
- definition: config.definition,
53
- collectionId: config.collection.id,
54
- collectionVersion: config.collection.version,
55
- collectionPath: path,
56
- logger,
57
- defaultLocale: serverConfig.i18n.content.defaultLocale,
58
- slugifier: serverConfig.slugifier,
59
- requestContext: await getAdminRequestContext(),
60
- }
52
+ const serverConfig = getServerConfig()
53
+ const ctx: DocumentLifecycleContext = {
54
+ db: serverConfig.db,
55
+ definition: config.definition,
56
+ collectionId: config.collection.id,
57
+ collectionVersion: config.collection.version,
58
+ collectionPath: path,
59
+ logger,
60
+ defaultLocale: serverConfig.i18n.content.defaultLocale,
61
+ slugifier: serverConfig.slugifier,
62
+ requestContext: await getAdminRequestContext(),
63
+ }
61
64
 
62
- const result = await createDocument(ctx, {
63
- data: structuredClone(documentData),
64
- status: documentData.status,
65
- locale: locale ?? serverConfig.i18n.content.defaultLocale,
66
- path: explicitPath,
67
- availableLocales,
68
- })
65
+ let result: CreateDocumentResult
66
+ try {
67
+ result = await createDocument(ctx, {
68
+ data: structuredClone(documentData),
69
+ status: documentData.status,
70
+ locale: locale ?? serverConfig.i18n.content.defaultLocale,
71
+ path: explicitPath,
72
+ availableLocales,
73
+ })
74
+ } catch (error) {
75
+ const committedFailure = toCommittedDocumentHookFailureResponse(error)
76
+ if (committedFailure != null) return committedFailure
77
+ throw error
78
+ }
69
79
 
70
- // The new document's id is returned so the create view can navigate
71
- // straight to the edit view (create → edit).
72
- return {
73
- status: 'ok' as const,
74
- documentId: result.documentId,
75
- documentVersionId: result.documentVersionId,
76
- }
77
- })
80
+ // The new document's id is returned so the create view can navigate
81
+ // straight to the edit view (create → edit).
82
+ return {
83
+ status: 'ok' as const,
84
+ documentId: result.documentId,
85
+ documentVersionId: result.documentVersionId,
86
+ revision: result.revision,
87
+ }
88
+ })
89
+ )
@@ -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
@@ -14,6 +15,10 @@ import type { DeleteLocaleResult, DocumentLifecycleContext } from '@byline/core/
14
15
  import { deleteLocale } from '@byline/core/services'
15
16
 
16
17
  import { ensureCollection } from '../../integrations/api-utils.js'
18
+ import {
19
+ type CollectionDocumentCommittedHookFailureResponse,
20
+ toCommittedDocumentHookFailureResponse,
21
+ } from './save-outcome.js'
17
22
 
18
23
  // ---------------------------------------------------------------------------
19
24
  // Delete one content locale's data from a document
@@ -30,30 +35,48 @@ import { ensureCollection } from '../../integrations/api-utils.js'
30
35
  * transport layer.
31
36
  */
32
37
  export const deleteDocumentLocale = createServerFn({ method: 'POST' })
33
- .validator((input: { collection: string; id: string; locale: string }) => input)
34
- .handler(async ({ data: input }): Promise<DeleteLocaleResult> => {
35
- const { collection: path, id, locale } = input
36
- const logger = getLogger()
37
- const config = await ensureCollection(path)
38
- if (!config) {
39
- throw ERR_NOT_FOUND({
40
- message: 'Collection not found',
41
- details: { collectionPath: path },
42
- }).log(logger)
43
- }
38
+ .validator(
39
+ (input: { expectedRevision: number; collection: string; id: string; locale: string }) => input
40
+ )
41
+ .handler(
42
+ withDocumentMutationErrors(
43
+ async ({
44
+ data: input,
45
+ }): Promise<DeleteLocaleResult | CollectionDocumentCommittedHookFailureResponse> => {
46
+ const { collection: path, id, locale } = input
47
+ const logger = getLogger()
48
+ const config = await ensureCollection(path)
49
+ if (!config) {
50
+ throw ERR_NOT_FOUND({
51
+ message: 'Collection not found',
52
+ details: { collectionPath: path },
53
+ }).log(logger)
54
+ }
44
55
 
45
- const serverConfig = getServerConfig()
46
- const ctx: DocumentLifecycleContext = {
47
- db: serverConfig.db,
48
- definition: config.definition,
49
- collectionId: config.collection.id,
50
- collectionVersion: config.collection.version,
51
- collectionPath: path,
52
- logger,
53
- defaultLocale: serverConfig.i18n.content.defaultLocale,
54
- slugifier: serverConfig.slugifier,
55
- requestContext: await getAdminRequestContext(),
56
- }
56
+ const serverConfig = getServerConfig()
57
+ const ctx: DocumentLifecycleContext = {
58
+ db: serverConfig.db,
59
+ definition: config.definition,
60
+ collectionId: config.collection.id,
61
+ collectionVersion: config.collection.version,
62
+ collectionPath: path,
63
+ logger,
64
+ defaultLocale: serverConfig.i18n.content.defaultLocale,
65
+ slugifier: serverConfig.slugifier,
66
+ requestContext: await getAdminRequestContext(),
67
+ }
57
68
 
58
- return deleteLocale(ctx, { documentId: id, locale })
59
- })
69
+ try {
70
+ return await deleteLocale(ctx, {
71
+ documentId: id,
72
+ expectedRevision: input.expectedRevision,
73
+ locale,
74
+ })
75
+ } catch (error) {
76
+ const committedFailure = toCommittedDocumentHookFailureResponse(error)
77
+ if (committedFailure != null) return committedFailure
78
+ throw error
79
+ }
80
+ }
81
+ )
82
+ )
@@ -16,6 +16,10 @@ describe('delete outcome transport and admin interpretation', () => {
16
16
  // Simulate malformed runtime data from an older or compromised source. The
17
17
  // core type rejects this shape, but the transport still sanitizes it.
18
18
  const unsafeResult = {
19
+ documentId: 'doc-1',
20
+ revision: 2,
21
+ affectedDocuments: [{ documentId: 'child', revision: 3 }],
22
+ scheduledPublicationsNeedReconfirmation: true,
19
23
  deletedVersionCount: 2,
20
24
  outcome: 'committed-with-side-effect-failures',
21
25
  sideEffectFailures: [
@@ -36,6 +40,10 @@ describe('delete outcome transport and admin interpretation', () => {
36
40
 
37
41
  expect(response).toEqual({
38
42
  status: 'ok',
43
+ documentId: 'doc-1',
44
+ revision: 2,
45
+ affectedDocuments: [{ documentId: 'child', revision: 3 }],
46
+ scheduledPublicationsNeedReconfirmation: true,
39
47
  deletedVersionCount: 2,
40
48
  outcome: 'committed-with-side-effect-failures',
41
49
  sideEffectFailures: [
@@ -53,6 +61,10 @@ describe('delete outcome transport and admin interpretation', () => {
53
61
 
54
62
  it('preserves the committed success discriminant and empty failures', () => {
55
63
  const response = toDeleteDocumentResponse({
64
+ documentId: 'doc-1',
65
+ revision: 2,
66
+ affectedDocuments: [{ documentId: 'child', revision: 3 }],
67
+ scheduledPublicationsNeedReconfirmation: true,
56
68
  deletedVersionCount: 2,
57
69
  outcome: 'committed',
58
70
  sideEffectFailures: [],
@@ -60,6 +72,10 @@ describe('delete outcome transport and admin interpretation', () => {
60
72
 
61
73
  expect(response).toEqual({
62
74
  status: 'ok',
75
+ documentId: 'doc-1',
76
+ revision: 2,
77
+ affectedDocuments: [{ documentId: 'child', revision: 3 }],
78
+ scheduledPublicationsNeedReconfirmation: true,
63
79
  deletedVersionCount: 2,
64
80
  outcome: 'committed',
65
81
  sideEffectFailures: [],
@@ -10,6 +10,7 @@ import type {
10
10
  DeleteDocumentResult,
11
11
  DeleteDocumentSideEffectCode,
12
12
  DeleteDocumentSideEffectPhase,
13
+ StructuralMutationReceipt,
13
14
  } from '@byline/core'
14
15
  import { ErrorCodes } from '@byline/core'
15
16
 
@@ -21,22 +22,28 @@ export interface DeleteDocumentPublicSideEffectFailure {
21
22
  code: DeleteDocumentPublicSideEffectCode
22
23
  }
23
24
 
24
- export type DeleteDocumentResponse =
25
- | {
26
- status: 'ok'
27
- deletedVersionCount: number
28
- outcome: 'committed'
29
- sideEffectFailures: []
30
- }
31
- | {
32
- status: 'ok'
33
- deletedVersionCount: number
34
- outcome: 'committed-with-side-effect-failures'
35
- sideEffectFailures: [
36
- DeleteDocumentPublicSideEffectFailure,
37
- ...DeleteDocumentPublicSideEffectFailure[],
38
- ]
39
- }
25
+ export type DeleteDocumentResponse = StructuralMutationReceipt &
26
+ (
27
+ | {
28
+ status: 'ok'
29
+ documentId: string
30
+ revision: number
31
+ deletedVersionCount: number
32
+ outcome: 'committed'
33
+ sideEffectFailures: []
34
+ }
35
+ | {
36
+ status: 'ok'
37
+ documentId: string
38
+ revision: number
39
+ deletedVersionCount: number
40
+ outcome: 'committed-with-side-effect-failures'
41
+ sideEffectFailures: [
42
+ DeleteDocumentPublicSideEffectFailure,
43
+ ...DeleteDocumentPublicSideEffectFailure[],
44
+ ]
45
+ }
46
+ )
40
47
 
41
48
  function sanitizePhase(phase: DeleteDocumentSideEffectPhase): DeleteDocumentPublicSideEffectPhase {
42
49
  if (phase === 'afterTreeChange' || phase === 'afterDelete') {
@@ -67,6 +74,10 @@ export function toDeleteDocumentResponse(result: DeleteDocumentResult): DeleteDo
67
74
  const [firstFailure, ...remainingFailures] = result.sideEffectFailures
68
75
  return {
69
76
  status: 'ok',
77
+ documentId: result.documentId,
78
+ revision: result.revision,
79
+ affectedDocuments: result.affectedDocuments,
80
+ scheduledPublicationsNeedReconfirmation: result.scheduledPublicationsNeedReconfirmation,
70
81
  deletedVersionCount: result.deletedVersionCount,
71
82
  outcome: result.outcome,
72
83
  sideEffectFailures: [sanitizeFailure(firstFailure), ...remainingFailures.map(sanitizeFailure)],
@@ -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
@@ -21,43 +22,48 @@ import { toDeleteDocumentResponse } from './delete-outcome.js'
21
22
  // ---------------------------------------------------------------------------
22
23
 
23
24
  export const deleteDocument = createServerFn({ method: 'POST' })
24
- .validator((input: { collection: string; id: string }) => input)
25
- .handler(async ({ data: input }) => {
26
- const { collection: path, id } = input
27
- const logger = getLogger()
28
- const config = await ensureCollection(path)
29
- if (!config) {
30
- throw ERR_NOT_FOUND({
31
- message: 'Collection not found',
32
- details: { collectionPath: path },
33
- }).log(logger)
34
- }
25
+ .validator((input: { expectedRevision: number; collection: string; id: string }) => input)
26
+ .handler(
27
+ withDocumentMutationErrors(async ({ data: input }) => {
28
+ const { collection: path, id } = input
29
+ const logger = getLogger()
30
+ const config = await ensureCollection(path)
31
+ if (!config) {
32
+ throw ERR_NOT_FOUND({
33
+ message: 'Collection not found',
34
+ details: { collectionPath: path },
35
+ }).log(logger)
36
+ }
35
37
 
36
- const serverConfig = getServerConfig()
37
- // Resolve the storage provider so the lifecycle service can clean up
38
- // uploaded files and variants on deletion. With per-field upload
39
- // config, a collection may have one (or more) image/file fields with
40
- // their own storage. The delete path needs *a* provider; we pick the
41
- // first upload-capable field's, falling back to the site-wide
42
- // default. Multi-storage collections are out of scope today —
43
- // deletion routes everything through one provider, which is fine
44
- // when all upload fields target the same backend (the common case).
45
- const firstUploadField = getUploadFields(config.definition)[0]
46
- const storage = firstUploadField?.upload?.storage ?? serverConfig.storage
47
- const db = serverConfig.db
48
- const ctx: DocumentLifecycleContext = {
49
- db,
50
- definition: config.definition,
51
- collectionId: config.collection.id,
52
- collectionVersion: config.collection.version,
53
- collectionPath: path,
54
- ...(storage ? { storage } : {}),
55
- logger,
56
- defaultLocale: serverConfig.i18n.content.defaultLocale,
57
- slugifier: serverConfig.slugifier,
58
- requestContext: await getAdminRequestContext(),
59
- }
38
+ const serverConfig = getServerConfig()
39
+ // Resolve the storage provider so the lifecycle service can clean up
40
+ // uploaded files and variants on deletion. With per-field upload
41
+ // config, a collection may have one (or more) image/file fields with
42
+ // their own storage. The delete path needs *a* provider; we pick the
43
+ // first upload-capable field's, falling back to the site-wide
44
+ // default. Multi-storage collections are out of scope today —
45
+ // deletion routes everything through one provider, which is fine
46
+ // when all upload fields target the same backend (the common case).
47
+ const firstUploadField = getUploadFields(config.definition)[0]
48
+ const storage = firstUploadField?.upload?.storage ?? serverConfig.storage
49
+ const db = serverConfig.db
50
+ const ctx: DocumentLifecycleContext = {
51
+ db,
52
+ definition: config.definition,
53
+ collectionId: config.collection.id,
54
+ collectionVersion: config.collection.version,
55
+ collectionPath: path,
56
+ ...(storage ? { storage } : {}),
57
+ logger,
58
+ defaultLocale: serverConfig.i18n.content.defaultLocale,
59
+ slugifier: serverConfig.slugifier,
60
+ requestContext: await getAdminRequestContext(),
61
+ }
60
62
 
61
- const result = await deleteDocumentService(ctx, { documentId: id })
62
- return toDeleteDocumentResponse(result)
63
- })
63
+ const result = await deleteDocumentService(ctx, {
64
+ documentId: id,
65
+ expectedRevision: input.expectedRevision,
66
+ })
67
+ return toDeleteDocumentResponse(result)
68
+ })
69
+ )
@@ -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
@@ -14,6 +15,10 @@ import type { DocumentLifecycleContext, DuplicateDocumentResult } from '@byline/
14
15
  import { duplicateDocument } from '@byline/core/services'
15
16
 
16
17
  import { ensureCollection } from '../../integrations/api-utils.js'
18
+ import {
19
+ type CollectionDocumentCommittedHookFailureResponse,
20
+ toCommittedDocumentHookFailureResponse,
21
+ } from './save-outcome.js'
17
22
 
18
23
  // ---------------------------------------------------------------------------
19
24
  // Duplicate document
@@ -31,30 +36,45 @@ import { ensureCollection } from '../../integrations/api-utils.js'
31
36
  * TanStack Start's transport layer for the client to branch on.
32
37
  */
33
38
  export const duplicateCollectionDocument = createServerFn({ method: 'POST' })
34
- .validator((input: { collection: string; id: string }) => input)
35
- .handler(async ({ data: input }): Promise<DuplicateDocumentResult> => {
36
- const { collection: path, id: sourceDocumentId } = input
37
- const logger = getLogger()
38
- const config = await ensureCollection(path)
39
- if (!config) {
40
- throw ERR_NOT_FOUND({
41
- message: 'Collection not found',
42
- details: { collectionPath: path },
43
- }).log(logger)
44
- }
39
+ .validator((input: { expectedRevision: number; collection: string; id: string }) => input)
40
+ .handler(
41
+ withDocumentMutationErrors(
42
+ async ({
43
+ data: input,
44
+ }): Promise<DuplicateDocumentResult | CollectionDocumentCommittedHookFailureResponse> => {
45
+ const { collection: path, id: sourceDocumentId } = input
46
+ const logger = getLogger()
47
+ const config = await ensureCollection(path)
48
+ if (!config) {
49
+ throw ERR_NOT_FOUND({
50
+ message: 'Collection not found',
51
+ details: { collectionPath: path },
52
+ }).log(logger)
53
+ }
45
54
 
46
- const serverConfig = getServerConfig()
47
- const ctx: DocumentLifecycleContext = {
48
- db: serverConfig.db,
49
- definition: config.definition,
50
- collectionId: config.collection.id,
51
- collectionVersion: config.collection.version,
52
- collectionPath: path,
53
- logger,
54
- defaultLocale: serverConfig.i18n.content.defaultLocale,
55
- slugifier: serverConfig.slugifier,
56
- requestContext: await getAdminRequestContext(),
57
- }
55
+ const serverConfig = getServerConfig()
56
+ const ctx: DocumentLifecycleContext = {
57
+ db: serverConfig.db,
58
+ definition: config.definition,
59
+ collectionId: config.collection.id,
60
+ collectionVersion: config.collection.version,
61
+ collectionPath: path,
62
+ logger,
63
+ defaultLocale: serverConfig.i18n.content.defaultLocale,
64
+ slugifier: serverConfig.slugifier,
65
+ requestContext: await getAdminRequestContext(),
66
+ }
58
67
 
59
- return duplicateDocument(ctx, { sourceDocumentId })
60
- })
68
+ try {
69
+ return await duplicateDocument(ctx, {
70
+ sourceDocumentId,
71
+ expectedRevision: input.expectedRevision,
72
+ })
73
+ } catch (error) {
74
+ const committedFailure = toCommittedDocumentHookFailureResponse(error)
75
+ if (committedFailure != null) return committedFailure
76
+ throw error
77
+ }
78
+ }
79
+ )
80
+ )
@@ -1,3 +1,4 @@
1
+ import { withEditableRevision } from '../editable-revision.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
@@ -51,13 +52,16 @@ const getDocumentFn = createServerFn({ method: 'GET' })
51
52
  const client = getAdminBylineClient()
52
53
  const handle = client.collection(path)
53
54
 
54
- const { options: readOptions, populatedTree } = resolveAdminDocumentRead(config.definition, {
55
+ const {
56
+ options: { status: _status, ...readOptions },
57
+ populatedTree,
58
+ } = resolveAdminDocumentRead(config.definition, {
55
59
  locale,
56
60
  depth,
57
61
  populateRelations,
58
62
  })
59
63
 
60
- const document = await handle.findById(id, {
64
+ const document = await handle.findByIdForEdit(id, {
61
65
  ...readOptions,
62
66
  })
63
67
 
@@ -141,13 +145,13 @@ const getDocumentFn = createServerFn({ method: 'GET' })
141
145
  actor?.hasAbility(documentAbilityKey(config.definition, 'changeStatus')) === true &&
142
146
  actor.hasAbility(documentAbilityKey(config.definition, 'publish'))
143
147
  if (canSchedulePublication) {
144
- scheduledPublish = await handle.getScheduledPublish(id)
148
+ scheduledPublish = document.scheduledPublication
145
149
  }
146
150
  }
147
151
  const serializedSchedule = scheduledPublish == null ? null : serialise(scheduledPublish)
148
152
 
149
153
  return {
150
- ...(parsed as Record<string, any>),
154
+ ...withEditableRevision(parsed, document),
151
155
  _publishedVersion: publishedVersion,
152
156
  ...(restoreWarnings && restoreWarnings.length > 0
153
157
  ? { _restoreWarnings: restoreWarnings }
@@ -18,6 +18,7 @@ export * from './list'
18
18
  export * from './reindex'
19
19
  export * from './reorder'
20
20
  export * from './restore-version'
21
+ export * from './save-outcome'
21
22
  export * from './scheduled-publication'
22
23
  export * from './set-list-view-preference'
23
24
  export * from './stats'