@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
@@ -0,0 +1,50 @@
1
+ import { BylineError, ErrorCodes, getDocumentRevisionValidationDetails, getDocumentStaleDetails, getLockConflictDetails } from "@byline/core";
2
+ import { getDocumentHookCommittedDetails } from "@byline/core/services";
3
+ import { committedTreeReceipt } from "./structural-receipt.js";
4
+ function documentMutationError(error) {
5
+ const stale = getDocumentStaleDetails(error);
6
+ if (stale) return {
7
+ code: ErrorCodes.DOCUMENT_STALE,
8
+ message: 'This document has changed. Reload before making changes.',
9
+ details: stale
10
+ };
11
+ const validation = getDocumentRevisionValidationDetails(error);
12
+ if (validation) return {
13
+ code: ErrorCodes.VALIDATION,
14
+ message: 'Reload this document before making changes.',
15
+ details: validation
16
+ };
17
+ const lock = getLockConflictDetails(error);
18
+ if (lock) return {
19
+ code: ErrorCodes.LOCK_CONFLICT,
20
+ message: 'This change could not be saved because another operation was using the document. Reload before trying again.',
21
+ details: lock
22
+ };
23
+ const tree = committedTreeReceipt(error);
24
+ if (tree) return {
25
+ code: ErrorCodes.TREE_HOOK_COMMITTED,
26
+ message: 'The structure was saved, but a follow-up action failed.',
27
+ details: tree
28
+ };
29
+ const committed = getDocumentHookCommittedDetails(error);
30
+ if (committed) return {
31
+ code: ErrorCodes.DOCUMENT_HOOK_COMMITTED,
32
+ message: 'The document was saved, but a follow-up action failed.',
33
+ details: committed
34
+ };
35
+ if (error instanceof BylineError && error.code === ErrorCodes.NOT_FOUND) return {
36
+ code: ErrorCodes.NOT_FOUND,
37
+ message: 'This document is no longer available.'
38
+ };
39
+ return error;
40
+ }
41
+ function withDocumentMutationErrors(handler) {
42
+ return async (input)=>{
43
+ try {
44
+ return await handler(input);
45
+ } catch (error) {
46
+ throw documentMutationError(error);
47
+ }
48
+ };
49
+ }
50
+ export { documentMutationError, withDocumentMutationErrors };
@@ -0,0 +1,9 @@
1
+ /** Restore metadata stripped by schema parsing only onto the exact source version.
2
+ * Never attach by array position: filtering/reordering must not transfer tokens. */
3
+ export declare function withEditableRevision<T extends object>(document: T, observation: {
4
+ id: string;
5
+ versionId: string;
6
+ revision: number;
7
+ } | undefined): T & {
8
+ revision: number;
9
+ };
@@ -0,0 +1,11 @@
1
+ import { ERR_DATABASE, isDocumentRevision } from "@byline/core";
2
+ function withEditableRevision(document, observation) {
3
+ if (!observation || !isDocumentRevision(observation.revision) || Reflect.get(document, 'id') !== observation.id || Reflect.get(document, 'versionId') !== observation.versionId) throw ERR_DATABASE({
4
+ message: 'Editable response no longer matches its source observation'
5
+ });
6
+ return {
7
+ ...document,
8
+ revision: observation.revision
9
+ };
10
+ }
11
+ export { withEditableRevision };
@@ -17,4 +17,4 @@ export interface SingletonDocumentReadInput {
17
17
  * is deliberately composed one layer up, where host database and actor
18
18
  * context are available without widening the singleton transport contract.
19
19
  */
20
- export declare function readSingletonDocument(data: SingletonDocumentReadInput): Promise<import("@byline/client").SingletonDocument<Record<string, any>> | null>;
20
+ export declare function readSingletonDocument(data: SingletonDocumentReadInput): Promise<Omit<import("@byline/client").EditableDocument<Record<string, any>>, "path"> | null>;
@@ -1,10 +1,14 @@
1
1
  import { getAdminBylineClient } from "@byline/client/server";
2
- import { getCollectionDefinition } from "@byline/core";
2
+ import { ERR_NOT_FOUND, getCollectionDefinition } from "@byline/core";
3
3
  import { resolveAdminDocumentRead } from "./admin-document-presentation.js";
4
4
  import { serialise } from "./serialise.js";
5
5
  async function readSingletonDocument(data) {
6
6
  const handle = getAdminBylineClient().singleton(data.singleton);
7
- const { options } = resolveAdminDocumentRead(getCollectionDefinition(data.singleton), data);
8
- return serialise(await handle.get(options));
7
+ const { options: { status: _status, ...options } } = resolveAdminDocumentRead(getCollectionDefinition(data.singleton), data);
8
+ const snapshot = await handle.getForEdit(options);
9
+ if (null == snapshot) throw ERR_NOT_FOUND({
10
+ message: 'Singleton document is unavailable'
11
+ });
12
+ return 'empty' === snapshot.state ? null : serialise(snapshot.document);
9
13
  }
10
14
  export { readSingletonDocument };
@@ -1,12 +1,10 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
1
  export declare const cancelSingletonScheduledPublish: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
2
+ expectedRevision: number;
9
3
  singleton: string;
10
4
  }) => {
5
+ expectedRevision: number;
11
6
  singleton: string;
12
- }, Promise<import("@byline/client").DocumentPublishScheduleInfo | null>>;
7
+ }, Promise<{
8
+ schedule: import("@byline/client").DocumentPublishScheduleInfo | null;
9
+ revision: number;
10
+ }>>;
@@ -1,7 +1,10 @@
1
+ import { withDocumentMutationErrors } from "../document-mutation-errors.js";
1
2
  import { createServerFn } from "@tanstack/react-start";
