@byline/host-tanstack-start 3.21.0 → 4.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/admin-activity/list.js +15 -5
- package/dist/admin-shell/admin-roles/delete.js +2 -1
- package/dist/admin-shell/admin-roles/list.js +2 -1
- package/dist/admin-shell/admin-users/delete.js +2 -1
- package/dist/admin-shell/admin-users/list.js +4 -7
- package/dist/admin-shell/chrome/admin-app-bar.js +2 -1
- package/dist/admin-shell/chrome/admin-menu-drawer.js +9 -8
- package/dist/admin-shell/chrome/admin-menu-provider.js +2 -1
- package/dist/admin-shell/chrome/dashboard.js +4 -3
- package/dist/admin-shell/chrome/sign-in-page.d.ts +2 -2
- package/dist/admin-shell/chrome/sign-in-page.js +2 -2
- package/dist/admin-shell/collections/create.js +4 -3
- package/dist/admin-shell/collections/document-history.js +6 -1
- package/dist/admin-shell/collections/edit.js +18 -14
- package/dist/admin-shell/collections/history.js +5 -4
- package/dist/admin-shell/collections/list.js +8 -7
- package/dist/admin-shell/collections/restore-version-modal.js +2 -1
- package/dist/admin-shell/collections/tree-list.js +53 -17
- package/dist/admin-shell/collections/view-menu.js +9 -8
- package/dist/auth/auth-context.d.ts +13 -0
- package/dist/auth/auth-context.js +4 -0
- package/dist/auth/request-scope.d.ts +16 -0
- package/dist/auth/request-scope.js +24 -0
- package/dist/integrations/abilities.js +8 -7
- package/dist/integrations/byline-public-client.d.ts +38 -0
- package/dist/integrations/byline-public-client.js +8 -4
- package/dist/integrations/byline-viewer-client.d.ts +6 -0
- package/dist/integrations/byline-viewer-client.js +22 -17
- package/dist/integrations/start-errors.js +2 -1
- package/dist/routes/admin-path.d.ts +22 -0
- package/dist/routes/admin-path.js +35 -0
- package/dist/routes/create-admin-account-route.js +3 -2
- package/dist/routes/create-admin-activity-route.js +3 -2
- package/dist/routes/create-admin-dashboard-route.js +2 -1
- package/dist/routes/create-admin-layout-route.d.ts +1 -1
- package/dist/routes/create-admin-layout-route.js +3 -1
- package/dist/routes/create-admin-permissions-route.js +3 -2
- package/dist/routes/create-admin-role-edit-route.js +4 -3
- package/dist/routes/create-admin-roles-list-route.js +3 -2
- package/dist/routes/create-admin-user-edit-route.js +4 -3
- package/dist/routes/create-admin-users-list-route.js +3 -2
- package/dist/routes/create-collection-api-route.js +5 -4
- package/dist/routes/create-collection-create-route.js +4 -3
- package/dist/routes/create-collection-edit-route.js +4 -3
- package/dist/routes/create-collection-history-route.js +5 -4
- package/dist/routes/create-collection-list-route.js +3 -2
- package/dist/routes/create-sign-in-route.js +3 -2
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.js +2 -0
- package/dist/routes/sign-in-path.d.ts +13 -0
- package/dist/routes/sign-in-path.js +19 -0
- package/dist/server-fns/collections/delete-outcome.d.ts +30 -0
- package/dist/server-fns/collections/delete-outcome.js +34 -0
- package/dist/server-fns/collections/delete.d.ts +1 -4
- package/dist/server-fns/collections/delete.js +2 -4
- package/dist/server-fns/collections/index.d.ts +1 -0
- package/dist/server-fns/collections/index.js +1 -0
- package/dist/server-fns/collections/tree-mutation.d.ts +46 -0
- package/dist/server-fns/collections/tree-mutation.js +109 -0
- package/dist/server-fns/collections/tree-read.d.ts +6 -0
- package/dist/server-fns/collections/tree-read.js +15 -0
- package/dist/server-fns/collections/tree.d.ts +5 -4
- package/dist/server-fns/collections/tree.js +15 -12
- package/dist/server-fns/collections/update.js +2 -1
- package/dist/vite/client-hook-boundary.d.ts +19 -0
- package/dist/vite/client-hook-boundary.js +20 -0
- package/dist/vite/index.d.ts +8 -0
- package/dist/vite/index.js +1 -0
- package/package.json +15 -9
- package/src/admin-shell/admin-activity/list.tsx +13 -3
- package/src/admin-shell/admin-roles/delete.tsx +2 -1
- package/src/admin-shell/admin-roles/list.tsx +2 -1
- package/src/admin-shell/admin-users/delete.tsx +3 -2
- package/src/admin-shell/admin-users/list.tsx +9 -8
- package/src/admin-shell/chrome/admin-app-bar.tsx +2 -1
- package/src/admin-shell/chrome/admin-menu-drawer.tsx +13 -8
- package/src/admin-shell/chrome/admin-menu-provider.tsx +2 -1
- package/src/admin-shell/chrome/dashboard.tsx +4 -3
- package/src/admin-shell/chrome/sign-in-page.tsx +3 -3
- package/src/admin-shell/collections/create.tsx +4 -3
- package/src/admin-shell/collections/document-history.tsx +5 -0
- package/src/admin-shell/collections/edit.tsx +20 -13
- package/src/admin-shell/collections/history.tsx +6 -5
- package/src/admin-shell/collections/list.tsx +8 -7
- package/src/admin-shell/collections/restore-version-modal.tsx +2 -1
- package/src/admin-shell/collections/tree-list.tsx +78 -22
- package/src/admin-shell/collections/view-menu.tsx +9 -8
- package/src/auth/auth-context.test.node.ts +87 -0
- package/src/auth/auth-context.ts +18 -0
- package/src/auth/request-scope.test.node.ts +93 -0
- package/src/auth/request-scope.ts +62 -0
- package/src/integrations/abilities.tsx +14 -4
- package/src/integrations/byline-client.ts +6 -4
- package/src/integrations/byline-public-client.ts +18 -4
- package/src/integrations/byline-viewer-client.ts +30 -18
- package/src/integrations/request-context-contract.test.node.ts +131 -0
- package/src/integrations/request-context-factories.test.node.ts +140 -0
- package/src/integrations/start-errors.test.node.ts +22 -0
- package/src/integrations/start-errors.ts +1 -0
- package/src/routes/admin-path.test.node.ts +129 -0
- package/src/routes/admin-path.ts +66 -0
- package/src/routes/auth-routes.test.node.ts +39 -0
- package/src/routes/create-admin-account-route.tsx +3 -2
- package/src/routes/create-admin-activity-route.tsx +3 -2
- package/src/routes/create-admin-dashboard-route.tsx +2 -1
- package/src/routes/create-admin-layout-route.tsx +4 -4
- package/src/routes/create-admin-permissions-route.tsx +3 -2
- package/src/routes/create-admin-role-edit-route.tsx +7 -3
- package/src/routes/create-admin-roles-list-route.tsx +3 -2
- package/src/routes/create-admin-user-edit-route.tsx +7 -3
- package/src/routes/create-admin-users-list-route.tsx +3 -2
- package/src/routes/create-collection-api-route.tsx +8 -4
- package/src/routes/create-collection-create-route.tsx +7 -3
- package/src/routes/create-collection-edit-route.tsx +7 -3
- package/src/routes/create-collection-history-route.tsx +8 -4
- package/src/routes/create-collection-list-route.tsx +3 -2
- package/src/routes/create-sign-in-route.tsx +9 -3
- package/src/routes/index.ts +11 -0
- package/src/routes/sign-in-path.test.node.ts +79 -0
- package/src/routes/sign-in-path.ts +36 -0
- package/src/server-fns/collections/delete-outcome.test.node.ts +72 -0
- package/src/server-fns/collections/delete-outcome.ts +80 -0
- package/src/server-fns/collections/delete.ts +2 -1
- package/src/server-fns/collections/get.ts +2 -0
- package/src/server-fns/collections/index.ts +1 -0
- package/src/server-fns/collections/tree-mutation.test.node.ts +225 -0
- package/src/server-fns/collections/tree-mutation.ts +165 -0
- package/src/server-fns/collections/tree-read.test.node.ts +46 -0
- package/src/server-fns/collections/tree-read.ts +26 -0
- package/src/server-fns/collections/tree.ts +26 -10
- package/src/server-fns/collections/update.ts +3 -0
- package/src/vite/client-hook-boundary.test.node.ts +50 -0
- package/src/vite/client-hook-boundary.ts +45 -0
- package/src/vite/index.ts +9 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ErrorCodes, TREE_HOOK_COMMITTED_MARKER, TREE_PLACEMENT_STALE_MARKER } from "@byline/core";
|
|
2
|
+
function hasErrorCode(error, code) {
|
|
3
|
+
return 'object' == typeof error && null != error && 'code' in error && error.code === code;
|
|
4
|
+
}
|
|
5
|
+
function hasMessageMarker(error, marker) {
|
|
6
|
+
return error instanceof Error && error.message.startsWith(marker);
|
|
7
|
+
}
|
|
8
|
+
function isTreeMutationConflict(error) {
|
|
9
|
+
if (hasErrorCode(error, ErrorCodes.CONFLICT)) return true;
|
|
10
|
+
return hasMessageMarker(error, TREE_PLACEMENT_STALE_MARKER);
|
|
11
|
+
}
|
|
12
|
+
function isCommittedTreeHookFailure(error) {
|
|
13
|
+
if (hasErrorCode(error, ErrorCodes.TREE_HOOK_COMMITTED)) return true;
|
|
14
|
+
return hasMessageMarker(error, TREE_HOOK_COMMITTED_MARKER);
|
|
15
|
+
}
|
|
16
|
+
function getAdminTreeMoveUiEffects(outcome) {
|
|
17
|
+
if ('mutation-failed' === outcome.status) return {
|
|
18
|
+
rollback: true,
|
|
19
|
+
preserveOptimistic: false,
|
|
20
|
+
structuralFailure: isTreeMutationConflict(outcome.error) ? 'conflict' : 'generic',
|
|
21
|
+
committedHookWarning: false,
|
|
22
|
+
refreshWarning: null != outcome.refreshError
|
|
23
|
+
};
|
|
24
|
+
if ('committed-hook-failed' === outcome.status) return {
|
|
25
|
+
rollback: false,
|
|
26
|
+
preserveOptimistic: null != outcome.refreshError,
|
|
27
|
+
structuralFailure: null,
|
|
28
|
+
committedHookWarning: true,
|
|
29
|
+
refreshWarning: null != outcome.refreshError
|
|
30
|
+
};
|
|
31
|
+
if ('refresh-failed' === outcome.status) return {
|
|
32
|
+
rollback: false,
|
|
33
|
+
preserveOptimistic: true,
|
|
34
|
+
structuralFailure: null,
|
|
35
|
+
committedHookWarning: false,
|
|
36
|
+
refreshWarning: true
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
rollback: 'suppressed' === outcome.status,
|
|
40
|
+
preserveOptimistic: false,
|
|
41
|
+
structuralFailure: null,
|
|
42
|
+
committedHookWarning: false,
|
|
43
|
+
refreshWarning: false
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function shouldSyncAdminTreeRows(rows, preservedRows, isMoving) {
|
|
47
|
+
return !isMoving && rows !== preservedRows;
|
|
48
|
+
}
|
|
49
|
+
async function executeAdminTreeMove(mutate, refresh) {
|
|
50
|
+
try {
|
|
51
|
+
await mutate();
|
|
52
|
+
} catch (error) {
|
|
53
|
+
const status = isCommittedTreeHookFailure(error) ? 'committed-hook-failed' : 'mutation-failed';
|
|
54
|
+
try {
|
|
55
|
+
await refresh();
|
|
56
|
+
return {
|
|
57
|
+
status,
|
|
58
|
+
error
|
|
59
|
+
};
|
|
60
|
+
} catch (refreshError) {
|
|
61
|
+
return {
|
|
62
|
+
status,
|
|
63
|
+
error,
|
|
64
|
+
refreshError
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
await refresh();
|
|
70
|
+
return {
|
|
71
|
+
status: 'ok'
|
|
72
|
+
};
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return {
|
|
75
|
+
status: 'refresh-failed',
|
|
76
|
+
error
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function createAdminTreeMoveController() {
|
|
81
|
+
let moving = false;
|
|
82
|
+
return {
|
|
83
|
+
isMoving: ()=>moving,
|
|
84
|
+
execute: async (mutate, refresh)=>{
|
|
85
|
+
if (moving) return {
|
|
86
|
+
status: 'suppressed'
|
|
87
|
+
};
|
|
88
|
+
moving = true;
|
|
89
|
+
try {
|
|
90
|
+
return await executeAdminTreeMove(mutate, refresh);
|
|
91
|
+
} finally{
|
|
92
|
+
moving = false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function placeAdminTreeNode(handle, documentId, options) {
|
|
98
|
+
return handle.placeTreeNode(documentId, {
|
|
99
|
+
...options,
|
|
100
|
+
reconcile: options.reconcile ?? true
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function removeAdminTreeNode(handle, documentId, options = {}) {
|
|
104
|
+
return handle.removeFromTree(documentId, {
|
|
105
|
+
...options,
|
|
106
|
+
reconcile: options.reconcile ?? true
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
export { createAdminTreeMoveController, executeAdminTreeMove, getAdminTreeMoveUiEffects, isCommittedTreeHookFailure, isTreeMutationConflict, placeAdminTreeNode, removeAdminTreeNode, shouldSyncAdminTreeRows };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ClientDocument, CollectionHandle, TreeParentResult } from '@byline/client';
|
|
2
|
+
import type { ReadContext } from '@byline/core';
|
|
3
|
+
/** Admin tree placement reads must include drafts. */
|
|
4
|
+
export declare function getAdminTreeParent(handle: Pick<CollectionHandle, 'getTreeParent'>, documentId: string): Promise<TreeParentResult>;
|
|
5
|
+
/** Find unplaced nodes through the same row scope as the structural tree read. */
|
|
6
|
+
export declare function getAdminUnplacedTreeDocuments(handle: Pick<CollectionHandle, 'find'>, placed: ReadonlySet<string>, readContext: ReadContext, locale?: string): Promise<ClientDocument[]>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function getAdminTreeParent(handle, documentId) {
|
|
2
|
+
return handle.getTreeParent(documentId, {
|
|
3
|
+
status: 'any'
|
|
4
|
+
});
|
|
5
|
+
}
|
|
6
|
+
async function getAdminUnplacedTreeDocuments(handle, placed, readContext, locale) {
|
|
7
|
+
const all = await handle.find({
|
|
8
|
+
status: 'any',
|
|
9
|
+
locale,
|
|
10
|
+
pageSize: 1000,
|
|
11
|
+
_readContext: readContext
|
|
12
|
+
});
|
|
13
|
+
return all.docs.filter((doc)=>!placed.has(doc.id));
|
|
14
|
+
}
|
|
15
|
+
export { getAdminTreeParent, getAdminUnplacedTreeDocuments };
|
|
@@ -11,12 +11,14 @@ export declare const placeTreeNode: import("@tanstack/react-start").RequiredFetc
|
|
|
11
11
|
parentDocumentId: string | null;
|
|
12
12
|
beforeDocumentId?: string | null;
|
|
13
13
|
afterDocumentId?: string | null;
|
|
14
|
+
reconcile?: boolean;
|
|
14
15
|
}) => {
|
|
15
16
|
collection: string;
|
|
16
17
|
documentId: string;
|
|
17
18
|
parentDocumentId: string | null;
|
|
18
19
|
beforeDocumentId?: string | null;
|
|
19
20
|
afterDocumentId?: string | null;
|
|
21
|
+
reconcile?: boolean;
|
|
20
22
|
}, Promise<{
|
|
21
23
|
status: 'ok';
|
|
22
24
|
orderKey: string;
|
|
@@ -24,9 +26,11 @@ export declare const placeTreeNode: import("@tanstack/react-start").RequiredFetc
|
|
|
24
26
|
export declare const removeFromTree: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
|
|
25
27
|
collection: string;
|
|
26
28
|
documentId: string;
|
|
29
|
+
reconcile?: boolean;
|
|
27
30
|
}) => {
|
|
28
31
|
collection: string;
|
|
29
32
|
documentId: string;
|
|
33
|
+
reconcile?: boolean;
|
|
30
34
|
}, Promise<{
|
|
31
35
|
status: 'ok';
|
|
32
36
|
}>>;
|
|
@@ -47,10 +51,7 @@ export declare const getTreeParent: import("@tanstack/react-start").RequiredFetc
|
|
|
47
51
|
}) => {
|
|
48
52
|
collection: string;
|
|
49
53
|
documentId: string;
|
|
50
|
-
}, Promise<
|
|
51
|
-
placed: boolean;
|
|
52
|
-
parentDocumentId: string | null;
|
|
53
|
-
}>>;
|
|
54
|
+
}, Promise<import("@byline/client").TreeParentResult>>;
|
|
54
55
|
export interface CollectionTreeRow {
|
|
55
56
|
id: string;
|
|
56
57
|
/** Parent document id, or `null` for a root or unplaced node. Drives the
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createServerFn } from "@tanstack/react-start";
|
|
2
|
-
import { ERR_NOT_FOUND, getLogger } from "@byline/core";
|
|
2
|
+
import { ERR_NOT_FOUND, createReadContext, getLogger } from "@byline/core";
|
|
3
3
|
import { ensureCollection } from "../../integrations/api-utils.js";
|
|
4
4
|
import { getAdminBylineClient } from "../../integrations/byline-client.js";
|
|
5
|
+
import { placeAdminTreeNode, removeAdminTreeNode } from "./tree-mutation.js";
|
|
6
|
+
import { getAdminTreeParent, getAdminUnplacedTreeDocuments } from "./tree-read.js";
|
|
5
7
|
const placeTreeNode = createServerFn({
|
|
6
8
|
method: 'POST'
|
|
7
9
|
}).validator((input)=>input).handler(async ({ data })=>{
|
|
@@ -14,10 +16,11 @@ const placeTreeNode = createServerFn({
|
|
|
14
16
|
}
|
|
15
17
|
}).log(getLogger());
|
|
16
18
|
const handle = getAdminBylineClient().collection(path);
|
|
17
|
-
const result = await handle
|
|
19
|
+
const result = await placeAdminTreeNode(handle, documentId, {
|
|
18
20
|
parentDocumentId: data.parentDocumentId,
|
|
19
21
|
beforeDocumentId: data.beforeDocumentId ?? null,
|
|
20
|
-
afterDocumentId: data.afterDocumentId ?? null
|
|
22
|
+
afterDocumentId: data.afterDocumentId ?? null,
|
|
23
|
+
reconcile: data.reconcile
|
|
21
24
|
});
|
|
22
25
|
return {
|
|
23
26
|
status: 'ok',
|
|
@@ -35,7 +38,9 @@ const removeFromTree = createServerFn({
|
|
|
35
38
|
collectionPath: path
|
|
36
39
|
}
|
|
37
40
|
}).log(getLogger());
|
|
38
|
-
await getAdminBylineClient().collection(path)
|
|
41
|
+
await removeAdminTreeNode(getAdminBylineClient().collection(path), documentId, {
|
|
42
|
+
reconcile: data.reconcile
|
|
43
|
+
});
|
|
39
44
|
return {
|
|
40
45
|
status: 'ok'
|
|
41
46
|
};
|
|
@@ -76,7 +81,7 @@ const getTreeParent = createServerFn({
|
|
|
76
81
|
collectionPath: path
|
|
77
82
|
}
|
|
78
83
|
}).log(getLogger());
|
|
79
|
-
return getAdminBylineClient().collection(path)
|
|
84
|
+
return getAdminTreeParent(getAdminBylineClient().collection(path), documentId);
|
|
80
85
|
});
|
|
81
86
|
const getCollectionTree = createServerFn({
|
|
82
87
|
method: 'GET'
|
|
@@ -90,9 +95,11 @@ const getCollectionTree = createServerFn({
|
|
|
90
95
|
}
|
|
91
96
|
}).log(getLogger());
|
|
92
97
|
const handle = getAdminBylineClient().collection(path);
|
|
98
|
+
const readContext = createReadContext();
|
|
93
99
|
const forest = await handle.getSubtree({
|
|
94
100
|
status: 'any',
|
|
95
|
-
locale: data.locale
|
|
101
|
+
locale: data.locale,
|
|
102
|
+
_readContext: readContext
|
|
96
103
|
});
|
|
97
104
|
const rows = [];
|
|
98
105
|
const placed = new Set();
|
|
@@ -115,12 +122,8 @@ const getCollectionTree = createServerFn({
|
|
|
115
122
|
}
|
|
116
123
|
};
|
|
117
124
|
walk(forest, 0, null);
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
pageSize: 1000,
|
|
121
|
-
_bypassBeforeRead: true
|
|
122
|
-
});
|
|
123
|
-
for (const doc of all.docs)if (!placed.has(doc.id)) rows.push({
|
|
125
|
+
const unplaced = await getAdminUnplacedTreeDocuments(handle, placed, readContext, data.locale);
|
|
126
|
+
for (const doc of unplaced)rows.push({
|
|
124
127
|
id: doc.id,
|
|
125
128
|
parentId: null,
|
|
126
129
|
depth: 0,
|
|
@@ -65,7 +65,8 @@ const updateCollectionDocumentSystemFields = createServerFn({
|
|
|
65
65
|
documentId: id,
|
|
66
66
|
locale: locale ?? serverConfig.i18n.content.defaultLocale,
|
|
67
67
|
path: explicitPath,
|
|
68
|
-
availableLocales
|
|
68
|
+
availableLocales,
|
|
69
|
+
reconcile: true
|
|
69
70
|
});
|
|
70
71
|
return {
|
|
71
72
|
status: 'ok'
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* Production client-bundle guard for server-only Byline lifecycle modules.
|
|
10
|
+
*
|
|
11
|
+
* Collection schemas are isomorphic, but registered lifecycle hooks may
|
|
12
|
+
* import Node APIs, caches, storage SDKs, and other server-only dependencies.
|
|
13
|
+
* This Vite plugin inspects emitted client chunks and fails the build if a
|
|
14
|
+
* hook implementation, server hook registry, or shared lifecycle helper
|
|
15
|
+
* becomes reachable from the browser bundle.
|
|
16
|
+
*/
|
|
17
|
+
import type { Plugin } from 'vite';
|
|
18
|
+
export declare function findServerHookModules(moduleIds: Iterable<string>): string[];
|
|
19
|
+
export declare function bylineClientHookBoundary(): Plugin;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const SERVER_HOOK_MODULE = /\/byline\/collections\/(?:.+\/)?(?:hooks|[^/]+-hooks|[^/]+-lifecycle-hooks|[^/]+-side-effects)\.[cm]?[jt]sx?$/;
|
|
2
|
+
function findServerHookModules(moduleIds) {
|
|
3
|
+
return [
|
|
4
|
+
...moduleIds
|
|
5
|
+
].filter((moduleId)=>{
|
|
6
|
+
const normalized = moduleId.replaceAll('\\', '/').split('?', 1)[0];
|
|
7
|
+
return SERVER_HOOK_MODULE.test(normalized);
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function bylineClientHookBoundary() {
|
|
11
|
+
return {
|
|
12
|
+
name: 'byline:client-hook-boundary',
|
|
13
|
+
generateBundle (_options, bundle) {
|
|
14
|
+
if (this.environment?.name !== 'client') return;
|
|
15
|
+
const leaked = findServerHookModules(Object.values(bundle).flatMap((output)=>'chunk' === output.type ? Object.keys(output.modules) : []));
|
|
16
|
+
if (leaked.length > 0) this.error(`server lifecycle modules entered the client bundle:\n${leaked.join('\n')}`);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export { bylineClientHookBoundary, findServerHookModules };
|
|
@@ -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 { bylineClientHookBoundary } from './client-hook-boundary.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { bylineClientHookBoundary } from "./client-hook-boundary.js";
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "4.0.0",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20.9.0"
|
|
9
9
|
},
|
|
@@ -108,6 +108,10 @@
|
|
|
108
108
|
"./routes/*": {
|
|
109
109
|
"types": "./dist/routes/*.d.ts",
|
|
110
110
|
"import": "./dist/routes/*.js"
|
|
111
|
+
},
|
|
112
|
+
"./vite": {
|
|
113
|
+
"types": "./dist/vite/index.d.ts",
|
|
114
|
+
"import": "./dist/vite/index.js"
|
|
111
115
|
}
|
|
112
116
|
},
|
|
113
117
|
"dependencies": {
|
|
@@ -119,19 +123,20 @@
|
|
|
119
123
|
"react-swipeable": "^7.0.2",
|
|
120
124
|
"uuid": "^14.0.1",
|
|
121
125
|
"zod": "^4.4.3",
|
|
122
|
-
"@byline/admin": "
|
|
123
|
-
"@byline/
|
|
124
|
-
"@byline/
|
|
125
|
-
"@byline/
|
|
126
|
-
"@byline/
|
|
127
|
-
"@byline/
|
|
128
|
-
"@byline/
|
|
126
|
+
"@byline/admin": "4.0.0",
|
|
127
|
+
"@byline/auth": "4.0.0",
|
|
128
|
+
"@byline/ui": "4.0.0",
|
|
129
|
+
"@byline/i18n": "4.0.0",
|
|
130
|
+
"@byline/ai": "4.0.0",
|
|
131
|
+
"@byline/client": "4.0.0",
|
|
132
|
+
"@byline/core": "4.0.0"
|
|
129
133
|
},
|
|
130
134
|
"peerDependencies": {
|
|
131
135
|
"@tanstack/react-router": "^1.167.0",
|
|
132
136
|
"@tanstack/react-start": "^1.167.0",
|
|
133
137
|
"react": "^19.0.0",
|
|
134
|
-
"react-dom": "^19.0.0"
|
|
138
|
+
"react-dom": "^19.0.0",
|
|
139
|
+
"vite": "^8.0.0"
|
|
135
140
|
},
|
|
136
141
|
"devDependencies": {
|
|
137
142
|
"@biomejs/biome": "2.5.2",
|
|
@@ -148,6 +153,7 @@
|
|
|
148
153
|
"rimraf": "^6.1.3",
|
|
149
154
|
"typescript": "^7.0.2",
|
|
150
155
|
"typescript-plugin-css-modules": "^5.2.0",
|
|
156
|
+
"vite": "^8.1.3",
|
|
151
157
|
"vitest": "^4.1.10"
|
|
152
158
|
},
|
|
153
159
|
"publishConfig": {
|
|
@@ -15,6 +15,7 @@ import { useTranslation } from '@byline/i18n/react'
|
|
|
15
15
|
import { Button, Container, Section, Select, Table } from '@byline/ui/react'
|
|
16
16
|
import cx from 'classnames'
|
|
17
17
|
|
|
18
|
+
import { getAdminRoutePath } from '../../routes/admin-path.js'
|
|
18
19
|
import { Link, useNavigate } from '../chrome/loose-router.js'
|
|
19
20
|
import { RouterPager } from '../chrome/router-pager.js'
|
|
20
21
|
import styles from './list.module.css'
|
|
@@ -33,6 +34,10 @@ const ACTION_KEYS: Record<string, string> = {
|
|
|
33
34
|
'document.locales.changed': 'activity.actions.localesChanged',
|
|
34
35
|
'document.status.changed': 'activity.actions.statusChanged',
|
|
35
36
|
'document.deleted': 'activity.actions.deleted',
|
|
37
|
+
'document.tree.placed': 'activity.actions.treePlaced',
|
|
38
|
+
'document.tree.reparented': 'activity.actions.treeReparented',
|
|
39
|
+
'document.tree.reordered': 'activity.actions.treeReordered',
|
|
40
|
+
'document.tree.removed': 'activity.actions.treeRemoved',
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
/** The selectable action types, in display order. `_all` clears the filter. */
|
|
@@ -43,12 +48,17 @@ const ACTION_FILTER_VALUES = [
|
|
|
43
48
|
'document.path.changed',
|
|
44
49
|
'document.locales.changed',
|
|
45
50
|
'document.deleted',
|
|
51
|
+
'document.tree.placed',
|
|
52
|
+
'document.tree.reparented',
|
|
53
|
+
'document.tree.reordered',
|
|
54
|
+
'document.tree.removed',
|
|
46
55
|
] as const
|
|
47
56
|
|
|
48
57
|
/** Render an audit before/after value inline: arrays comma-join, nullish → em-dash. */
|
|
49
58
|
function formatAuditValue(value: unknown): string {
|
|
50
59
|
if (value == null) return '—'
|
|
51
60
|
if (Array.isArray(value)) return value.length > 0 ? value.join(', ') : '—'
|
|
61
|
+
if (typeof value === 'object') return JSON.stringify(value)
|
|
52
62
|
return String(value)
|
|
53
63
|
}
|
|
54
64
|
|
|
@@ -81,7 +91,7 @@ export const ActivitySystemView = ({ data }: { data: SystemActivityResponse }) =
|
|
|
81
91
|
} else {
|
|
82
92
|
params[key] = value
|
|
83
93
|
}
|
|
84
|
-
navigate({ to: '
|
|
94
|
+
navigate({ to: getAdminRoutePath('activity'), search: params })
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
const collectionItems = [
|
|
@@ -134,7 +144,7 @@ export const ActivitySystemView = ({ data }: { data: SystemActivityResponse }) =
|
|
|
134
144
|
<Button
|
|
135
145
|
size="sm"
|
|
136
146
|
variant="text"
|
|
137
|
-
onClick={() => navigate({ to: '
|
|
147
|
+
onClick={() => navigate({ to: getAdminRoutePath('activity'), search: {} })}
|
|
138
148
|
>
|
|
139
149
|
{t('activity.filters.clear')}
|
|
140
150
|
</Button>
|
|
@@ -190,7 +200,7 @@ export const ActivitySystemView = ({ data }: { data: SystemActivityResponse }) =
|
|
|
190
200
|
<Table.Cell>
|
|
191
201
|
{col != null && entry.documentId != null ? (
|
|
192
202
|
<Link
|
|
193
|
-
to={'
|
|
203
|
+
to={getAdminRoutePath('collections', '$collection', '$id')}
|
|
194
204
|
params={{ collection: col.path, id: entry.documentId }}
|
|
195
205
|
>
|
|
196
206
|
{collectionLabel}
|
|
@@ -24,6 +24,7 @@ import { useTranslation } from '@byline/i18n/react'
|
|
|
24
24
|
import { Alert, Button, LoaderEllipsis, Modal } from '@byline/ui/react'
|
|
25
25
|
import cx from 'classnames'
|
|
26
26
|
|
|
27
|
+
import { getAdminRoutePath } from '../../routes/admin-path.js'
|
|
27
28
|
import { type AdminRoleResponse, deleteAdminRole } from '../../server-fns/admin-roles/index.js'
|
|
28
29
|
import { useNavigate } from '../chrome/loose-router.js'
|
|
29
30
|
import styles from './delete.module.css'
|
|
@@ -52,7 +53,7 @@ export function DeleteRole({ role, onClose }: DeleteRoleProps) {
|
|
|
52
53
|
// forced to re-fetch — without this, TanStack Router can serve
|
|
53
54
|
// the cached pre-delete list.
|
|
54
55
|
await router.invalidate()
|
|
55
|
-
navigate({ to: '
|
|
56
|
+
navigate({ to: getAdminRoutePath('roles') })
|
|
56
57
|
} catch (err) {
|
|
57
58
|
const code = getErrorCode(err)
|
|
58
59
|
if (code === 'admin.roles.versionConflict') {
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
} from '@byline/ui/react'
|
|
47
47
|
import cx from 'classnames'
|
|
48
48
|
|
|
49
|
+
import { getAdminRoutePath } from '../../routes/admin-path.js'
|
|
49
50
|
import { reorderAdminRoles } from '../../server-fns/admin-roles/reorder.js'
|
|
50
51
|
import { Link } from '../chrome/loose-router.js'
|
|
51
52
|
import { formatNumber } from '../chrome/utils.js'
|
|
@@ -97,7 +98,7 @@ const DraggableRow: React.FC<DraggableRowProps> = ({ item, disabled }) => {
|
|
|
97
98
|
</div>
|
|
98
99
|
</Table.Cell>
|
|
99
100
|
<Table.Cell>
|
|
100
|
-
<Link to={'
|
|
101
|
+
<Link to={getAdminRoutePath('roles', '$id')} params={{ id: item.id }}>
|
|
101
102
|
{item.name}
|
|
102
103
|
</Link>
|
|
103
104
|
</Table.Cell>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* component only lays out the content + action row — the modal header
|
|
16
16
|
* and dismiss button are already drawn by the container.
|
|
17
17
|
*
|
|
18
|
-
* On success we navigate
|
|
18
|
+
* On success we navigate back to the configured admin users list; the record that hosts
|
|
19
19
|
* this view no longer exists. The `onSuccess` callback on `PanelProps`
|
|
20
20
|
* is unused here because `delete` produces no updated user.
|
|
21
21
|
*/
|
|
@@ -27,6 +27,7 @@ import { useTranslation } from '@byline/i18n/react'
|
|
|
27
27
|
import { Alert, Button, LoaderEllipsis, Modal } from '@byline/ui/react'
|
|
28
28
|
import cx from 'classnames'
|
|
29
29
|
|
|
30
|
+
import { getAdminRoutePath } from '../../routes/admin-path.js'
|
|
30
31
|
import { type AdminUserResponse, deleteAdminUser } from '../../server-fns/admin-users/index.js'
|
|
31
32
|
import { useNavigate } from '../chrome/loose-router.js'
|
|
32
33
|
import styles from './delete.module.css'
|
|
@@ -62,7 +63,7 @@ export function DeleteUser({ user, onClose }: DeleteUserProps) {
|
|
|
62
63
|
// forced to re-fetch — without this, TanStack Router can serve
|
|
63
64
|
// the cached pre-delete list.
|
|
64
65
|
await router.invalidate()
|
|
65
|
-
navigate({ to: '
|
|
66
|
+
navigate({ to: getAdminRoutePath('users') })
|
|
66
67
|
} catch (err) {
|
|
67
68
|
const code = getErrorCode(err)
|
|
68
69
|
if (code === 'admin.users.selfDeleteForbidden') {
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from '@byline/ui/react'
|
|
29
29
|
import cx from 'classnames'
|
|
30
30
|
|
|
31
|
+
import { getAdminRoutePath } from '../../routes/admin-path.js'
|
|
31
32
|
import { Link, useNavigate } from '../chrome/loose-router.js'
|
|
32
33
|
import { RouterPager } from '../chrome/router-pager.js'
|
|
33
34
|
import {
|
|
@@ -45,7 +46,7 @@ import type {
|
|
|
45
46
|
// are fixed at module scope; the `label` field is filled in per-render
|
|
46
47
|
// inside the component so it can flow through `t(...)`. Same pattern the
|
|
47
48
|
// account container's `panels` map uses.
|
|
48
|
-
type ColumnTemplate = Omit<TableHeadingCellSortableProps, 'lng' | 'label'> & {
|
|
49
|
+
type ColumnTemplate = Omit<TableHeadingCellSortableProps, 'lng' | 'label' | 'path'> & {
|
|
49
50
|
labelKey: string
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -53,7 +54,6 @@ const columnTemplates: ColumnTemplate[] = [
|
|
|
53
54
|
{
|
|
54
55
|
fieldName: 'given_name',
|
|
55
56
|
labelKey: 'adminUsers.list.columns.givenName',
|
|
56
|
-
path: '/admin/users',
|
|
57
57
|
sortable: true,
|
|
58
58
|
scope: 'col',
|
|
59
59
|
align: 'left',
|
|
@@ -62,7 +62,6 @@ const columnTemplates: ColumnTemplate[] = [
|
|
|
62
62
|
{
|
|
63
63
|
fieldName: 'family_name',
|
|
64
64
|
labelKey: 'adminUsers.list.columns.familyName',
|
|
65
|
-
path: '/admin/users',
|
|
66
65
|
sortable: true,
|
|
67
66
|
scope: 'col',
|
|
68
67
|
align: 'left',
|
|
@@ -71,7 +70,6 @@ const columnTemplates: ColumnTemplate[] = [
|
|
|
71
70
|
{
|
|
72
71
|
fieldName: 'email',
|
|
73
72
|
labelKey: 'adminUsers.list.columns.email',
|
|
74
|
-
path: '/admin/users',
|
|
75
73
|
sortable: true,
|
|
76
74
|
scope: 'col',
|
|
77
75
|
align: 'left',
|
|
@@ -80,7 +78,6 @@ const columnTemplates: ColumnTemplate[] = [
|
|
|
80
78
|
{
|
|
81
79
|
fieldName: 'updated_at',
|
|
82
80
|
labelKey: 'adminUsers.list.columns.updated',
|
|
83
|
-
path: '/admin/users',
|
|
84
81
|
sortable: true,
|
|
85
82
|
scope: 'col',
|
|
86
83
|
align: 'right',
|
|
@@ -89,7 +86,6 @@ const columnTemplates: ColumnTemplate[] = [
|
|
|
89
86
|
{
|
|
90
87
|
fieldName: 'created_at',
|
|
91
88
|
labelKey: 'adminUsers.list.columns.created',
|
|
92
|
-
path: '/admin/users',
|
|
93
89
|
sortable: true,
|
|
94
90
|
scope: 'col',
|
|
95
91
|
align: 'right',
|
|
@@ -136,7 +132,12 @@ export function AdminUsersListView({ data }: { data: AdminUserListResponse }) {
|
|
|
136
132
|
const [isCreateDrawerOpen, setIsCreateDrawerOpen] = useState(false)
|
|
137
133
|
|
|
138
134
|
const tableColumnDefs = useMemo(
|
|
139
|
-
() =>
|
|
135
|
+
() =>
|
|
136
|
+
columnTemplates.map(({ labelKey, ...rest }) => ({
|
|
137
|
+
...rest,
|
|
138
|
+
label: t(labelKey),
|
|
139
|
+
path: getAdminRoutePath('users'),
|
|
140
|
+
})),
|
|
140
141
|
[t]
|
|
141
142
|
)
|
|
142
143
|
|
|
@@ -232,7 +233,7 @@ export function AdminUsersListView({ data }: { data: AdminUserListResponse }) {
|
|
|
232
233
|
})}
|
|
233
234
|
>
|
|
234
235
|
<Table.Cell>
|
|
235
|
-
<Link to={'
|
|
236
|
+
<Link to={getAdminRoutePath('users', '$id')} params={{ id: user.id }}>
|
|
236
237
|
{user.given_name ?? (
|
|
237
238
|
<span
|
|
238
239
|
className={cx('muted byline-admin-users-list-not-set', styles.notSet)}
|
|
@@ -14,6 +14,7 @@ import { LanguageMenu, useTranslation } from '@byline/i18n/react'
|
|
|
14
14
|
import { Button } from '@byline/ui/react'
|
|
15
15
|
import cx from 'classnames'
|
|
16
16
|
|
|
17
|
+
import { getSignInRoutePath } from '../../routes/sign-in-path.js'
|
|
17
18
|
import { adminSignOut, type CurrentAdminUser } from '../../server-fns/auth/index.js'
|
|
18
19
|
import styles from './admin-app-bar.module.css'
|
|
19
20
|
import { AdminDrawerToggle } from './admin-drawer-toggle.js'
|
|
@@ -48,7 +49,7 @@ export function AdminAppBar({ user }: AdminAppBarProps) {
|
|
|
48
49
|
console.warn('sign-out request failed', err)
|
|
49
50
|
}
|
|
50
51
|
// Full-page navigation so the admin guard re-runs with cleared cookies.
|
|
51
|
-
window.location.assign(
|
|
52
|
+
window.location.assign(getSignInRoutePath())
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
return (
|
|
@@ -27,6 +27,11 @@ import cx from 'classnames'
|
|
|
27
27
|
import { useSwipeable } from 'react-swipeable'
|
|
28
28
|
|
|
29
29
|
import { useAbilities } from '../../integrations/abilities.jsx'
|
|
30
|
+
import {
|
|
31
|
+
getAdminRoutePath,
|
|
32
|
+
isAdminRoutePathActive,
|
|
33
|
+
isRoutePathWithin,
|
|
34
|
+
} from '../../routes/admin-path.js'
|
|
30
35
|
import styles from './admin-menu-drawer.module.css'
|
|
31
36
|
import { useAdminMenu } from './admin-menu-provider.jsx'
|
|
32
37
|
import { Link } from './loose-router.js'
|
|
@@ -35,8 +40,8 @@ import { PreviewToggle } from './preview-toggle.jsx'
|
|
|
35
40
|
const isActive = (currentPath: string, linkHref: string): boolean => {
|
|
36
41
|
// Root admin highlights only on exact match so it doesn't stay lit on
|
|
37
42
|
// every admin subpath.
|
|
38
|
-
if (linkHref ===
|
|
39
|
-
return currentPath
|
|
43
|
+
if (linkHref === getAdminRoutePath()) return isAdminRoutePathActive(currentPath)
|
|
44
|
+
return isRoutePathWithin(currentPath, linkHref)
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
interface MenuItemProps {
|
|
@@ -119,7 +124,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
|
|
|
119
124
|
<nav className={cx('byline-admin-menu-drawer admin-menu-drawer', styles.nav)}>
|
|
120
125
|
<ul>
|
|
121
126
|
<MenuItem
|
|
122
|
-
to=
|
|
127
|
+
to={getAdminRoutePath()}
|
|
123
128
|
label={t('chrome.menu.dashboard')}
|
|
124
129
|
icon={<HomeIcon width="20px" height="20px" />}
|
|
125
130
|
pathname={pathname}
|
|
@@ -130,7 +135,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
|
|
|
130
135
|
<li className="menu-separator" />
|
|
131
136
|
{canReadUsers && (
|
|
132
137
|
<MenuItem
|
|
133
|
-
to=
|
|
138
|
+
to={getAdminRoutePath('users')}
|
|
134
139
|
label={t('chrome.menu.adminUsers')}
|
|
135
140
|
icon={<UsersIcon width="20px" height="20px" />}
|
|
136
141
|
pathname={pathname}
|
|
@@ -139,7 +144,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
|
|
|
139
144
|
)}
|
|
140
145
|
{canReadRoles && (
|
|
141
146
|
<MenuItem
|
|
142
|
-
to=
|
|
147
|
+
to={getAdminRoutePath('roles')}
|
|
143
148
|
label={t('chrome.menu.adminRoles')}
|
|
144
149
|
icon={<RolesIcon width="20px" height="20px" />}
|
|
145
150
|
pathname={pathname}
|
|
@@ -148,7 +153,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
|
|
|
148
153
|
)}
|
|
149
154
|
{canReadPermissions && (
|
|
150
155
|
<MenuItem
|
|
151
|
-
to=
|
|
156
|
+
to={getAdminRoutePath('permissions')}
|
|
152
157
|
label={t('chrome.menu.permissions')}
|
|
153
158
|
icon={<SettingsSlidersIcon width="20px" height="20px" />}
|
|
154
159
|
pathname={pathname}
|
|
@@ -157,7 +162,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
|
|
|
157
162
|
)}
|
|
158
163
|
{canReadActivity && (
|
|
159
164
|
<MenuItem
|
|
160
|
-
to=
|
|
165
|
+
to={getAdminRoutePath('activity')}
|
|
161
166
|
label={t('chrome.menu.activity')}
|
|
162
167
|
icon={<ActivityIcon width="20px" height="20px" />}
|
|
163
168
|
pathname={pathname}
|
|
@@ -169,7 +174,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
|
|
|
169
174
|
<li className="menu-separator" />
|
|
170
175
|
<PreviewToggle compact={compact} />
|
|
171
176
|
<MenuItem
|
|
172
|
-
to=
|
|
177
|
+
to={getAdminRoutePath('account')}
|
|
173
178
|
label={t('chrome.account')}
|
|
174
179
|
icon={<UserIcon />}
|
|
175
180
|
pathname={pathname}
|