@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.
Files changed (142) hide show
  1. package/dist/admin-shell/collections/edit.d.ts +1 -1
  2. package/dist/admin-shell/collections/edit.js +151 -31
  3. package/dist/admin-shell/collections/history.js +4 -2
  4. package/dist/admin-shell/collections/list.js +30 -6
  5. package/dist/admin-shell/collections/restore-version-modal.d.ts +3 -1
  6. package/dist/admin-shell/collections/restore-version-modal.js +9 -4
  7. package/dist/admin-shell/collections/tree-list.js +16 -6
  8. package/dist/admin-shell/collections/version-history.d.ts +2 -1
  9. package/dist/admin-shell/collections/version-history.js +11 -2
  10. package/dist/admin-shell/document-mutation-notice.d.ts +6 -0
  11. package/dist/admin-shell/document-mutation-notice.js +53 -0
  12. package/dist/admin-shell/document-mutation-state.d.ts +22 -0
  13. package/dist/admin-shell/document-mutation-state.js +71 -0
  14. package/dist/admin-shell/document-mutation-state.test.d.ts +1 -0
  15. package/dist/admin-shell/scheduled-publications/list.js +15 -5
  16. package/dist/admin-shell/singletons/history.js +4 -2
  17. package/dist/admin-shell/singletons/view.d.ts +1 -1
  18. package/dist/admin-shell/singletons/view.js +98 -19
  19. package/dist/integrations/byline-field-services.js +2 -9
  20. package/dist/routes/create-collection-edit-route.js +1 -1
  21. package/dist/routes/create-collection-list-route.d.ts +0 -7
  22. package/dist/routes/create-collection-list-route.js +7 -9
  23. package/dist/routes/create-singleton-route.d.ts +2 -0
  24. package/dist/routes/create-singleton-route.js +4 -3
  25. package/dist/server-fns/collections/copy-to-locale.d.ts +2 -7
  26. package/dist/server-fns/collections/copy-to-locale.js +4 -2
  27. package/dist/server-fns/collections/create.d.ts +1 -7
  28. package/dist/server-fns/collections/create.js +5 -3
  29. package/dist/server-fns/collections/delete-locale.d.ts +2 -7
  30. package/dist/server-fns/collections/delete-locale.js +4 -2
  31. package/dist/server-fns/collections/delete-outcome.d.ts +7 -3
  32. package/dist/server-fns/collections/delete-outcome.js +4 -0
  33. package/dist/server-fns/collections/delete.d.ts +2 -7
  34. package/dist/server-fns/collections/delete.js +5 -3
  35. package/dist/server-fns/collections/duplicate.d.ts +2 -7
  36. package/dist/server-fns/collections/duplicate.js +5 -3
  37. package/dist/server-fns/collections/get.d.ts +1 -7
  38. package/dist/server-fns/collections/get.js +5 -4
  39. package/dist/server-fns/collections/list.d.ts +0 -7
  40. package/dist/server-fns/collections/list.js +11 -3
  41. package/dist/server-fns/collections/reorder.d.ts +7 -8
  42. package/dist/server-fns/collections/reorder.js +20 -76
  43. package/dist/server-fns/collections/restore-version.d.ts +3 -7
  44. package/dist/server-fns/collections/restore-version.js +5 -2
  45. package/dist/server-fns/collections/save-outcome.d.ts +1 -0
  46. package/dist/server-fns/collections/save-outcome.js +1 -0
  47. package/dist/server-fns/collections/scheduled-publication.d.ts +49 -9
  48. package/dist/server-fns/collections/scheduled-publication.js +31 -21
  49. package/dist/server-fns/collections/status.d.ts +8 -7
  50. package/dist/server-fns/collections/status.js +13 -5
  51. package/dist/server-fns/collections/tree-mutation.d.ts +7 -6
  52. package/dist/server-fns/collections/tree-mutation.js +17 -5
  53. package/dist/server-fns/collections/tree.d.ts +14 -8
  54. package/dist/server-fns/collections/tree.js +21 -17
  55. package/dist/server-fns/collections/update.d.ts +12 -11
  56. package/dist/server-fns/collections/update.js +34 -20
  57. package/dist/server-fns/collections/upload.d.ts +1 -0
  58. package/dist/server-fns/collections/upload.js +3 -2
  59. package/dist/server-fns/document-mutation-errors.d.ts +5 -0
  60. package/dist/server-fns/document-mutation-errors.js +50 -0
  61. package/dist/server-fns/editable-revision.d.ts +9 -0
  62. package/dist/server-fns/editable-revision.js +11 -0
  63. package/dist/server-fns/singleton-document-read.d.ts +1 -1
  64. package/dist/server-fns/singleton-document-read.js +7 -3
  65. package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +6 -8
  66. package/dist/server-fns/singletons/cancel-scheduled-publish.js +4 -1
  67. package/dist/server-fns/singletons/change-status.d.ts +2 -7
  68. package/dist/server-fns/singletons/change-status.js +4 -1
  69. package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +5 -8
  70. package/dist/server-fns/singletons/confirm-scheduled-publish.js +4 -2
  71. package/dist/server-fns/singletons/copy-to-locale.d.ts +2 -7
  72. package/dist/server-fns/singletons/copy-to-locale.js +4 -2
  73. package/dist/server-fns/singletons/get.d.ts +4 -4
  74. package/dist/server-fns/singletons/restore-version.d.ts +2 -7
  75. package/dist/server-fns/singletons/restore-version.js +6 -3
  76. package/dist/server-fns/singletons/schedule-publish.d.ts +5 -8
  77. package/dist/server-fns/singletons/schedule-publish.js +4 -2
  78. package/dist/server-fns/singletons/unpublish.d.ts +2 -7
  79. package/dist/server-fns/singletons/unpublish.js +4 -1
  80. package/dist/server-fns/singletons/update.d.ts +4 -11
  81. package/dist/server-fns/singletons/update.js +5 -4
  82. package/dist/server-fns/structural-receipt.d.ts +4 -0
  83. package/dist/server-fns/structural-receipt.js +31 -0
  84. package/package.json +11 -11
  85. package/src/admin-shell/collections/edit.tsx +173 -54
  86. package/src/admin-shell/collections/history.tsx +11 -2
  87. package/src/admin-shell/collections/list.tsx +22 -6
  88. package/src/admin-shell/collections/restore-version-modal.tsx +12 -3
  89. package/src/admin-shell/collections/save-navigation.test.tsx +156 -8
  90. package/src/admin-shell/collections/tree-list.tsx +16 -6
  91. package/src/admin-shell/collections/version-history.tsx +13 -3
  92. package/src/admin-shell/document-mutation-notice.tsx +45 -0
  93. package/src/admin-shell/document-mutation-state.test.tsx +146 -0
  94. package/src/admin-shell/document-mutation-state.ts +95 -0
  95. package/src/admin-shell/scheduled-publications/list.tsx +16 -6
  96. package/src/admin-shell/singletons/history.test.tsx +3 -1
  97. package/src/admin-shell/singletons/history.tsx +10 -2
  98. package/src/admin-shell/singletons/view.test.tsx +59 -18
  99. package/src/admin-shell/singletons/view.tsx +110 -29
  100. package/src/integrations/byline-field-services.ts +2 -3
  101. package/src/routes/create-collection-edit-route.tsx +1 -0
  102. package/src/routes/create-collection-list-route.tsx +13 -2
  103. package/src/routes/create-singleton-route.test.node.ts +10 -2
  104. package/src/routes/create-singleton-route.tsx +6 -4
  105. package/src/server-fns/collections/copy-to-locale.ts +41 -36
  106. package/src/server-fns/collections/create.ts +54 -50
  107. package/src/server-fns/collections/delete-locale.ts +41 -32
  108. package/src/server-fns/collections/delete-outcome.test.node.ts +16 -0
  109. package/src/server-fns/collections/delete-outcome.ts +27 -16
  110. package/src/server-fns/collections/delete.ts +44 -38
  111. package/src/server-fns/collections/duplicate.ts +38 -32
  112. package/src/server-fns/collections/get.ts +8 -4
  113. package/src/server-fns/collections/list.ts +8 -3
  114. package/src/server-fns/collections/reorder.ts +32 -124
  115. package/src/server-fns/collections/restore-version.ts +47 -39
  116. package/src/server-fns/collections/revision-input.test.node.ts +108 -0
  117. package/src/server-fns/collections/save-outcome.test.node.ts +3 -0
  118. package/src/server-fns/collections/save-outcome.ts +2 -0
  119. package/src/server-fns/collections/scheduled-publication.ts +138 -111
  120. package/src/server-fns/collections/status.ts +86 -64
  121. package/src/server-fns/collections/tree-mutation.test.node.ts +87 -29
  122. package/src/server-fns/collections/tree-mutation.ts +28 -11
  123. package/src/server-fns/collections/tree.ts +150 -122
  124. package/src/server-fns/collections/update.ts +110 -86
  125. package/src/server-fns/collections/upload-singleton.test.node.ts +3 -7
  126. package/src/server-fns/collections/upload.ts +81 -77
  127. package/src/server-fns/document-mutation-errors.ts +67 -0
  128. package/src/server-fns/document-mutation-transport.test.node.ts +211 -0
  129. package/src/server-fns/editable-revision.test.node.ts +23 -0
  130. package/src/server-fns/editable-revision.ts +18 -0
  131. package/src/server-fns/singleton-document-read.ts +7 -3
  132. package/src/server-fns/singletons/cancel-scheduled-publish.ts +10 -3
  133. package/src/server-fns/singletons/change-status.ts +10 -3
  134. package/src/server-fns/singletons/confirm-scheduled-publish.ts +12 -6
  135. package/src/server-fns/singletons/copy-to-locale.ts +20 -15
  136. package/src/server-fns/singletons/get.ts +3 -3
  137. package/src/server-fns/singletons/restore-version.ts +17 -12
  138. package/src/server-fns/singletons/schedule-publish.ts +18 -7
  139. package/src/server-fns/singletons/singletons.test.node.ts +63 -25
  140. package/src/server-fns/singletons/unpublish.ts +10 -3
  141. package/src/server-fns/singletons/update.ts +21 -16
  142. package/src/server-fns/structural-receipt.ts +41 -0