2
3
  import { getAdminBylineClient } from "@byline/client/server";
3
4
  import { serialise } from "../serialise.js";
4
5
  const cancelSingletonScheduledPublish = createServerFn({
5
6
  method: 'POST'
6
- }).validator((input)=>input).handler(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).cancelScheduledPublish()));
7
+ }).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).cancelScheduledPublish({
8
+ expectedRevision: data.expectedRevision
9
+ }))));
7
10
  export { cancelSingletonScheduledPublish };
@@ -1,14 +1,9 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
1
  export declare const changeSingletonStatus: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
2
+ expectedRevision: number;
9
3
  singleton: string;
10
4
  status: string;
11
5
  }) => {
6
+ expectedRevision: number;
12
7
  singleton: string;
13
8
  status: string;
14
9
  }, Promise<import("@byline/core").ChangeStatusResult>>;
@@ -1,7 +1,10 @@
1
+ import { withDocumentMutationErrors } from "../document-mutation-errors.js";
1
2
  import { createServerFn } from "@tanstack/react-start";
2
3
  import { getAdminBylineClient } from "@byline/client/server";
3
4
  import { serialise } from "../serialise.js";
4
5
  const changeSingletonStatus = createServerFn({
5
6
  method: 'POST'
6
- }).validator((input)=>input).handler(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).changeStatus(data.status)));
7
+ }).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).changeStatus(data.status, {
8
+ expectedRevision: data.expectedRevision
9
+ }))));
7
10
  export { changeSingletonStatus };
@@ -1,14 +1,11 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
1
  export declare const confirmSingletonScheduledPublish: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
2
+ expectedRevision: number;
9
3
  singleton: string;
10
4
  expectedVersionId: string;
11
5
  }) => {
6
+ expectedRevision: number;
12
7
  singleton: string;
13
8
  expectedVersionId: string;
14
- }, Promise<import("@byline/client").DocumentPublishScheduleInfo>>;
9
+ }, Promise<import("@byline/client").DocumentPublishScheduleInfo & {
10
+ revision: number;
11
+ }>>;
@@ -1,9 +1,11 @@
1
+ import { withDocumentMutationErrors } from "../document-mutation-errors.js";
1
2
  import { createServerFn } from "@tanstack/react-start";
