@byline/host-tanstack-start 4.18.0 → 4.19.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 (45) hide show
  1. package/dist/admin-shell/collections/create.js +22 -5
  2. package/dist/admin-shell/collections/edit.js +85 -17
  3. package/dist/admin-shell/collections/restore-version-modal.js +14 -2
  4. package/dist/admin-shell/collections/save-navigation.test.d.ts +8 -0
  5. package/dist/admin-shell/singletons/view.js +10 -4
  6. package/dist/server-fns/collections/copy-to-locale.d.ts +2 -1
  7. package/dist/server-fns/collections/copy-to-locale.js +13 -6
  8. package/dist/server-fns/collections/create.d.ts +1 -1
  9. package/dist/server-fns/collections/create.js +15 -7
  10. package/dist/server-fns/collections/delete-locale.d.ts +2 -1
  11. package/dist/server-fns/collections/delete-locale.js +11 -4
  12. package/dist/server-fns/collections/duplicate.d.ts +2 -1
  13. package/dist/server-fns/collections/duplicate.js +10 -3
  14. package/dist/server-fns/collections/index.d.ts +1 -0
  15. package/dist/server-fns/collections/index.js +1 -0
  16. package/dist/server-fns/collections/restore-version.d.ts +1 -1
  17. package/dist/server-fns/collections/restore-version.js +17 -10
  18. package/dist/server-fns/collections/save-outcome.d.ts +23 -0
  19. package/dist/server-fns/collections/save-outcome.js +18 -0
  20. package/dist/server-fns/collections/update.d.ts +3 -1
  21. package/dist/server-fns/collections/update.js +17 -7
  22. package/dist/server-fns/singletons/copy-to-locale.js +14 -5
  23. package/dist/server-fns/singletons/restore-version.js +10 -1
  24. package/dist/server-fns/singletons/update.js +12 -5
  25. package/package.json +10 -10
  26. package/src/admin-shell/collections/create.tsx +22 -3
  27. package/src/admin-shell/collections/edit.tsx +63 -10
  28. package/src/admin-shell/collections/restore-version-modal.tsx +11 -2
  29. package/src/admin-shell/collections/save-navigation.test.tsx +384 -0
  30. package/src/admin-shell/singletons/history.test.tsx +43 -0
  31. package/src/admin-shell/singletons/view.test.tsx +27 -0
  32. package/src/admin-shell/singletons/view.tsx +35 -16
  33. package/src/server-fns/collections/copy-to-locale.ts +43 -29
  34. package/src/server-fns/collections/create.ts +16 -8
  35. package/src/server-fns/collections/delete-locale.ts +38 -24
  36. package/src/server-fns/collections/duplicate.ts +38 -24
  37. package/src/server-fns/collections/index.ts +1 -0
  38. package/src/server-fns/collections/restore-version.ts +16 -9
  39. package/src/server-fns/collections/save-outcome.test.node.ts +77 -0
  40. package/src/server-fns/collections/save-outcome.ts +55 -0
  41. package/src/server-fns/collections/update.ts +23 -8
  42. package/src/server-fns/singletons/copy-to-locale.ts +16 -9
  43. package/src/server-fns/singletons/restore-version.ts +12 -3
  44. package/src/server-fns/singletons/singletons.test.node.ts +34 -0
  45. package/src/server-fns/singletons/update.ts +12 -5
@@ -5,7 +5,7 @@ import { useTranslation } from "@byline/i18n/react";
5
5
  import { Container, Section, useToastManager } from "@byline/ui/react";
6
6
  import { getAdminRoutePath } from "../../routes/admin-path.js";
7
7
  import { decodeListReturnState } from "../../routes/list-return-state.js";
8
- import { createCollectionDocument } from "../../server-fns/collections/index.js";
8
+ import { createCollectionDocument, hasCommittedDocumentHookFailure } from "../../server-fns/collections/index.js";
9
9
  import { useNavigate } from "../chrome/loose-router.js";
10
10
  import { useTanStackNavigationGuard } from "./tanstack-navigation-guard.js";
