@byline/host-tanstack-start 4.14.0 → 4.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/admin-shell/chrome/admin-layout_module.css +1 -1
  2. package/dist/admin-shell/chrome/admin-menu-drawer.d.ts +3 -1
  3. package/dist/admin-shell/chrome/admin-menu-drawer.js +14 -2
  4. package/dist/admin-shell/chrome/admin-menu-drawer_module.css +7 -6
  5. package/dist/admin-shell/chrome/dashboard.js +2 -2
  6. package/dist/admin-shell/collections/edit.js +142 -2
  7. package/dist/admin-shell/scheduled-publications/list.d.ts +4 -0
  8. package/dist/admin-shell/scheduled-publications/list.js +295 -0
  9. package/dist/admin-shell/scheduled-publications/list.module.js +25 -0
  10. package/dist/admin-shell/scheduled-publications/list_module.css +97 -0
  11. package/dist/routes/create-admin-layout-route.js +8 -3
  12. package/dist/routes/create-admin-scheduled-publications-route.d.ts +8 -0
  13. package/dist/routes/create-admin-scheduled-publications-route.js +66 -0
  14. package/dist/routes/index.d.ts +1 -0
  15. package/dist/routes/index.js +1 -0
  16. package/dist/server-fns/collections/get.d.ts +5 -0
  17. package/dist/server-fns/collections/get.js +19 -3
  18. package/dist/server-fns/collections/index.d.ts +1 -0
  19. package/dist/server-fns/collections/index.js +1 -0
  20. package/dist/server-fns/collections/scheduled-publication-response.d.ts +11 -0
  21. package/dist/server-fns/collections/scheduled-publication-response.js +5 -0
  22. package/dist/server-fns/collections/scheduled-publication.d.ts +111 -0
  23. package/dist/server-fns/collections/scheduled-publication.js +157 -0
  24. package/package.json +8 -8
  25. package/src/admin-shell/chrome/admin-layout.module.css +6 -1
  26. package/src/admin-shell/chrome/admin-menu-drawer.module.css +19 -6
  27. package/src/admin-shell/chrome/admin-menu-drawer.tsx +23 -1
  28. package/src/admin-shell/chrome/dashboard.tsx +11 -4
  29. package/src/admin-shell/collections/edit.tsx +140 -1
  30. package/src/admin-shell/scheduled-publications/list.module.css +105 -0
  31. package/src/admin-shell/scheduled-publications/list.tsx +292 -0
  32. package/src/routes/create-admin-layout-route.tsx +8 -3
  33. package/src/routes/create-admin-scheduled-publications-route.tsx +83 -0
  34. package/src/routes/index.ts +1 -0
  35. package/src/server-fns/collections/get.ts +30 -1
  36. package/src/server-fns/collections/index.ts +1 -0
  37. package/src/server-fns/collections/scheduled-publication-response.test.node.ts +45 -0
  38. package/src/server-fns/collections/scheduled-publication-response.ts +22 -0
  39. package/src/server-fns/collections/scheduled-publication.ts +270 -0
@@ -0,0 +1,97 @@
1
+ .head-oj4QbV {
2
+ margin-bottom: var(--spacing-16);
3
+ }
4
+
5
+ .title-lm1z74 {
6
+ margin-bottom: var(--spacing-4);
7
+ }
8
+
9
+ .intro-clPFM7 {
10
+ color: var(--gray-500);
11
+ margin: 0;
12
+ }
13
+
14
+ .health-KAg5x5 {
15
+ margin-bottom: var(--spacing-16);
16
+ }
17
+
18
+ .controls-dRon5S {
19
+ justify-content: space-between;
20
+ align-items: flex-end;
21
+ gap: var(--spacing-16);
22
+ margin-bottom: var(--spacing-4);
23
+ flex-wrap: wrap;
24
+ display: flex;
25
+ }
26
+
27
+ .filters-help-DWQ3Wy {
28
+ margin: 0 0 var(--spacing-16);
29
+ color: var(--gray-500);
30
+ font-size: var(--font-size-sm);
31
+ }
32
+
33
+ .filters-x7PuG5 {
34
+ align-items: flex-end;
35
+ gap: var(--spacing-12);
36
+ flex-wrap: wrap;
37
+ display: flex;
38
+ }
39
+
40
+ .filter-TXeSAI {
41
+ gap: var(--spacing-4);
42
+ flex-direction: column;
43
+ min-width: 12rem;
44
+ display: flex;
45
+ }
46
+
47
+ .filter-authorizer-rDbHwh .byline-input-wrapper {
48
+ min-width: 22rem;
49
+ }
50
+
51
+ .filter-actions-dbQWCO {
52
+ align-items: center;
53
+ gap: var(--spacing-8);
54
+ padding-bottom: .1rem;
55
+ display: flex;
56
+ }
57
+
58
+ .tableWrap-wqdsOy {
59
+ margin-top: .75rem;
60
+ overflow-x: auto;
61
+ }
62
+
63
+ .collection-sBqska, .zone-ikRViN, .error-frhrx4 {
64
+ margin-top: var(--spacing-4);
65
+ color: var(--gray-500);
66
+ font-size: var(--font-size-xs);
67
+ display: block;
68
+ }
69
+
70
+ .instant-PTgCBK {
71
+ white-space: nowrap;
72
+ }
73
+
74
+ .authorizer-orWXlf {
75
+ font-family: var(--font-family-mono, monospace);
76
+ font-size: var(--font-size-xs);
77
+ overflow-wrap: anywhere;
78
+ }
79
+
80
+ .error-frhrx4 {
81
+ max-width: 24rem;
82
+ color: var(--red-600);
83
+ white-space: normal;
84
+ }
85
+
86
+ .empty-K7NcOV {
87
+ padding: var(--spacing-32) 0;
88
+ color: var(--gray-500);
89
+ text-align: center;
90
+ }
91
+
92
+ .pagerBottom-Sf0_9J {
93
+ margin-top: var(--spacing-16);
94
+ justify-content: flex-end;
95
+ display: flex;
96
+ }
97
+
@@ -19,6 +19,7 @@ import { bylineAdminServices } from "../integrations/byline-admin-services.js";
19
19
  import { BylineAiAdminProvider } from "../integrations/byline-ai.js";