2
3
  import { getAdminBylineClient } from "@byline/client/server";
3
4
  import { serialise } from "../serialise.js";
4
5
  const confirmSingletonScheduledPublish = createServerFn({
5
6
  method: 'POST'
6
- }).validator((input)=>input).handler(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).confirmScheduledPublish({
7
+ }).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).confirmScheduledPublish({
8
+ expectedRevision: data.expectedRevision,
7
9
  expectedVersionId: data.expectedVersionId
8
- })));
10
+ }))));
9
11
  export { confirmSingletonScheduledPublish };
@@ -1,16 +1,11 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
1
  export declare const copySingletonToLocale: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
2
+ expectedRevision: number;
9
3
  singleton: string;
10
4
  sourceLocale: string;
11
5
  targetLocale: string;
12
6
  overwrite?: boolean;
13
7
  }) => {
8
+ expectedRevision: number;
14
9
  singleton: string;
15
10
  sourceLocale: string;
16
11
  targetLocale: string;
@@ -1,11 +1,22 @@
1
+ import { withDocumentMutationErrors } from "../document-mutation-errors.js";
1
2
  import { createServerFn } from "@tanstack/react-start";
2
3
  import { getAdminBylineClient } from "@byline/client/server";
4
+ import { toCommittedDocumentHookFailureResponse } from "../collections/save-outcome.js";
3
5
  import { serialise } from "../serialise.js";
4
6
  const copySingletonToLocale = createServerFn({
5
7
  method: 'POST'
6
- }).validator((input)=>input).handler(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).copyToLocale({
7
- sourceLocale: data.sourceLocale,
8
- targetLocale: data.targetLocale,
9
- overwrite: data.overwrite
10
- })));
8
+ }).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>{
9
+ try {
10
+ return serialise(await getAdminBylineClient().singleton(data.singleton).copyToLocale({
11
+ expectedRevision: data.expectedRevision,
12
+ sourceLocale: data.sourceLocale,
13
+ targetLocale: data.targetLocale,
14
+ overwrite: data.overwrite
15
+ }));
16
+ } catch (error) {
17
+ const committedFailure = toCommittedDocumentHookFailureResponse(error);
18
+ if (null != committedFailure) return committedFailure;
19
+ throw error;
20
+ }
21
+ }));
11
22
  export { copySingletonToLocale };
@@ -17,8 +17,8 @@ export interface GetSingletonInput {
17
17
  * - this shared host layer owns lenient reads, raw missing-locale values,
18
18
  * relation-summary population, transport serialization, and preservation of
19
19
  * restore warnings and locale metadata;
20
- * - the route loader composes published-version metadata plus scheduled-
21
- * publication enablement, capability, and schedule aliases because those
22
- * require host-only database and actor context beyond the singleton handle.
20
+ * - the route loader composes the informational published-version badge, feature
21
+ * enablement, capability, and schedule aliases. Schedule state itself comes
22
+ * from the same editable snapshot as the source document.
23
23
  */
24
- export declare const getSingleton: import("@tanstack/react-start").RequiredFetcher<undefined, (input: GetSingletonInput) => GetSingletonInput, Promise<import("@byline/client").SingletonDocument<Record<string, any>> | null>>;
24
+ export declare const getSingleton: import("@tanstack/react-start").RequiredFetcher<undefined, (input: GetSingletonInput) => GetSingletonInput, Promise<Omit<import("@byline/client").EditableDocument<Record<string, any>>, "path"> | null>>;
@@ -1,14 +1,9 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
1
  export declare const restoreSingletonVersion: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
2
+ expectedRevision: number;
9
3
  singleton: string;
10
4
  versionId: string;
11
5
  }) => {
6
+ expectedRevision: number;
12
7
  singleton: string;
13
8
  versionId: string;
14
9
  }, Promise<import("@byline/core").SingletonSaveResult>>;
@@ -1,7 +1,19 @@
1
+ import { withDocumentMutationErrors } from "../document-mutation-errors.js";
1
2
  import { createServerFn } from "@tanstack/react-start";