@@ -6,7 +6,12 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
- import { ERR_DOCUMENT_HOOK_COMMITTED, ErrorCodes } from '@byline/core'
9
+ import {
10
+ ERR_DOCUMENT_HOOK_COMMITTED,
11
+ ERR_LOCK_CONFLICT,
12
+ ErrorCodes,
13
+ getLockConflictDetails,
14
+ } from '@byline/core'
10
15
  import { beforeEach, describe, expect, it, vi } from 'vitest'
11
16
 
12
17
  const mocks = vi.hoisted(() => ({
@@ -57,7 +62,7 @@ const invoke = (serverFunction: unknown, data: Record<string, any>) =>
57
62
  (serverFunction as ServerFunction)({ data })
58
63
 
59
64
  const handle = {
60
- get: vi.fn(),
65
+ getForEdit: vi.fn(),
61
66
  update: vi.fn(),
62
67
  changeStatus: vi.fn(),
63
68
  unpublish: vi.fn(),
@@ -84,14 +89,13 @@ describe('singleton server functions', () => {
84
89
  const cases = [
85
90
  {
86
91
  serverFunction: getSingleton,
87
- method: 'get',
92
+ method: 'getForEdit',
88
93
  input: { singleton: 'site-settings', locale: 'fr' },
89
94
  args: [
90
95
  {
91
96
  locale: 'fr',
92
97
  populate: undefined,
93
98
  depth: undefined,
94
- status: 'any',
95
99
  onMissingLocale: 'empty',
96
100
  lenient: true,
97
101
  },
@@ -105,23 +109,23 @@ describe('singleton server functions', () => {
105
109
  singleton: 'site-settings',
106
110
  data: { title: 'Changed' },
107
111
  locale: 'fr',
108
- expectedVersionId: 'version-1',
112
+ expectedRevision: 7,
109
113
  },
110
- args: [{ title: 'Changed' }, { locale: 'fr', expectedVersionId: 'version-1' }],
114
+ args: [{ title: 'Changed' }, { locale: 'fr', expectedRevision: 7 }],
111
115
  result: { versionId: 'version-2' },
112
116
  },
113
117
  {
114
118
  serverFunction: changeSingletonStatus,
115
119
  method: 'changeStatus',
116
- input: { singleton: 'site-settings', status: 'published' },
117
- args: ['published'],
120
+ input: { singleton: 'site-settings', status: 'published', expectedRevision: 7 },
121
+ args: ['published', { expectedRevision: 7 }],
118
122
  result: { newStatus: 'published' },
119
123
  },
120
124
  {
121
125
  serverFunction: unpublishSingleton,
122
126
  method: 'unpublish',
123
- input: { singleton: 'site-settings' },
124
- args: [],
127
+ input: { singleton: 'site-settings', expectedRevision: 7 },
128
+ args: [{ expectedRevision: 7 }],
125
129
  result: { archivedCount: 1 },
126
130
  },
127
131
  {
@@ -130,11 +134,13 @@ describe('singleton server functions', () => {
130
134
  input: {
131
135
  singleton: 'site-settings',
132
136
  publishAt: '2026-09-01T10:00:00Z',
137
+ expectedRevision: 7,
133
138
  expectedVersionId: 'version-2',
134
139
  },
135
140
  args: [
136
141
  {
137
142
  publishAt: '2026-09-01T10:00:00Z',
143
+ expectedRevision: 7,
138
144
  expectedVersionId: 'version-2',
139
145
  },
140
146
  ],
@@ -143,15 +149,15 @@ describe('singleton server functions', () => {
143
149
  {
144
150
  serverFunction: confirmSingletonScheduledPublish,
145
151
  method: 'confirmScheduledPublish',
146
- input: { singleton: 'site-settings', expectedVersionId: 'version-3' },
147
- args: [{ expectedVersionId: 'version-3' }],
152
+ input: { singleton: 'site-settings', expectedRevision: 7, expectedVersionId: 'version-3' },
153
+ args: [{ expectedRevision: 7, expectedVersionId: 'version-3' }],
148
154
  result: { state: 'armed' },
149
155
  },
150
156
  {
151
157
  serverFunction: cancelSingletonScheduledPublish,
152
158
  method: 'cancelScheduledPublish',
153
- input: { singleton: 'site-settings' },
154
- args: [],
159
+ input: { singleton: 'site-settings', expectedRevision: 7 },
160
+ args: [{ expectedRevision: 7 }],
155
161
  result: { state: 'cancelled' },
156
162
  },
157
163
  {
@@ -185,8 +191,8 @@ describe('singleton server functions', () => {
185
191
  {
186
192
  serverFunction: restoreSingletonVersion,
187
193
  method: 'restoreVersion',
188
- input: { singleton: 'site-settings', versionId: 'version-1' },
189
- args: ['version-1'],
194
+ input: { singleton: 'site-settings', versionId: 'version-1', expectedRevision: 7 },
195
+ args: ['version-1', { expectedRevision: 7 }],
190
196
  result: { versionId: 'version-4' },
191
197
  },
192
198
  {
@@ -194,18 +200,23 @@ describe('singleton server functions', () => {
194
200
  method: 'copyToLocale',
195
201
  input: {
196
202
  singleton: 'site-settings',
203
+ expectedRevision: 7,
197
204
  sourceLocale: 'en',
198
205
  targetLocale: 'fr',
199
206
  overwrite: true,
200
207
  },
201
- args: [{ sourceLocale: 'en', targetLocale: 'fr', overwrite: true }],
208
+ args: [{ expectedRevision: 7, sourceLocale: 'en', targetLocale: 'fr', overwrite: true }],
202
209
  result: { versionId: 'version-5' },
203
210
  },
204
211
  ] as const
205
212
 
206
213
  for (const testCase of cases) {
207
214
  const method = handle[testCase.method]
208
- method.mockResolvedValueOnce(testCase.result)
215
+ method.mockResolvedValueOnce(
216
+ testCase.method === 'getForEdit'
217
+ ? { state: 'document', document: testCase.result }
218
+ : testCase.result
219
+ )
209
220
 
210
221
  const result = await invoke(testCase.serverFunction, testCase.input)
211
222
 
@@ -219,18 +230,18 @@ describe('singleton server functions', () => {
219
230
  }
220
231
  })
221
232
 
222
- it('forwards the optimistic version to update unchanged', async () => {
233
+ it('forwards the observed revision to update unchanged', async () => {
223
234
  handle.update.mockResolvedValueOnce({ versionId: 'version-next' })
224
235
 
225
236
  await invoke(updateSingleton, {
226
237
  singleton: 'site-settings',
227
238
  data: { title: 'Changed' },
228
- expectedVersionId: 'version-current',
239
+ expectedRevision: 7,
229
240
  })
230
241
 
231
242
  expect(handle.update).toHaveBeenCalledWith(
232
243
  { title: 'Changed' },
233
- { locale: undefined, expectedVersionId: 'version-current' }
244
+ { locale: undefined, expectedRevision: 7 }
234
245
  )
235
246
  })
236
247
 
@@ -254,6 +265,7 @@ describe('singleton server functions', () => {
254
265
  phase: 'afterSave',
255
266
  documentId: 'doc-settings',
256
267
  documentVersionId: 'version-next',
268
+ revision: 8,
257
269
  sideEffectCode: ErrorCodes.UNHANDLED,
258
270
  },
259
271
  })
@@ -263,13 +275,14 @@ describe('singleton server functions', () => {
263
275
  status: 'committed-hook-failed',
264
276
  documentId: 'doc-settings',
265
277
  documentVersionId: 'version-next',
278
+ revision: 8,
266
279
  sideEffectFailure: { phase: 'afterSave', code: ErrorCodes.UNHANDLED },
267
280
  })
268
281
  })
269
282
 
270
283
  it('preserves authentication failures from the handle', async () => {
271
284
  const error = { code: 'ERR_UNAUTHENTICATED', message: 'sign in required' }
272
- handle.get.mockRejectedValueOnce(error)
285
+ handle.getForEdit.mockRejectedValueOnce(error)
273
286
 
274
287
  await expect(invoke(getSingleton, { singleton: 'site-settings' })).rejects.toBe(error)
275
288
  })
@@ -301,9 +314,34 @@ describe('singleton server functions', () => {
301
314
  expect(mocks.resolveActorLabels).toHaveBeenCalledWith([])
302
315
  })
303
316
 
317
+ it('preserves confirmed lock failures as safe wire data through the host', async () => {
318
+ const conflict = ERR_LOCK_CONFLICT({
319
+ message: 'private diagnostic',
320
+ cause: new Error('driver SQL'),
321
+ })
322
+ handle.update.mockRejectedValueOnce(conflict)
323
+ let received: unknown
324
+ try {
325
+ await invoke(updateSingleton, {
326
+ singleton: 'site-settings',
327
+ data: { title: 'Changed' },
328
+ expectedRevision: 6,
329
+ })
330
+ } catch (error) {
331
+ received = error
332
+ }
333
+ expect(received).not.toHaveProperty('cause')
334
+ expect(getLockConflictDetails(JSON.parse(JSON.stringify(received)))).toEqual({
335
+ reason: 'lock_conflict',
336
+ rolledBack: true,
337
+ retryable: true,
338
+ })
339
+ expect(handle.update).toHaveBeenCalledTimes(1)
340
+ })
341
+
304
342
  it('keeps missing-slot and stale-write failures distinguishable', async () => {
305
343
  const missing = { code: 'ERR_NOT_FOUND', message: 'slot is empty' }
306
- const stale = { code: 'ERR_CONFLICT', message: 'version changed' }
344
+ const stale = { code: 'ERR_DOCUMENT_STALE', message: 'document changed' }
307
345
  handle.changeStatus.mockRejectedValueOnce(missing)
308
346
  handle.update.mockRejectedValueOnce(stale)
309
347
 
@@ -314,9 +352,9 @@ describe('singleton server functions', () => {
314
352
  invoke(updateSingleton, {
315
353
  singleton: 'site-settings',
316
354
  data: { title: 'Changed' },
317
- expectedVersionId: 'stale',
355
+ expectedRevision: 6,
318
356
  })
319
- ).rejects.toMatchObject({ code: 'ERR_CONFLICT' })
357
+ ).rejects.toMatchObject({ code: 'ERR_DOCUMENT_STALE' })
320
358
  })
321
359
 
322
360
  it('serialises schedule dates at the transport boundary', async () => {
@@ -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
@@ -13,7 +14,13 @@ import { getAdminBylineClient } from '@byline/client/server'
13
14
  import { serialise } from '../serialise.js'
14
15
 
15
16
  export const unpublishSingleton = createServerFn({ method: 'POST' })
16
- .validator((input: { singleton: string }) => input)
17
- .handler(async ({ data }) =>
18
- serialise(await getAdminBylineClient().singleton(data.singleton).unpublish())
17
+ .validator((input: { expectedRevision: number; singleton: string }) => input)
18
+ .handler(
19
+ withDocumentMutationErrors(async ({ data }) =>
20
+ serialise(
21
+ await getAdminBylineClient()
22
+ .singleton(data.singleton)
23
+ .unpublish({ expectedRevision: data.expectedRevision })
24
+ )
25
+ )
19
26
  )
@@ -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
@@ -9,30 +10,34 @@
9
10
  import { createServerFn } from '@tanstack/react-start'
10
11
 
11
12
  import { getAdminBylineClient } from '@byline/client/server'
13
+ import type { SingletonSavePrecondition } from '@byline/core'
12
14
 
13
15
  import { toCommittedDocumentHookFailureResponse } from '../collections/save-outcome.js'
14
16
  import { serialise } from '../serialise.js'
15
17
 
16
- export interface UpdateSingletonInput {
18
+ export type UpdateSingletonInput = SingletonSavePrecondition & {
17
19
  singleton: string
18
20
  data: Record<string, any>
19
21
  locale?: string
20
- expectedVersionId?: string
21
22
  }
22
23
 
23
24
  export const updateSingleton = createServerFn({ method: 'POST' })
24
25
  .validator((input: UpdateSingletonInput) => input)
25
- .handler(async ({ data }) => {
26
- const { singleton, data: fields, locale, expectedVersionId } = data
27
- try {
28
- const result = await getAdminBylineClient().singleton(singleton).update(fields, {
29
- locale,
30
- expectedVersionId,
31
- })
32
- return serialise(result)
33
- } catch (error) {
34
- const committedFailure = toCommittedDocumentHookFailureResponse(error)
35
- if (committedFailure != null) return committedFailure
36
- throw error
37
- }
38
- })
26
+ .handler(
27
+ withDocumentMutationErrors(async ({ data }) => {
28
+ const { singleton, data: fields, locale, ...precondition } = data
29
+ try {
30
+ const result = await getAdminBylineClient()
31
+ .singleton(singleton)
32
+ .update(fields, {
33
+ locale,
34
+ ...precondition,
35
+ })
36
+ return serialise(result)
37
+ } catch (error) {
38
+ const committedFailure = toCommittedDocumentHookFailureResponse(error)
39
+ if (committedFailure != null) return committedFailure
40
+ throw error
41
+ }
42
+ })
43
+ )
@@ -0,0 +1,41 @@
1
+ import { ErrorCodes, isDocumentRevision, type StructuralMutationReceipt } from '@byline/core'
2
+
3
+ /** Copy only the authorized receipt fields emitted by the lifecycle service. */
4
+ export function structuralReceipt(value: unknown): StructuralMutationReceipt | null {
5
+ if (typeof value !== 'object' || value === null) return null
6
+ const documentId = Reflect.get(value, 'documentId')
7
+ const revision = Reflect.get(value, 'revision')
8
+ const suspended = Reflect.get(value, 'scheduledPublicationsNeedReconfirmation')
9
+ const affected = Reflect.get(value, 'affectedDocuments')
10
+ if (
11
+ typeof documentId !== 'string' ||
12
+ !isDocumentRevision(revision) ||
13
+ typeof suspended !== 'boolean' ||
14
+ !Array.isArray(affected)
15
+ )
16
+ return null
17
+ const affectedDocuments = []
18
+ for (const row of affected) {
19
+ if (typeof row !== 'object' || row === null) return null
20
+ const id = Reflect.get(row, 'documentId')
21
+ const token = Reflect.get(row, 'revision')
22
+ if (typeof id !== 'string' || !isDocumentRevision(token)) return null
23
+ affectedDocuments.push({ documentId: id, revision: token })
24
+ }
25
+ return {
26
+ documentId,
27
+ revision,
28
+ affectedDocuments,
29
+ scheduledPublicationsNeedReconfirmation: suspended,
30
+ }
31
+ }
32
+
33
+ export function committedTreeReceipt(error: unknown): StructuralMutationReceipt | null {
34
+ if (
35
+ typeof error !== 'object' ||
36
+ error === null ||
37
+ Reflect.get(error, 'code') !== ErrorCodes.TREE_HOOK_COMMITTED
38
+ )
39
+ return null
40
+ return structuralReceipt(Reflect.get(error, 'details'))
41
+ }