@byline/host-tanstack-start 4.19.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin-shell/collections/edit.d.ts +1 -1
- package/dist/admin-shell/collections/edit.js +151 -31
- package/dist/admin-shell/collections/history.js +4 -2
- package/dist/admin-shell/collections/list.js +30 -6
- package/dist/admin-shell/collections/restore-version-modal.d.ts +3 -1
- package/dist/admin-shell/collections/restore-version-modal.js +9 -4
- package/dist/admin-shell/collections/tree-list.js +16 -6
- package/dist/admin-shell/collections/version-history.d.ts +2 -1
- package/dist/admin-shell/collections/version-history.js +11 -2
- package/dist/admin-shell/document-mutation-notice.d.ts +6 -0
- package/dist/admin-shell/document-mutation-notice.js +53 -0
- package/dist/admin-shell/document-mutation-state.d.ts +22 -0
- package/dist/admin-shell/document-mutation-state.js +71 -0
- package/dist/admin-shell/document-mutation-state.test.d.ts +1 -0
- package/dist/admin-shell/scheduled-publications/list.js +15 -5
- package/dist/admin-shell/singletons/history.js +4 -2
- package/dist/admin-shell/singletons/view.d.ts +1 -1
- package/dist/admin-shell/singletons/view.js +98 -19
- package/dist/integrations/byline-field-services.js +2 -9
- package/dist/routes/create-collection-edit-route.js +1 -1
- package/dist/routes/create-collection-list-route.d.ts +0 -7
- package/dist/routes/create-collection-list-route.js +7 -9
- package/dist/routes/create-singleton-route.d.ts +2 -0
- package/dist/routes/create-singleton-route.js +4 -3
- package/dist/server-fns/collections/copy-to-locale.d.ts +2 -7
- package/dist/server-fns/collections/copy-to-locale.js +4 -2
- package/dist/server-fns/collections/create.d.ts +1 -7
- package/dist/server-fns/collections/create.js +5 -3
- package/dist/server-fns/collections/delete-locale.d.ts +2 -7
- package/dist/server-fns/collections/delete-locale.js +4 -2
- package/dist/server-fns/collections/delete-outcome.d.ts +7 -3
- package/dist/server-fns/collections/delete-outcome.js +4 -0
- package/dist/server-fns/collections/delete.d.ts +2 -7
- package/dist/server-fns/collections/delete.js +5 -3
- package/dist/server-fns/collections/duplicate.d.ts +2 -7
- package/dist/server-fns/collections/duplicate.js +5 -3
- package/dist/server-fns/collections/get.d.ts +1 -7
- package/dist/server-fns/collections/get.js +5 -4
- package/dist/server-fns/collections/list.d.ts +0 -7
- package/dist/server-fns/collections/list.js +11 -3
- package/dist/server-fns/collections/reorder.d.ts +7 -8
- package/dist/server-fns/collections/reorder.js +20 -76
- package/dist/server-fns/collections/restore-version.d.ts +3 -7
- package/dist/server-fns/collections/restore-version.js +5 -2
- package/dist/server-fns/collections/save-outcome.d.ts +1 -0
- package/dist/server-fns/collections/save-outcome.js +1 -0
- package/dist/server-fns/collections/scheduled-publication.d.ts +49 -9
- package/dist/server-fns/collections/scheduled-publication.js +31 -21
- package/dist/server-fns/collections/status.d.ts +8 -7
- package/dist/server-fns/collections/status.js +13 -5
- package/dist/server-fns/collections/tree-mutation.d.ts +7 -6
- package/dist/server-fns/collections/tree-mutation.js +17 -5
- package/dist/server-fns/collections/tree.d.ts +14 -8
- package/dist/server-fns/collections/tree.js +21 -17
- package/dist/server-fns/collections/update.d.ts +12 -11
- package/dist/server-fns/collections/update.js +34 -20
- package/dist/server-fns/collections/upload.d.ts +1 -0
- package/dist/server-fns/collections/upload.js +3 -2
- package/dist/server-fns/document-mutation-errors.d.ts +5 -0
- package/dist/server-fns/document-mutation-errors.js +50 -0
- package/dist/server-fns/editable-revision.d.ts +9 -0
- package/dist/server-fns/editable-revision.js +11 -0
- package/dist/server-fns/singleton-document-read.d.ts +1 -1
- package/dist/server-fns/singleton-document-read.js +7 -3
- package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +6 -8
- package/dist/server-fns/singletons/cancel-scheduled-publish.js +4 -1
- package/dist/server-fns/singletons/change-status.d.ts +2 -7
- package/dist/server-fns/singletons/change-status.js +4 -1
- package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +5 -8
- package/dist/server-fns/singletons/confirm-scheduled-publish.js +4 -2
- package/dist/server-fns/singletons/copy-to-locale.d.ts +2 -7
- package/dist/server-fns/singletons/copy-to-locale.js +4 -2
- package/dist/server-fns/singletons/get.d.ts +4 -4
- package/dist/server-fns/singletons/restore-version.d.ts +2 -7
- package/dist/server-fns/singletons/restore-version.js +6 -3
- package/dist/server-fns/singletons/schedule-publish.d.ts +5 -8
- package/dist/server-fns/singletons/schedule-publish.js +4 -2
- package/dist/server-fns/singletons/unpublish.d.ts +2 -7
- package/dist/server-fns/singletons/unpublish.js +4 -1
- package/dist/server-fns/singletons/update.d.ts +4 -11
- package/dist/server-fns/singletons/update.js +5 -4
- package/dist/server-fns/structural-receipt.d.ts +4 -0
- package/dist/server-fns/structural-receipt.js +31 -0
- package/package.json +11 -11
- package/src/admin-shell/collections/edit.tsx +173 -54
- package/src/admin-shell/collections/history.tsx +11 -2
- package/src/admin-shell/collections/list.tsx +22 -6
- package/src/admin-shell/collections/restore-version-modal.tsx +12 -3
- package/src/admin-shell/collections/save-navigation.test.tsx +156 -8
- package/src/admin-shell/collections/tree-list.tsx +16 -6
- package/src/admin-shell/collections/version-history.tsx +13 -3
- package/src/admin-shell/document-mutation-notice.tsx +45 -0
- package/src/admin-shell/document-mutation-state.test.tsx +146 -0
- package/src/admin-shell/document-mutation-state.ts +95 -0
- package/src/admin-shell/scheduled-publications/list.tsx +16 -6
- package/src/admin-shell/singletons/history.test.tsx +3 -1
- package/src/admin-shell/singletons/history.tsx +10 -2
- package/src/admin-shell/singletons/view.test.tsx +59 -18
- package/src/admin-shell/singletons/view.tsx +110 -29
- package/src/integrations/byline-field-services.ts +2 -3
- package/src/routes/create-collection-edit-route.tsx +1 -0
- package/src/routes/create-collection-list-route.tsx +13 -2
- package/src/routes/create-singleton-route.test.node.ts +10 -2
- package/src/routes/create-singleton-route.tsx +6 -4
- package/src/server-fns/collections/copy-to-locale.ts +41 -36
- package/src/server-fns/collections/create.ts +54 -50
- package/src/server-fns/collections/delete-locale.ts +41 -32
- package/src/server-fns/collections/delete-outcome.test.node.ts +16 -0
- package/src/server-fns/collections/delete-outcome.ts +27 -16
- package/src/server-fns/collections/delete.ts +44 -38
- package/src/server-fns/collections/duplicate.ts +38 -32
- package/src/server-fns/collections/get.ts +8 -4
- package/src/server-fns/collections/list.ts +8 -3
- package/src/server-fns/collections/reorder.ts +32 -124
- package/src/server-fns/collections/restore-version.ts +47 -39
- package/src/server-fns/collections/revision-input.test.node.ts +108 -0
- package/src/server-fns/collections/save-outcome.test.node.ts +3 -0
- package/src/server-fns/collections/save-outcome.ts +2 -0
- package/src/server-fns/collections/scheduled-publication.ts +138 -111
- package/src/server-fns/collections/status.ts +86 -64
- package/src/server-fns/collections/tree-mutation.test.node.ts +87 -29
- package/src/server-fns/collections/tree-mutation.ts +28 -11
- package/src/server-fns/collections/tree.ts +150 -122
- package/src/server-fns/collections/update.ts +110 -86
- package/src/server-fns/collections/upload-singleton.test.node.ts +3 -7
- package/src/server-fns/collections/upload.ts +81 -77
- package/src/server-fns/document-mutation-errors.ts +67 -0
- package/src/server-fns/document-mutation-transport.test.node.ts +211 -0
- package/src/server-fns/editable-revision.test.node.ts +23 -0
- package/src/server-fns/editable-revision.ts +18 -0
- package/src/server-fns/singleton-document-read.ts +7 -3
- package/src/server-fns/singletons/cancel-scheduled-publish.ts +10 -3
- package/src/server-fns/singletons/change-status.ts +10 -3
- package/src/server-fns/singletons/confirm-scheduled-publish.ts +12 -6
- package/src/server-fns/singletons/copy-to-locale.ts +20 -15
- package/src/server-fns/singletons/get.ts +3 -3
- package/src/server-fns/singletons/restore-version.ts +17 -12
- package/src/server-fns/singletons/schedule-publish.ts +18 -7
- package/src/server-fns/singletons/singletons.test.node.ts +63 -25
- package/src/server-fns/singletons/unpublish.ts +10 -3
- package/src/server-fns/singletons/update.ts +21 -16
- package/src/server-fns/structural-receipt.ts +41 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
1
2
|
/**
|
|
2
3
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
4
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -11,22 +12,15 @@ import { createServerFn } from '@tanstack/react-start'
|
|
|
11
12
|
import { getAdminRequestContext } from '@byline/client/server'
|
|
12
13
|
import { ERR_NOT_FOUND, getLogger, getServerConfig } from '@byline/core'
|
|
13
14
|
import type { DocumentPatch } from '@byline/core/patches'
|
|
14
|
-
import type {
|
|
15
|
-
|
|
16
|
-
UpdateDocumentWithPatchesResult,
|
|
17
|
-
} from '@byline/core/services'
|
|
18
|
-
import { updateDocumentSystemFields, updateDocumentWithPatches } from '@byline/core/services'
|
|
15
|
+
import type { DocumentLifecycleContext } from '@byline/core/services'
|
|
16
|
+
import { saveDocument, updateDocumentSystemFields } from '@byline/core/services'
|
|
19
17
|
|
|
20
18
|
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
21
19
|
import { toCommittedDocumentHookFailureResponse } from './save-outcome.js'
|
|
22
20
|
|
|
23
21
|
// ---------------------------------------------------------------------------
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// Document-grain system fields (`path`, `availableLocales`) are deliberately
|
|
27
|
-
// NOT handled here: they are written through their own non-versioned path
|
|
28
|
-
// (`updateCollectionDocumentSystemFields` below) so that editing them does not
|
|
29
|
-
// mint a new version or reset workflow status. See docs/08-internationalization/index.md.
|
|
22
|
+
// Save content patches and optional metadata in one guarded transaction.
|
|
23
|
+
// Empty patches retain the non-versioned metadata-only behavior.
|
|
30
24
|
// ---------------------------------------------------------------------------
|
|
31
25
|
|
|
32
26
|
export const updateCollectionDocumentWithPatches = createServerFn({ method: 'POST' })
|
|
@@ -35,54 +29,69 @@ export const updateCollectionDocumentWithPatches = createServerFn({ method: 'POS
|
|
|
35
29
|
collection: string
|
|
36
30
|
id: string
|
|
37
31
|
patches: DocumentPatch[]
|
|
38
|
-
|
|
32
|
+
expectedRevision: number
|
|
33
|
+
path?: string | null
|
|
34
|
+
availableLocales?: string[]
|
|
39
35
|
locale?: string
|
|
40
36
|
}) => input
|
|
41
37
|
)
|
|
42
|
-
.handler(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
.handler(
|
|
39
|
+
withDocumentMutationErrors(async ({ data: input }) => {
|
|
40
|
+
const {
|
|
41
|
+
collection: path,
|
|
42
|
+
id,
|
|
43
|
+
patches,
|
|
44
|
+
expectedRevision,
|
|
45
|
+
locale,
|
|
46
|
+
path: explicitPath,
|
|
47
|
+
availableLocales,
|
|
48
|
+
} = input
|
|
49
|
+
const logger = getLogger()
|
|
50
|
+
const config = await ensureCollection(path)
|
|
51
|
+
if (!config) {
|
|
52
|
+
throw ERR_NOT_FOUND({
|
|
53
|
+
message: 'Collection not found',
|
|
54
|
+
details: { collectionPath: path },
|
|
55
|
+
}).log(logger)
|
|
56
|
+
}
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
const serverConfig = getServerConfig()
|
|
59
|
+
const ctx: DocumentLifecycleContext = {
|
|
60
|
+
db: serverConfig.db,
|
|
61
|
+
definition: config.definition,
|
|
62
|
+
collectionId: config.collection.id,
|
|
63
|
+
collectionVersion: config.collection.version,
|
|
64
|
+
collectionPath: path,
|
|
65
|
+
logger,
|
|
66
|
+
defaultLocale: serverConfig.i18n.content.defaultLocale,
|
|
67
|
+
slugifier: serverConfig.slugifier,
|
|
68
|
+
requestContext: await getAdminRequestContext(),
|
|
69
|
+
}
|
|
65
70
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
let result: Awaited<ReturnType<typeof saveDocument>>
|
|
72
|
+
try {
|
|
73
|
+
result = await saveDocument(ctx, {
|
|
74
|
+
documentId: id,
|
|
75
|
+
patches,
|
|
76
|
+
expectedRevision,
|
|
77
|
+
path: explicitPath ?? undefined,
|
|
78
|
+
availableLocales,
|
|
79
|
+
locale: locale ?? serverConfig.i18n.content.defaultLocale,
|
|
80
|
+
})
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const committedFailure = toCommittedDocumentHookFailureResponse(error)
|
|
83
|
+
if (committedFailure != null) return committedFailure
|
|
84
|
+
throw error
|
|
85
|
+
}
|
|
79
86
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
return {
|
|
88
|
+
status: 'ok' as const,
|
|
89
|
+
documentId: result.documentId,
|
|
90
|
+
...('documentVersionId' in result ? { documentVersionId: result.documentVersionId } : {}),
|
|
91
|
+
revision: result.revision,
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
)
|
|
86
95
|
|
|
87
96
|
// ---------------------------------------------------------------------------
|
|
88
97
|
// System-managed, document-grain fields (path + advertised locales)
|
|
@@ -97,6 +106,7 @@ export const updateCollectionDocumentSystemFields = createServerFn({ method: 'PO
|
|
|
97
106
|
(input: {
|
|
98
107
|
collection: string
|
|
99
108
|
id: string
|
|
109
|
+
expectedRevision: number
|
|
100
110
|
locale?: string
|
|
101
111
|
/** Path override; `null`/omitted means no path write. */
|
|
102
112
|
path?: string | null
|
|
@@ -104,39 +114,53 @@ export const updateCollectionDocumentSystemFields = createServerFn({ method: 'PO
|
|
|
104
114
|
availableLocales?: string[]
|
|
105
115
|
}) => input
|
|
106
116
|
)
|
|
107
|
-
.handler(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
.handler(
|
|
118
|
+
withDocumentMutationErrors(async ({ data: input }) => {
|
|
119
|
+
const {
|
|
120
|
+
collection: path,
|
|
121
|
+
id,
|
|
122
|
+
expectedRevision,
|
|
123
|
+
locale,
|
|
124
|
+
path: explicitPath,
|
|
125
|
+
availableLocales,
|
|
126
|
+
} = input
|
|
127
|
+
const logger = getLogger()
|
|
128
|
+
const config = await ensureCollection(path)
|
|
129
|
+
if (!config) {
|
|
130
|
+
throw ERR_NOT_FOUND({
|
|
131
|
+
message: 'Collection not found',
|
|
132
|
+
details: { collectionPath: path },
|
|
133
|
+
}).log(logger)
|
|
134
|
+
}
|
|
117
135
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
const serverConfig = getServerConfig()
|
|
137
|
+
const ctx: DocumentLifecycleContext = {
|
|
138
|
+
db: serverConfig.db,
|
|
139
|
+
definition: config.definition,
|
|
140
|
+
collectionId: config.collection.id,
|
|
141
|
+
collectionVersion: config.collection.version,
|
|
142
|
+
collectionPath: path,
|
|
143
|
+
logger,
|
|
144
|
+
defaultLocale: serverConfig.i18n.content.defaultLocale,
|
|
145
|
+
slugifier: serverConfig.slugifier,
|
|
146
|
+
requestContext: await getAdminRequestContext(),
|
|
147
|
+
}
|
|
130
148
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
149
|
+
try {
|
|
150
|
+
const result = await updateDocumentSystemFields(ctx, {
|
|
151
|
+
expectedRevision,
|
|
152
|
+
documentId: id,
|
|
153
|
+
locale: locale ?? serverConfig.i18n.content.defaultLocale,
|
|
154
|
+
path: explicitPath,
|
|
155
|
+
availableLocales,
|
|
156
|
+
// Reconciliation still requires a current observation.
|
|
157
|
+
reconcile: true,
|
|
158
|
+
})
|
|
159
|
+
return { status: 'ok' as const, revision: result.revision }
|
|
160
|
+
} catch (error) {
|
|
161
|
+
const committedFailure = toCommittedDocumentHookFailureResponse(error)
|
|
162
|
+
if (committedFailure != null) return committedFailure
|
|
163
|
+
throw error
|
|
164
|
+
}
|
|
139
165
|
})
|
|
140
|
-
|
|
141
|
-
return { status: 'ok' as const }
|
|
142
|
-
})
|
|
166
|
+
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { testAdapter } from '../../../../core/src/storage/db-adapter.test-helper.js'
|
|
1
2
|
/**
|
|
2
3
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
4
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -7,12 +8,7 @@
|
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import { AdminAuth, createRequestContext } from '@byline/auth'
|
|
10
|
-
import {
|
|
11
|
-
defineServerConfig,
|
|
12
|
-
defineSingleton,
|
|
13
|
-
type IDbAdapter,
|
|
14
|
-
type IStorageProvider,
|
|
15
|
-
} from '@byline/core'
|
|
11
|
+
import { defineServerConfig, defineSingleton, type IStorageProvider } from '@byline/core'
|
|
16
12
|
import { defineLogger } from '@byline/core/logger'
|
|
17
13
|
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
|
|
18
14
|
|
|
@@ -112,7 +108,7 @@ beforeAll(() => {
|
|
|
112
108
|
)
|
|
113
109
|
defineServerConfig({
|
|
114
110
|
collections: [singleton],
|
|
115
|
-
db:
|
|
111
|
+
db: testAdapter(),
|
|
116
112
|
storage,
|
|
117
113
|
i18n: {
|
|
118
114
|
admin: { defaultLocale: 'en', locales: ['en'] },
|
|
@@ -14,6 +14,7 @@ import { getLogger, withLogContext } from '@byline/core/logger'
|
|
|
14
14
|
import { uploadField as coreUploadField } from '@byline/core/services'
|
|
15
15
|
|
|
16
16
|
import { ensureDocumentResource } from '../../integrations/api-utils.js'
|
|
17
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Result of an upload through the host transport. The legacy top-level
|
|
@@ -25,6 +26,7 @@ export interface UploadFieldResult {
|
|
|
25
26
|
/** Present when the upload endpoint created a document (createDocument=true, the default). */
|
|
26
27
|
documentId?: string
|
|
27
28
|
documentVersionId?: string
|
|
29
|
+
revision?: number
|
|
28
30
|
storedFile: StoredFileValue
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -149,88 +151,90 @@ function resolveUploadField(
|
|
|
149
151
|
*/
|
|
150
152
|
export const uploadCollectionField = createServerFn({ method: 'POST' })
|
|
151
153
|
.validator(parseUploadFormData)
|
|
152
|
-
.handler(
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
.handler(
|
|
155
|
+
withDocumentMutationErrors(async ({ data }) => {
|
|
156
|
+
const { collectionPath, shouldCreateDocument, fieldName, file, fields } = data
|
|
157
|
+
const logger = getLogger()
|
|
155
158
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
159
|
+
return withLogContext(
|
|
160
|
+
{ domain: 'api', module: 'upload', function: 'uploadCollectionField' },
|
|
161
|
+
async () => {
|
|
162
|
+
// Upload is the one field transport shared by both document-resource
|
|
163
|
+
// kinds. The core service selects the kind-aware ability and rejects
|
|
164
|
+
// singleton document creation before hooks or storage; the admin form
|
|
165
|
+
// always requests the field-only branch (`createDocument=false`).
|
|
166
|
+
const config = await ensureDocumentResource(collectionPath)
|
|
167
|
+
if (config == null) {
|
|
168
|
+
throw ERR_NOT_FOUND(
|
|
169
|
+
{
|
|
170
|
+
message: 'Document resource not found.',
|
|
171
|
+
details: { collectionPath },
|
|
172
|
+
},
|
|
173
|
+
uploadCollectionField
|
|
174
|
+
).log(logger)
|
|
175
|
+
}
|
|
173
176
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
177
|
+
const serverConfig = getServerConfig()
|
|
178
|
+
const targetField = resolveUploadField(config.definition, collectionPath, fieldName)
|
|
179
|
+
// Per-field storage routing falls through to the site-wide default.
|
|
180
|
+
const storage = targetField.upload?.storage ?? serverConfig.storage
|
|
181
|
+
if (!storage) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`No storage provider configured for field '${targetField.name}' on collection ` +
|
|
184
|
+
`'${collectionPath}'. Set either field.upload.storage or the site-wide ` +
|
|
185
|
+
'ServerConfig.storage.'
|
|
186
|
+
)
|
|
187
|
+
}
|
|
185
188
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
let buffer: Buffer
|
|
190
|
+
try {
|
|
191
|
+
buffer = Buffer.from(await file.arrayBuffer())
|
|
192
|
+
} catch (err: unknown) {
|
|
193
|
+
logger.error({ err, collectionPath }, 'failed to read file buffer')
|
|
194
|
+
throw new Error('Failed to read uploaded file.')
|
|
195
|
+
}
|
|
193
196
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
197
|
+
const ctx: FieldUploadContext = {
|
|
198
|
+
db: serverConfig.db,
|
|
199
|
+
definition: config.definition,
|
|
200
|
+
collectionId: config.collection.id,
|
|
201
|
+
collectionVersion: config.collection.version,
|
|
202
|
+
collectionPath,
|
|
203
|
+
fieldName: targetField.name,
|
|
204
|
+
storage,
|
|
205
|
+
logger,
|
|
206
|
+
defaultLocale: serverConfig.i18n.content.defaultLocale,
|
|
207
|
+
slugifier: serverConfig.slugifier,
|
|
208
|
+
filenameSlugifier: serverConfig.uploads?.filenameSlugifier,
|
|
209
|
+
requestContext: await getAdminRequestContext(),
|
|
210
|
+
imageProcessor: {
|
|
211
|
+
extractMeta: extractImageMeta,
|
|
212
|
+
isBypassMimeType,
|
|
213
|
+
generateVariants: ({ buffer, mimeType, storedFile, storage, upload, logger }) =>
|
|
214
|
+
generateImageVariants(
|
|
215
|
+
buffer,
|
|
216
|
+
mimeType,
|
|
217
|
+
storedFile,
|
|
218
|
+
storage,
|
|
219
|
+
upload.sizes ?? [],
|
|
220
|
+
logger
|
|
221
|
+
),
|
|
222
|
+
},
|
|
223
|
+
}
|
|
221
224
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
225
|
+
return coreUploadField(ctx, {
|
|
226
|
+
buffer,
|
|
227
|
+
originalFilename: file.name || 'upload',
|
|
228
|
+
mimeType: file.type || 'application/octet-stream',
|
|
229
|
+
fileSize: file.size,
|
|
230
|
+
fields,
|
|
231
|
+
shouldCreateDocument,
|
|
232
|
+
locale: serverConfig.i18n.content.defaultLocale,
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
)
|
|
236
|
+
})
|
|
237
|
+
)
|
|
234
238
|
|
|
235
239
|
/**
|
|
236
240
|
* Upload a file to an upload-enabled collection.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BylineError,
|
|
3
|
+
ErrorCodes,
|
|
4
|
+
getDocumentRevisionValidationDetails,
|
|
5
|
+
getDocumentStaleDetails,
|
|
6
|
+
getLockConflictDetails,
|
|
7
|
+
} from '@byline/core'
|
|
8
|
+
import { getDocumentHookCommittedDetails } from '@byline/core/services'
|
|
9
|
+
|
|
10
|
+
import { committedTreeReceipt } from './structural-receipt.js'
|
|
11
|
+
|
|
12
|
+
/** Error subclasses lose custom properties in the server-function serializer.
|
|
13
|
+
* Throw only validated plain data for document failures; never transport causes,
|
|
14
|
+
* SQL, hook messages or an unverified current revision. */
|
|
15
|
+
export function documentMutationError(error: unknown): unknown {
|
|
16
|
+
const stale = getDocumentStaleDetails(error)
|
|
17
|
+
if (stale)
|
|
18
|
+
return {
|
|
19
|
+
code: ErrorCodes.DOCUMENT_STALE,
|
|
20
|
+
message: 'This document has changed. Reload before making changes.',
|
|
21
|
+
details: stale,
|
|
22
|
+
}
|
|
23
|
+
const validation = getDocumentRevisionValidationDetails(error)
|
|
24
|
+
if (validation)
|
|
25
|
+
return {
|
|
26
|
+
code: ErrorCodes.VALIDATION,
|
|
27
|
+
message: 'Reload this document before making changes.',
|
|
28
|
+
details: validation,
|
|
29
|
+
}
|
|
30
|
+
const lock = getLockConflictDetails(error)
|
|
31
|
+
if (lock)
|
|
32
|
+
return {
|
|
33
|
+
code: ErrorCodes.LOCK_CONFLICT,
|
|
34
|
+
message:
|
|
35
|
+
'This change could not be saved because another operation was using the document. Reload before trying again.',
|
|
36
|
+
details: lock,
|
|
37
|
+
}
|
|
38
|
+
const tree = committedTreeReceipt(error)
|
|
39
|
+
if (tree)
|
|
40
|
+
return {
|
|
41
|
+
code: ErrorCodes.TREE_HOOK_COMMITTED,
|
|
42
|
+
message: 'The structure was saved, but a follow-up action failed.',
|
|
43
|
+
details: tree,
|
|
44
|
+
}
|
|
45
|
+
const committed = getDocumentHookCommittedDetails(error)
|
|
46
|
+
if (committed)
|
|
47
|
+
return {
|
|
48
|
+
code: ErrorCodes.DOCUMENT_HOOK_COMMITTED,
|
|
49
|
+
message: 'The document was saved, but a follow-up action failed.',
|
|
50
|
+
details: committed,
|
|
51
|
+
}
|
|
52
|
+
if (error instanceof BylineError && error.code === ErrorCodes.NOT_FOUND)
|
|
53
|
+
return { code: ErrorCodes.NOT_FOUND, message: 'This document is no longer available.' }
|
|
54
|
+
return error
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function withDocumentMutationErrors<T, R>(
|
|
58
|
+
handler: (input: T) => Promise<R>
|
|
59
|
+
): (input: T) => Promise<R> {
|
|
60
|
+
return async (input) => {
|
|
61
|
+
try {
|
|
62
|
+
return await handler(input)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw documentMutationError(error)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|