2
3
  import { getAdminBylineClient } from "@byline/client/server";
4
+ import { toCommittedDocumentHookFailureResponse } from "../collections/save-outcome.js";
3
5
  import { serialise } from "../serialise.js";
4
6
  const restoreSingletonVersion = createServerFn({
5
7
  method: 'POST'
6
- }).validator((input)=>input).handler(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).restoreVersion(data.versionId)));
8
+ }).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>{
9
+ try {
10
+ return serialise(await getAdminBylineClient().singleton(data.singleton).restoreVersion(data.versionId, {
11
+ expectedRevision: data.expectedRevision
12
+ }));
13
+ } catch (error) {
14
+ const committedFailure = toCommittedDocumentHookFailureResponse(error);
15
+ if (null != committedFailure) return committedFailure;
16
+ throw error;
17
+ }
18
+ }));
7
19
  export { restoreSingletonVersion };
@@ -1,16 +1,13 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
1
  export declare const scheduleSingletonPublish: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
2
+ expectedRevision: number;
9
3
  singleton: string;
10
4
  publishAt: string;
11
5
  expectedVersionId: string;
12
6
  }) => {
7
+ expectedRevision: number;
13
8
  singleton: string;
14
9
  publishAt: string;
15
10
  expectedVersionId: string;
16
- }, Promise<import("@byline/client").DocumentPublishScheduleInfo>>;
11
+ }, Promise<import("@byline/client").DocumentPublishScheduleInfo & {
12
+ revision: number;
13
+ }>>;
@@ -1,10 +1,12 @@
1
+ import { withDocumentMutationErrors } from "../document-mutation-errors.js";
1
2
  import { createServerFn } from "@tanstack/react-start";
2
3
  import { getAdminBylineClient } from "@byline/client/server";
3
4
  import { serialise } from "../serialise.js";
4
5
  const scheduleSingletonPublish = createServerFn({
5
6
  method: 'POST'
6
- }).validator((input)=>input).handler(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).schedulePublish({
7
+ }).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).schedulePublish({
7
8
  publishAt: data.publishAt,
9
+ expectedRevision: data.expectedRevision,
8
10
  expectedVersionId: data.expectedVersionId
9
- })));
11
+ }))));
10
12
  export { scheduleSingletonPublish };
@@ -1,12 +1,7 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
1
  export declare const unpublishSingleton: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
2
+ expectedRevision: number;
9
3
  singleton: string;
10
4
  }) => {
5
+ expectedRevision: number;
11
6
  singleton: string;
12
7
  }, Promise<import("@byline/core").UnpublishResult>>;
@@ -1,7 +1,10 @@
1
+ import { withDocumentMutationErrors } from "../document-mutation-errors.js";
1
2
  import { createServerFn } from "@tanstack/react-start";
2
3
  import { getAdminBylineClient } from "@byline/client/server";
3
4
  import { serialise } from "../serialise.js";
4
5
  const unpublishSingleton = createServerFn({
5
6
  method: 'POST'
6
- }).validator((input)=>input).handler(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).unpublish()));
7
+ }).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).unpublish({
8
+ expectedRevision: data.expectedRevision
9
+ }))));
7
10
  export { unpublishSingleton };
@@ -1,14 +1,7 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
- export interface UpdateSingletonInput {
1
+ import type { SingletonSavePrecondition } from '@byline/core';
2
+ export type UpdateSingletonInput = SingletonSavePrecondition & {
9
3
  singleton: string;
10
4
  data: Record<string, any>;
11
5
  locale?: string;
12
- expectedVersionId?: string;
13
- }
14
- export declare const updateSingleton: import("@tanstack/react-start").RequiredFetcher<undefined, (input: UpdateSingletonInput) => UpdateSingletonInput, Promise<import("@byline/core").SingletonSaveResult>>;
6
+ };
7
+ export declare const updateSingleton: import("@tanstack/react-start").RequiredFetcher<undefined, (input: UpdateSingletonInput) => UpdateSingletonInput, Promise<import("@byline/core").UpdateSingletonResult>>;
@@ -1,14 +1,22 @@
1
+ import { withDocumentMutationErrors } from "../document-mutation-errors.js";
1
2
  import { createServerFn } from "@tanstack/react-start";