11
11
  const CreateView = ({ collectionDefinition, adminConfig, initialData, from })=>{
@@ -31,6 +31,17 @@ const CreateView = ({ collectionDefinition, adminConfig, initialData, from })=>{
31
31
  } : {}
32
32
  }
33
33
  });
34
+ const hookFailed = hasCommittedDocumentHookFailure(result);
35
+ if (hookFailed) toastManager.add({
36
+ title: t('collections.save.hookFailedToast'),
37
+ description: t("collections.save.hookFailedDescription"),
38
+ data: {
39
+ intent: 'warning',
40
+ iconType: 'warning',
41
+ icon: true,
42
+ close: true
43
+ }
44
+ });
34
45
  result?.documentId ? navigate({
35
46
  to: getAdminRoutePath('collections', '$collection', '$id'),
36
47
  params: {
@@ -38,9 +49,12 @@ const CreateView = ({ collectionDefinition, adminConfig, initialData, from })=>{
38
49
  id: result.documentId
39
50
  },
40
51
  search: {
41
- action: 'created',
52
+ ...hookFailed ? {} : {
53
+ action: 'created'
54
+ },
42
55
  from
43
- }
56
+ },
57
+ ignoreBlocker: true
44
58
  }) : navigate({
45
59
  to: getAdminRoutePath('collections', '$collection'),
46
60
  params: {
@@ -48,8 +62,11 @@ const CreateView = ({ collectionDefinition, adminConfig, initialData, from })=>{
48
62
  },
49
63
  search: {
50
64
  ...decodeListReturnState(from),
51
- action: 'created'
52
- }
65
+ ...hookFailed ? {} : {
66
+ action: 'created'
67
+ }
68
+ },
69
+ ignoreBlocker: true
53
70
  });
54
71
  } catch (err) {
55
72
  console.error(err);
@@ -6,7 +6,7 @@ import { useTranslation } from "@byline/i18n/react";
6
6
  import { Container, Section, useToastManager } from "@byline/ui/react";
7
7
  import { getAdminRoutePath } from "../../routes/admin-path.js";
8
8
  import { clearListReturnState } from "../../routes/list-return-storage.js";
9
- import { cancelCollectionDocumentScheduledPublish, confirmCollectionDocumentScheduledPublish, copyDocumentToLocale, deleteDocument, deleteDocumentLocale, duplicateCollectionDocument, hasDeleteSideEffectFailures, scheduleCollectionDocumentPublish, unpublishDocument, updateCollectionDocumentSystemFields, updateCollectionDocumentWithPatches, updateDocumentStatus } from "../../server-fns/collections/index.js";
9
+ import { cancelCollectionDocumentScheduledPublish, confirmCollectionDocumentScheduledPublish, copyDocumentToLocale, deleteDocument, deleteDocumentLocale, duplicateCollectionDocument, hasCommittedDocumentHookFailure, hasDeleteSideEffectFailures, scheduleCollectionDocumentPublish, unpublishDocument, updateCollectionDocumentSystemFields, updateCollectionDocumentWithPatches, updateDocumentStatus } from "../../server-fns/collections/index.js";
10
10
  import { useNavigate } from "../chrome/loose-router.js";
11
11
  import { useTanStackNavigationGuard } from "./tanstack-navigation-guard.js";
12
12
  import { ViewMenu } from "./view-menu.js";
@@ -43,6 +43,23 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
43
43
  }
44
44
  });
45
45
  };
46
+ const notifyCommittedHookFailure = ()=>{
47
+ const description = t("collections.save.hookFailedDescription");
48
+ toastManager.add({
49
+ title: t('collections.save.hookFailedToast'),
50
+ description,
51
+ data: {
52
+ intent: 'warning',
53
+ iconType: 'warning',
54
+ icon: true,
55
+ close: true
56
+ }
57
+ });
58
+ setEditState({
59
+ status: 'warning',
60
+ message: description
61
+ });
62
+ };
46
63
  const handleLocaleChange = (newLocale)=>{
47
64
  navigate({
48
65
  to: getAdminRoutePath('collections', '$collection', '$id'),
@@ -300,6 +317,20 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
300
317
  id: String(initialData.id)
301
318
  }
302
319
  });