20
20
  import { bylineFieldServices } from "../integrations/byline-field-services.js";
21
21
  import { getCurrentAdminUser } from "../server-fns/auth/index.js";
22
+ import { getScheduledPublicationRuntime } from "../server-fns/collections/index.js";
22
23
  import { getActiveLocaleFn, setAdminLocaleFn } from "../server-fns/i18n/index.js";
23
24
  import { getSignInRoutePath } from "./sign-in-path.js";
24
25
  function createAdminLayoutRoute(path) {
@@ -28,9 +29,11 @@ function createAdminLayoutRoute(path) {
28
29
  try {
29
30
  const user = await getCurrentAdminUser();
30
31
  const activeLocale = await getActiveLocaleFn();
32
+ const scheduledPublicationRuntime = await getScheduledPublicationRuntime();
31
33
  return {
32
34
  user,
33
- activeLocale
35
+ activeLocale,
36
+ scheduledPublicationRuntime
34
37
  };
35
38
  } catch {
36
39
  throw redirect({
@@ -42,7 +45,7 @@ function createAdminLayoutRoute(path) {
42
45
  }
43
46
  },
44
47
  component: function() {
45
- const { user, activeLocale } = Route.useRouteContext();
48
+ const { user, activeLocale, scheduledPublicationRuntime } = Route.useRouteContext();
46
49
  const { i18n } = getAdminConfig();
47
50
  const localeDefinitions = buildLocaleDefinitions(i18n.admin.locales, i18n.admin.localeDefinitions);
48
51
  useEffect(()=>{
@@ -76,7 +79,9 @@ function createAdminLayoutRoute(path) {
76
79
  /*#__PURE__*/ jsxs("main", {
77
80
  className: clsx('byline-admin-layout-main', admin_layout_module.main),
78
81
  children: [
79
- /*#__PURE__*/ jsx(AdminMenuDrawer, {}),
82
+ /*#__PURE__*/ jsx(AdminMenuDrawer, {
83
+ scheduledPublicationEnabled: scheduledPublicationRuntime.enabled
84
+ }),
80
85
  /*#__PURE__*/ jsx(Content, {
81
86
  children: /*#__PURE__*/ jsx(Outlet, {})
82
87
  })
@@ -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 declare function createAdminScheduledPublicationsRoute(path: string): any;
@@ -0,0 +1,66 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { createFileRoute, notFound } from "@tanstack/react-router";
3
+ import { useTranslation } from "@byline/i18n/react";
4
+ import { z } from "zod";
5
+ import { BreadcrumbsClient } from "../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js";
6
+ import { ScheduledPublicationsView } from "../admin-shell/scheduled-publications/list.js";
7
+ import { getScheduledPublicationRuntime, listScheduledPublications } from "../server-fns/collections/index.js";
8
+ import { getAdminRoutePath } from "./admin-path.js";
9
+ const searchSchema = z.object({
10
+ page: z.coerce.number().int().min(1).optional().catch(void 0),
11
+ page_size: z.coerce.number().int().min(1).max(100).optional().catch(void 0),
12
+ state: z["enum"]([
13
+ 'armed',
14
+ 'needs_reconfirm'
15
+ ]).optional().catch(void 0),
16
+ lastAuthorizedBy: z.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i).optional().catch(void 0)
17
+ });
18
+ function createAdminScheduledPublicationsRoute(path) {
19
+ const Route = createFileRoute(path)({
20
+ shouldReload: true,
21
+ validateSearch: searchSchema,
22
+ loaderDeps: ({ search })=>search,
23
+ loader: async ({ deps })=>{
24
+ const runtime = await getScheduledPublicationRuntime();
25
+ if (!runtime.enabled) throw notFound();
26
+ const data = await listScheduledPublications({
27
+ data: {
28
+ page: deps.page,
29
+ pageSize: deps.page_size,
30
+ states: null == deps.state ? void 0 : [
31
+ deps.state
32
+ ],
33
+ lastAuthorizedBy: deps.lastAuthorizedBy
34
+ }
35
+ });
36
+ return {
37
+ data
38
+ };
39
+ },
40
+ component: function() {
41
+ const { data } = Route.useLoaderData();
42
+ const { t } = useTranslation('byline-admin');
43
+ return /*#__PURE__*/ jsxs(Fragment, {
44
+ children: [
45
+ /*#__PURE__*/ jsx(BreadcrumbsClient, {
46
+ breadcrumbs: [
47
+ {
48
+ label: t('chrome.menu.dashboard'),
49
+ href: getAdminRoutePath()
50
+ },
51
+ {
52
+ label: t('scheduledPublication.list.title'),
53
+ href: getAdminRoutePath('scheduled-publications')
54
+ }
55
+ ]
56
+ }),
57
+ /*#__PURE__*/ jsx(ScheduledPublicationsView, {
58
+ data: data
59
+ })
60
+ ]
61
+ });
62
+ }
63
+ });
64
+ return Route;
65
+ }
66
+ export { createAdminScheduledPublicationsRoute };
@@ -28,6 +28,7 @@ export { createAdminLayoutRoute } from './create-admin-layout-route.js';
28
28
  export { createAdminPermissionsRoute } from './create-admin-permissions-route.js';
29
29
  export { createAdminRoleEditRoute } from './create-admin-role-edit-route.js';
30
30
  export { createAdminRolesListRoute } from './create-admin-roles-list-route.js';
31
+ export { createAdminScheduledPublicationsRoute } from './create-admin-scheduled-publications-route.js';
31
32
  export { createAdminUserEditRoute } from './create-admin-user-edit-route.js';
32
33
  export { createAdminUsersListRoute } from './create-admin-users-list-route.js';
33
34
  export { createCollectionApiRoute } from './create-collection-api-route.js';
@@ -7,6 +7,7 @@ export { createAdminLayoutRoute } from "./create-admin-layout-route.js";
7
7
  export { createAdminPermissionsRoute } from "./create-admin-permissions-route.js";
8
8
  export { createAdminRoleEditRoute } from "./create-admin-role-edit-route.js";
9
9
  export { createAdminRolesListRoute } from "./create-admin-roles-list-route.js";
10
+ export { createAdminScheduledPublicationsRoute } from "./create-admin-scheduled-publications-route.js";
10
11
  export { createAdminUserEditRoute } from "./create-admin-user-edit-route.js";
11
12
  export { createAdminUsersListRoute } from "./create-admin-users-list-route.js";
12
13
  export { createCollectionApiRoute } from "./create-collection-api-route.js";
@@ -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,5 @@
1
+ function omitScheduleExecutionState(schedule) {
2
+ const { executionToken: _executionToken, executionExpiresAt: _executionExpiresAt, ...editorialSchedule } = schedule;
3
+ return editorialSchedule;
4
+ }
5
+ export { omitScheduleExecutionState };
@@ -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 };
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.14.0",
6
+ "version": "4.15.0",
7
7
  "engines": {
8
8
  "node": ">=20.9.0"
9
9
  },
@@ -117,13 +117,13 @@
117
117
  "react-json-view-lite": "^2.5.0",
118
118
  "react-swipeable": "^7.0.2",
119
119
  "zod": "^4.4.3",
120
- "@byline/admin": "4.14.0",
121
- "@byline/auth": "4.14.0",
122
- "@byline/ai": "4.14.0",
123
- "@byline/client": "4.14.0",
124
- "@byline/core": "4.14.0",
125
- "@byline/i18n": "4.14.0",
126
- "@byline/ui": "4.14.0"
120
+ "@byline/admin": "4.15.0",
121
+ "@byline/auth": "4.15.0",
122
+ "@byline/ai": "4.15.0",
123
+ "@byline/ui": "4.15.0",
124
+ "@byline/core": "4.15.0",
125
+ "@byline/client": "4.15.0",
126
+ "@byline/i18n": "4.15.0"
127
127
  },
128
128
  "peerDependencies": {
129
129
  "@tanstack/react-router": "^1.167.0",
@@ -6,10 +6,15 @@
6
6
  * Override handle: .byline-admin-layout-main
7
7
  */
8
8
 
9
+ /* `min-height`, not `height`: the document is the scroll container, so with a
10
+ fixed 100vh height the flex row ends at the fold and the sticky menu drawer
11
+ inside it has nothing left to stick to — it scrolls away as soon as the
12
+ route content runs past one viewport. Growing with the content gives the
13
+ drawer a containing block tall enough to stay pinned all the way down. */
9
14
  .main,
10
15
  :global(.byline-admin-layout-main) {
11
16
  display: flex;
12
- height: 100vh;
17
+ min-height: 100vh;
13
18
  width: 100%;
14
19
  max-width: 100%;
15
20
  padding-top: 45px;