2
3
  import { getAdminBylineClient } from "@byline/client/server";
4
+ import { toCommittedDocumentHookFailureResponse } from "../collections/save-outcome.js";
3
5
  import { serialise } from "../serialise.js";
4
6
  const updateSingleton = createServerFn({
5
7
  method: 'POST'
6
- }).validator((input)=>input).handler(async ({ data })=>{
7
- const { singleton, data: fields, locale, expectedVersionId } = data;
8
- const result = await getAdminBylineClient().singleton(singleton).update(fields, {
9
- locale,
10
- expectedVersionId
11
- });
12
- return serialise(result);
13
- });
8
+ }).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>{
9
+ const { singleton, data: fields, locale, ...precondition } = data;
10
+ try {
11
+ const result = await getAdminBylineClient().singleton(singleton).update(fields, {
12
+ locale,
13
+ ...precondition
14
+ });
15
+ return serialise(result);
16
+ } catch (error) {
17
+ const committedFailure = toCommittedDocumentHookFailureResponse(error);
18
+ if (null != committedFailure) return committedFailure;
19
+ throw error;
20
+ }
21
+ }));
14
22
  export { updateSingleton };
@@ -0,0 +1,4 @@
1
+ import { type StructuralMutationReceipt } from '@byline/core';
2
+ /** Copy only the authorized receipt fields emitted by the lifecycle service. */
3
+ export declare function structuralReceipt(value: unknown): StructuralMutationReceipt | null;
4
+ export declare function committedTreeReceipt(error: unknown): StructuralMutationReceipt | null;
@@ -0,0 +1,31 @@
1
+ import { ErrorCodes, isDocumentRevision } from "@byline/core";
2
+ function structuralReceipt(value) {
3
+ if ('object' != typeof value || null === value) return null;
4
+ const documentId = Reflect.get(value, 'documentId');
5
+ const revision = Reflect.get(value, 'revision');
6
+ const suspended = Reflect.get(value, 'scheduledPublicationsNeedReconfirmation');
7
+ const affected = Reflect.get(value, 'affectedDocuments');
8
+ if ('string' != typeof documentId || !isDocumentRevision(revision) || 'boolean' != typeof suspended || !Array.isArray(affected)) return null;
9
+ const affectedDocuments = [];
10
+ for (const row of affected){
11
+ if ('object' != typeof row || null === row) return null;
12
+ const id = Reflect.get(row, 'documentId');
13
+ const token = Reflect.get(row, 'revision');
14
+ if ('string' != typeof id || !isDocumentRevision(token)) return null;
15
+ affectedDocuments.push({
16
+ documentId: id,
17
+ revision: token
18
+ });
19
+ }
20
+ return {
21
+ documentId,
22
+ revision,
23
+ affectedDocuments,
24
+ scheduledPublicationsNeedReconfirmation: suspended
25
+ };
26
+ }
27
+ function committedTreeReceipt(error) {
28
+ if ('object' != typeof error || null === error || Reflect.get(error, 'code') !== ErrorCodes.TREE_HOOK_COMMITTED) return null;
29
+ return structuralReceipt(Reflect.get(error, 'details'));
30
+ }
31
+ export { committedTreeReceipt, structuralReceipt };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "license": "MPL-2.0",
6
- "version": "4.18.0",
6
+ "version": "5.0.0",
7
7
  "engines": {
8
8
  "node": ">=20.9.0"
9
9
  },
@@ -125,15 +125,15 @@
125
125
  "react-json-view-lite": "^2.5.0",
126
126
  "react-swipeable": "^7.0.2",
127
127
  "zod": "^4.4.3",