320
+ if (hasCommittedDocumentHookFailure(result)) {
321
+ notifyCommittedHookFailure();
322
+ navigate({
323
+ to: getAdminRoutePath('collections', '$collection', '$id'),
324
+ params: {
325
+ collection: path,
326
+ id: result.documentId
327
+ },
328
+ search: (prev)=>({
329
+ ...prev
330
+ })
331
+ });
332
+ return;
333
+ }
303
334
  const description = result.pathRetried ? t("collections.edit.duplicatedAutoPathDescription", {
304
335
  path: result.newPath
305
336
  }) : t("collections.edit.duplicatedPathDescription", {
@@ -367,6 +398,22 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
367
398
  overwrite
368
399
  }
369
400
  });
401
+ if (hasCommittedDocumentHookFailure(result)) {
402
+ notifyCommittedHookFailure();
403
+ notifyScheduleSuspended();
404
+ navigate({
405
+ to: getAdminRoutePath('collections', '$collection', '$id'),
406
+ params: {
407
+ collection: path,
408
+ id: String(initialData.id)
409
+ },
410
+ search: (prev)=>({
411
+ ...prev,
412
+ locale: targetLocale
413
+ })
414
+ });
415
+ return;
416
+ }
370
417
  const sourceLabel = contentLocales.find((l)=>l.code === result.sourceLocale)?.label ?? result.sourceLocale;
371
418
  const targetLabel = contentLocales.find((l)=>l.code === result.targetLocale)?.label ?? result.targetLocale;
372
419
  const description = result.fieldsUpdated > 0 ? t("collections.edit.copiedFieldsDescription", {
@@ -440,6 +487,22 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
440
487
  locale: targetLocale
441
488
  }
442
489
  });
490
+ if (hasCommittedDocumentHookFailure(result)) {
491
+ notifyCommittedHookFailure();
492
+ notifyScheduleSuspended();
493
+ navigate({
494
+ to: getAdminRoutePath('collections', '$collection', '$id'),
495
+ params: {
496
+ collection: path,
497
+ id: String(initialData.id)
498
+ },
499
+ search: (prev)=>({
500
+ ...prev,
501
+ locale: defaultContentLocale
502
+ })
503
+ });
504
+ return;
505
+ }
443
506
  const targetLabel = contentLocales.find((l)=>l.code === result.locale)?.label ?? result.locale;
444
507
  const description = t("collections.edit.deletedLocaleDescription", {
445
508
  label: singular,
@@ -523,7 +586,7 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
523
586
  }
524
587
  });
525
588
  setEditState({
526
- status: 'success',
589
+ status: hasSideEffectFailures ? 'warning' : 'success',
527
590
  message: description
528
591
  });
529
592
  clearListReturnState(path, String(initialData.id));
@@ -559,6 +622,7 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
559
622
  };
