@byline/host-tanstack-start 4.19.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin-shell/collections/edit.d.ts +1 -1
- package/dist/admin-shell/collections/edit.js +151 -31
- package/dist/admin-shell/collections/history.js +4 -2
- package/dist/admin-shell/collections/list.js +30 -6
- package/dist/admin-shell/collections/restore-version-modal.d.ts +3 -1
- package/dist/admin-shell/collections/restore-version-modal.js +9 -4
- package/dist/admin-shell/collections/tree-list.js +16 -6
- package/dist/admin-shell/collections/version-history.d.ts +2 -1
- package/dist/admin-shell/collections/version-history.js +11 -2
- package/dist/admin-shell/document-mutation-notice.d.ts +6 -0
- package/dist/admin-shell/document-mutation-notice.js +53 -0
- package/dist/admin-shell/document-mutation-state.d.ts +22 -0
- package/dist/admin-shell/document-mutation-state.js +71 -0
- package/dist/admin-shell/document-mutation-state.test.d.ts +1 -0
- package/dist/admin-shell/scheduled-publications/list.js +15 -5
- package/dist/admin-shell/singletons/history.js +4 -2
- package/dist/admin-shell/singletons/view.d.ts +1 -1
- package/dist/admin-shell/singletons/view.js +98 -19
- package/dist/integrations/byline-field-services.js +2 -9
- package/dist/routes/create-collection-edit-route.js +1 -1
- package/dist/routes/create-collection-list-route.d.ts +0 -7
- package/dist/routes/create-collection-list-route.js +7 -9
- package/dist/routes/create-singleton-route.d.ts +2 -0
- package/dist/routes/create-singleton-route.js +4 -3
- package/dist/server-fns/collections/copy-to-locale.d.ts +2 -7
- package/dist/server-fns/collections/copy-to-locale.js +4 -2
- package/dist/server-fns/collections/create.d.ts +1 -7
- package/dist/server-fns/collections/create.js +5 -3
- package/dist/server-fns/collections/delete-locale.d.ts +2 -7
- package/dist/server-fns/collections/delete-locale.js +4 -2
- package/dist/server-fns/collections/delete-outcome.d.ts +7 -3
- package/dist/server-fns/collections/delete-outcome.js +4 -0
- package/dist/server-fns/collections/delete.d.ts +2 -7
- package/dist/server-fns/collections/delete.js +5 -3
- package/dist/server-fns/collections/duplicate.d.ts +2 -7
- package/dist/server-fns/collections/duplicate.js +5 -3
- package/dist/server-fns/collections/get.d.ts +1 -7
- package/dist/server-fns/collections/get.js +5 -4
- package/dist/server-fns/collections/list.d.ts +0 -7
- package/dist/server-fns/collections/list.js +11 -3
- package/dist/server-fns/collections/reorder.d.ts +7 -8
- package/dist/server-fns/collections/reorder.js +20 -76
- package/dist/server-fns/collections/restore-version.d.ts +3 -7
- package/dist/server-fns/collections/restore-version.js +5 -2
- package/dist/server-fns/collections/save-outcome.d.ts +1 -0
- package/dist/server-fns/collections/save-outcome.js +1 -0
- package/dist/server-fns/collections/scheduled-publication.d.ts +49 -9
- package/dist/server-fns/collections/scheduled-publication.js +31 -21
- package/dist/server-fns/collections/status.d.ts +8 -7
- package/dist/server-fns/collections/status.js +13 -5
- package/dist/server-fns/collections/tree-mutation.d.ts +7 -6
- package/dist/server-fns/collections/tree-mutation.js +17 -5
- package/dist/server-fns/collections/tree.d.ts +14 -8
- package/dist/server-fns/collections/tree.js +21 -17
- package/dist/server-fns/collections/update.d.ts +12 -11
- package/dist/server-fns/collections/update.js +34 -20
- package/dist/server-fns/collections/upload.d.ts +1 -0
- package/dist/server-fns/collections/upload.js +3 -2
- package/dist/server-fns/document-mutation-errors.d.ts +5 -0
- package/dist/server-fns/document-mutation-errors.js +50 -0
- package/dist/server-fns/editable-revision.d.ts +9 -0
- package/dist/server-fns/editable-revision.js +11 -0
- package/dist/server-fns/singleton-document-read.d.ts +1 -1
- package/dist/server-fns/singleton-document-read.js +7 -3
- package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +6 -8
- package/dist/server-fns/singletons/cancel-scheduled-publish.js +4 -1
- package/dist/server-fns/singletons/change-status.d.ts +2 -7
- package/dist/server-fns/singletons/change-status.js +4 -1
- package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +5 -8
- package/dist/server-fns/singletons/confirm-scheduled-publish.js +4 -2
- package/dist/server-fns/singletons/copy-to-locale.d.ts +2 -7
- package/dist/server-fns/singletons/copy-to-locale.js +4 -2
- package/dist/server-fns/singletons/get.d.ts +4 -4
- package/dist/server-fns/singletons/restore-version.d.ts +2 -7
- package/dist/server-fns/singletons/restore-version.js +6 -3
- package/dist/server-fns/singletons/schedule-publish.d.ts +5 -8
- package/dist/server-fns/singletons/schedule-publish.js +4 -2
- package/dist/server-fns/singletons/unpublish.d.ts +2 -7
- package/dist/server-fns/singletons/unpublish.js +4 -1
- package/dist/server-fns/singletons/update.d.ts +4 -11
- package/dist/server-fns/singletons/update.js +5 -4
- package/dist/server-fns/structural-receipt.d.ts +4 -0
- package/dist/server-fns/structural-receipt.js +31 -0
- package/package.json +11 -11
- package/src/admin-shell/collections/edit.tsx +173 -54
- package/src/admin-shell/collections/history.tsx +11 -2
- package/src/admin-shell/collections/list.tsx +22 -6
- package/src/admin-shell/collections/restore-version-modal.tsx +12 -3
- package/src/admin-shell/collections/save-navigation.test.tsx +156 -8
- package/src/admin-shell/collections/tree-list.tsx +16 -6
- package/src/admin-shell/collections/version-history.tsx +13 -3
- package/src/admin-shell/document-mutation-notice.tsx +45 -0
- package/src/admin-shell/document-mutation-state.test.tsx +146 -0
- package/src/admin-shell/document-mutation-state.ts +95 -0
- package/src/admin-shell/scheduled-publications/list.tsx +16 -6
- package/src/admin-shell/singletons/history.test.tsx +3 -1
- package/src/admin-shell/singletons/history.tsx +10 -2
- package/src/admin-shell/singletons/view.test.tsx +59 -18
- package/src/admin-shell/singletons/view.tsx +110 -29
- package/src/integrations/byline-field-services.ts +2 -3
- package/src/routes/create-collection-edit-route.tsx +1 -0
- package/src/routes/create-collection-list-route.tsx +13 -2
- package/src/routes/create-singleton-route.test.node.ts +10 -2
- package/src/routes/create-singleton-route.tsx +6 -4
- package/src/server-fns/collections/copy-to-locale.ts +41 -36
- package/src/server-fns/collections/create.ts +54 -50
- package/src/server-fns/collections/delete-locale.ts +41 -32
- package/src/server-fns/collections/delete-outcome.test.node.ts +16 -0
- package/src/server-fns/collections/delete-outcome.ts +27 -16
- package/src/server-fns/collections/delete.ts +44 -38
- package/src/server-fns/collections/duplicate.ts +38 -32
- package/src/server-fns/collections/get.ts +8 -4
- package/src/server-fns/collections/list.ts +8 -3
- package/src/server-fns/collections/reorder.ts +32 -124
- package/src/server-fns/collections/restore-version.ts +47 -39
- package/src/server-fns/collections/revision-input.test.node.ts +108 -0
- package/src/server-fns/collections/save-outcome.test.node.ts +3 -0
- package/src/server-fns/collections/save-outcome.ts +2 -0
- package/src/server-fns/collections/scheduled-publication.ts +138 -111
- package/src/server-fns/collections/status.ts +86 -64
- package/src/server-fns/collections/tree-mutation.test.node.ts +87 -29
- package/src/server-fns/collections/tree-mutation.ts +28 -11
- package/src/server-fns/collections/tree.ts +150 -122
- package/src/server-fns/collections/update.ts +110 -86
- package/src/server-fns/collections/upload-singleton.test.node.ts +3 -7
- package/src/server-fns/collections/upload.ts +81 -77
- package/src/server-fns/document-mutation-errors.ts +67 -0
- package/src/server-fns/document-mutation-transport.test.node.ts +211 -0
- package/src/server-fns/editable-revision.test.node.ts +23 -0
- package/src/server-fns/editable-revision.ts +18 -0
- package/src/server-fns/singleton-document-read.ts +7 -3
- package/src/server-fns/singletons/cancel-scheduled-publish.ts +10 -3
- package/src/server-fns/singletons/change-status.ts +10 -3
- package/src/server-fns/singletons/confirm-scheduled-publish.ts +12 -6
- package/src/server-fns/singletons/copy-to-locale.ts +20 -15
- package/src/server-fns/singletons/get.ts +3 -3
- package/src/server-fns/singletons/restore-version.ts +17 -12
- package/src/server-fns/singletons/schedule-publish.ts +18 -7
- package/src/server-fns/singletons/singletons.test.node.ts +63 -25
- package/src/server-fns/singletons/unpublish.ts +10 -3
- package/src/server-fns/singletons/update.ts +21 -16
- package/src/server-fns/structural-receipt.ts +41 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withDocumentMutationErrors } from "../document-mutation-errors.js";
|
|
1
2
|
import { createServerFn } from "@tanstack/react-start";
|
|
2
3
|
import { getAdminRequestContext } from "@byline/client/server";
|
|
3
4
|
import { ERR_NOT_FOUND, getLogger, getServerConfig } from "@byline/core";
|
|
@@ -5,7 +6,7 @@ import { changeDocumentStatus, unpublishDocument } from "@byline/core/services";
|
|
|
5
6
|
import { ensureCollection } from "../../integrations/api-utils.js";
|
|
6
7
|
const updateDocumentStatus = createServerFn({
|
|
7
8
|
method: 'POST'
|
|
8
|
-
}).validator((input)=>input).handler(async ({ data: input })=>{
|
|
9
|
+
}).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data: input })=>{
|
|
9
10
|
const { collection: path, id, status: nextStatus } = input;
|
|
10
11
|
const logger = getLogger();
|
|
11
12
|
const config = await ensureCollection(path);
|
|
@@ -29,17 +30,20 @@ const updateDocumentStatus = createServerFn({
|
|
|
29
30
|
};
|
|
30
31
|
const result = await changeDocumentStatus(ctx, {
|
|
31
32
|
documentId: id,
|
|
33
|
+
expectedRevision: input.expectedRevision,
|
|
32
34
|
nextStatus
|
|
33
35
|
});
|
|
34
36
|
return {
|
|
35
37
|
status: 'ok',
|
|
38
|
+
documentId: result.documentId,
|
|
39
|
+
revision: result.revision,
|
|
36
40
|
previousStatus: result.previousStatus,
|
|
37
41
|
newStatus: result.newStatus
|
|
38
42
|
};
|
|
39
|
-
});
|
|
43
|
+
}));
|
|
40
44
|
const status_unpublishDocument = createServerFn({
|
|
41
45
|
method: 'POST'
|
|
42
|
-
}).validator((input)=>input).handler(async ({ data: input })=>{
|
|
46
|
+
}).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data: input })=>{
|
|
43
47
|
const { collection: path, id } = input;
|
|
44
48
|
const logger = getLogger();
|
|
45
49
|
const config = await ensureCollection(path);
|
|
@@ -62,18 +66,22 @@ const status_unpublishDocument = createServerFn({
|
|
|
62
66
|
requestContext: await getAdminRequestContext()
|
|
63
67
|
};
|
|
64
68
|
const result = await unpublishDocument(ctx, {
|
|
65
|
-
documentId: id
|
|
69
|
+
documentId: id,
|
|
70
|
+
expectedRevision: input.expectedRevision
|
|
66
71
|
});
|
|
67
72
|
if (0 === result.archivedCount) throw ERR_NOT_FOUND({
|
|
68
73
|
message: 'No published version found for this document',
|
|
69
74
|
details: {
|
|
70
75
|
documentId: id,
|
|
76
|
+
expectedRevision: input.expectedRevision,
|
|
71
77
|
collectionPath: path
|
|
72
78
|
}
|
|
73
79
|
}).log(logger);
|
|
74
80
|
return {
|
|
75
81
|
status: 'ok',
|
|
82
|
+
documentId: result.documentId,
|
|
83
|
+
revision: result.revision,
|
|
76
84
|
archivedCount: result.archivedCount
|
|
77
85
|
};
|
|
78
|
-
});
|
|
86
|
+
}));
|
|
79
87
|
export { status_unpublishDocument as unpublishDocument, updateDocumentStatus };
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { CollectionHandle, PlaceTreeNodeOptions, RemoveFromTreeOptions } from '@byline/client';
|
|
2
|
+
import { structuralReceipt } from '../structural-receipt.js';
|
|
2
3
|
/** Prefer the code; the marker survives transports that reconstruct a code-less Error. */
|
|
3
4
|
export declare function isTreeMutationConflict(error: unknown): boolean;
|
|
4
5
|
/** Detect when tree/audit committed even if transport reconstruction dropped the code. */
|
|
5
6
|
export declare function isCommittedTreeHookFailure(error: unknown): boolean;
|
|
6
7
|
export type AdminTreeMoveOutcome = {
|
|
7
8
|
status: 'ok';
|
|
9
|
+
receipt?: ReturnType<typeof structuralReceipt>;
|
|
8
10
|
} | {
|
|
9
11
|
status: 'suppressed';
|
|
10
12
|
} | {
|
|
@@ -18,6 +20,7 @@ export type AdminTreeMoveOutcome = {
|
|
|
18
20
|
} | {
|
|
19
21
|
status: 'refresh-failed';
|
|
20
22
|
error: unknown;
|
|
23
|
+
receipt?: ReturnType<typeof structuralReceipt>;
|
|
21
24
|
};
|
|
22
25
|
export interface AdminTreeMoveUiEffects {
|
|
23
26
|
rollback: boolean;
|
|
@@ -38,9 +41,7 @@ export interface AdminTreeMoveController {
|
|
|
38
41
|
}
|
|
39
42
|
/** Single-flight controller shared by pointer and keyboard tree moves. */
|
|
40
43
|
export declare function createAdminTreeMoveController(): AdminTreeMoveController;
|
|
41
|
-
/**
|
|
42
|
-
export declare function placeAdminTreeNode(handle: Pick<CollectionHandle, 'placeTreeNode'>, documentId: string, options: PlaceTreeNodeOptions):
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/** Admin retries reconcile post-commit tree hooks unless explicitly disabled. */
|
|
46
|
-
export declare function removeAdminTreeNode(handle: Pick<CollectionHandle, 'removeFromTree'>, documentId: string, options?: RemoveFromTreeOptions): Promise<void>;
|
|
44
|
+
/** Request reconciliation for explicit caller actions; never retry or replace their revision. */
|
|
45
|
+
export declare function placeAdminTreeNode(handle: Pick<CollectionHandle, 'placeTreeNode'>, documentId: string, options: PlaceTreeNodeOptions): ReturnType<CollectionHandle['placeTreeNode']>;
|
|
46
|
+
/** Request reconciliation for explicit caller actions; never retry or replace their revision. */
|
|
47
|
+
export declare function removeAdminTreeNode(handle: Pick<CollectionHandle, 'removeFromTree'>, documentId: string, options: RemoveFromTreeOptions): ReturnType<CollectionHandle['removeFromTree']>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ErrorCodes, TREE_HOOK_COMMITTED_MARKER, TREE_PLACEMENT_STALE_MARKER } from "@byline/core";
|
|
1
|
+
import { ErrorCodes, TREE_HOOK_COMMITTED_MARKER, TREE_PLACEMENT_STALE_MARKER, getDocumentRevisionValidationDetails, getDocumentStaleDetails, getLockConflictDetails } from "@byline/core";
|
|
2
|
+
import { structuralReceipt } from "../structural-receipt.js";
|
|
2
3
|
function hasErrorCode(error, code) {
|
|
3
4
|
return 'object' == typeof error && null != error && 'code' in error && error.code === code;
|
|
4
5
|
}
|
|
@@ -47,9 +48,14 @@ function shouldSyncAdminTreeRows(rows, preservedRows, isMoving) {
|
|
|
47
48
|
return !isMoving && rows !== preservedRows;
|
|
48
49
|
}
|
|
49
50
|
async function executeAdminTreeMove(mutate, refresh) {
|
|
51
|
+
let receipt;
|
|
50
52
|
try {
|
|
51
|
-
await mutate();
|
|
53
|
+
receipt = structuralReceipt(await mutate());
|
|
52
54
|
} catch (error) {
|
|
55
|
+
if (getDocumentStaleDetails(error) || getDocumentRevisionValidationDetails(error) || getLockConflictDetails(error) || hasErrorCode(error, ErrorCodes.NOT_FOUND)) return {
|
|
56
|
+
status: 'mutation-failed',
|
|
57
|
+
error
|
|
58
|
+
};
|
|
53
59
|
const status = isCommittedTreeHookFailure(error) ? 'committed-hook-failed' : 'mutation-failed';
|
|
54
60
|
try {
|
|
55
61
|
await refresh();
|
|
@@ -68,12 +74,18 @@ async function executeAdminTreeMove(mutate, refresh) {
|
|
|
68
74
|
try {
|
|
69
75
|
await refresh();
|
|
70
76
|
return {
|
|
71
|
-
status: 'ok'
|
|
77
|
+
status: 'ok',
|
|
78
|
+
...receipt ? {
|
|
79
|
+
receipt
|
|
80
|
+
} : {}
|
|
72
81
|
};
|
|
73
82
|
} catch (error) {
|
|
74
83
|
return {
|
|
75
84
|
status: 'refresh-failed',
|
|
76
|
-
error
|
|
85
|
+
error,
|
|
86
|
+
...receipt ? {
|
|
87
|
+
receipt
|
|
88
|
+
} : {}
|
|
77
89
|
};
|
|
78
90
|
}
|
|
79
91
|
}
|
|
@@ -100,7 +112,7 @@ function placeAdminTreeNode(handle, documentId, options) {
|
|
|
100
112
|
reconcile: options.reconcile ?? true
|
|
101
113
|
});
|
|
102
114
|
}
|
|
103
|
-
function removeAdminTreeNode(handle, documentId, options
|
|
115
|
+
function removeAdminTreeNode(handle, documentId, options) {
|
|
104
116
|
return handle.removeFromTree(documentId, {
|
|
105
117
|
...options,
|
|
106
118
|
reconcile: options.reconcile ?? true
|
|
@@ -1,11 +1,5 @@
|
|
|
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 placeTreeNode: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
|
|
2
|
+
expectedRevision: number;
|
|
9
3
|
collection: string;
|
|
10
4
|
documentId: string;
|
|
11
5
|
parentDocumentId: string | null;
|
|
@@ -13,6 +7,7 @@ export declare const placeTreeNode: import("@tanstack/react-start").RequiredFetc
|
|
|
13
7
|
afterDocumentId?: string | null;
|
|
14
8
|
reconcile?: boolean;
|
|
15
9
|
}) => {
|
|
10
|
+
expectedRevision: number;
|
|
16
11
|
collection: string;
|
|
17
12
|
documentId: string;
|
|
18
13
|
parentDocumentId: string | null;
|
|
@@ -20,18 +15,28 @@ export declare const placeTreeNode: import("@tanstack/react-start").RequiredFetc
|
|
|
20
15
|
afterDocumentId?: string | null;
|
|
21
16
|
reconcile?: boolean;
|
|
22
17
|
}, Promise<{
|
|
23
|
-
|
|
18
|
+
documentId: string;
|
|
19
|
+
revision: number;
|
|
20
|
+
affectedDocuments: import("@byline/core").DocumentRevisionReceipt[];
|
|
21
|
+
scheduledPublicationsNeedReconfirmation: boolean;
|
|
24
22
|
orderKey: string;
|
|
23
|
+
status: 'ok';
|
|
25
24
|
}>>;
|
|
26
25
|
export declare const removeFromTree: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
|
|
26
|
+
expectedRevision: number;
|
|
27
27
|
collection: string;
|
|
28
28
|
documentId: string;
|
|
29
29
|
reconcile?: boolean;
|
|
30
30
|
}) => {
|
|
31
|
+
expectedRevision: number;
|
|
31
32
|
collection: string;
|
|
32
33
|
documentId: string;
|
|
33
34
|
reconcile?: boolean;
|
|
34
35
|
}, Promise<{
|
|
36
|
+
documentId: string;
|
|
37
|
+
revision: number;
|
|
38
|
+
affectedDocuments: import("@byline/core").DocumentRevisionReceipt[];
|
|
39
|
+
scheduledPublicationsNeedReconfirmation: boolean;
|
|
35
40
|
status: 'ok';
|
|
36
41
|
}>>;
|
|
37
42
|
export declare const getTreeAncestors: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
|
|
@@ -53,6 +58,7 @@ export declare const getTreeParent: import("@tanstack/react-start").RequiredFetc
|
|
|
53
58
|
documentId: string;
|
|
54
59
|
}, Promise<import("@byline/client").TreeParentResult>>;
|
|
55
60
|
export interface CollectionTreeRow {
|
|
61
|
+
revision: number;
|
|
56
62
|
id: string;
|
|
57
63
|
/** Parent document id, or `null` for a root or unplaced node. Drives the
|
|
58
64
|
* client-side tree reconstruction for drag-to-reorder / re-parent. */
|
|
@@ -1,12 +1,13 @@
|
|
|
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 { ERR_NOT_FOUND, createReadContext, getLogger } from "@byline/core";
|
|
4
5
|
import { ensureCollection } from "../../integrations/api-utils.js";
|
|
5
6
|
import { placeAdminTreeNode, removeAdminTreeNode } from "./tree-mutation.js";
|
|
6
|
-
import { getAdminTreeParent
|
|
7
|
+
import { getAdminTreeParent } from "./tree-read.js";
|
|
7
8
|
const placeTreeNode = createServerFn({
|
|
8
9
|
method: 'POST'
|
|
9
|
-
}).validator((input)=>input).handler(async ({ data })=>{
|
|
10
|
+
}).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>{
|
|
10
11
|
const { collection: path, documentId } = data;
|
|
11
12
|
const config = await ensureCollection(path);
|
|
12
13
|
if (!config) throw ERR_NOT_FOUND({
|
|
@@ -17,6 +18,7 @@ const placeTreeNode = createServerFn({
|
|
|
17
18
|
}).log(getLogger());
|
|
18
19
|
const handle = getAdminBylineClient().collection(path);
|
|
19
20
|
const result = await placeAdminTreeNode(handle, documentId, {
|
|
21
|
+
expectedRevision: data.expectedRevision,
|
|
20
22
|
parentDocumentId: data.parentDocumentId,
|
|
21
23
|
beforeDocumentId: data.beforeDocumentId ?? null,
|
|
22
24
|
afterDocumentId: data.afterDocumentId ?? null,
|
|
@@ -24,12 +26,12 @@ const placeTreeNode = createServerFn({
|
|
|
24
26
|
});
|
|
25
27
|
return {
|
|
26
28
|
status: 'ok',
|
|
27
|
-
|
|
29
|
+
...result
|
|
28
30
|
};
|
|
29
|
-
});
|
|
31
|
+
}));
|
|
30
32
|
const removeFromTree = createServerFn({
|
|
31
33
|
method: 'POST'
|
|
32
|
-
}).validator((input)=>input).handler(async ({ data })=>{
|
|
34
|
+
}).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>{
|
|
33
35
|
const { collection: path, documentId } = data;
|
|
34
36
|
const config = await ensureCollection(path);
|
|
35
37
|
if (!config) throw ERR_NOT_FOUND({
|
|
@@ -38,16 +40,18 @@ const removeFromTree = createServerFn({
|
|
|
38
40
|
collectionPath: path
|
|
39
41
|
}
|
|
40
42
|
}).log(getLogger());
|
|
41
|
-
await removeAdminTreeNode(getAdminBylineClient().collection(path), documentId, {
|
|
43
|
+
const result = await removeAdminTreeNode(getAdminBylineClient().collection(path), documentId, {
|
|
44
|
+
expectedRevision: data.expectedRevision,
|
|
42
45
|
reconcile: data.reconcile
|
|
43
46
|
});
|
|
44
47
|
return {
|
|
45
|
-
status: 'ok'
|
|
48
|
+
status: 'ok',
|
|
49
|
+
...result
|
|
46
50
|
};
|
|
47
|
-
});
|
|
51
|
+
}));
|
|
48
52
|
const getTreeAncestors = createServerFn({
|
|
49
53
|
method: 'GET'
|
|
50
|
-
}).validator((input)=>input).handler(async ({ data })=>{
|
|
54
|
+
}).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>{
|
|
51
55
|
const { collection: path, documentId } = data;
|
|
52
56
|
const config = await ensureCollection(path);
|
|
53
57
|
if (!config) throw ERR_NOT_FOUND({
|
|
@@ -69,10 +73,10 @@ const getTreeAncestors = createServerFn({
|
|
|
69
73
|
path: doc.path
|
|
70
74
|
};
|
|
71
75
|
});
|
|
72
|
-
});
|
|
76
|
+
}));
|
|
73
77
|
const getTreeParent = createServerFn({
|
|
74
78
|
method: 'GET'
|
|
75
|
-
}).validator((input)=>input).handler(async ({ data })=>{
|
|
79
|
+
}).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>{
|
|
76
80
|
const { collection: path, documentId } = data;
|
|
77
81
|
const config = await ensureCollection(path);
|
|
78
82
|
if (!config) throw ERR_NOT_FOUND({
|
|
@@ -82,10 +86,10 @@ const getTreeParent = createServerFn({
|
|
|
82
86
|
}
|
|
83
87
|
}).log(getLogger());
|
|
84
88
|
return getAdminTreeParent(getAdminBylineClient().collection(path), documentId);
|
|
85
|
-
});
|
|
89
|
+
}));
|
|
86
90
|
const getCollectionTree = createServerFn({
|
|
87
91
|
method: 'GET'
|
|
88
|
-
}).validator((input)=>input).handler(async ({ data })=>{
|
|
92
|
+
}).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>{
|
|
89
93
|
const { collection: path } = data;
|
|
90
94
|
const config = await ensureCollection(path);
|
|
91
95
|
if (!config) throw ERR_NOT_FOUND({
|
|
@@ -96,8 +100,7 @@ const getCollectionTree = createServerFn({
|
|
|
96
100
|
}).log(getLogger());
|
|
97
101
|
const handle = getAdminBylineClient().collection(path);
|
|
98
102
|
const readContext = createReadContext();
|
|
99
|
-
const forest = await handle.
|
|
100
|
-
status: 'any',
|
|
103
|
+
const { forest, unplaced } = await handle.getTreeForEdit({
|
|
101
104
|
locale: data.locale,
|
|
102
105
|
_readContext: readContext
|
|
103
106
|
});
|
|
@@ -108,6 +111,7 @@ const getCollectionTree = createServerFn({
|
|
|
108
111
|
const doc = node.document;
|
|
109
112
|
rows.push({
|
|
110
113
|
id: doc.id,
|
|
114
|
+
revision: doc.revision,
|
|
111
115
|
parentId,
|
|
112
116
|
depth,
|
|
113
117
|
unplaced: false,
|
|
@@ -122,9 +126,9 @@ const getCollectionTree = createServerFn({
|
|
|
122
126
|
}
|
|
123
127
|
};
|
|
124
128
|
walk(forest, 0, null);
|
|
125
|
-
const unplaced = await getAdminUnplacedTreeDocuments(handle, placed, readContext, data.locale);
|
|
126
129
|
for (const doc of unplaced)rows.push({
|
|
127
130
|
id: doc.id,
|
|
131
|
+
revision: doc.revision,
|
|
128
132
|
parentId: null,
|
|
129
133
|
depth: 0,
|
|
130
134
|
unplaced: true,
|
|
@@ -143,5 +147,5 @@ const getCollectionTree = createServerFn({
|
|
|
143
147
|
}
|
|
144
148
|
}
|
|
145
149
|
};
|
|
146
|
-
});
|
|
150
|
+
}));
|
|
147
151
|
export { getCollectionTree, getTreeAncestors, getTreeParent, placeTreeNode, removeFromTree };
|
|
@@ -1,31 +1,30 @@
|
|
|
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
|
import type { DocumentPatch } from '@byline/core/patches';
|
|
9
2
|
export declare const updateCollectionDocumentWithPatches: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
|
|
10
3
|
collection: string;
|
|
11
4
|
id: string;
|
|
12
5
|
patches: DocumentPatch[];
|
|
13
|
-
|
|
6
|
+
expectedRevision: number;
|
|
7
|
+
path?: string | null;
|
|
8
|
+
availableLocales?: string[];
|
|
14
9
|
locale?: string;
|
|
15
10
|
}) => {
|
|
16
11
|
collection: string;
|
|
17
12
|
id: string;
|
|
18
13
|
patches: DocumentPatch[];
|
|
19
|
-
|
|
14
|
+
expectedRevision: number;
|
|
15
|
+
path?: string | null;
|
|
16
|
+
availableLocales?: string[];
|
|
20
17
|
locale?: string;
|
|
21
18
|
}, Promise<import("./save-outcome.js").CollectionDocumentCommittedHookFailureResponse | {
|
|
22
19
|
status: 'ok';
|
|
23
20
|
documentId: string;
|
|
24
|
-
documentVersionId
|
|
21
|
+
documentVersionId?: string | undefined;
|
|
22
|
+
revision: number;
|
|
25
23
|
}>>;
|
|
26
24
|
export declare const updateCollectionDocumentSystemFields: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
|
|
27
25
|
collection: string;
|
|
28
26
|
id: string;
|
|
27
|
+
expectedRevision: number;
|
|
29
28
|
locale?: string;
|
|
30
29
|
/** Path override; `null`/omitted means no path write. */
|
|
31
30
|
path?: string | null;
|
|
@@ -34,11 +33,13 @@ export declare const updateCollectionDocumentSystemFields: import("@tanstack/rea
|
|
|
34
33
|
}) => {
|
|
35
34
|
collection: string;
|
|
36
35
|
id: string;
|
|
36
|
+
expectedRevision: number;
|
|
37
37
|
locale?: string;
|
|
38
38
|
/** Path override; `null`/omitted means no path write. */
|
|
39
39
|
path?: string | null;
|
|
40
40
|
/** Editorial advertised-locale set; omitted means no advertised-locale write. */
|
|
41
41
|
availableLocales?: string[];
|
|
42
|
-
}, Promise<{
|
|
42
|
+
}, Promise<import("./save-outcome.js").CollectionDocumentCommittedHookFailureResponse | {
|
|
43
43
|
status: 'ok';
|
|
44
|
+
revision: number;
|
|
44
45
|
}>>;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { withDocumentMutationErrors } from "../document-mutation-errors.js";
|
|
1
2
|
import { createServerFn } from "@tanstack/react-start";
|
|
2
3
|
import { getAdminRequestContext } from "@byline/client/server";
|
|
3
4
|
import { ERR_NOT_FOUND, getLogger, getServerConfig } from "@byline/core";
|
|
4
|
-
import {
|
|
5
|
+
import { saveDocument, updateDocumentSystemFields } from "@byline/core/services";
|
|
5
6
|
import { ensureCollection } from "../../integrations/api-utils.js";
|
|
6
7
|
import { toCommittedDocumentHookFailureResponse } from "./save-outcome.js";
|
|
7
8
|
const updateCollectionDocumentWithPatches = createServerFn({
|
|
8
9
|
method: 'POST'
|
|
9
|
-
}).validator((input)=>input).handler(async ({ data: input })=>{
|
|
10
|
-
const { collection: path, id, patches,
|
|
10
|
+
}).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data: input })=>{
|
|
11
|
+
const { collection: path, id, patches, expectedRevision, locale, path: explicitPath, availableLocales } = input;
|
|
11
12
|
const logger = getLogger();
|
|
12
13
|
const config = await ensureCollection(path);
|
|
13
14
|
if (!config) throw ERR_NOT_FOUND({
|
|
@@ -30,10 +31,12 @@ const updateCollectionDocumentWithPatches = createServerFn({
|
|
|
30
31
|
};
|
|
31
32
|
let result;
|
|
32
33
|
try {
|
|
33
|
-
result = await
|
|
34
|
+
result = await saveDocument(ctx, {
|
|
34
35
|
documentId: id,
|
|
35
36
|
patches,
|
|
36
|
-
|
|
37
|
+
expectedRevision,
|
|
38
|
+
path: explicitPath ?? void 0,
|
|
39
|
+
availableLocales,
|
|
37
40
|
locale: locale ?? serverConfig.i18n.content.defaultLocale
|
|
38
41
|
});
|
|
39
42
|
} catch (error) {
|
|
@@ -44,13 +47,16 @@ const updateCollectionDocumentWithPatches = createServerFn({
|
|
|
44
47
|
return {
|
|
45
48
|
status: 'ok',
|
|
46
49
|
documentId: result.documentId,
|
|
47
|
-
documentVersionId
|
|
50
|
+
...'documentVersionId' in result ? {
|
|
51
|
+
documentVersionId: result.documentVersionId
|
|
52
|
+
} : {},
|
|
53
|
+
revision: result.revision
|
|
48
54
|
};
|
|
49
|
-
});
|
|
55
|
+
}));
|
|
50
56
|
const updateCollectionDocumentSystemFields = createServerFn({
|
|
51
57
|
method: 'POST'
|
|
52
|
-
}).validator((input)=>input).handler(async ({ data: input })=>{
|
|
53
|
-
const { collection: path, id, locale, path: explicitPath, availableLocales } = input;
|
|
58
|
+
}).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data: input })=>{
|
|
59
|
+
const { collection: path, id, expectedRevision, locale, path: explicitPath, availableLocales } = input;
|
|
54
60
|
const logger = getLogger();
|
|
55
61
|
const config = await ensureCollection(path);
|
|
56
62
|
if (!config) throw ERR_NOT_FOUND({
|
|
@@ -71,15 +77,23 @@ const updateCollectionDocumentSystemFields = createServerFn({
|
|
|
71
77
|
slugifier: serverConfig.slugifier,
|
|
72
78
|
requestContext: await getAdminRequestContext()
|
|
73
79
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
try {
|
|
81
|
+
const result = await updateDocumentSystemFields(ctx, {
|
|
82
|
+
expectedRevision,
|
|
83
|
+
documentId: id,
|
|
84
|
+
locale: locale ?? serverConfig.i18n.content.defaultLocale,
|
|
85
|
+
path: explicitPath,
|
|
86
|
+
availableLocales,
|
|
87
|
+
reconcile: true
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
status: 'ok',
|
|
91
|
+
revision: result.revision
|
|
92
|
+
};
|
|
93
|
+
} catch (error) {
|
|
94
|
+
const committedFailure = toCommittedDocumentHookFailureResponse(error);
|
|
95
|
+
if (null != committedFailure) return committedFailure;
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
85
99
|
export { updateCollectionDocumentSystemFields, updateCollectionDocumentWithPatches };
|
|
@@ -9,6 +9,7 @@ export interface UploadFieldResult {
|
|
|
9
9
|
/** Present when the upload endpoint created a document (createDocument=true, the default). */
|
|
10
10
|
documentId?: string;
|
|
11
11
|
documentVersionId?: string;
|
|
12
|
+
revision?: number;
|
|
12
13
|
storedFile: StoredFileValue;
|
|
13
14
|
}
|
|
14
15
|
interface UploadDocumentInput {
|
|
@@ -5,6 +5,7 @@ import { extractImageMeta, generateImageVariants, isBypassMimeType } from "@byli
|
|
|
5
5
|
import { getLogger, withLogContext } from "@byline/core/logger";
|
|
6
6
|
import { uploadField } from "@byline/core/services";
|
|
7
7
|
import { ensureDocumentResource } from "../../integrations/api-utils.js";
|
|
8
|
+
import { withDocumentMutationErrors } from "../document-mutation-errors.js";
|
|
8
9
|
function parseUploadFormData(data) {
|
|
9
10
|
if (!(data instanceof FormData)) throw new Error('Expected FormData upload payload.');
|
|
10
11
|
const collectionEntry = data.get('collection');
|
|
@@ -56,7 +57,7 @@ function resolveUploadField(definition, collectionPath, requested) {
|
|
|
56
57
|
}
|
|
57
58
|
const uploadCollectionField = createServerFn({
|
|
58
59
|
method: 'POST'
|
|
59
|
-
}).validator(parseUploadFormData).handler(async ({ data })=>{
|
|
60
|
+
}).validator(parseUploadFormData).handler(withDocumentMutationErrors(async ({ data })=>{
|
|
60
61
|
const { collectionPath, shouldCreateDocument, fieldName, file, fields } = data;
|
|
61
62
|
const logger = getLogger();
|
|
62
63
|
return withLogContext({
|
|
@@ -114,7 +115,7 @@ const uploadCollectionField = createServerFn({
|
|
|
114
115
|
locale: serverConfig.i18n.content.defaultLocale
|
|
115
116
|
});
|
|
116
117
|
});
|
|
117
|
-
});
|
|
118
|
+
}));
|
|
118
119
|
async function upload_uploadField(collection, formData, createDocument = true) {
|
|
119
120
|
const payload = new FormData();
|
|
120
121
|
for (const [key, value] of formData.entries())payload.append(key, value);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Error subclasses lose custom properties in the server-function serializer.
|
|
2
|
+
* Throw only validated plain data for document failures; never transport causes,
|
|
3
|
+
* SQL, hook messages or an unverified current revision. */
|
|
4
|
+
export declare function documentMutationError(error: unknown): unknown;
|
|
5
|
+
export declare function withDocumentMutationErrors<T, R>(handler: (input: T) => Promise<R>): (input: T) => Promise<R>;
|
|
@@ -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").
|
|
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
|
-
|
|
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<
|
|
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 };
|