128
- "@byline/admin": "4.18.0",
129
- "@byline/ai": "4.18.0",
130
- "@byline/analytics-agent": "4.18.0",
131
- "@byline/client": "4.18.0",
132
- "@byline/auth": "4.18.0",
133
- "@byline/analytics": "4.18.0",
134
- "@byline/i18n": "4.18.0",
135
- "@byline/core": "4.18.0",
136
- "@byline/ui": "4.18.0"
128
+ "@byline/admin": "5.0.0",
129
+ "@byline/analytics": "5.0.0",
130
+ "@byline/ai": "5.0.0",
131
+ "@byline/analytics-agent": "5.0.0",
132
+ "@byline/core": "5.0.0",
133
+ "@byline/i18n": "5.0.0",
134
+ "@byline/ui": "5.0.0",
135
+ "@byline/client": "5.0.0",
136
+ "@byline/auth": "5.0.0"
137
137
  },
138
138
  "peerDependencies": {
139
139
  "@tanstack/react-router": "^1.167.0",
@@ -174,7 +174,7 @@
174
174
  "build": "run-s build:*",
175
175
  "build:clean": "node scripts/clean.js dist build",
176
176
  "build:code": "rslib build",
177
- "typecheck": "tsc --noEmit",
177
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.revision-tests.json",
178
178
  "clean": "node scripts/clean.js node_modules dist build types .turbo",
179
179
  "lint": "biome check --write --unsafe --diagnostic-level=error",
180
180
  "test": "vitest run --mode=jsdom && vitest run --mode=node",
@@ -15,7 +15,10 @@ import { Container, Section, useToastManager } from '@byline/ui/react'
15
15
 
16
16
  import { getAdminRoutePath } from '../../routes/admin-path.js'
17
17
  import { decodeListReturnState } from '../../routes/list-return-state.js'
18
- import { createCollectionDocument } from '../../server-fns/collections/index.js'
18
+ import {
19
+ createCollectionDocument,
20
+ hasCommittedDocumentHookFailure,
21
+ } from '../../server-fns/collections/index.js'
19
22
  import { useNavigate } from '../chrome/loose-router.js'
20
23
  import { useTanStackNavigationGuard } from './tanstack-navigation-guard.js'
21
24
 
@@ -65,6 +68,14 @@ export const CreateView = ({
65
68
  ...(systemAvailableLocales ? { availableLocales: systemAvailableLocales } : {}),
66
69
  },
67
70
  })
71
+ const hookFailed = hasCommittedDocumentHookFailure(result)
72
+ if (hookFailed) {
73
+ toastManager.add({
74
+ title: t('collections.save.hookFailedToast'),
75
+ description: t('collections.save.hookFailedDescription'),
76
+ data: { intent: 'warning', iconType: 'warning', icon: true, close: true },
77
+ })
78
+ }
68
79
  // Create → edit: land the editor on the new document, where the rest
69
80
  // of the work (content, status, relations) happens. `from` threads
70
81
  // forward so closing the brand-new document also returns to the
@@ -75,13 +86,21 @@ export const CreateView = ({
75
86
  navigate({
76
87
  to: getAdminRoutePath('collections', '$collection', '$id'),
77
88
  params: { collection: path, id: result.documentId } as never,
78
- search: { action: 'created', from },
89
+ search: { ...(hookFailed ? {} : { action: 'created' as const }), from },
90
+ // Persistence has succeeded, but FormRenderer cannot commit its
91
+ // clean baseline until this handler returns. Do not let that brief
92
+ // dirty-state overlap block the intentional create → edit redirect.
93
+ ignoreBlocker: true,
79
94
  })
80
95
  } else {
81
96
  navigate({
82
97
  to: getAdminRoutePath('collections', '$collection'),
83
98
  params: { collection: path },
84
- search: { ...decodeListReturnState(from), action: 'created' },
99
+ search: {
100
+ ...decodeListReturnState(from),
101
+ ...(hookFailed ? {} : { action: 'created' as const }),
102
+ },
103
+ ignoreBlocker: true,
85
104
  })
86
105
  }
87
106
  } catch (err) {