@byline/host-tanstack-start 4.14.1 → 4.16.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/chrome/admin-menu-drawer.d.ts +4 -1
- package/dist/admin-shell/chrome/admin-menu-drawer.js +26 -2
- package/dist/admin-shell/chrome/admin-menu-drawer_module.css +1 -1
- package/dist/admin-shell/collections/edit.js +142 -2
- package/dist/admin-shell/scheduled-publications/list.d.ts +4 -0
- package/dist/admin-shell/scheduled-publications/list.js +295 -0
- package/dist/admin-shell/scheduled-publications/list.module.js +25 -0
- package/dist/admin-shell/scheduled-publications/list_module.css +97 -0
- package/dist/integrations/analytics-agent.d.ts +20 -0
- package/dist/integrations/analytics-agent.js +57 -0
- package/dist/integrations/analytics-events.d.ts +51 -0
- package/dist/integrations/analytics-events.js +129 -0
- package/dist/integrations/serve-analytics-agent.d.ts +8 -0
- package/dist/integrations/serve-analytics-agent.js +11 -0
- package/dist/routes/analytics-range.d.ts +12 -0
- package/dist/routes/analytics-range.js +35 -0
- package/dist/routes/create-admin-analytics-route.d.ts +8 -0
- package/dist/routes/create-admin-analytics-route.js +108 -0
- package/dist/routes/create-admin-layout-route.js +15 -4
- package/dist/routes/create-admin-scheduled-publications-route.d.ts +8 -0
- package/dist/routes/create-admin-scheduled-publications-route.js +66 -0
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.js +2 -0
- package/dist/server-fns/analytics/index.d.ts +9 -0
- package/dist/server-fns/analytics/index.js +2 -0
- package/dist/server-fns/analytics/maintenance.d.ts +22 -0
- package/dist/server-fns/analytics/maintenance.js +42 -0
- package/dist/server-fns/analytics/queries.d.ts +25 -0
- package/dist/server-fns/analytics/queries.js +54 -0
- package/dist/server-fns/collections/get.d.ts +5 -0
- package/dist/server-fns/collections/get.js +19 -3
- package/dist/server-fns/collections/index.d.ts +1 -0
- package/dist/server-fns/collections/index.js +1 -0
- package/dist/server-fns/collections/scheduled-publication-response.d.ts +11 -0
- package/dist/server-fns/collections/scheduled-publication-response.js +5 -0
- package/dist/server-fns/collections/scheduled-publication.d.ts +111 -0
- package/dist/server-fns/collections/scheduled-publication.js +157 -0
- package/dist/vite/analytics-dev.d.ts +22 -0
- package/dist/vite/analytics-dev.js +28 -0
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.js +1 -0
- package/package.json +14 -8
- package/src/admin-shell/chrome/admin-menu-drawer.module.css +7 -1
- package/src/admin-shell/chrome/admin-menu-drawer.tsx +37 -1
- package/src/admin-shell/collections/edit.tsx +140 -1
- package/src/admin-shell/scheduled-publications/list.module.css +105 -0
- package/src/admin-shell/scheduled-publications/list.tsx +292 -0
- package/src/integrations/analytics-agent.tsx +91 -0
- package/src/integrations/analytics-events.test.node.ts +245 -0
- package/src/integrations/analytics-events.ts +224 -0
- package/src/integrations/serve-analytics-agent.test.node.ts +20 -0
- package/src/integrations/serve-analytics-agent.ts +19 -0
- package/src/routes/analytics-range.ts +55 -0
- package/src/routes/create-admin-analytics-route.test.node.ts +46 -0
- package/src/routes/create-admin-analytics-route.tsx +109 -0
- package/src/routes/create-admin-layout-route.tsx +19 -7
- package/src/routes/create-admin-scheduled-publications-route.tsx +83 -0
- package/src/routes/index.ts +2 -0
- package/src/server-fns/analytics/index.ts +23 -0
- package/src/server-fns/analytics/maintenance.ts +54 -0
- package/src/server-fns/analytics/queries.ts +86 -0
- package/src/server-fns/collections/get.ts +30 -1
- package/src/server-fns/collections/index.ts +1 -0
- package/src/server-fns/collections/scheduled-publication-response.test.node.ts +45 -0
- package/src/server-fns/collections/scheduled-publication-response.ts +22 -0
- package/src/server-fns/collections/scheduled-publication.ts +270 -0
- package/src/vite/analytics-dev.test.node.ts +126 -0
- package/src/vite/analytics-dev.ts +56 -0
- package/src/vite/index.ts +1 -0
package/dist/routes/index.d.ts
CHANGED
|
@@ -23,11 +23,13 @@ export { getSignInRoutePath } from './sign-in-path.js';
|
|
|
23
23
|
*/
|
|
24
24
|
export { createAdminAccountRoute } from './create-admin-account-route.js';
|
|
25
25
|
export { createAdminActivityRoute } from './create-admin-activity-route.js';
|
|
26
|
+
export { createAdminAnalyticsRoute } from './create-admin-analytics-route.js';
|
|
26
27
|
export { createAdminDashboardRoute } from './create-admin-dashboard-route.js';
|
|
27
28
|
export { createAdminLayoutRoute } from './create-admin-layout-route.js';
|
|
28
29
|
export { createAdminPermissionsRoute } from './create-admin-permissions-route.js';
|
|
29
30
|
export { createAdminRoleEditRoute } from './create-admin-role-edit-route.js';
|
|
30
31
|
export { createAdminRolesListRoute } from './create-admin-roles-list-route.js';
|
|
32
|
+
export { createAdminScheduledPublicationsRoute } from './create-admin-scheduled-publications-route.js';
|
|
31
33
|
export { createAdminUserEditRoute } from './create-admin-user-edit-route.js';
|
|
32
34
|
export { createAdminUsersListRoute } from './create-admin-users-list-route.js';
|
|
33
35
|
export { createCollectionApiRoute } from './create-collection-api-route.js';
|
package/dist/routes/index.js
CHANGED
|
@@ -2,11 +2,13 @@ export { getAdminRouteId, getAdminRoutePath, isAdminRoutePathActive, isRoutePath
|
|
|
2
2
|
export { getSignInRoutePath } from "./sign-in-path.js";
|
|
3
3
|
export { createAdminAccountRoute } from "./create-admin-account-route.js";
|
|
4
4
|
export { createAdminActivityRoute } from "./create-admin-activity-route.js";
|
|
5
|
+
export { createAdminAnalyticsRoute } from "./create-admin-analytics-route.js";
|
|
5
6
|
export { createAdminDashboardRoute } from "./create-admin-dashboard-route.js";
|
|
6
7
|
export { createAdminLayoutRoute } from "./create-admin-layout-route.js";
|
|
7
8
|
export { createAdminPermissionsRoute } from "./create-admin-permissions-route.js";
|
|
8
9
|
export { createAdminRoleEditRoute } from "./create-admin-role-edit-route.js";
|
|
9
10
|
export { createAdminRolesListRoute } from "./create-admin-roles-list-route.js";
|
|
11
|
+
export { createAdminScheduledPublicationsRoute } from "./create-admin-scheduled-publications-route.js";
|
|
10
12
|
export { createAdminUserEditRoute } from "./create-admin-user-edit-route.js";
|
|
11
13
|
export { createAdminUsersListRoute } from "./create-admin-users-list-route.js";
|
|
12
14
|
export { createCollectionApiRoute } from "./create-collection-api-route.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
export { type DeleteAnalyticsEventsInput, deleteAnalyticsEvents, rebuildAnalyticsDay, } from './maintenance.js';
|
|
9
|
+
export { type AnalyticsRuntimeState, type AnalyticsTopInput, getAnalyticsCountries, getAnalyticsReferrers, getAnalyticsReportCoverage, getAnalyticsRuntime, getAnalyticsSummary, getAnalyticsTop, } from './queries.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
export interface DeleteAnalyticsEventsInput {
|
|
9
|
+
from: string;
|
|
10
|
+
to: string;
|
|
11
|
+
visitorHash?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const deleteAnalyticsEvents: import("@tanstack/react-start").RequiredFetcher<undefined, (input: DeleteAnalyticsEventsInput) => DeleteAnalyticsEventsInput, Promise<{
|
|
14
|
+
deleted: number;
|
|
15
|
+
}>>;
|
|
16
|
+
export declare const rebuildAnalyticsDay: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
|
|
17
|
+
day: string;
|
|
18
|
+
}) => {
|
|
19
|
+
day: string;
|
|
20
|
+
}, Promise<{
|
|
21
|
+
rebuilt: string;
|
|
22
|
+
}>>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
2
|
+
import { assertAdminActor } from "@byline/admin";
|
|
3
|
+
import { ANALYTICS_ABILITIES } from "@byline/admin/analytics";
|
|
4
|
+
import { getAdminRequestContext } from "@byline/client/server";
|
|
5
|
+
const deleteAnalyticsEvents = createServerFn({
|
|
6
|
+
method: 'POST'
|
|
7
|
+
}).validator((input)=>input).handler(async ({ data })=>{
|
|
8
|
+
await assertAnalyticsMaintenance();
|
|
9
|
+
const { getAnalytics } = await import("@byline/analytics");
|
|
10
|
+
const from = parseInstant(data.from, 'from');
|
|
11
|
+
const to = parseInstant(data.to, 'to');
|
|
12
|
+
const deleted = await getAnalytics().deleteEvents({
|
|
13
|
+
from,
|
|
14
|
+
to,
|
|
15
|
+
...null == data.visitorHash ? {} : {
|
|
16
|
+
visitorHash: data.visitorHash
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
deleted
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
const rebuildAnalyticsDay = createServerFn({
|
|
24
|
+
method: 'POST'
|
|
25
|
+
}).validator((input)=>input).handler(async ({ data })=>{
|
|
26
|
+
await assertAnalyticsMaintenance();
|
|
27
|
+
const { getAnalytics } = await import("@byline/analytics");
|
|
28
|
+
await getAnalytics().rebuildDay(data.day);
|
|
29
|
+
return {
|
|
30
|
+
rebuilt: data.day
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
async function assertAnalyticsMaintenance() {
|
|
34
|
+
const context = await getAdminRequestContext();
|
|
35
|
+
assertAdminActor(context, ANALYTICS_ABILITIES.maintain);
|
|
36
|
+
}
|
|
37
|
+
function parseInstant(value, label) {
|
|
38
|
+
const date = new Date(value);
|
|
39
|
+
if (Number.isNaN(date.valueOf())) throw new Error(`analytics ${label} must be an ISO instant`);
|
|
40
|
+
return date;
|
|
41
|
+
}
|
|
42
|
+
export { deleteAnalyticsEvents, rebuildAnalyticsDay };
|
|
@@ -0,0 +1,25 @@
|
|
|
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 { AnalyticsCountryTotal, AnalyticsDateRange, AnalyticsEventKind, AnalyticsPathTotal, AnalyticsRankedTotals, AnalyticsReferrerTotal, AnalyticsReportCoverage, AnalyticsSummary } from '@byline/analytics';
|
|
9
|
+
export interface AnalyticsTopInput extends AnalyticsDateRange {
|
|
10
|
+
kind: AnalyticsEventKind;
|
|
11
|
+
limit?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface AnalyticsRuntimeState {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare const getAnalyticsRuntime: import("@tanstack/react-start").OptionalFetcher<undefined, undefined, Promise<AnalyticsRuntimeState>>;
|
|
17
|
+
export declare const getAnalyticsSummary: import("@tanstack/react-start").RequiredFetcher<undefined, (input: AnalyticsDateRange) => AnalyticsDateRange, Promise<AnalyticsSummary>>;
|
|
18
|
+
export declare const getAnalyticsReportCoverage: import("@tanstack/react-start").OptionalFetcher<undefined, undefined, Promise<AnalyticsReportCoverage>>;
|
|
19
|
+
export declare const getAnalyticsTop: import("@tanstack/react-start").RequiredFetcher<undefined, (input: AnalyticsTopInput) => AnalyticsTopInput, Promise<AnalyticsRankedTotals<AnalyticsPathTotal>>>;
|
|
20
|
+
export declare const getAnalyticsReferrers: import("@tanstack/react-start").RequiredFetcher<undefined, (input: AnalyticsDateRange & {
|
|
21
|
+
limit?: number;
|
|
22
|
+
}) => AnalyticsDateRange & {
|
|
23
|
+
limit?: number;
|
|
24
|
+
}, Promise<AnalyticsRankedTotals<AnalyticsReferrerTotal>>>;
|
|
25
|
+
export declare const getAnalyticsCountries: import("@tanstack/react-start").RequiredFetcher<undefined, (input: AnalyticsDateRange) => AnalyticsDateRange, Promise<AnalyticsCountryTotal[]>>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
2
|
+
import { assertAdminActor, requireAdminActor } from "@byline/admin";
|
|
3
|
+
import { ANALYTICS_ABILITIES } from "@byline/admin/analytics";
|
|
4
|
+
import { getAdminRequestContext } from "@byline/client/server";
|
|
5
|
+
const getAnalyticsRuntime = createServerFn({
|
|
6
|
+
method: 'GET'
|
|
7
|
+
}).handler(async ()=>{
|
|
8
|
+
const context = await getAdminRequestContext();
|
|
9
|
+
requireAdminActor(context, 'reading analytics runtime state');
|
|
10
|
+
const { isAnalyticsRegistered } = await import("@byline/analytics");
|
|
11
|
+
return {
|
|
12
|
+
enabled: isAnalyticsRegistered()
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
const getAnalyticsSummary = createServerFn({
|
|
16
|
+
method: 'GET'
|
|
17
|
+
}).validator((input)=>input).handler(async ({ data })=>{
|
|
18
|
+
await assertAnalyticsRead();
|
|
19
|
+
const { getAnalytics } = await import("@byline/analytics");
|
|
20
|
+
return getAnalytics().getSummary(data);
|
|
21
|
+
});
|
|
22
|
+
const getAnalyticsReportCoverage = createServerFn({
|
|
23
|
+
method: 'GET'
|
|
24
|
+
}).handler(async ()=>{
|
|
25
|
+
await assertAnalyticsRead();
|
|
26
|
+
const { getAnalytics } = await import("@byline/analytics");
|
|
27
|
+
return getAnalytics().getReportCoverage();
|
|
28
|
+
});
|
|
29
|
+
const getAnalyticsTop = createServerFn({
|
|
30
|
+
method: 'GET'
|
|
31
|
+
}).validator((input)=>input).handler(async ({ data })=>{
|
|
32
|
+
await assertAnalyticsRead();
|
|
33
|
+
const { getAnalytics } = await import("@byline/analytics");
|
|
34
|
+
return getAnalytics().getTopPaths(data);
|
|
35
|
+
});
|
|
36
|
+
const getAnalyticsReferrers = createServerFn({
|
|
37
|
+
method: 'GET'
|
|
38
|
+
}).validator((input)=>input).handler(async ({ data })=>{
|
|
39
|
+
await assertAnalyticsRead();
|
|
40
|
+
const { getAnalytics } = await import("@byline/analytics");
|
|
41
|
+
return getAnalytics().getReferrers(data);
|
|
42
|
+
});
|
|
43
|
+
const getAnalyticsCountries = createServerFn({
|
|
44
|
+
method: 'GET'
|
|
45
|
+
}).validator((input)=>input).handler(async ({ data })=>{
|
|
46
|
+
await assertAnalyticsRead();
|
|
47
|
+
const { getAnalytics } = await import("@byline/analytics");
|
|
48
|
+
return getAnalytics().getCountries(data);
|
|
49
|
+
});
|
|
50
|
+
async function assertAnalyticsRead() {
|
|
51
|
+
const context = await getAdminRequestContext();
|
|
52
|
+
assertAdminActor(context, ANALYTICS_ABILITIES.read);
|
|
53
|
+
}
|
|
54
|
+
export { getAnalyticsCountries, getAnalyticsReferrers, getAnalyticsReportCoverage, getAnalyticsRuntime, getAnalyticsSummary, getAnalyticsTop };
|
|
@@ -20,6 +20,11 @@ export declare function getCollectionDocument(collection: string, id: string, lo
|
|
|
20
20
|
_restoreWarnings?: string[] | undefined;
|
|
21
21
|
availableLocales?: string[] | undefined;
|
|
22
22
|
_availableVersionLocales?: string[] | undefined;
|
|
23
|
+
_scheduledPublicationEnabled: boolean;
|
|
24
|
+
_canSchedulePublication: boolean;
|
|
25
|
+
_scheduledPublish: import("@byline/client").DocumentPublishScheduleInfo | null;
|
|
26
|
+
scheduledPublishAt?: Date | undefined;
|
|
27
|
+
scheduledPublishVersionId?: string | undefined;
|
|
23
28
|
} | null>;
|
|
24
29
|
/**
|
|
25
30
|
* Fetch a specific historical version of a document. Returns `null` when the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createServerFn } from "@tanstack/react-start";
|
|
2
|
-
import { getAdminBylineClient } from "@byline/client/server";
|
|
3
|
-
import { BylineError, ERR_NOT_FOUND, ErrorCodes, buildRelationSummaryPopulateMap, getCollectionAdminConfig, getCollectionDefinition, getCollectionSchemasForPath, getLogger, getServerConfig } from "@byline/core";
|
|
2
|
+
import { getAdminBylineClient, getAdminRequestContext } from "@byline/client/server";
|
|
3
|
+
import { BylineError, ERR_NOT_FOUND, ErrorCodes, buildRelationSummaryPopulateMap, collectionAbilityKey, getCollectionAdminConfig, getCollectionDefinition, getCollectionSchemasForPath, getLogger, getServerConfig } from "@byline/core";
|
|
4
4
|
import { ensureCollection } from "../../integrations/api-utils.js";
|
|
5
5
|
import { serialise } from "./utils.js";
|
|
6
6
|
const getDocumentFn = createServerFn({
|
|
@@ -72,6 +72,15 @@ const getDocumentFn = createServerFn({
|
|
|
72
72
|
const restoreWarnings = serialised._restoreWarnings;
|
|
73
73
|
const availableLocales = serialised.availableLocales;
|
|
74
74
|
const availableVersionLocales = serialised._availableVersionLocales;
|
|
75
|
+
const scheduledPublicationEnabled = getServerConfig().scheduledPublication?.enabled === true;
|
|
76
|
+
let canSchedulePublication = false;
|
|
77
|
+
let scheduledPublish = null;
|
|
78
|
+
if (scheduledPublicationEnabled) {
|
|
79
|
+
const actor = (await getAdminRequestContext()).actor;
|
|
80
|
+
canSchedulePublication = actor?.hasAbility(collectionAbilityKey(path, 'changeStatus')) === true && actor.hasAbility(collectionAbilityKey(path, 'publish'));
|
|
81
|
+
if (canSchedulePublication) scheduledPublish = await handle.getScheduledPublish(id);
|
|
82
|
+
}
|
|
83
|
+
const serializedSchedule = null == scheduledPublish ? null : serialise(scheduledPublish);
|
|
75
84
|
return {
|
|
76
85
|
...parsed,
|
|
77
86
|
_publishedVersion: publishedVersion,
|
|
@@ -83,7 +92,14 @@ const getDocumentFn = createServerFn({
|
|
|
83
92
|
} : {},
|
|
84
93
|
...Array.isArray(availableVersionLocales) ? {
|
|
85
94
|
_availableVersionLocales: availableVersionLocales
|
|
86
|
-
} : {}
|
|
95
|
+
} : {},
|
|
96
|
+
_scheduledPublicationEnabled: scheduledPublicationEnabled,
|
|
97
|
+
_canSchedulePublication: canSchedulePublication,
|
|
98
|
+
_scheduledPublish: serializedSchedule,
|
|
99
|
+
...null == serializedSchedule ? {} : {
|
|
100
|
+
scheduledPublishAt: serializedSchedule.publishAt,
|
|
101
|
+
scheduledPublishVersionId: serializedSchedule.targetVersionId
|
|
102
|
+
}
|
|
87
103
|
};
|
|
88
104
|
});
|
|
89
105
|
const getDocumentByVersionFn = createServerFn({
|
|
@@ -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 './scheduled-publication';
|
|
20
21
|
export * from './set-list-view-preference';
|
|
21
22
|
export * from './stats';
|
|
22
23
|
export * from './status';
|
|
@@ -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 "./scheduled-publication.js";
|
|
14
15
|
export * from "./set-list-view-preference.js";
|
|
15
16
|
export * from "./stats.js";
|
|
16
17
|
export * from "./status.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type { DocumentPublishScheduleInfo } from '@byline/client';
|
|
9
|
+
import type { DocumentPublishSchedule } from '@byline/core';
|
|
10
|
+
/** Remove sweep-owned fencing credentials before a schedule crosses a transport boundary. */
|
|
11
|
+
export declare function omitScheduleExecutionState(schedule: DocumentPublishScheduleInfo | DocumentPublishSchedule): DocumentPublishScheduleInfo;
|
|
@@ -0,0 +1,111 @@
|
|
|
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 { DocumentPublishScheduleInfo } from '@byline/client';
|
|
9
|
+
import { type DocumentPublishScheduleState, type RecurringTaskHealth } from '@byline/core';
|
|
10
|
+
export interface ScheduledPublicationRuntime {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
health: {
|
|
13
|
+
status: 'healthy' | 'failed' | 'stale' | 'not_started';
|
|
14
|
+
task: SerializedRecurringTaskHealth | null;
|
|
15
|
+
} | null;
|
|
16
|
+
}
|
|
17
|
+
type SerializedRecurringTaskHealth = Omit<RecurringTaskHealth, 'nextRunAt' | 'lastStartedAt' | 'lastSucceededAt' | 'lastFailedAt' | 'databaseNow'> & {
|
|
18
|
+
nextRunAt: string;
|
|
19
|
+
lastStartedAt: string | null;
|
|
20
|
+
lastSucceededAt: string | null;
|
|
21
|
+
lastFailedAt: string | null;
|
|
22
|
+
databaseNow: string;
|
|
23
|
+
};
|
|
24
|
+
export type SerializedDocumentPublishSchedule = Omit<DocumentPublishScheduleInfo, 'publishAt' | 'suspendedAt' | 'lastAuthorizedAt' | 'scheduledAt' | 'updatedAt' | 'lastAttemptAt' | 'nextAttemptAt'> & {
|
|
25
|
+
publishAt: string;
|
|
26
|
+
suspendedAt: string | null;
|
|
27
|
+
lastAuthorizedAt: string;
|
|
28
|
+
scheduledAt: string;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
lastAttemptAt: string | null;
|
|
31
|
+
nextAttemptAt: string;
|
|
32
|
+
};
|
|
33
|
+
export interface ScheduledPublicationListItem extends SerializedDocumentPublishSchedule {
|
|
34
|
+
collectionPath: string;
|
|
35
|
+
collectionLabel: string;
|
|
36
|
+
documentPath: string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Display name for `lastAuthorizedBy` — full name, or email when the account
|
|
39
|
+
* has no name set. `null` when the account no longer exists, which is a real
|
|
40
|
+
* state rather than an error: the authorizer column is not a foreign key, so
|
|
41
|
+
* a schedule outlives the account that authorized it. Callers fall back to
|
|
42
|
+
* showing the id.
|
|
43
|
+
*/
|
|
44
|
+
lastAuthorizedByName: string | null;
|
|
45
|
+
}
|
|
46
|
+
export interface ScheduledPublicationListResponse {
|
|
47
|
+
schedules: ScheduledPublicationListItem[];
|
|
48
|
+
meta: {
|
|
49
|
+
page: number;
|
|
50
|
+
pageSize: number;
|
|
51
|
+
total: number;
|
|
52
|
+
totalPages: number;
|
|
53
|
+
};
|
|
54
|
+
runtime: ScheduledPublicationRuntime;
|
|
55
|
+
}
|
|
56
|
+
/** Authenticated feature discovery for the admin shell and health warning. */
|
|
57
|
+
export declare const getScheduledPublicationRuntime: import("@tanstack/react-start").OptionalFetcher<undefined, undefined, Promise<ScheduledPublicationRuntime>>;
|
|
58
|
+
export declare const scheduleCollectionDocumentPublish: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
|
|
59
|
+
collection: string;
|
|
60
|
+
id: string;
|
|
61
|
+
publishAt: string;
|
|
62
|
+
expectedVersionId: string;
|
|
63
|
+
}) => {
|
|
64
|
+
collection: string;
|
|
65
|
+
id: string;
|
|
66
|
+
publishAt: string;
|
|
67
|
+
expectedVersionId: string;
|
|
68
|
+
}, Promise<SerializedDocumentPublishSchedule>>;
|
|
69
|
+
export declare const confirmCollectionDocumentScheduledPublish: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
|
|
70
|
+
collection: string;
|
|
71
|
+
id: string;
|
|
72
|
+
expectedVersionId: string;
|
|
73
|
+
}) => {
|
|
74
|
+
collection: string;
|
|
75
|
+
id: string;
|
|
76
|
+
expectedVersionId: string;
|
|
77
|
+
}, Promise<SerializedDocumentPublishSchedule>>;
|
|
78
|
+
export declare const cancelCollectionDocumentScheduledPublish: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
|
|
79
|
+
collection: string;
|
|
80
|
+
id: string;
|
|
81
|
+
}) => {
|
|
82
|
+
collection: string;
|
|
83
|
+
id: string;
|
|
84
|
+
}, Promise<{
|
|
85
|
+
status: 'not_found';
|
|
86
|
+
schedule?: undefined;
|
|
87
|
+
} | {
|
|
88
|
+
status: 'cancelled';
|
|
89
|
+
schedule: SerializedDocumentPublishSchedule;
|
|
90
|
+
}>>;
|
|
91
|
+
export declare const listScheduledPublications: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
|
|
92
|
+
states?: readonly DocumentPublishScheduleState[];
|
|
93
|
+
lastAuthorizedBy?: string;
|
|
94
|
+
page?: number;
|
|
95
|
+
pageSize?: number;
|
|
96
|
+
}) => {
|
|
97
|
+
states?: readonly DocumentPublishScheduleState[];
|
|
98
|
+
lastAuthorizedBy?: string;
|
|
99
|
+
page?: number;
|
|
100
|
+
pageSize?: number;
|
|
101
|
+
}, Promise<{
|
|
102
|
+
schedules: ScheduledPublicationListItem[];
|
|
103
|
+
meta: {
|
|
104
|
+
page: number;
|
|
105
|
+
pageSize: number;
|
|
106
|
+
total: number;
|
|
107
|
+
totalPages: number;
|
|
108
|
+
};
|
|
109
|
+
runtime: ScheduledPublicationRuntime;
|
|
110
|
+
}>>;
|
|
111
|
+
export {};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
2
|
+
import { getAdminBylineClient, getAdminRequestContext } from "@byline/client/server";
|
|
3
|
+
import { ERR_NOT_FOUND, ERR_VALIDATION } from "@byline/core";
|
|
4
|
+
import { listDocumentPublishSchedules } from "@byline/core/services";
|
|
5
|
+
import { bylineCore } from "../../integrations/byline-core.js";
|
|
6
|
+
import { omitScheduleExecutionState } from "./scheduled-publication-response.js";
|
|
7
|
+
import { serialise } from "./utils.js";
|
|
8
|
+
const TASK_NAME = 'documents.publish-scheduled';
|
|
9
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10
|
+
function assertFeatureEnabled() {
|
|
11
|
+
if (bylineCore().config.scheduledPublication?.enabled === true) return;
|
|
12
|
+
throw ERR_NOT_FOUND({
|
|
13
|
+
message: 'Scheduled publication is not enabled for this installation'
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function serializeSchedule(schedule) {
|
|
17
|
+
return serialise(omitScheduleExecutionState(schedule));
|
|
18
|
+
}
|
|
19
|
+
async function scheduledPublicationRuntime() {
|
|
20
|
+
const core = bylineCore();
|
|
21
|
+
if (core.config.scheduledPublication?.enabled !== true) return {
|
|
22
|
+
enabled: false,
|
|
23
|
+
health: null
|
|
24
|
+
};
|
|
25
|
+
const scheduler = core.db.scheduler;
|
|
26
|
+
if (null == scheduler) return {
|
|
27
|
+
enabled: true,
|
|
28
|
+
health: {
|
|
29
|
+
status: 'not_started',
|
|
30
|
+
task: null
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const [task] = await scheduler.health([
|
|
34
|
+
TASK_NAME
|
|
35
|
+
]);
|
|
36
|
+
if (null == task) return {
|
|
37
|
+
enabled: true,
|
|
38
|
+
health: {
|
|
39
|
+
status: 'not_started',
|
|
40
|
+
task: null
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const staleAfter = task.nextRunAt.getTime() + 2 * task.intervalMs;
|
|
44
|
+
const status = task.leaseExpired ? 'stale' : 'failed' === task.lastStatus ? 'failed' : staleAfter < task.databaseNow.getTime() ? 'stale' : 'healthy';
|
|
45
|
+
return {
|
|
46
|
+
enabled: true,
|
|
47
|
+
health: {
|
|
48
|
+
status,
|
|
49
|
+
task: serialise(task)
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const getScheduledPublicationRuntime = createServerFn({
|
|
54
|
+
method: 'GET'
|
|
55
|
+
}).handler(async ()=>{
|
|
56
|
+
await getAdminRequestContext();
|
|
57
|
+
return scheduledPublicationRuntime();
|
|
58
|
+
});
|
|
59
|
+
const scheduleCollectionDocumentPublish = createServerFn({
|
|
60
|
+
method: 'POST'
|
|
61
|
+
}).validator((input)=>input).handler(async ({ data })=>{
|
|
62
|
+
assertFeatureEnabled();
|
|
63
|
+
const schedule = await getAdminBylineClient().collection(data.collection).schedulePublish(data.id, {
|
|
64
|
+
publishAt: data.publishAt,
|
|
65
|
+
expectedVersionId: data.expectedVersionId
|
|
66
|
+
});
|
|
67
|
+
return serializeSchedule(schedule);
|
|
68
|
+
});
|
|
69
|
+
const confirmCollectionDocumentScheduledPublish = createServerFn({
|
|
70
|
+
method: 'POST'
|
|
71
|
+
}).validator((input)=>input).handler(async ({ data })=>{
|
|
72
|
+
assertFeatureEnabled();
|
|
73
|
+
const schedule = await getAdminBylineClient().collection(data.collection).confirmScheduledPublish(data.id, {
|
|
74
|
+
expectedVersionId: data.expectedVersionId
|
|
75
|
+
});
|
|
76
|
+
return serializeSchedule(schedule);
|
|
77
|
+
});
|
|
78
|
+
const cancelCollectionDocumentScheduledPublish = createServerFn({
|
|
79
|
+
method: 'POST'
|
|
80
|
+
}).validator((input)=>input).handler(async ({ data })=>{
|
|
81
|
+
assertFeatureEnabled();
|
|
82
|
+
const schedule = await getAdminBylineClient().collection(data.collection).cancelScheduledPublish(data.id);
|
|
83
|
+
return null == schedule ? {
|
|
84
|
+
status: 'not_found'
|
|
85
|
+
} : {
|
|
86
|
+
status: 'cancelled',
|
|
87
|
+
schedule: serializeSchedule(schedule)
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
const listScheduledPublications = createServerFn({
|
|
91
|
+
method: 'GET'
|
|
92
|
+
}).validator((input)=>input).handler(async ({ data })=>{
|
|
93
|
+
assertFeatureEnabled();
|
|
94
|
+
if (data.states?.some((state)=>'armed' !== state && 'needs_reconfirm' !== state) === true) throw ERR_VALIDATION({
|
|
95
|
+
message: 'state must be armed or needs_reconfirm'
|
|
96
|
+
});
|
|
97
|
+
if (null != data.lastAuthorizedBy && !UUID_RE.test(data.lastAuthorizedBy)) throw ERR_VALIDATION({
|
|
98
|
+
message: 'lastAuthorizedBy must be a UUID'
|
|
99
|
+
});
|
|
100
|
+
const core = bylineCore();
|
|
101
|
+
const page = data.page ?? 1;
|
|
102
|
+
const pageSize = data.pageSize ?? 25;
|
|
103
|
+
const requestContext = await getAdminRequestContext();
|
|
104
|
+
const result = await listDocumentPublishSchedules(core, requestContext, {
|
|
105
|
+
states: data.states,
|
|
106
|
+
lastAuthorizedBy: data.lastAuthorizedBy,
|
|
107
|
+
page,
|
|
108
|
+
pageSize
|
|
109
|
+
});
|
|
110
|
+
const collections = new Map(core.collections.map((definition)=>{
|
|
111
|
+
const record = core.getCollectionRecord(definition.path);
|
|
112
|
+
return [
|
|
113
|
+
record.collectionId,
|
|
114
|
+
definition
|
|
115
|
+
];
|
|
116
|
+
}));
|
|
117
|
+
const authorizerIds = [
|
|
118
|
+
...new Set(result.schedules.map((schedule)=>schedule.lastAuthorizedBy).filter((id)=>null != id))
|
|
119
|
+
];
|
|
120
|
+
const authorizerNames = new Map();
|
|
121
|
+
const adminUsers = core.adminStore?.adminUsers;
|
|
122
|
+
if (authorizerIds.length > 0 && null != adminUsers) {
|
|
123
|
+
const users = await adminUsers.getByIds(authorizerIds);
|
|
124
|
+
for (const user of users){
|
|
125
|
+
const fullName = [
|
|
126
|
+
user.given_name,
|
|
127
|
+
user.family_name
|
|
128
|
+
].filter(Boolean).join(' ').trim();
|
|
129
|
+
authorizerNames.set(user.id, fullName.length > 0 ? fullName : user.email);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const schedules = await Promise.all(result.schedules.map(async (schedule)=>{
|
|
133
|
+
const definition = collections.get(schedule.collectionId);
|
|
134
|
+
const documentPath = await core.db.queries.documents.getCurrentPath({
|
|
135
|
+
collection_id: schedule.collectionId,
|
|
136
|
+
document_id: schedule.documentId
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
...serializeSchedule(schedule),
|
|
140
|
+
collectionPath: definition?.path ?? schedule.collectionId,
|
|
141
|
+
collectionLabel: definition?.labels.plural ?? definition?.path ?? schedule.collectionId,
|
|
142
|
+
documentPath,
|
|
143
|
+
lastAuthorizedByName: null == schedule.lastAuthorizedBy ? null : authorizerNames.get(schedule.lastAuthorizedBy) ?? null
|
|
144
|
+
};
|
|
145
|
+
}));
|
|
146
|
+
return {
|
|
147
|
+
schedules,
|
|
148
|
+
meta: {
|
|
149
|
+
page,
|
|
150
|
+
pageSize,
|
|
151
|
+
total: result.total,
|
|
152
|
+
totalPages: Math.max(1, Math.ceil(result.total / pageSize))
|
|
153
|
+
},
|
|
154
|
+
runtime: await scheduledPublicationRuntime()
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
export { cancelCollectionDocumentScheduledPublish, confirmCollectionDocumentScheduledPublish, getScheduledPublicationRuntime, listScheduledPublications, scheduleCollectionDocumentPublish };
|
|
@@ -0,0 +1,22 @@
|
|
|
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 { Plugin } from 'vite';
|
|
9
|
+
/**
|
|
10
|
+
* Complete the first-party analytics transport during Vite development.
|
|
11
|
+
*
|
|
12
|
+
* Browser script requests carry `Sec-Fetch-Dest: script`, which Vite routes
|
|
13
|
+
* into its module pipeline before TanStack Start can serve the agent route.
|
|
14
|
+
* This plugin serves the agent directly. The application event route remains
|
|
15
|
+
* responsible for its development identity because POST server routes can
|
|
16
|
+
* bypass Vite's ordinary middleware chain. Production continues to use the
|
|
17
|
+
* application-owned agent route.
|
|
18
|
+
*/
|
|
19
|
+
export declare function bylineAnalyticsDev(options?: {
|
|
20
|
+
/** Public path that serves the standalone agent. */
|
|
21
|
+
agentPath?: string;
|
|
22
|
+
}): Plugin;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { serveAnalyticsAgent } from "../integrations/serve-analytics-agent.js";
|
|
2
|
+
function bylineAnalyticsDev(options = {}) {
|
|
3
|
+
const agentPath = options.agentPath ?? '/b.js';
|
|
4
|
+
return {
|
|
5
|
+
name: 'byline:analytics-dev',
|
|
6
|
+
apply: 'serve',
|
|
7
|
+
configureServer (server) {
|
|
8
|
+
server.middlewares.use(async (request, response, next)=>{
|
|
9
|
+
const pathname = request.url?.split('?', 1)[0];
|
|
10
|
+
if ('GET' === request.method && pathname === agentPath) {
|
|
11
|
+
try {
|
|
12
|
+
const agentResponse = serveAnalyticsAgent();
|
|
13
|
+
response.statusCode = agentResponse.status;
|
|
14
|
+
agentResponse.headers.forEach((value, name)=>{
|
|
15
|
+
response.setHeader(name, value);
|
|
16
|
+
});
|
|
17
|
+
response.end(await agentResponse.text());
|
|
18
|
+
} catch (error) {
|
|
19
|
+
next(error);
|
|
20
|
+
}
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
next();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export { bylineAnalyticsDev };
|
package/dist/vite/index.d.ts
CHANGED
package/dist/vite/index.js
CHANGED
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "4.
|
|
6
|
+
"version": "4.16.0",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20.9.0"
|
|
9
9
|
},
|
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
"types": "./dist/server-fns/admin-account/index.d.ts",
|
|
46
46
|
"import": "./dist/server-fns/admin-account/index.js"
|
|
47
47
|
},
|
|
48
|
+
"./server-fns/analytics": {
|
|
49
|
+
"types": "./dist/server-fns/analytics/index.d.ts",
|
|
50
|
+
"import": "./dist/server-fns/analytics/index.js"
|
|
51
|
+
},
|
|
48
52
|
"./server-fns/admin-activity": {
|
|
49
53
|
"types": "./dist/server-fns/admin-activity/index.d.ts",
|
|
50
54
|
"import": "./dist/server-fns/admin-activity/index.js"
|
|
@@ -117,13 +121,15 @@
|
|
|
117
121
|
"react-json-view-lite": "^2.5.0",
|
|
118
122
|
"react-swipeable": "^7.0.2",
|
|
119
123
|
"zod": "^4.4.3",
|
|
120
|
-
"@byline/
|
|
121
|
-
"@byline/
|
|
122
|
-
"@byline/ai": "4.
|
|
123
|
-
"@byline/
|
|
124
|
-
"@byline/client": "4.
|
|
125
|
-
"@byline/core": "4.
|
|
126
|
-
"@byline/
|
|
124
|
+
"@byline/admin": "4.16.0",
|
|
125
|
+
"@byline/analytics-agent": "4.16.0",
|
|
126
|
+
"@byline/ai": "4.16.0",
|
|
127
|
+
"@byline/analytics": "4.16.0",
|
|
128
|
+
"@byline/client": "4.16.0",
|
|
129
|
+
"@byline/core": "4.16.0",
|
|
130
|
+
"@byline/auth": "4.16.0",
|
|
131
|
+
"@byline/i18n": "4.16.0",
|
|
132
|
+
"@byline/ui": "4.16.0"
|
|
127
133
|
},
|
|
128
134
|
"peerDependencies": {
|
|
129
135
|
"@tanstack/react-router": "^1.167.0",
|