@byline/host-tanstack-start 3.2.1 → 3.3.1

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.
@@ -4,7 +4,7 @@ import { FormRenderer } from "@byline/admin/react";
4
4
  import { getDefaultStatus, getWorkflowStatuses } from "@byline/core";
5
5
  import { useTranslation } from "@byline/i18n/react";
6
6
  import { Container, Section, useToastManager } from "@byline/ui/react";
7
- import { copyDocumentToLocale, deleteDocument, deleteDocumentLocale, duplicateCollectionDocument, unpublishDocument, updateCollectionDocumentWithPatches, updateDocumentStatus } from "../../server-fns/collections/index.js";
7
+ import { copyDocumentToLocale, deleteDocument, deleteDocumentLocale, duplicateCollectionDocument, unpublishDocument, updateCollectionDocumentSystemFields, updateCollectionDocumentWithPatches, updateDocumentStatus } from "../../server-fns/collections/index.js";
8
8
  import { useNavigate } from "../chrome/loose-router.js";
9
9
  import { useTanStackNavigationGuard } from "./tanstack-navigation-guard.js";
10
10
  import { ViewMenu } from "./view-menu.js";
@@ -409,23 +409,30 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
409
409
  });
410
410
  }
411
411
  };
412
- const handleSubmit = async ({ data: _data, patches, systemPath, systemAvailableLocales })=>{
412
+ const handleSubmit = async ({ data: _data, patches, contentDirty, pathDirty, systemPath, availableLocalesDirty, systemAvailableLocales })=>{
413
413
  try {
414
- await updateCollectionDocumentWithPatches({
414
+ if (pathDirty || availableLocalesDirty) await updateCollectionDocumentSystemFields({
415
415
  data: {
416
416
  collection: path,
417
417
  id: String(initialData.id),
418
- patches,
419
- versionId: initialData.versionId,
420
418
  locale,
421
- ...systemPath ? {
422
- path: systemPath
419
+ ...pathDirty ? {
420
+ path: systemPath ?? null
423
421
  } : {},
424
- ...systemAvailableLocales ? {
425
- availableLocales: systemAvailableLocales
422
+ ...availableLocalesDirty ? {
423
+ availableLocales: systemAvailableLocales ?? []
426
424
  } : {}
427
425
  }
428
426
  });
427
+ if (contentDirty) await updateCollectionDocumentWithPatches({
428
+ data: {
429
+ collection: path,
430
+ id: String(initialData.id),
431
+ patches,
432
+ versionId: initialData.versionId,
433
+ locale
434
+ }
435
+ });
429
436
  const description = t("collections.edit.updatedDescription", {
430
437
  label: singularLower
431
438
  });
@@ -12,15 +12,30 @@ export declare const updateCollectionDocumentWithPatches: import("@tanstack/reac
12
12
  patches: DocumentPatch[];
13
13
  versionId?: string;
14
14
  locale?: string;
15
- path?: string;
16
- availableLocales?: string[];
17
15
  }) => {
18
16
  collection: string;
19
17
  id: string;
20
18
  patches: DocumentPatch[];
21
19
  versionId?: string;
22
20
  locale?: string;
23
- path?: string;
21
+ }, Promise<{
22
+ status: "ok";
23
+ }>>;
24
+ export declare const updateCollectionDocumentSystemFields: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
25
+ collection: string;
26
+ id: string;
27
+ locale?: string;
28
+ /** Path override; `null`/omitted means no path write. */
29
+ path?: string | null;
30
+ /** Editorial advertised-locale set; omitted means no advertised-locale write. */
31
+ availableLocales?: string[];
32
+ }) => {
33
+ collection: string;
34
+ id: string;
35
+ locale?: string;
36
+ /** Path override; `null`/omitted means no path write. */
37
+ path?: string | null;
38
+ /** Editorial advertised-locale set; omitted means no advertised-locale write. */
24
39
  availableLocales?: string[];
25
40
  }, Promise<{
26
41
  status: "ok";
@@ -1,12 +1,12 @@
1
1
  import { createServerFn } from "@tanstack/react-start";
2
2
  import { ERR_NOT_FOUND, getLogger, getServerConfig } from "@byline/core";
3
- import { updateDocumentWithPatches } from "@byline/core/services";
3
+ import { updateDocumentSystemFields, updateDocumentWithPatches } from "@byline/core/services";
4
4
  import { getAdminRequestContext } from "../../auth/auth-context.js";
5
5
  import { ensureCollection } from "../../integrations/api-utils.js";
6
6
  const updateCollectionDocumentWithPatches = createServerFn({
7
7
  method: 'POST'
8
8
  }).inputValidator((input)=>input).handler(async ({ data: input })=>{
9
- const { collection: path, id, patches, versionId, locale, path: explicitPath, availableLocales } = input;
9
+ const { collection: path, id, patches, versionId, locale } = input;
10
10
  const logger = getLogger();
11
11
  const config = await ensureCollection(path);
12
12
  if (!config) throw ERR_NOT_FOUND({
@@ -31,6 +31,38 @@ const updateCollectionDocumentWithPatches = createServerFn({
31
31
  documentId: id,
32
32
  patches,
33
33
  documentVersionId: versionId,
34
+ locale: locale ?? serverConfig.i18n.content.defaultLocale
35
+ });
36
+ return {
37
+ status: 'ok'
38
+ };
39
+ });
40
+ const updateCollectionDocumentSystemFields = createServerFn({
41
+ method: 'POST'
42
+ }).inputValidator((input)=>input).handler(async ({ data: input })=>{
43
+ const { collection: path, id, locale, path: explicitPath, availableLocales } = input;
44
+ const logger = getLogger();
45
+ const config = await ensureCollection(path);
46
+ if (!config) throw ERR_NOT_FOUND({
47
+ message: 'Collection not found',
48
+ details: {
49
+ collectionPath: path
50
+ }
51
+ }).log(logger);
52
+ const serverConfig = getServerConfig();
53
+ const ctx = {
54
+ db: serverConfig.db,
55
+ definition: config.definition,
56
+ collectionId: config.collection.id,
57
+ collectionVersion: config.collection.version,
58
+ collectionPath: path,
59
+ logger,
60
+ defaultLocale: serverConfig.i18n.content.defaultLocale,
61
+ slugifier: serverConfig.slugifier,
62
+ requestContext: await getAdminRequestContext()
63
+ };
64
+ await updateDocumentSystemFields(ctx, {
65
+ documentId: id,
34
66
  locale: locale ?? serverConfig.i18n.content.defaultLocale,
35
67
  path: explicitPath,
36
68
  availableLocales
@@ -39,4 +71,4 @@ const updateCollectionDocumentWithPatches = createServerFn({
39
71
  status: 'ok'
40
72
  };
41
73
  });
42
- export { updateCollectionDocumentWithPatches };
74
+ export { updateCollectionDocumentSystemFields, updateCollectionDocumentWithPatches };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "license": "MPL-2.0",
6
- "version": "3.2.1",
6
+ "version": "3.3.1",
7
7
  "engines": {
8
8
  "node": ">=20.9.0"
9
9
  },
@@ -115,13 +115,13 @@
115
115
  "react-swipeable": "^7.0.2",
116
116
  "uuid": "^14.0.0",
117
117
  "zod": "^4.4.3",
118
- "@byline/client": "3.2.1",
119
- "@byline/auth": "3.2.1",
120
- "@byline/core": "3.2.1",
121
- "@byline/admin": "3.2.1",
122
- "@byline/ai": "3.2.1",
123
- "@byline/i18n": "3.2.1",
124
- "@byline/ui": "3.2.1"
118
+ "@byline/auth": "3.3.1",
119
+ "@byline/core": "3.3.1",
120
+ "@byline/client": "3.3.1",
121
+ "@byline/i18n": "3.3.1",
122
+ "@byline/admin": "3.3.1",
123
+ "@byline/ui": "3.3.1",
124
+ "@byline/ai": "3.3.1"
125
125
  },
126
126
  "peerDependencies": {
127
127
  "@tanstack/react-router": "^1.167.0",
@@ -21,6 +21,7 @@ import {
21
21
  deleteDocumentLocale,
22
22
  duplicateCollectionDocument,
23
23
  unpublishDocument,
24
+ updateCollectionDocumentSystemFields,
24
25
  updateCollectionDocumentWithPatches,
25
26
  updateDocumentStatus,
26
27
  } from '../../server-fns/collections/index.js'
@@ -386,28 +387,57 @@ export const EditView = ({
386
387
  const handleSubmit = async ({
387
388
  data: _data,
388
389
  patches,
390
+ contentDirty,
391
+ pathDirty,
389
392
  systemPath,
393
+ availableLocalesDirty,
390
394
  systemAvailableLocales,
391
395
  }: {
392
396
  // biome-ignore lint/suspicious/noExplicitAny: data is collection-specific
393
397
  data: any
394
398
  // biome-ignore lint/suspicious/noExplicitAny: patches list shape
395
399
  patches: any[]
400
+ /** Document field data / patches changed → versioned write. */
401
+ contentDirty: boolean
402
+ /** Path widget changed → non-versioned direct write. */
403
+ pathDirty: boolean
396
404
  systemPath?: string | null
405
+ /** Available-locales widget changed → non-versioned direct write. */
406
+ availableLocalesDirty: boolean
397
407
  systemAvailableLocales?: string[]
398
408
  }) => {
399
409
  try {
400
- await updateCollectionDocumentWithPatches({
401
- data: {
402
- collection: path,
403
- id: String(initialData.id),
404
- patches,
405
- versionId: initialData.versionId as string | undefined,
406
- locale,
407
- ...(systemPath ? { path: systemPath } : {}),
408
- ...(systemAvailableLocales ? { availableLocales: systemAvailableLocales } : {}),
409
- },
410
- })
410
+ // Document-grain system fields write first via their own non-versioned
411
+ // path — so a path conflict surfaces before we mint a content version,
412
+ // and these immediate writes never reset workflow status. See
413
+ // docs/I18N.md.
414
+ if (pathDirty || availableLocalesDirty) {
415
+ await updateCollectionDocumentSystemFields({
416
+ data: {
417
+ collection: path,
418
+ id: String(initialData.id),
419
+ locale,
420
+ ...(pathDirty ? { path: systemPath ?? null } : {}),
421
+ ...(availableLocalesDirty ? { availableLocales: systemAvailableLocales ?? [] } : {}),
422
+ },
423
+ })
424
+ }
425
+
426
+ // Content (field data / patches) follows the normal versioned path —
427
+ // mints a new draft version. Skipped entirely when only the system
428
+ // fields changed, so a path/advertising edit never creates an empty
429
+ // content version.
430
+ if (contentDirty) {
431
+ await updateCollectionDocumentWithPatches({
432
+ data: {
433
+ collection: path,
434
+ id: String(initialData.id),
435
+ patches,
436
+ versionId: initialData.versionId as string | undefined,
437
+ locale,
438
+ },
439
+ })
440
+ }
411
441
 
412
442
  const description = t('collections.edit.updatedDescription', { label: singularLower })
413
443
  toastManager.add({
@@ -11,13 +11,18 @@ import { createServerFn } from '@tanstack/react-start'
11
11
  import { ERR_NOT_FOUND, getLogger, getServerConfig } from '@byline/core'
12
12
  import type { DocumentPatch } from '@byline/core/patches'
13
13
  import type { DocumentLifecycleContext } from '@byline/core/services'
14
- import { updateDocumentWithPatches } from '@byline/core/services'
14
+ import { updateDocumentSystemFields, updateDocumentWithPatches } from '@byline/core/services'
15
15
 
16
16
  import { getAdminRequestContext } from '../../auth/auth-context.js'
17
17
  import { ensureCollection } from '../../integrations/api-utils.js'
18
18
 
19
19
  // ---------------------------------------------------------------------------
20
20
  // Apply patches (patch-based update — creates a new immutable version)
21
+ //
22
+ // Document-grain system fields (`path`, `availableLocales`) are deliberately
23
+ // NOT handled here: they are written through their own non-versioned path
24
+ // (`updateCollectionDocumentSystemFields` below) so that editing them does not
25
+ // mint a new version or reset workflow status. See docs/I18N.md.
21
26
  // ---------------------------------------------------------------------------
22
27
 
23
28
  export const updateCollectionDocumentWithPatches = createServerFn({ method: 'POST' })
@@ -28,20 +33,10 @@ export const updateCollectionDocumentWithPatches = createServerFn({ method: 'POS
28
33
  patches: DocumentPatch[]
29
34
  versionId?: string
30
35
  locale?: string
31
- path?: string
32
- availableLocales?: string[]
33
36
  }) => input
34
37
  )
35
38
  .handler(async ({ data: input }) => {
36
- const {
37
- collection: path,
38
- id,
39
- patches,
40
- versionId,
41
- locale,
42
- path: explicitPath,
43
- availableLocales,
44
- } = input
39
+ const { collection: path, id, patches, versionId, locale } = input
45
40
  const logger = getLogger()
46
41
  const config = await ensureCollection(path)
47
42
  if (!config) {
@@ -69,6 +64,58 @@ export const updateCollectionDocumentWithPatches = createServerFn({ method: 'POS
69
64
  patches,
70
65
  documentVersionId: versionId,
71
66
  locale: locale ?? serverConfig.i18n.content.defaultLocale,
67
+ })
68
+
69
+ return { status: 'ok' as const }
70
+ })
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // System-managed, document-grain fields (path + advertised locales)
74
+ //
75
+ // Non-versioned, immediate write — does NOT create a new version or change
76
+ // workflow status. Backs the admin path / available-locales widgets'
77
+ // direct-write Save (the `direct-write` and `both` dirty-reason cases).
78
+ // ---------------------------------------------------------------------------
79
+
80
+ export const updateCollectionDocumentSystemFields = createServerFn({ method: 'POST' })
81
+ .inputValidator(
82
+ (input: {
83
+ collection: string
84
+ id: string
85
+ locale?: string
86
+ /** Path override; `null`/omitted means no path write. */
87
+ path?: string | null
88
+ /** Editorial advertised-locale set; omitted means no advertised-locale write. */
89
+ availableLocales?: string[]
90
+ }) => input
91
+ )
92
+ .handler(async ({ data: input }) => {
93
+ const { collection: path, id, locale, path: explicitPath, availableLocales } = input
94
+ const logger = getLogger()
95
+ const config = await ensureCollection(path)
96
+ if (!config) {
97
+ throw ERR_NOT_FOUND({
98
+ message: 'Collection not found',
99
+ details: { collectionPath: path },
100
+ }).log(logger)
101
+ }
102
+
103
+ const serverConfig = getServerConfig()
104
+ const ctx: DocumentLifecycleContext = {
105
+ db: serverConfig.db,
106
+ definition: config.definition,
107
+ collectionId: config.collection.id,
108
+ collectionVersion: config.collection.version,
109
+ collectionPath: path,
110
+ logger,
111
+ defaultLocale: serverConfig.i18n.content.defaultLocale,
112
+ slugifier: serverConfig.slugifier,
113
+ requestContext: await getAdminRequestContext(),
114
+ }
115
+
116
+ await updateDocumentSystemFields(ctx, {
117
+ documentId: id,
118
+ locale: locale ?? serverConfig.i18n.content.defaultLocale,
72
119
  path: explicitPath,
73
120
  availableLocales,
74
121
  })