560
623
  const handleSubmit = async ({ data: _data, patches, contentDirty, pathDirty, systemPath, availableLocalesDirty, systemAvailableLocales })=>{
561
624
  try {
625
+ let hookFailed = false;
562
626
  if (pathDirty || availableLocalesDirty) await updateCollectionDocumentSystemFields({
563
627
  data: {
564
628
  collection: path,
@@ -572,33 +636,36 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
572
636
  } : {}
573
637
  }
574
638
  });
575
- if (contentDirty) await updateCollectionDocumentWithPatches({
576
- data: {
577
- collection: path,
578
- id: String(initialData.id),
579
- patches,
580
- versionId: initialData.versionId,
581
- locale
582
- }
583
- });
639
+ if (contentDirty) {
640
+ const result = await updateCollectionDocumentWithPatches({
641
+ data: {
642
+ collection: path,
643
+ id: String(initialData.id),
644
+ patches,
645
+ versionId: initialData.versionId,
646
+ locale
647
+ }
648
+ });
649
+ hookFailed = hasCommittedDocumentHookFailure(result);
650
+ }
584
651
  if (contentDirty && scheduledPublication?.state === 'armed') notifyScheduleSuspended();
585
- const description = t("collections.edit.updatedDescription", {
652
+ const description = hookFailed ? t("collections.save.hookFailedDescription") : t("collections.edit.updatedDescription", {
586
653
  label: singularLower
587
654
  });
588
655
  toastManager.add({
589
- title: t('collections.edit.updateTitle', {
656
+ title: hookFailed ? t('collections.save.hookFailedToast') : t('collections.edit.updateTitle', {
590
657
  label: singular
591
658
  }),
592
659
  description,
593
660
  data: {
594
- intent: 'success',
595
- iconType: 'success',
661
+ intent: hookFailed ? 'warning' : 'success',
662
+ iconType: hookFailed ? 'warning' : 'success',
596
663
  icon: true,
597
664
  close: true
598
665
  }
599
666
  });
600
667
  setEditState({
601
- status: 'success',
668
+ status: hookFailed ? 'warning' : 'success',
602
669
  message: description
603
670
  });
604
671
  navigate({
@@ -609,7 +676,8 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
609
676
  },
610
677
  search: (prev)=>({
611
678
  ...prev
612
- })
679
+ }),
680
+ ignoreBlocker: true
613
681
  });
614
682
  } catch (err) {
615
683
  console.error('Network error:', err);
@@ -2,11 +2,13 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { useTranslation } from "@byline/i18n/react";
5
- import { Alert, Button, LoaderEllipsis, Modal } from "@byline/ui/react";
5
+ import { Alert, Button, LoaderEllipsis, Modal, useToastManager } from "@byline/ui/react";
6
6
  import clsx from "clsx";
7
+ import { hasCommittedDocumentHookFailure } from "../../server-fns/collections/save-outcome.js";
7
8
  import restore_version_modal_module from "./restore-version-modal.module.js";
8
9
  function RestoreVersionModal({ versionLabel, versionNumber, restoreStatusLabel, onClose, restoreVersion, onRestoreComplete }) {
9
10
  const { t } = useTranslation('byline-admin');
11
+ const toastManager = useToastManager();
10
12
  const [error, setError] = useState(null);
11
13
  const [pending, setPending] = useState(false);
12
14
  async function handleRestore() {
@@ -14,7 +16,17 @@ function RestoreVersionModal({ versionLabel, versionNumber, restoreStatusLabel,
14
16
  setPending(true);
15
17
  setError(null);
16
18
  try {
17
- await restoreVersion();
19
+ const result = await restoreVersion();
20
+ if (hasCommittedDocumentHookFailure(result)) toastManager.add({
21
+ title: t('collections.save.hookFailedToast'),
22
+ description: t("collections.save.hookFailedDescription"),
23
+ data: {
24
+ intent: 'warning',
25
+ iconType: 'warning',
26
+ icon: true,
27
+ close: true
28
+ }
29
+ });
18
30
  onClose();
19
31
  await onRestoreComplete();
20
32
  } catch (err) {
@@ -0,0 +1,8 @@
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 {};
@@ -6,6 +6,7 @@ import { ErrorCodes, getDefaultStatus, getWorkflow, getWorkflowStatuses, validat
6
6
  import { useTranslation } from "@byline/i18n/react";
7
7
  import { Container, Section, useToastManager } from "@byline/ui/react";
8
8
  import { getAdminRoutePath } from "../../routes/admin-path.js";
9
+ import { hasCommittedDocumentHookFailure } from "../../server-fns/collections/index.js";
9
10
  import { cancelSingletonScheduledPublish, changeSingletonStatus, confirmSingletonScheduledPublish, copySingletonToLocale, scheduleSingletonPublish, unpublishSingleton, updateSingleton } from "../../server-fns/singletons/index.js";
10
11
  import { useNavigate } from "../chrome/loose-router.js";
11
12
  import { useTanStackNavigationGuard } from "../collections/tanstack-navigation-guard.js";
@@ -49,9 +50,12 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
49
50
  if (scheduledPublication?.state !== 'armed') return;
50
51
  toast(t('scheduledPublication.toast.suspendedTitle'), t("scheduledPublication.toast.suspendedDescription"), 'warning');
51
52
  };
53
+ const notifyCommittedHookFailure = ()=>{
54
+ toast(t('collections.save.hookFailedToast'), t("collections.save.hookFailedDescription"), 'warning');
55
+ };
52
56
  const handleSubmit = async ({ data })=>{
53
57
  try {
54
- await updateSingleton({
58
+ const result = await updateSingleton({
55
59
  data: {
56
60
  singleton: path,
57
61
  data,
@@ -60,7 +64,8 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
60
64
  }
61
65
  });
62
66
  if (null != document) notifyScheduleSuspended();
63
- toast(t('singletons.edit.updateTitle', {
67
+ if (hasCommittedDocumentHookFailure(result)) notifyCommittedHookFailure();
68
+ else toast(t('singletons.edit.updateTitle', {
64
69
  label
65
70
  }), t("singletons.edit.updatedDescription", {
66
71
  label: label.toLowerCase()
@@ -122,7 +127,7 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
122
127
  };
123
128
  const handleCopyToLocale = async ({ targetLocale, overwrite })=>{
124
129
  try {
125
- await copySingletonToLocale({
130
+ const result = await copySingletonToLocale({
126
131
  data: {
127
132
  singleton: path,
128
133
  sourceLocale: locale ?? defaultContentLocale,
@@ -133,7 +138,8 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
133
138
  const sourceLocale = locale ?? defaultContentLocale;
134
139
  const sourceLabel = contentLocales.find((entry)=>entry.code === sourceLocale)?.label ?? sourceLocale;
135
140
  const targetLabel = contentLocales.find((entry)=>entry.code === targetLocale)?.label ?? targetLocale;
136
- toast(t('collections.edit.copyToLocaleTitle', {
141
+ if (hasCommittedDocumentHookFailure(result)) notifyCommittedHookFailure();
142
+ else toast(t('collections.edit.copyToLocaleTitle', {
137
143
  label
138
144
  }), t('collections.edit.copiedSuccessMessage', {
139
145
  source: sourceLabel,
@@ -6,6 +6,7 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
  import type { CopyToLocaleResult } from '@byline/core/services';
9
+ import { type CollectionDocumentCommittedHookFailureResponse } from './save-outcome.js';
9
10
  /**
10
11
  * Copy a document's content from `sourceLocale` into `targetLocale` on
11
12
  * the same document. Mirrors the thin-wrapper pattern of the other
@@ -27,4 +28,4 @@ export declare const copyDocumentToLocale: import("@tanstack/react-start").Requi
27
28
  sourceLocale: string;
28
29
  targetLocale: string;
29
30
  overwrite: boolean;
30
- }, Promise<CopyToLocaleResult>>;
31
+ }, Promise<CollectionDocumentCommittedHookFailureResponse | CopyToLocaleResult>>;
@@ -3,6 +3,7 @@ import { getAdminRequestContext } from "@byline/client/server";
3
3
  import { ERR_NOT_FOUND, getLogger, getServerConfig } from "@byline/core";
4
4
  import { copyToLocale } from "@byline/core/services";
5
5
  import { ensureCollection } from "../../integrations/api-utils.js";
6
+ import { toCommittedDocumentHookFailureResponse } from "./save-outcome.js";
6
7
  const copyDocumentToLocale = createServerFn({
7
8
  method: 'POST'
8
9
  }).validator((input)=>input).handler(async ({ data: input })=>{
@@ -27,11 +28,17 @@ const copyDocumentToLocale = createServerFn({
27
28
  slugifier: serverConfig.slugifier,
28
29
  requestContext: await getAdminRequestContext()
29
30
  };
30
- return copyToLocale(ctx, {
31
- documentId: id,
32
- sourceLocale,
33
- targetLocale,
34
- overwrite
35
- });
31
+ try {
32
+ return await copyToLocale(ctx, {
33
+ documentId: id,
34
+ sourceLocale,
35
+ targetLocale,
36
+ overwrite
37
+ });
38
+ } catch (error) {
39
+ const committedFailure = toCommittedDocumentHookFailureResponse(error);
40
+ if (null != committedFailure) return committedFailure;
41
+ throw error;
42
+ }
36
43
  });
37
44
  export { copyDocumentToLocale };
@@ -17,7 +17,7 @@ export declare const createCollectionDocument: import("@tanstack/react-start").R
17
17
  locale?: string;
18
18
  path?: string;
19
19
  availableLocales?: string[];
20
- }, Promise<{
20
+ }, Promise<import("./save-outcome.js").CollectionDocumentCommittedHookFailureResponse | {
21
21
  status: 'ok';
22
22
  documentId: string;
23
23
  documentVersionId: string;
@@ -3,6 +3,7 @@ import { getAdminRequestContext } from "@byline/client/server";
3
3
  import { ERR_NOT_FOUND, getLogger, getServerConfig } from "@byline/core";
4
4
  import { createDocument } from "@byline/core/services";
5
5
  import { ensureCollection } from "../../integrations/api-utils.js";
6
+ import { toCommittedDocumentHookFailureResponse } from "./save-outcome.js";
6
7
  const createCollectionDocument = createServerFn({
7
8
  method: 'POST'
8
9
  }).validator((input)=>input).handler(async ({ data: input })=>{
@@ -27,13 +28,20 @@ const createCollectionDocument = createServerFn({
27
28
  slugifier: serverConfig.slugifier,
28
29
  requestContext: await getAdminRequestContext()
29
30
  };
30
- const result = await createDocument(ctx, {
31
- data: structuredClone(documentData),
32
- status: documentData.status,
33
- locale: locale ?? serverConfig.i18n.content.defaultLocale,
34
- path: explicitPath,
35
- availableLocales
36
- });
31
+ let result;
32
+ try {
33
+ result = await createDocument(ctx, {
34
+ data: structuredClone(documentData),
35
+ status: documentData.status,
36
+ locale: locale ?? serverConfig.i18n.content.defaultLocale,
37
+ path: explicitPath,
38
+ availableLocales
39
+ });
40
+ } catch (error) {
41
+ const committedFailure = toCommittedDocumentHookFailureResponse(error);
42
+ if (null != committedFailure) return committedFailure;
43
+ throw error;
44
+ }
37
45
  return {
38
46
  status: 'ok',
39
47
  documentId: result.documentId,
@@ -6,6 +6,7 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
  import type { DeleteLocaleResult } from '@byline/core/services';
9
+ import { type CollectionDocumentCommittedHookFailureResponse } from './save-outcome.js';
9
10
  /**
10
11
  * Remove a single (non-default) content locale from a document, leaving every
11
12
  * other locale untouched. Mirrors the thin-wrapper pattern of the other
@@ -24,4 +25,4 @@ export declare const deleteDocumentLocale: import("@tanstack/react-start").Requi
24
25
  collection: string;
25
26
  id: string;
26
27
  locale: string;
27
- }, Promise<DeleteLocaleResult>>;
28
+ }, Promise<CollectionDocumentCommittedHookFailureResponse | DeleteLocaleResult>>;
@@ -3,6 +3,7 @@ import { getAdminRequestContext } from "@byline/client/server";
3
3
  import { ERR_NOT_FOUND, getLogger, getServerConfig } from "@byline/core";
4
4
  import { deleteLocale } from "@byline/core/services";
5
5
  import { ensureCollection } from "../../integrations/api-utils.js";
6
+ import { toCommittedDocumentHookFailureResponse } from "./save-outcome.js";
6
7
  const deleteDocumentLocale = createServerFn({
7
8
  method: 'POST'
8
9
  }).validator((input)=>input).handler(async ({ data: input })=>{
@@ -27,9 +28,15 @@ const deleteDocumentLocale = createServerFn({
27
28
  slugifier: serverConfig.slugifier,
28
29
  requestContext: await getAdminRequestContext()
29
30
  };
30
- return deleteLocale(ctx, {
31
- documentId: id,
32
- locale
33
- });
31
+ try {
32
+ return await deleteLocale(ctx, {
33
+ documentId: id,
34
+ locale
35
+ });
36
+ } catch (error) {
37
+ const committedFailure = toCommittedDocumentHookFailureResponse(error);
38
+ if (null != committedFailure) return committedFailure;
39
+ throw error;
40
+ }
34
41
  });
35
42
  export { deleteDocumentLocale };
@@ -6,6 +6,7 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
  import type { DuplicateDocumentResult } from '@byline/core/services';
9
+ import { type CollectionDocumentCommittedHookFailureResponse } from './save-outcome.js';
9
10
  /**
10
11
  * Duplicate an existing document — clones all locales into a brand-new
11
12
  * document in one atomic write. Returns the new document's id so the UI
@@ -23,4 +24,4 @@ export declare const duplicateCollectionDocument: import("@tanstack/react-start"
23
24
  }) => {
24
25
  collection: string;
25
26
  id: string;
26
- }, Promise<DuplicateDocumentResult>>;
27
+ }, Promise<CollectionDocumentCommittedHookFailureResponse | DuplicateDocumentResult>>;
@@ -3,6 +3,7 @@ import { getAdminRequestContext } from "@byline/client/server";
3
3
  import { ERR_NOT_FOUND, getLogger, getServerConfig } from "@byline/core";
4
4
  import { duplicateDocument } from "@byline/core/services";
5
5
  import { ensureCollection } from "../../integrations/api-utils.js";
6
+ import { toCommittedDocumentHookFailureResponse } from "./save-outcome.js";
6
7
  const duplicateCollectionDocument = createServerFn({
7
8
  method: 'POST'
8
9
  }).validator((input)=>input).handler(async ({ data: input })=>{
@@ -27,8 +28,14 @@ const duplicateCollectionDocument = createServerFn({
27
28
  slugifier: serverConfig.slugifier,
28
29
  requestContext: await getAdminRequestContext()
29
30
  };
30
- return duplicateDocument(ctx, {
31
- sourceDocumentId
32
- });
31
+ try {
32
+ return await duplicateDocument(ctx, {
33
+ sourceDocumentId
34
+ });
35
+ } catch (error) {
36
+ const committedFailure = toCommittedDocumentHookFailureResponse(error);
37
+ if (null != committedFailure) return committedFailure;
38
+ throw error;
39
+ }
33
40
  });
34
41
  export { duplicateCollectionDocument };
@@ -17,6 +17,7 @@ export * from './list';
17
17
  export * from './reindex';
18
18
  export * from './reorder';
19
19
  export * from './restore-version';
20
+ export * from './save-outcome';
20
21
  export * from './scheduled-publication';
21
22
  export * from './set-list-view-preference';
22
23
  export * from './stats';
@@ -11,6 +11,7 @@ export * from "./list.js";
11
11
  export * from "./reindex.js";
12
12
  export * from "./reorder.js";
13
13
  export * from "./restore-version.js";
14
+ export * from "./save-outcome.js";
14
15
  export * from "./scheduled-publication.js";
15
16
  export * from "./set-list-view-preference.js";
16
17
  export * from "./stats.js";
@@ -13,7 +13,7 @@ export declare const restoreDocumentVersion: import("@tanstack/react-start").Req
13
13
  collection: string;
14
14
  id: string;
15
15
  versionId: string;
16
- }, Promise<{
16
+ }, Promise<import("./save-outcome.js").CollectionDocumentCommittedHookFailureResponse | {
17
17
  status: 'ok';
18
18
  documentId: string;
19
19
  documentVersionId: string;
@@ -3,6 +3,7 @@ import { getAdminRequestContext } from "@byline/client/server";
3
3
  import { ERR_NOT_FOUND, getLogger, getServerConfig } from "@byline/core";
4
4
  import { restoreDocumentVersion } from "@byline/core/services";
5
5
  import { ensureCollection } from "../../integrations/api-utils.js";
6
+ import { toCommittedDocumentHookFailureResponse } from "./save-outcome.js";
6
7
  const restore_version_restoreDocumentVersion = createServerFn({
7
8
  method: 'POST'
8
9
  }).validator((input)=>input).handler(async ({ data: input })=>{
@@ -27,15 +28,21 @@ const restore_version_restoreDocumentVersion = createServerFn({
27
28
  slugifier: serverConfig.slugifier,
28
29
  requestContext: await getAdminRequestContext()
29
30
  };
30
- const result = await restoreDocumentVersion(ctx, {
31
- documentId: id,
32
- sourceVersionId: versionId
33
- });
34
- return {
35
- status: 'ok',
36
- documentId: result.documentId,
37
- documentVersionId: result.documentVersionId,
38
- sourceVersionId: result.sourceVersionId
39
- };
31
+ try {
32
+ const result = await restoreDocumentVersion(ctx, {
33
+ documentId: id,
34
+ sourceVersionId: versionId
35
+ });
36
+ return {
37
+ status: 'ok',
38
+ documentId: result.documentId,
39
+ documentVersionId: result.documentVersionId,
40
+ sourceVersionId: result.sourceVersionId
41
+ };
42
+ } catch (error) {
43
+ const committedFailure = toCommittedDocumentHookFailureResponse(error);
44
+ if (null != committedFailure) return committedFailure;
45
+ throw error;
46
+ }
40
47
  });
41
48
  export { restore_version_restoreDocumentVersion as restoreDocumentVersion };
@@ -0,0 +1,23 @@
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
+ import { type DocumentHookCommittedPhase, type DocumentHookSideEffectCode } from '@byline/core/services';
9
+ export interface CollectionDocumentCommittedHookFailureResponse {
10
+ status: 'committed-hook-failed';
11
+ documentId: string;
12
+ documentVersionId: string;
13
+ sideEffectFailure: {
14
+ phase: DocumentHookCommittedPhase;
15
+ code: DocumentHookSideEffectCode;
16
+ };
17
+ }
18
+ /**
19
+ * Convert only validated core committed-hook errors into safe wire data. Raw
20
+ * hook errors, messages, stack traces, and storage paths remain server-side.
21
+ */
22
+ export declare function toCommittedDocumentHookFailureResponse(error: unknown): CollectionDocumentCommittedHookFailureResponse | null;
23
+ export declare function hasCommittedDocumentHookFailure(result: unknown): result is CollectionDocumentCommittedHookFailureResponse;
@@ -0,0 +1,18 @@
1
+ import { getDocumentHookCommittedDetails } from "@byline/core/services";
2
+ function toCommittedDocumentHookFailureResponse(error) {
3
+ const details = getDocumentHookCommittedDetails(error);
4
+ if (null == details) return null;
5
+ return {
6
+ status: 'committed-hook-failed',
7
+ documentId: details.documentId,
8
+ documentVersionId: details.documentVersionId,
9
+ sideEffectFailure: {
10
+ phase: details.phase,
11
+ code: details.sideEffectCode
12
+ }
13
+ };
14
+ }
15
+ function hasCommittedDocumentHookFailure(result) {
16
+ return 'object' == typeof result && null != result && 'status' in result && 'committed-hook-failed' === result.status;
17
+ }
18
+ export { hasCommittedDocumentHookFailure, toCommittedDocumentHookFailureResponse };
@@ -18,8 +18,10 @@ export declare const updateCollectionDocumentWithPatches: import("@tanstack/reac
18
18
  patches: DocumentPatch[];
19
19
  versionId?: string;
20
20
  locale?: string;
21
- }, Promise<{
21
+ }, Promise<import("./save-outcome.js").CollectionDocumentCommittedHookFailureResponse | {
22
22
  status: 'ok';
23
+ documentId: string;
24
+ documentVersionId: string;
23
25
  }>>;
24
26
  export declare const updateCollectionDocumentSystemFields: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
25
27
  collection: string;