@phtngyn/cms 0.1.0-beta.1 → 0.1.0-beta.3

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 (143) hide show
  1. package/README.md +212 -12
  2. package/bin/cms.mjs +2 -0
  3. package/package.json +30 -15
  4. package/src/{Configuration-DyKo0sLS.js → Configuration-CDMYfTre.js} +26 -147
  5. package/src/Configuration-CDMYfTre.js.map +1 -0
  6. package/src/Configuration.d.ts +24 -8
  7. package/src/Configuration.js +0 -1
  8. package/src/Configuration.js.map +1 -1
  9. package/src/ContentTransfer-C3Av9sWz.js +53 -0
  10. package/src/ContentTransfer-C3Av9sWz.js.map +1 -0
  11. package/src/{DocumentEngine-DYObwMDY.js → DocumentEngine-J7g-emdf.js} +75 -27
  12. package/src/DocumentEngine-J7g-emdf.js.map +1 -0
  13. package/src/DocumentModel.d.ts +8 -9
  14. package/src/DocumentModel.js +13 -3
  15. package/src/DocumentModel.js.map +1 -1
  16. package/src/Forms-BsJMev5_.d.ts +74 -0
  17. package/src/Forms-CfcI3tPU.js +79 -0
  18. package/src/Forms-CfcI3tPU.js.map +1 -0
  19. package/src/Forms-Dj6NuW66.js +367 -0
  20. package/src/Forms-Dj6NuW66.js.map +1 -0
  21. package/src/Forms.d.ts +51 -0
  22. package/src/Forms.js +13 -0
  23. package/src/Forms.js.map +1 -0
  24. package/src/{HttpPaths-BbS21Jy8.js → HttpPaths-c9nLZ9H2.js} +6 -2
  25. package/src/HttpPaths-c9nLZ9H2.js.map +1 -0
  26. package/src/I18n.d.ts +20 -0
  27. package/src/I18n.js +1 -0
  28. package/src/Module.js +59 -14
  29. package/src/Module.js.map +1 -1
  30. package/src/S3MediaObjects-CpPSzJto.js +2777 -0
  31. package/src/S3MediaObjects-CpPSzJto.js.map +1 -0
  32. package/src/Seo-402YumpT.d.ts +198 -0
  33. package/src/cli/content.js +258 -0
  34. package/src/cli/content.js.map +1 -0
  35. package/src/cli.js +71 -0
  36. package/src/cli.js.map +1 -0
  37. package/src/{connection-CBTY-TzB.js → connection-CBAy2qGy.js} +5 -7
  38. package/src/connection-CBAy2qGy.js.map +1 -0
  39. package/src/{installation-DeuBiFRE.js → installation-CNfIriVm.js} +1007 -2246
  40. package/src/installation-CNfIriVm.js.map +1 -0
  41. package/src/internal/authoring-catalog.js +10 -4
  42. package/src/internal/authoring-catalog.js.map +1 -1
  43. package/src/internal/authoring-compiler.js +0 -1
  44. package/src/internal/authoring-compiler.js.map +1 -1
  45. package/src/internal/authoring-metadata.js +4 -4
  46. package/src/internal/authoring-metadata.js.map +1 -1
  47. package/src/internal/cms-config.js +0 -1
  48. package/src/internal/cms-config.js.map +1 -1
  49. package/src/internal/configuration.js +26 -17
  50. package/src/internal/configuration.js.map +1 -1
  51. package/src/internal/document-model.js +69 -21
  52. package/src/internal/document-model.js.map +1 -1
  53. package/src/internal/form-capabilities.js +12 -0
  54. package/src/internal/form-capabilities.js.map +1 -0
  55. package/src/internal/i18n.js +47 -0
  56. package/src/internal/i18n.js.map +1 -0
  57. package/src/internal/studio-app.js +2 -2
  58. package/src/internal/studio-app.js.map +1 -1
  59. package/src/{location-DtYv2aes.js → location-BTqdfIzX.js} +2 -3
  60. package/src/location-BTqdfIzX.js.map +1 -0
  61. package/src/{metadata-DE1Mc2td.js → metadata-BEZPWh-3.js} +3 -72
  62. package/src/metadata-BEZPWh-3.js.map +1 -0
  63. package/src/{models-C3witQBK.js → models-B4IoTmp7.js} +3 -2
  64. package/src/models-B4IoTmp7.js.map +1 -0
  65. package/src/{models-BkMgeaOL.js → models-Bah5B3bT.js} +205 -51
  66. package/src/models-Bah5B3bT.js.map +1 -0
  67. package/src/runtime/blocks.js +22 -9
  68. package/src/runtime/blocks.js.map +1 -1
  69. package/src/runtime/collaboration.js.map +1 -1
  70. package/src/runtime/delivery/DocumentRenderer.js +7 -4
  71. package/src/runtime/delivery/DocumentRenderer.js.map +1 -1
  72. package/src/runtime/delivery/blocks.js +3 -1
  73. package/src/runtime/delivery/blocks.js.map +1 -1
  74. package/src/runtime/delivery/context.js +1 -1
  75. package/src/runtime/delivery/document-page.js +12 -12
  76. package/src/runtime/delivery/document-page.js.map +1 -1
  77. package/src/runtime/delivery/document.js +4 -2
  78. package/src/runtime/delivery/document.js.map +1 -1
  79. package/src/runtime/delivery/error.js.map +1 -1
  80. package/src/runtime/delivery/plugin.js +28 -2
  81. package/src/runtime/delivery/plugin.js.map +1 -1
  82. package/src/runtime/delivery/types.d.ts +5 -0
  83. package/src/runtime/delivery/useCmsDocumentSeo.d.ts +59 -0
  84. package/src/runtime/delivery/useCmsDocumentSeo.js +101 -0
  85. package/src/runtime/delivery/useCmsDocumentSeo.js.map +1 -0
  86. package/src/runtime/delivery/useDocument.js +2 -2
  87. package/src/runtime/delivery/useDocument.js.map +1 -1
  88. package/src/runtime/delivery/useDocumentQuery.js +2 -2
  89. package/src/runtime/delivery/useDocumentQuery.js.map +1 -1
  90. package/src/runtime/forms/document.js +38 -0
  91. package/src/runtime/forms/document.js.map +1 -0
  92. package/src/runtime/forms/useCmsForm.js +190 -0
  93. package/src/runtime/forms/useCmsForm.js.map +1 -0
  94. package/src/runtime/i18n/translator.js +51 -0
  95. package/src/runtime/i18n/translator.js.map +1 -0
  96. package/src/runtime/i18n/useCmsI18n.d.ts +11 -0
  97. package/src/runtime/i18n/useCmsI18n.js +37 -0
  98. package/src/runtime/i18n/useCmsI18n.js.map +1 -0
  99. package/src/runtime/installation.js +1 -1
  100. package/src/runtime/plugin.js +3 -3
  101. package/src/runtime/plugin.js.map +1 -1
  102. package/src/runtime/preview/comark-document.js +0 -1
  103. package/src/runtime/preview/comark-document.js.map +1 -1
  104. package/src/runtime/preview/connection.js +1 -1
  105. package/src/runtime/preview/controller.js +10 -3
  106. package/src/runtime/preview/controller.js.map +1 -1
  107. package/src/runtime/preview/overlay.js.map +1 -1
  108. package/src/runtime/preview/plugin.client.js +59 -48
  109. package/src/runtime/preview/plugin.client.js.map +1 -1
  110. package/src/runtime/preview/protocol.js.map +1 -1
  111. package/src/runtime/preview/store.js +1 -1
  112. package/src/runtime/preview/targets.js +0 -1
  113. package/src/runtime/preview/targets.js.map +1 -1
  114. package/src/runtime/sitemap-entries.js +17 -0
  115. package/src/runtime/sitemap-entries.js.map +1 -0
  116. package/src/runtime/sitemap.js +24 -0
  117. package/src/runtime/sitemap.js.map +1 -0
  118. package/src/runtime/studio-app.js.map +1 -1
  119. package/src/runtime/studio-entry.js +2 -2
  120. package/src/runtime/studio-entry.js.map +1 -1
  121. package/src/runtime/studio-navigation.js +1 -1
  122. package/src/scalars-B2lgGUnj.js +26 -0
  123. package/src/scalars-B2lgGUnj.js.map +1 -0
  124. package/src/{store-CfFFx9Ua.js → store-b2CSXgMt.js} +12 -7
  125. package/src/store-b2CSXgMt.js.map +1 -0
  126. package/studio/{CreateDocumentDialog--L9lTfx6.js → CreateDocumentDialog-GGey1uBc.js} +44 -44
  127. package/studio/{collaboration-DUuu678R.js → collaboration-DcK2F6v9.js} +7 -6
  128. package/studio/{documentPlanning-BXX04Woe.js → documentPlanning-DRErYmBU.js} +7340 -6469
  129. package/studio/{editor-PexnXkJY.js → editor-Bzlh3zXt.js} +7965 -7955
  130. package/studio/host.css +1 -1
  131. package/studio/host.js +10416 -7981
  132. package/src/Configuration-DyKo0sLS.js.map +0 -1
  133. package/src/DocumentEngine-DYObwMDY.js.map +0 -1
  134. package/src/HttpPaths-BbS21Jy8.js.map +0 -1
  135. package/src/StudioHttp-jR2-bc01.js +0 -718
  136. package/src/StudioHttp-jR2-bc01.js.map +0 -1
  137. package/src/connection-CBTY-TzB.js.map +0 -1
  138. package/src/installation-DeuBiFRE.js.map +0 -1
  139. package/src/location-DtYv2aes.js.map +0 -1
  140. package/src/metadata-DE1Mc2td.js.map +0 -1
  141. package/src/models-BkMgeaOL.js.map +0 -1
  142. package/src/models-C3witQBK.js.map +0 -1
  143. package/src/store-CfFFx9Ua.js.map +0 -1
@@ -0,0 +1,2777 @@
1
+ import { c as RevisionId, d as SourcePath, f as nonNegativeInt, i as DocumentVersion, n as DocumentHistoryCursor, o as PublicPath, r as DocumentId, s as RelativeDocumentPath, t as DocumentGeneration, u as Slug } from "./scalars-B2lgGUnj.js";
2
+ import { i as FormResult, o as FormSubmission } from "./Forms-CfcI3tPU.js";
3
+ import { a as encodeCmsEditorComponentAttributes, d as createCmsComarkRenderData, m as toComarkDocument, p as mutableJsonObject, s as projectCmsEditorComponentAttributes } from "./Forms-Dj6NuW66.js";
4
+ import { $ as StudioActor, B as SeoMediaUnavailable, C as PublishedDocumentLookup, D as RemoveDocumentInput, E as PublishedDocumentSurroundQuery, Et as RequestId, F as SaveSeoInput, G as SeoUrl, I as SeoCanonicalOrigin, J as AccessDenied, K as SeoVersionConflict, L as SeoCapabilities, N as GlobalSeoSettings, O as RestoreDocumentInput, P as PublishSeoInput, Q as Role, R as SeoConfiguration, S as PublishDocumentInput, T as PublishedDocumentQuery, U as SeoSnapshot, V as SeoMutationResult, W as SeoState, X as DOCUMENT_ACTIONS, Y as AuthenticationRequired, Z as MEDIA_ACTIONS, _ as DocumentSurround, _t as MediaReference, at as MediaCleanupReport, bt as MediaTooLarge, c as DocumentIssue, ct as MediaFolder, d as DocumentNavigationQuery, dt as MediaInvalid, et as StudioEntryDenied, f as DocumentQueryScalar, g as DocumentSummary, gt as MediaQuery, ht as MediaPage, i as CreateDocumentInput, it as MediaByteSize, j as UpdateDocumentInput, k as UndeleteDocumentInput, l as DocumentMutationResult, lt as MediaId, m as DocumentSource, mt as MediaObjectKey, n as CmsMarkdownDocument, nt as ImageMimeType, o as DeletedDocument, ot as MediaConflict, p as DocumentRevisionAction, pt as MediaNotFound, q as SitemapEntry, r as CompiledDocument, rt as MediaAsset, s as DocumentBrowseQuery, u as DocumentNavigationItem, ut as MediaIntegrityReport, v as DocumentValidationResult, vt as MediaReferenced, w as PublishedDocumentPage, x as MoveDocumentInput, xt as MediaUpload, y as DuplicateDocumentInput, z as SeoConfirmationRequired } from "./models-Bah5B3bT.js";
5
+ import { u as STUDIO_API_PATH } from "./HttpPaths-c9nLZ9H2.js";
6
+ import { i as withoutEditorMetadata } from "./metadata-BEZPWh-3.js";
7
+ import { d as EditorRenderFailed, i as MediaConfiguration, r as DocumentConfiguration } from "./Configuration-CDMYfTre.js";
8
+ import { i as ContentSnapshot, n as ContentApplyResult, o as ContentTransferConflict, r as ContentPlan, s as ContentTransferInput, t as ContentApplyInput } from "./ContentTransfer-C3Av9sWz.js";
9
+ import { a as normalizeDocumentRelativePath, c as resolveDocumentAddress, i as localizeDocumentPath, n as referenceTarget, o as normalizePublicPath, r as DocumentCompilationFailed, s as parseDocumentAddress, t as createDocumentEngine } from "./DocumentEngine-J7g-emdf.js";
10
+ import { Context, Crypto, Data, DateTime, Effect, Encoding, Layer, Option, Predicate, PubSub, Redacted, Ref, Result, Schedule, Schema, Semaphore, Stream, flow } from "effect";
11
+ import { NodeStream } from "@effect/platform-node-shared";
12
+ import { Multipart } from "effect/unstable/http";
13
+ import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema } from "effect/unstable/httpapi";
14
+ import { renderMarkdown } from "comark/render";
15
+ import { diffArrays } from "diff";
16
+ import { CopyObjectCommand, CreateBucketCommand, DeleteObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
17
+
18
+ //#region ../protocol/src/internal/documents/errors.ts
19
+ var DocumentNotFound = class extends Schema.TaggedError()("Document.NotFound", {
20
+ documentId: DocumentId,
21
+ revisionId: Schema.optionalKey(RevisionId)
22
+ }) {};
23
+ var DocumentPathConflict = class extends Schema.TaggedError()("Document.PathConflict", { sourcePath: SourcePath }) {};
24
+ var DocumentVersionConflict = class extends Schema.TaggedError()("Document.VersionConflict", {
25
+ documentId: DocumentId,
26
+ expectedVersion: DocumentVersion,
27
+ actualVersion: DocumentVersion
28
+ }) {};
29
+ var DocumentMediaUnavailable = class extends Schema.TaggedError()("Document.MediaUnavailable", {
30
+ documentId: DocumentId,
31
+ mediaKeys: Schema.Array(MediaObjectKey).check(Schema.isMinLength(1))
32
+ }) {};
33
+ var DocumentValidationFailed = class extends Schema.TaggedError()("Document.ValidationFailed", { issues: Schema.Array(DocumentIssue) }) {};
34
+ var PublishedDocumentNotFound = class extends Schema.TaggedError()("PublishedDocument.NotFound", { lookup: PublishedDocumentLookup }) {};
35
+
36
+ //#endregion
37
+ //#region ../protocol/src/Activity.ts
38
+ /**
39
+ * Immutable completed Activity facts. Pages are ordered newest-first; cursors are opaque,
40
+ * stable continuation tokens and an invalid cursor is a caller-visible failure.
41
+ */
42
+ const ActivityId = Schema.String.check(Schema.isUUID()).pipe(Schema.brand("ActivityId"));
43
+ const ActivityCursor = Schema.NonEmptyString.pipe(Schema.brand("ActivityCursor"));
44
+ const ACTIVITY_ACTIONS = [
45
+ "document.created",
46
+ "document.updated",
47
+ "document.moved",
48
+ "document.trashed",
49
+ "document.revision.restored",
50
+ "document.untrashed",
51
+ "document.published",
52
+ "document.unpublished",
53
+ "media.uploaded",
54
+ "media.replaced",
55
+ "media.removed",
56
+ "media.cleaned",
57
+ "seo.saved",
58
+ "seo.published",
59
+ "user.created",
60
+ "user.removed",
61
+ "user.role.changed",
62
+ "user.sessions.revoked",
63
+ "authentication.signed-in",
64
+ "authentication.signed-out",
65
+ "authentication.password.changed",
66
+ "authentication.password.reset"
67
+ ];
68
+ const ActivityAction = Schema.Literals(ACTIVITY_ACTIONS);
69
+ const DocumentActivityTarget = Schema.Struct({
70
+ kind: Schema.tag("document"),
71
+ documentId: DocumentId
72
+ });
73
+ const MediaActivityTarget = Schema.Struct({
74
+ kind: Schema.tag("media"),
75
+ mediaId: MediaId
76
+ });
77
+ const MediaLibraryActivityTarget = Schema.Struct({ kind: Schema.tag("media-library") });
78
+ const ActivityTarget = Schema.Union([
79
+ DocumentActivityTarget,
80
+ MediaActivityTarget,
81
+ MediaLibraryActivityTarget,
82
+ Schema.Struct({ kind: Schema.tag("seo") }),
83
+ Schema.Struct({
84
+ kind: Schema.tag("user"),
85
+ userId: Schema.NonEmptyString
86
+ })
87
+ ]).pipe(Schema.toTaggedUnion("kind"));
88
+ const ActivityTargetKind = Schema.Literals([
89
+ "document",
90
+ "media",
91
+ "media-library",
92
+ "seo",
93
+ "user"
94
+ ]);
95
+ const ActivityContext = Schema.Struct({
96
+ actor: StudioActor,
97
+ requestId: RequestId,
98
+ reason: Schema.optionalKey(Schema.NonEmptyString.check(Schema.isMaxLength(1e3)))
99
+ }).annotate({ identifier: "ActivityContext" });
100
+ const ActivityEntry = Schema.Struct({
101
+ id: ActivityId,
102
+ action: ActivityAction,
103
+ actor: StudioActor,
104
+ requestId: RequestId,
105
+ reason: ActivityContext.fields.reason,
106
+ target: ActivityTarget,
107
+ details: Schema.JsonObject,
108
+ occurredAt: Schema.DateTimeUtcFromString
109
+ }).annotate({ identifier: "ActivityEntry" });
110
+ const ActivityEntryDraft = Schema.Struct({
111
+ action: ActivityAction,
112
+ context: ActivityContext,
113
+ target: ActivityTarget,
114
+ details: Schema.JsonObject
115
+ }).annotate({ identifier: "ActivityEntryDraft" });
116
+ const ActivityQuery = Schema.Struct({
117
+ action: Schema.optionalKey(ActivityAction),
118
+ actorId: Schema.optionalKey(StudioActor.fields.id),
119
+ cursor: Schema.optionalKey(ActivityCursor),
120
+ limit: Schema.optionalKey(Schema.Int.check(Schema.isBetween({
121
+ minimum: 1,
122
+ maximum: 100
123
+ }))),
124
+ requestId: Schema.optionalKey(RequestId),
125
+ targetId: Schema.optionalKey(Schema.NonEmptyString),
126
+ targetKind: Schema.optionalKey(ActivityTargetKind)
127
+ }).annotate({ identifier: "ActivityQuery" });
128
+ const ActivityPage = Schema.Struct({
129
+ entries: Schema.Array(ActivityEntry),
130
+ nextCursor: Schema.optionalKey(ActivityCursor),
131
+ total: nonNegativeInt
132
+ }).annotate({ identifier: "ActivityPage" });
133
+ var ActivityCursorInvalid = class extends Schema.TaggedError()("Activity.CursorInvalid", {}) {};
134
+
135
+ //#endregion
136
+ //#region ../protocol/src/internal/documents/history.ts
137
+ const DocumentRevisionSummary = Schema.Struct({
138
+ action: DocumentRevisionAction,
139
+ activity: ActivityEntry,
140
+ createdAt: Schema.DateTimeUtcFromString,
141
+ documentId: DocumentId,
142
+ revisionId: RevisionId,
143
+ sourcePath: SourcePath,
144
+ version: DocumentVersion
145
+ }).annotate({ identifier: "DocumentRevisionSummary" });
146
+ const DocumentRevision = Schema.Struct({
147
+ ...DocumentRevisionSummary.fields,
148
+ markdown: Schema.String
149
+ }).annotate({ identifier: "DocumentRevision" });
150
+ const DocumentHistoryQuery = Schema.Struct({
151
+ documentId: DocumentId,
152
+ cursor: Schema.optionalKey(DocumentHistoryCursor),
153
+ limit: Schema.optionalKey(Schema.Int.check(Schema.isBetween({
154
+ minimum: 1,
155
+ maximum: 100
156
+ })))
157
+ }).annotate({ identifier: "DocumentHistoryQuery" });
158
+ const DocumentRevisionPage = Schema.Struct({
159
+ revisions: Schema.Array(DocumentRevisionSummary),
160
+ nextCursor: Schema.optionalKey(DocumentHistoryCursor),
161
+ total: nonNegativeInt
162
+ }).annotate({ identifier: "DocumentRevisionPage" });
163
+ const LineNumber = Schema.Int.check(Schema.isGreaterThan(0));
164
+ const DocumentRevisionChange = Schema.Union([
165
+ Schema.Struct({
166
+ kind: Schema.tag("equal"),
167
+ beforeLine: LineNumber,
168
+ afterLine: LineNumber,
169
+ line: Schema.String
170
+ }),
171
+ Schema.Struct({
172
+ kind: Schema.tag("added"),
173
+ afterLine: LineNumber,
174
+ line: Schema.String
175
+ }),
176
+ Schema.Struct({
177
+ kind: Schema.tag("removed"),
178
+ beforeLine: LineNumber,
179
+ line: Schema.String
180
+ })
181
+ ]).pipe(Schema.toTaggedUnion("kind"));
182
+ const CompareDocumentRevisionsInput = Schema.Struct({
183
+ documentId: DocumentId,
184
+ fromRevisionId: RevisionId,
185
+ toRevisionId: RevisionId
186
+ }).annotate({ identifier: "CompareDocumentRevisionsInput" });
187
+ const DocumentRevisionComparison = Schema.Struct({
188
+ from: DocumentRevision,
189
+ to: DocumentRevision,
190
+ changes: Schema.Array(DocumentRevisionChange)
191
+ }).annotate({ identifier: "DocumentRevisionComparison" });
192
+ var DocumentHistoryInvalidCursor = class extends Schema.TaggedError()("DocumentHistory.InvalidCursor", {}) {};
193
+ var DocumentHistoryComparisonTooLarge = class extends Schema.TaggedError()("DocumentHistory.ComparisonTooLarge", {
194
+ maximumEditLength: Schema.Int.check(Schema.isGreaterThan(0)),
195
+ maximumLines: Schema.Int.check(Schema.isGreaterThan(0))
196
+ }) {};
197
+
198
+ //#endregion
199
+ //#region ../protocol/src/Collaboration.ts
200
+ /**
201
+ * Collaboration handshake and WebSocket envelopes. Command results correlate by RequestId;
202
+ * checkpoints carry the durable state vector needed for retry and recovery.
203
+ */
204
+ const StateVector = Schema.String.check(Schema.isBase64()).pipe(Schema.brand("StateVector"));
205
+ const HttpErrorStatus = Schema.Int.check(Schema.isBetween({
206
+ minimum: 400,
207
+ maximum: 599
208
+ }));
209
+ const CollaborationUser = Schema.Struct({
210
+ color: Schema.NonEmptyString,
211
+ id: Schema.NonEmptyString,
212
+ name: Schema.NonEmptyString
213
+ });
214
+ const CollaborationTicket = Schema.Struct({
215
+ authoringRevision: Schema.NonEmptyString,
216
+ authoredStateVector: StateVector,
217
+ documentName: Schema.NonEmptyString,
218
+ source: DocumentSource,
219
+ token: Schema.NonEmptyString,
220
+ url: Schema.NonEmptyString,
221
+ user: CollaborationUser,
222
+ workingStateVector: StateVector
223
+ }).annotate({ identifier: "CollaborationTicket" });
224
+ const CollaborationPreview = DocumentValidationResult.pipe(Schema.fieldsAssign({
225
+ markdown: Schema.String,
226
+ preview: Schema.Struct({
227
+ body: CmsMarkdownDocument,
228
+ fallbacks: Schema.Record(Schema.String, Schema.String)
229
+ })
230
+ })).annotate({ identifier: "CollaborationPreview" });
231
+ const CheckpointMessage = Schema.Struct({ type: Schema.tag("checkpoint") });
232
+ const PreviewMessage = Schema.Struct({
233
+ type: Schema.tag("preview"),
234
+ requestId: RequestId
235
+ });
236
+ const PublishMessage = Schema.Struct({
237
+ type: Schema.tag("publish"),
238
+ requestId: RequestId
239
+ });
240
+ const CollaborationClientMessage = Schema.Union([
241
+ CheckpointMessage,
242
+ PreviewMessage,
243
+ PublishMessage
244
+ ]).pipe(Schema.toTaggedUnion("type"));
245
+ const requestFailure = {
246
+ error: Schema.NonEmptyString,
247
+ statusCode: HttpErrorStatus
248
+ };
249
+ const RequestFailure = Schema.Struct({
250
+ status: Schema.tag("failure"),
251
+ ...requestFailure
252
+ });
253
+ const PreviewOutcome = Schema.Union([Schema.Struct({
254
+ status: Schema.tag("success"),
255
+ result: CollaborationPreview
256
+ }), RequestFailure]).pipe(Schema.toTaggedUnion("status"));
257
+ const PublishCheckpoint = Schema.Struct({
258
+ authoredStateVector: StateVector,
259
+ result: Schema.optionalKey(DocumentMutationResult)
260
+ });
261
+ const PublishOutcome = Schema.Union([Schema.Struct({
262
+ status: Schema.tag("success"),
263
+ authoredStateVector: StateVector,
264
+ result: DocumentMutationResult
265
+ }), Schema.Struct({
266
+ status: Schema.tag("failure"),
267
+ ...requestFailure,
268
+ checkpoint: Schema.optionalKey(PublishCheckpoint)
269
+ })]).pipe(Schema.toTaggedUnion("status"));
270
+ const WorkingSavedMessage = Schema.Struct({
271
+ type: Schema.tag("working-saved"),
272
+ workingStateVector: StateVector
273
+ });
274
+ const WorkingSaveErrorMessage = Schema.Struct({
275
+ type: Schema.tag("working-save-error"),
276
+ error: Schema.NonEmptyString
277
+ });
278
+ const PreviewResultMessage = Schema.Struct({
279
+ type: Schema.tag("preview-result"),
280
+ requestId: RequestId,
281
+ outcome: PreviewOutcome
282
+ });
283
+ const PublishResultMessage = Schema.Struct({
284
+ type: Schema.tag("publish-result"),
285
+ requestId: RequestId,
286
+ outcome: PublishOutcome
287
+ });
288
+ const CollaborationServerMessage = Schema.Union([
289
+ WorkingSavedMessage,
290
+ WorkingSaveErrorMessage,
291
+ PreviewResultMessage,
292
+ PublishResultMessage
293
+ ]).pipe(Schema.toTaggedUnion("type"));
294
+
295
+ //#endregion
296
+ //#region ../protocol/src/Users.ts
297
+ /**
298
+ * User administration inputs, offset-paginated directory results, and caller-visible command
299
+ * failures. Commands carry no idempotency key; clients resolve ambiguous outcomes by listing again.
300
+ */
301
+ const UserEmail = Schema.NonEmptyString.check(Schema.isMaxLength(320), Schema.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/u)).annotate({ identifier: "UserEmail" });
302
+ const UserName = Schema.NonEmptyString.check(Schema.isMaxLength(200)).annotate({ identifier: "UserName" });
303
+ const UserPassword = Schema.NonEmptyString.check(Schema.isMinLength(8), Schema.isMaxLength(200)).annotate({ identifier: "UserPassword" });
304
+ const User = Schema.Struct({
305
+ id: Schema.NonEmptyString,
306
+ email: UserEmail,
307
+ name: UserName,
308
+ role: Role,
309
+ emailVerified: Schema.Boolean,
310
+ banned: Schema.Boolean,
311
+ createdAt: Schema.DateTimeUtcFromString,
312
+ updatedAt: Schema.DateTimeUtcFromString
313
+ }).annotate({ identifier: "User" });
314
+ const UserPage = Schema.Struct({
315
+ users: Schema.Array(User),
316
+ total: nonNegativeInt
317
+ }).annotate({ identifier: "UserPage" });
318
+ const UserQuery = Schema.Struct({
319
+ limit: Schema.optionalKey(Schema.Int.check(Schema.isBetween({
320
+ minimum: 1,
321
+ maximum: 100
322
+ }))),
323
+ offset: Schema.optionalKey(nonNegativeInt),
324
+ search: Schema.optionalKey(Schema.NonEmptyString.check(Schema.isMaxLength(200)))
325
+ }).annotate({ identifier: "UserQuery" });
326
+ const CreateUserInput = Schema.Struct({
327
+ email: UserEmail,
328
+ name: UserName,
329
+ password: UserPassword,
330
+ role: Role
331
+ }).annotate({ identifier: "CreateUserInput" });
332
+ var UserAlreadyExists = class extends Schema.TaggedError()("User.AlreadyExists", { email: UserEmail }) {};
333
+ var UserNotFound = class extends Schema.TaggedError()("User.NotFound", { userId: User.fields.id }) {};
334
+ var UserSelfRemovalDenied = class extends Schema.TaggedError()("User.SelfRemovalDenied", { userId: User.fields.id }) {};
335
+ var UserSelfRoleChangeDenied = class extends Schema.TaggedError()("User.SelfRoleChangeDenied", { userId: User.fields.id }) {};
336
+ var UserAdministrationDenied = class extends Schema.TaggedError()("UserAdministration.Denied", {}) {};
337
+
338
+ //#endregion
339
+ //#region ../protocol/src/Initialization.ts
340
+ const InitializationInput = Schema.Struct({
341
+ email: UserEmail,
342
+ initializationToken: Schema.NonEmptyString.check(Schema.isMaxLength(512)),
343
+ name: UserName,
344
+ password: UserPassword
345
+ }).annotate({ identifier: "InitializationInput" });
346
+ const InitializationState = Schema.Struct({ needsAdministrator: Schema.Boolean }).annotate({ identifier: "InitializationState" });
347
+ var InitializationAlreadyCompleted = class extends Schema.TaggedError()("Initialization.AlreadyCompleted", {}) {};
348
+ var InitializationProofInvalid = class extends Schema.TaggedError()("Initialization.ProofInvalid", {}) {};
349
+
350
+ //#endregion
351
+ //#region ../protocol/src/internal/http/failures.ts
352
+ var StudioInternalError = class extends Schema.TaggedError()("Studio.InternalError", { message: Schema.Literal("The request could not be completed.") }) {};
353
+ function studioInternalError() {
354
+ return new StudioInternalError({ message: "The request could not be completed." });
355
+ }
356
+
357
+ //#endregion
358
+ //#region ../protocol/src/internal/http/delivery.ts
359
+ const DeliverySurroundPayload = Schema.Struct({
360
+ lookup: PublishedDocumentLookup,
361
+ query: PublishedDocumentSurroundQuery
362
+ }).annotate({ identifier: "DeliverySurroundPayload" });
363
+ const InternalError$1 = StudioInternalError.pipe(HttpApiSchema.status(500));
364
+ const PublishedNotFound = PublishedDocumentNotFound.pipe(HttpApiSchema.status(404));
365
+ const Forbidden$1 = AccessDenied.pipe(HttpApiSchema.status(403));
366
+ const PublishedDeliveryApi = HttpApiGroup.make("delivery").add(HttpApiEndpoint.post("read", "/read", {
367
+ payload: PublishedDocumentLookup,
368
+ success: CompiledDocument,
369
+ error: [PublishedNotFound, InternalError$1]
370
+ }), HttpApiEndpoint.post("query", "/query", {
371
+ payload: PublishedDocumentQuery,
372
+ success: PublishedDocumentPage,
373
+ error: InternalError$1
374
+ }), HttpApiEndpoint.post("navigation", "/navigation", {
375
+ payload: DocumentNavigationQuery,
376
+ success: Schema.Array(DocumentNavigationItem),
377
+ error: InternalError$1
378
+ }), HttpApiEndpoint.post("surround", "/surround", {
379
+ payload: DeliverySurroundPayload,
380
+ success: DocumentSurround,
381
+ error: [PublishedNotFound, InternalError$1]
382
+ }), HttpApiEndpoint.get("seo", "/seo", {
383
+ success: SeoSnapshot,
384
+ error: InternalError$1
385
+ }), HttpApiEndpoint.get("sitemap", "/sitemap", {
386
+ success: Schema.Array(SitemapEntry),
387
+ error: InternalError$1
388
+ })).prefix("/delivery");
389
+ const PreviewDeliveryApi = HttpApiGroup.make("previewDelivery").add(HttpApiEndpoint.post("read", "/read", {
390
+ payload: PublishedDocumentLookup,
391
+ success: CompiledDocument,
392
+ error: [
393
+ PublishedNotFound,
394
+ InternalError$1,
395
+ Forbidden$1
396
+ ]
397
+ }), HttpApiEndpoint.post("query", "/query", {
398
+ payload: PublishedDocumentQuery,
399
+ success: PublishedDocumentPage,
400
+ error: [InternalError$1, Forbidden$1]
401
+ }), HttpApiEndpoint.post("navigation", "/navigation", {
402
+ payload: DocumentNavigationQuery,
403
+ success: Schema.Array(DocumentNavigationItem),
404
+ error: [InternalError$1, Forbidden$1]
405
+ }), HttpApiEndpoint.post("surround", "/surround", {
406
+ payload: DeliverySurroundPayload,
407
+ success: DocumentSurround,
408
+ error: [
409
+ PublishedNotFound,
410
+ InternalError$1,
411
+ Forbidden$1
412
+ ]
413
+ }), HttpApiEndpoint.get("seo", "/seo", {
414
+ success: SeoSnapshot,
415
+ error: [InternalError$1, Forbidden$1]
416
+ })).prefix("/preview");
417
+
418
+ //#endregion
419
+ //#region ../protocol/src/StudioHttp.ts
420
+ /**
421
+ * Studio HTTP paths, middleware contracts, payloads, and the composed CMS API. Every expected
422
+ * caller failure is a tagged schema with an explicit status; unclassified failures become the
423
+ * deliberately opaque `StudioInternalError`.
424
+ */
425
+ const ApiVersion = Schema.NonEmptyString.pipe(Schema.brand("ApiVersion"));
426
+ const StudioBootstrap = Schema.Struct({
427
+ basePath: PublicPath,
428
+ documents: DocumentConfiguration,
429
+ media: MediaConfiguration,
430
+ seo: Schema.optionalKey(SeoConfiguration),
431
+ version: ApiVersion
432
+ }).annotate({ identifier: "StudioBootstrap" });
433
+ var StudioBadRequest = class extends Schema.TaggedError()("Studio.BadRequest", { kind: Schema.Literals([
434
+ "Headers",
435
+ "Params",
436
+ "Payload",
437
+ "Query"
438
+ ]) }) {};
439
+ var StudioRequestConflict = class extends Schema.TaggedError()("Studio.RequestConflict", { retryable: Schema.Literal(true) }) {};
440
+ var StudioSchemaError = class extends HttpApiMiddleware.Service()("@internal/protocol/StudioSchemaError", { error: [StudioBadRequest.pipe(HttpApiSchema.status(400)), StudioInternalError.pipe(HttpApiSchema.status(500))] }) {};
441
+ var CurrentStudioRequest = class extends Context.Service()("@internal/protocol/CurrentStudioRequest") {};
442
+ var StudioAuthentication = class extends HttpApiMiddleware.Service()("@internal/protocol/StudioAuthentication", { error: [
443
+ AuthenticationRequired.pipe(HttpApiSchema.status(401)),
444
+ StudioEntryDenied.pipe(HttpApiSchema.status(403)),
445
+ StudioInternalError.pipe(HttpApiSchema.status(500))
446
+ ] }) {};
447
+ const ValidateDocumentPayload = Schema.Struct({
448
+ expectedVersion: UpdateDocumentInput.fields.expectedVersion,
449
+ markdown: UpdateDocumentInput.fields.markdown
450
+ });
451
+ const UpdateDocumentPayload = ValidateDocumentPayload.pipe(Schema.fieldsAssign({ reason: UpdateDocumentInput.fields.reason }));
452
+ const MoveDocumentPayload = Schema.Struct({
453
+ expectedVersion: MoveDocumentInput.fields.expectedVersion,
454
+ location: MoveDocumentInput.fields.location,
455
+ reason: MoveDocumentInput.fields.reason
456
+ });
457
+ const PublishDocumentPayload = Schema.Struct({
458
+ expectedVersion: PublishDocumentInput.fields.expectedVersion,
459
+ reason: PublishDocumentInput.fields.reason
460
+ });
461
+ const RemoveDocumentPayload = Schema.Struct({
462
+ expectedVersion: RemoveDocumentInput.fields.expectedVersion,
463
+ reason: RemoveDocumentInput.fields.reason
464
+ });
465
+ const RestoreDocumentPayload = Schema.Struct({
466
+ expectedVersion: RestoreDocumentInput.fields.expectedVersion,
467
+ revisionId: RestoreDocumentInput.fields.revisionId,
468
+ reason: RestoreDocumentInput.fields.reason
469
+ });
470
+ const UndeleteDocumentPayload = Schema.Struct({
471
+ expectedVersion: UndeleteDocumentInput.fields.expectedVersion,
472
+ reason: UndeleteDocumentInput.fields.reason
473
+ });
474
+ const ChangeRolePayload = Schema.Struct({ role: Role }).annotate({ identifier: "ChangeRolePayload" });
475
+ const MediaUploadQuery = Schema.Struct({
476
+ declaredByteSize: MediaUpload.fields.declaredByteSize,
477
+ folder: Schema.optionalKey(MediaFolder)
478
+ }).annotate({ identifier: "MediaUploadQuery" });
479
+ const MediaUploadPayload = Schema.Struct({ file: Multipart.SingleFileSchema }).pipe(HttpApiSchema.asMultipartStream({ maxParts: 1 }));
480
+ const NotFound = DocumentNotFound.pipe(HttpApiSchema.status(404));
481
+ const VersionConflict = DocumentVersionConflict.pipe(HttpApiSchema.status(409));
482
+ const Conflict = Schema.Union([
483
+ DocumentMediaUnavailable,
484
+ DocumentPathConflict,
485
+ DocumentVersionConflict
486
+ ]).pipe(HttpApiSchema.status(409));
487
+ const ValidationFailed = DocumentValidationFailed.pipe(HttpApiSchema.status(422));
488
+ const RequestConflict = StudioRequestConflict.pipe(HttpApiSchema.status(409));
489
+ const InternalError = StudioInternalError.pipe(HttpApiSchema.status(500));
490
+ const Forbidden = AccessDenied.pipe(HttpApiSchema.status(403));
491
+ const MutationErrors = [
492
+ NotFound,
493
+ Conflict,
494
+ ValidationFailed,
495
+ RequestConflict,
496
+ Forbidden,
497
+ InternalError
498
+ ];
499
+ const MediaNotFoundResponse = MediaNotFound.pipe(HttpApiSchema.status(404));
500
+ const MediaInvalidQuery = MediaInvalid.pipe(HttpApiSchema.status(400));
501
+ const MediaInvalidUpload = MediaInvalid.pipe(HttpApiSchema.status(422));
502
+ const MediaTooLargeResponse = MediaTooLarge.pipe(HttpApiSchema.status(413));
503
+ const MediaReferencedResponse = MediaReferenced.pipe(HttpApiSchema.status(409));
504
+ const MediaConflictResponse = MediaConflict.pipe(HttpApiSchema.status(409));
505
+ const InvalidActivityCursor = ActivityCursorInvalid.pipe(HttpApiSchema.status(400));
506
+ const InvalidHistoryCursor = DocumentHistoryInvalidCursor.pipe(HttpApiSchema.status(400));
507
+ const HistoryComparisonTooLarge = DocumentHistoryComparisonTooLarge.pipe(HttpApiSchema.status(422));
508
+ const ContentConflict = ContentTransferConflict.pipe(HttpApiSchema.status(409));
509
+ const AlreadyInitialized = InitializationAlreadyCompleted.pipe(HttpApiSchema.status(409));
510
+ const InitializationProofRejected = InitializationProofInvalid.pipe(HttpApiSchema.status(403));
511
+ const UserAdministrationForbidden = UserAdministrationDenied.pipe(HttpApiSchema.status(403));
512
+ const UserDuplicate = UserAlreadyExists.pipe(HttpApiSchema.status(409));
513
+ const MissingUser = UserNotFound.pipe(HttpApiSchema.status(404));
514
+ const UserSelfRemoval = UserSelfRemovalDenied.pipe(HttpApiSchema.status(409));
515
+ const UserSelfRoleChange = UserSelfRoleChangeDenied.pipe(HttpApiSchema.status(409));
516
+ const SeoConflict = SeoVersionConflict.pipe(HttpApiSchema.status(409));
517
+ const SeoConfirmation = SeoConfirmationRequired.pipe(HttpApiSchema.status(409));
518
+ const SeoMediaInvalid = SeoMediaUnavailable.pipe(HttpApiSchema.status(422));
519
+ const StudioDocumentsApi = HttpApiGroup.make("documents").add(HttpApiEndpoint.get("browse", "/", {
520
+ query: DocumentBrowseQuery,
521
+ success: Schema.Array(DocumentSummary),
522
+ error: [Forbidden, InternalError]
523
+ }), HttpApiEndpoint.get("trash", "/trash", {
524
+ success: Schema.Array(DeletedDocument),
525
+ error: [Forbidden, InternalError]
526
+ }), HttpApiEndpoint.get("read", "/:documentId", {
527
+ params: { documentId: DocumentId },
528
+ success: DocumentSource,
529
+ error: [
530
+ NotFound,
531
+ Forbidden,
532
+ InternalError
533
+ ]
534
+ }), HttpApiEndpoint.post("create", "/", {
535
+ payload: CreateDocumentInput,
536
+ success: DocumentMutationResult.pipe(HttpApiSchema.status(201)),
537
+ error: MutationErrors
538
+ }), HttpApiEndpoint.put("update", "/:documentId", {
539
+ params: { documentId: DocumentId },
540
+ payload: UpdateDocumentPayload,
541
+ success: DocumentMutationResult,
542
+ error: MutationErrors
543
+ }), HttpApiEndpoint.post("validate", "/:documentId/validate", {
544
+ params: { documentId: DocumentId },
545
+ payload: ValidateDocumentPayload,
546
+ success: DocumentValidationResult,
547
+ error: [
548
+ NotFound,
549
+ VersionConflict,
550
+ Forbidden,
551
+ InternalError
552
+ ]
553
+ }), HttpApiEndpoint.post("move", "/:documentId/move", {
554
+ params: { documentId: DocumentId },
555
+ payload: MoveDocumentPayload,
556
+ success: DocumentMutationResult,
557
+ error: MutationErrors
558
+ }), HttpApiEndpoint.post("duplicate", "/:documentId/duplicate", {
559
+ params: { documentId: DocumentId },
560
+ payload: Schema.Struct({
561
+ ...MoveDocumentPayload.fields,
562
+ includeChildren: DuplicateDocumentInput.fields.includeChildren
563
+ }),
564
+ success: DocumentMutationResult,
565
+ error: MutationErrors
566
+ }), HttpApiEndpoint.post("unpublish", "/:documentId/unpublish", {
567
+ params: { documentId: DocumentId },
568
+ payload: PublishDocumentPayload,
569
+ success: DocumentMutationResult,
570
+ error: MutationErrors
571
+ }), HttpApiEndpoint.post("publish", "/:documentId/publish", {
572
+ params: { documentId: DocumentId },
573
+ payload: PublishDocumentPayload,
574
+ success: DocumentMutationResult,
575
+ error: MutationErrors
576
+ }), HttpApiEndpoint.post("remove", "/:documentId/remove", {
577
+ params: { documentId: DocumentId },
578
+ payload: RemoveDocumentPayload,
579
+ success: DocumentMutationResult,
580
+ error: MutationErrors
581
+ }), HttpApiEndpoint.post("restore", "/:documentId/restore", {
582
+ params: { documentId: DocumentId },
583
+ payload: RestoreDocumentPayload,
584
+ success: DocumentMutationResult,
585
+ error: MutationErrors
586
+ }), HttpApiEndpoint.post("undelete", "/:documentId/undelete", {
587
+ params: { documentId: DocumentId },
588
+ payload: UndeleteDocumentPayload,
589
+ success: DocumentMutationResult,
590
+ error: MutationErrors
591
+ })).middleware(StudioAuthentication).prefix("/documents");
592
+ const StudioContentApi = HttpApiGroup.make("content").add(HttpApiEndpoint.get("snapshot", "/snapshot", {
593
+ success: ContentSnapshot,
594
+ error: [Forbidden, InternalError]
595
+ }), HttpApiEndpoint.post("plan", "/plan", {
596
+ payload: ContentTransferInput,
597
+ success: ContentPlan,
598
+ error: [
599
+ ContentConflict,
600
+ Forbidden,
601
+ InternalError
602
+ ]
603
+ }), HttpApiEndpoint.post("apply", "/apply", {
604
+ payload: ContentApplyInput,
605
+ success: ContentApplyResult,
606
+ error: [
607
+ ContentConflict,
608
+ ValidationFailed,
609
+ Forbidden,
610
+ InternalError
611
+ ]
612
+ })).middleware(StudioAuthentication).prefix("/content");
613
+ const StudioMediaApi = HttpApiGroup.make("media").add(HttpApiEndpoint.get("search", "/", {
614
+ query: MediaQuery,
615
+ success: MediaPage,
616
+ error: [
617
+ MediaInvalidQuery,
618
+ Forbidden,
619
+ InternalError
620
+ ]
621
+ }), HttpApiEndpoint.post("upload", "/", {
622
+ query: MediaUploadQuery,
623
+ payload: MediaUploadPayload,
624
+ success: MediaAsset.pipe(HttpApiSchema.status(201)),
625
+ error: [
626
+ MediaInvalidUpload,
627
+ MediaTooLargeResponse,
628
+ Forbidden,
629
+ InternalError
630
+ ]
631
+ }), HttpApiEndpoint.put("replace", "/:mediaId", {
632
+ params: { mediaId: MediaId },
633
+ query: MediaUploadQuery,
634
+ payload: MediaUploadPayload,
635
+ success: MediaAsset,
636
+ error: [
637
+ MediaNotFoundResponse,
638
+ MediaConflictResponse,
639
+ MediaInvalidUpload,
640
+ MediaTooLargeResponse,
641
+ Forbidden,
642
+ InternalError
643
+ ]
644
+ }), HttpApiEndpoint.post("cleanup", "/cleanup", {
645
+ success: MediaCleanupReport,
646
+ error: [Forbidden, InternalError]
647
+ }), HttpApiEndpoint.get("integrity", "/integrity", {
648
+ success: MediaIntegrityReport,
649
+ error: [Forbidden, InternalError]
650
+ }), HttpApiEndpoint.get("get", "/:mediaId", {
651
+ params: { mediaId: MediaId },
652
+ success: MediaAsset,
653
+ error: [
654
+ MediaNotFoundResponse,
655
+ Forbidden,
656
+ InternalError
657
+ ]
658
+ }), HttpApiEndpoint.delete("remove", "/:mediaId", {
659
+ params: { mediaId: MediaId },
660
+ success: HttpApiSchema.NoContent,
661
+ error: [
662
+ MediaNotFoundResponse,
663
+ MediaConflictResponse,
664
+ MediaReferencedResponse,
665
+ Forbidden,
666
+ InternalError
667
+ ]
668
+ })).middleware(StudioAuthentication).prefix("/media");
669
+ const StudioConfigurationApi = HttpApiGroup.make("configuration").add(HttpApiEndpoint.get("bootstrap", "/bootstrap", { success: StudioBootstrap })).middleware(StudioAuthentication);
670
+ const StudioSessionApi = HttpApiGroup.make("session").add(HttpApiEndpoint.get("current", "/session", { success: StudioActor })).middleware(StudioAuthentication);
671
+ const InitializationApi = HttpApiGroup.make("initialization").add(HttpApiEndpoint.get("status", "/initialization", {
672
+ success: InitializationState,
673
+ error: InternalError
674
+ }), HttpApiEndpoint.post("claim", "/initialization", {
675
+ payload: InitializationInput,
676
+ success: User.pipe(HttpApiSchema.status(201)),
677
+ error: [
678
+ AlreadyInitialized,
679
+ InitializationProofRejected,
680
+ InternalError
681
+ ]
682
+ }));
683
+ const StudioDiagnosticsApi = HttpApiGroup.make("diagnostics").add(HttpApiEndpoint.get("documents", "/diagnostics/documents", {
684
+ success: Schema.Array(DocumentIssue),
685
+ error: [Forbidden, InternalError]
686
+ })).middleware(StudioAuthentication);
687
+ const StudioHistoryApi = HttpApiGroup.make("history").add(HttpApiEndpoint.get("list", "/", {
688
+ query: DocumentHistoryQuery,
689
+ success: DocumentRevisionPage,
690
+ error: [
691
+ InvalidHistoryCursor,
692
+ Forbidden,
693
+ InternalError
694
+ ]
695
+ }), HttpApiEndpoint.get("read", "/:documentId/:revisionId", {
696
+ params: {
697
+ documentId: DocumentId,
698
+ revisionId: DocumentRevision.fields.revisionId
699
+ },
700
+ success: DocumentRevision,
701
+ error: [
702
+ NotFound,
703
+ Forbidden,
704
+ InternalError
705
+ ]
706
+ }), HttpApiEndpoint.post("compare", "/compare", {
707
+ payload: CompareDocumentRevisionsInput,
708
+ success: DocumentRevisionComparison,
709
+ error: [
710
+ NotFound,
711
+ HistoryComparisonTooLarge,
712
+ Forbidden,
713
+ InternalError
714
+ ]
715
+ })).middleware(StudioAuthentication).prefix("/history");
716
+ const StudioActivityApi = HttpApiGroup.make("activity").add(HttpApiEndpoint.get("list", "/", {
717
+ query: ActivityQuery,
718
+ success: ActivityPage,
719
+ error: [
720
+ InvalidActivityCursor,
721
+ Forbidden,
722
+ InternalError
723
+ ]
724
+ })).middleware(StudioAuthentication).prefix("/activity");
725
+ const StudioSeoApi = HttpApiGroup.make("seo").add(HttpApiEndpoint.get("read", "/", {
726
+ success: SeoState,
727
+ error: [Forbidden, InternalError]
728
+ }), HttpApiEndpoint.put("save", "/", {
729
+ payload: SaveSeoInput,
730
+ success: SeoMutationResult,
731
+ error: [
732
+ SeoConflict,
733
+ SeoConfirmation,
734
+ SeoMediaInvalid,
735
+ Forbidden,
736
+ InternalError
737
+ ]
738
+ }), HttpApiEndpoint.post("publish", "/publish", {
739
+ payload: PublishSeoInput,
740
+ success: SeoMutationResult,
741
+ error: [
742
+ SeoConflict,
743
+ SeoConfirmation,
744
+ SeoMediaInvalid,
745
+ Forbidden,
746
+ InternalError
747
+ ]
748
+ })).middleware(StudioAuthentication).prefix("/seo");
749
+ const StudioUserAdministrationApi = HttpApiGroup.make("users").add(HttpApiEndpoint.get("list", "/", {
750
+ query: UserQuery,
751
+ success: UserPage,
752
+ error: [UserAdministrationForbidden, InternalError]
753
+ }), HttpApiEndpoint.post("create", "/", {
754
+ payload: CreateUserInput,
755
+ success: User.pipe(HttpApiSchema.status(201)),
756
+ error: [
757
+ UserDuplicate,
758
+ UserAdministrationForbidden,
759
+ InternalError
760
+ ]
761
+ }), HttpApiEndpoint.post("changeRole", "/:userId/role", {
762
+ params: { userId: User.fields.id },
763
+ payload: ChangeRolePayload,
764
+ success: User,
765
+ error: [
766
+ MissingUser,
767
+ UserSelfRoleChange,
768
+ UserAdministrationForbidden,
769
+ InternalError
770
+ ]
771
+ }), HttpApiEndpoint.post("revokeSessions", "/:userId/sessions/revoke", {
772
+ params: { userId: User.fields.id },
773
+ success: User,
774
+ error: [
775
+ MissingUser,
776
+ UserAdministrationForbidden,
777
+ InternalError
778
+ ]
779
+ }), HttpApiEndpoint.delete("remove", "/:userId", {
780
+ params: { userId: User.fields.id },
781
+ success: User,
782
+ error: [
783
+ MissingUser,
784
+ UserSelfRemoval,
785
+ UserAdministrationForbidden,
786
+ InternalError
787
+ ]
788
+ })).middleware(StudioAuthentication).prefix("/users");
789
+ const StudioCollaborationApi = HttpApiGroup.make("collaboration").add(HttpApiEndpoint.post("ticket", "/:documentId/ticket", {
790
+ params: { documentId: DocumentId },
791
+ success: CollaborationTicket,
792
+ error: [
793
+ NotFound,
794
+ Forbidden,
795
+ InternalError
796
+ ]
797
+ })).middleware(StudioAuthentication).prefix("/collaboration");
798
+ const FormsApi = HttpApiGroup.make("forms").add(HttpApiEndpoint.post("submit", "/submit", {
799
+ payload: FormSubmission,
800
+ success: FormResult
801
+ })).prefix("/forms");
802
+ var StudioApi = class extends HttpApi.make("StudioApi").add(FormsApi, InitializationApi, PublishedDeliveryApi, StudioConfigurationApi, StudioSessionApi, StudioDiagnosticsApi, PreviewDeliveryApi.middleware(StudioAuthentication), StudioDocumentsApi, StudioContentApi, StudioHistoryApi, StudioActivityApi, StudioSeoApi, StudioUserAdministrationApi, StudioMediaApi, StudioCollaborationApi).middleware(StudioSchemaError).prefix(STUDIO_API_PATH) {};
803
+
804
+ //#endregion
805
+ //#region ../document/src/Seo.ts
806
+ function resolveDocumentSeo({ document, global, capabilities }) {
807
+ const authored = document.seo;
808
+ const baseTitle = authored?.title ?? document.title;
809
+ const title = global.titleTemplate?.replaceAll("%s", baseTitle) ?? baseTitle;
810
+ const description = authored?.description ?? document.description ?? global.defaultDescription;
811
+ const socialTitle = authored?.socialTitle ?? global.socialTitle ?? title;
812
+ const socialDescription = authored?.socialDescription ?? global.socialDescription ?? description;
813
+ const socialImage = authored?.socialImage ?? global.socialImage;
814
+ const canonicalUrl = authored?.canonicalUrl ?? SeoUrl.make(new URL(document.path, `${global.canonicalOrigin}/`).href);
815
+ const structuredData = resolveStructuredData(authored?.structuredData, capabilities);
816
+ const identity = resolveIdentity(global.identity, capabilities);
817
+ return {
818
+ title,
819
+ ...description === void 0 ? void 0 : { description },
820
+ socialTitle,
821
+ ...socialDescription === void 0 ? void 0 : { socialDescription },
822
+ ...socialImage === void 0 ? void 0 : { socialImage },
823
+ canonicalUrl,
824
+ index: global.index && (authored?.index ?? true),
825
+ follow: global.follow && (authored?.follow ?? true),
826
+ ...structuredData === void 0 ? void 0 : { structuredData },
827
+ ...identity === void 0 ? void 0 : { identity }
828
+ };
829
+ }
830
+ function resolveStructuredData(authored, capabilities) {
831
+ if (authored && capabilities.structuredDataTypes.includes(authored.type)) return authored;
832
+ return capabilities.structuredDataTypes.includes("WebPage") ? { type: "WebPage" } : void 0;
833
+ }
834
+ function resolveIdentity(identity, capabilities) {
835
+ return identity && capabilities.identityTypes.includes(identity.type) ? identity : void 0;
836
+ }
837
+
838
+ //#endregion
839
+ //#region ../server/src/internal/access-control.ts
840
+ const VIEWER_ACTIONS = /* @__PURE__ */ new Set([
841
+ "document.browse",
842
+ "document.read",
843
+ "document.trash",
844
+ "document.history",
845
+ "media.read",
846
+ "media.search",
847
+ "media.integrity"
848
+ ]);
849
+ const EDITOR_ACTIONS = /* @__PURE__ */ new Set([...DOCUMENT_ACTIONS.filter((action) => action !== "document.transfer"), ...MEDIA_ACTIONS.filter((action) => action !== "media.cleanup")]);
850
+ var AccessControl = class extends Context.Service()("@internal/server/AccessControl") {};
851
+ const defaultAccessControlLayer = Layer.succeed(AccessControl, AccessControl.of({ require: Effect.fn("AccessControl.require")(function* (context, decision) {
852
+ if (isAllowed(context, decision.action)) return;
853
+ return yield* new AccessDenied({
854
+ action: decision.action,
855
+ actorId: context.actor.id,
856
+ resource: decision.resource
857
+ });
858
+ }) }));
859
+ function isAllowed(context, action) {
860
+ return context.actor.roles.some((role) => {
861
+ if (role === "administrator") return true;
862
+ return role === "editor" && EDITOR_ACTIONS.has(action) || role === "viewer" && VIEWER_ACTIONS.has(action);
863
+ });
864
+ }
865
+
866
+ //#endregion
867
+ //#region ../document/src/internal/editor/render.ts
868
+ const renderEditorMarkdown = Effect.fn("EditorDocument.renderMarkdown")((document) => {
869
+ return Effect.tryPromise({
870
+ try: () => renderMarkdown(toComarkDocument(withoutEditorMetadata(document))),
871
+ catch: (cause) => new EditorRenderFailed({ cause })
872
+ });
873
+ });
874
+
875
+ //#endregion
876
+ //#region ../document/src/internal/document-engine/relocate-links.ts
877
+ /** Rewrites only semantic page links, preserving bindings and unrelated content. */
878
+ function rewriteDocumentRoutes(document, destinations, components = []) {
879
+ let changed = false;
880
+ function link(value) {
881
+ const target = referenceTarget("a", /* @__PURE__ */ new Map([["href", value]]), document.path, void 0);
882
+ if (target?.kind !== "document" || !Predicate.isString(value)) return value;
883
+ const destination = destinations.get(target.path);
884
+ const sourceMoved = destinations.has(document.path);
885
+ if (!destination && (!sourceMoved || value.startsWith("/"))) return value;
886
+ const suffix = value.match(/[?#].*$/u)?.[0] ?? "";
887
+ const next = `${destination ?? target.path}${suffix}`;
888
+ if (next !== value) changed = true;
889
+ return next;
890
+ }
891
+ function properties(fields, values) {
892
+ const result = mutableJsonObject(values);
893
+ for (const field of fields) {
894
+ const value = values[field.name] === void 0 ? field.defaultValue : values[field.name];
895
+ if (field.input === "link") {
896
+ const next = link(value);
897
+ if (next !== void 0 && next !== value) result[field.name] = next;
898
+ } else if (field.fields) {
899
+ const next = Array.isArray(value) ? value.map((item) => Schema.is(Schema.JsonObject)(item) ? properties(field.fields ?? [], item) : item) : Schema.is(Schema.JsonObject)(value) ? properties(field.fields, value) : value;
900
+ if (next !== void 0 && JSON.stringify(next) !== JSON.stringify(value)) result[field.name] = next;
901
+ }
902
+ }
903
+ return result;
904
+ }
905
+ function visit(node, renderData) {
906
+ if (Predicate.isString(node) || node[0] === null) return node;
907
+ const [tag, attributes, ...children] = node;
908
+ const projection = projectCmsEditorComponentAttributes(attributes, renderData);
909
+ let props = properties(components.find((component) => component.name.toLowerCase() === tag.toLowerCase())?.authoring.props ?? [], projection.props);
910
+ if (tag === "a") {
911
+ const href = link(projection.props.href);
912
+ if (href !== void 0) props = Object.assign({}, props, { href });
913
+ }
914
+ return [
915
+ tag,
916
+ JSON.stringify(props) === JSON.stringify(projection.props) ? attributes : encodeCmsEditorComponentAttributes(props, projection.bindings),
917
+ ...children.map((child) => visit(child, projection.renderData))
918
+ ];
919
+ }
920
+ const nodes = document.body.nodes.map((node) => visit(node, createCmsComarkRenderData(document.body)));
921
+ return changed ? Object.assign({}, document.body, { nodes }) : void 0;
922
+ }
923
+
924
+ //#endregion
925
+ //#region ../document/src/DocumentPlanning.ts
926
+ function planDocumentCreation(configuration, input) {
927
+ return Result.gen(function* () {
928
+ const collection = configuration.types.find((candidate) => candidate.collection === input.collection);
929
+ if (!collection) return yield* invalid$1(`Document collection "${input.collection}" does not exist.`, "collection");
930
+ const title = input.title.trim();
931
+ if (!title) return yield* invalid$1("A document title is required.", "title");
932
+ const locale = yield* resolveLocale(configuration, input.locale);
933
+ const address = yield* parseDocumentAddress(collection.addressPattern).pipe(Result.mapError(addressInvalid));
934
+ const relativePath = yield* resolveRelativePath(collection, input.location);
935
+ const data = Object.fromEntries(address.tokens.flatMap((token) => {
936
+ if (token.name === "slug" && relativePath) return [[token.name, relativePath.split("/").at(-1) ?? relativePath]];
937
+ return [];
938
+ }));
939
+ const relativeKey = yield* normalizePublicPath(relativePath).pipe(Result.mapError(addressInvalid));
940
+ const key = yield* resolveDocumentAddress(address, relativeKey, data).pipe(Result.mapError(addressInvalid));
941
+ const sourcePath = documentSourcePath(collection, relativePath, locale);
942
+ const path = localizeDocumentPath(key, locale, configuration.locales);
943
+ return Object.assign({
944
+ collection,
945
+ data,
946
+ key,
947
+ path,
948
+ sourcePath,
949
+ title
950
+ }, locale === void 0 ? void 0 : { locale });
951
+ });
952
+ }
953
+ const createInitialDocumentSource = Effect.fn("DocumentPlanning.createInitialSource")(function* (plan) {
954
+ const body = CmsMarkdownDocument.make({
955
+ frontmatter: initialFrontmatter(plan),
956
+ meta: {},
957
+ nodes: [[
958
+ "h1",
959
+ {},
960
+ plan.title
961
+ ]]
962
+ });
963
+ return {
964
+ body,
965
+ markdown: yield* renderEditorMarkdown(body)
966
+ };
967
+ });
968
+ function planDocumentMove(configuration, document, location) {
969
+ return Result.gen(function* () {
970
+ const collection = configuration.types.find((candidate) => candidate.collection === document.collection);
971
+ if (!collection) return yield* invalid$1(`Document collection "${document.collection}" does not exist.`, "documentId");
972
+ return documentSourcePath(collection, yield* resolveRelativePath(collection, location), document.locale);
973
+ });
974
+ }
975
+ function documentRelativePath(collection, entry) {
976
+ let sourcePath = entry.sourcePath;
977
+ if (entry.document.locale && sourcePath.startsWith(`${entry.document.locale}/`)) sourcePath = sourcePath.slice(entry.document.locale.length + 1);
978
+ if (collection.directory !== "." && sourcePath.startsWith(`${collection.directory}/`)) sourcePath = sourcePath.slice(collection.directory.length + 1);
979
+ sourcePath = sourcePath.replace(/\.(?:md|mdc)$/iu, "");
980
+ return sourcePath.toLocaleLowerCase() === "index" ? void 0 : sourcePath.replace(/\/index$/iu, "");
981
+ }
982
+ function documentSourcePath(collection, relativePath, locale) {
983
+ const sourceName = relativePath ? `${relativePath}.md` : "index.md";
984
+ return SourcePath.make([
985
+ locale,
986
+ collection.directory === "." ? void 0 : collection.directory,
987
+ sourceName
988
+ ].filter((value) => value !== void 0).join("/"));
989
+ }
990
+ function initialFrontmatter(plan) {
991
+ let frontmatter = {
992
+ ...plan.data,
993
+ title: plan.title
994
+ };
995
+ for (const field of plan.collection.authoringFields) if (field.defaultValue !== void 0 && nestedValue$1(frontmatter, field.path) === void 0) frontmatter = setNestedValue(frontmatter, field.path.split("."), field.defaultValue);
996
+ return frontmatter;
997
+ }
998
+ function nestedValue$1(value, path) {
999
+ return path.split(".").reduce((current, segment) => isJsonObject(current) ? current[segment] : void 0, value);
1000
+ }
1001
+ function setNestedValue(target, path, value) {
1002
+ const [segment, ...rest] = path;
1003
+ if (!segment) return target;
1004
+ if (rest.length === 0) return Object.assign({}, target, { [segment]: value });
1005
+ const current = target[segment];
1006
+ return Object.assign({}, target, { [segment]: setNestedValue(isJsonObject(current) ? current : {}, rest, value) });
1007
+ }
1008
+ const isJsonObject = Schema.is(Schema.JsonObject);
1009
+ function resolveLocale(configuration, requested) {
1010
+ const locales = configuration.locales;
1011
+ if (!locales) return requested === void 0 ? Result.succeed(void 0) : invalid$1(`Document locale "${requested}" is not configured.`, "locale");
1012
+ const locale = requested ?? locales.defaultLocale;
1013
+ return locales.locales.includes(locale) ? Result.succeed(locale) : invalid$1(`Document locale "${locale}" does not exist.`, "locale");
1014
+ }
1015
+ function resolveRelativePath(collection, location) {
1016
+ if (location.kind === "home") return collection.addressPattern === "/:path*" ? Result.succeed("") : invalid$1("Only a root catch-all collection can use the home location.", "location");
1017
+ if (location.parent !== void 0 && !collection.hierarchy) return invalid$1(`Document collection "${collection.collection}" does not allow hierarchy.`, "location.parent");
1018
+ return normalizeDocumentRelativePath([location.parent, location.slug].filter((value) => value !== void 0).join("/")).pipe(Result.mapError((error) => addressInvalid({
1019
+ ...error,
1020
+ field: "location"
1021
+ })));
1022
+ }
1023
+ function addressInvalid(error) {
1024
+ return new DocumentValidationFailed({ issues: [validationIssue(error.message, error.field)] });
1025
+ }
1026
+ function invalid$1(message, field) {
1027
+ return Result.fail(new DocumentValidationFailed({ issues: [validationIssue(message, field)] }));
1028
+ }
1029
+ function validationIssue(message, field) {
1030
+ return field === void 0 ? {
1031
+ code: "document.invalid",
1032
+ message,
1033
+ severity: "error"
1034
+ } : {
1035
+ code: "document.invalid",
1036
+ field,
1037
+ message,
1038
+ severity: "error"
1039
+ };
1040
+ }
1041
+ /** Updates route-owned frontmatter when relocating a page in a slug-addressed collection. */
1042
+ const relocateDocumentMarkdown = Effect.fn("DocumentPlanning.relocateMarkdown")(function* (configuration, document, location, markdown) {
1043
+ const collection = configuration.types.find((type) => type.collection === document.collection);
1044
+ if (!collection || !collection.addressPattern.includes(":slug") || location.kind === "home") return markdown;
1045
+ return yield* renderEditorMarkdown({
1046
+ ...document.body,
1047
+ frontmatter: {
1048
+ ...document.body.frontmatter,
1049
+ slug: location.slug
1050
+ }
1051
+ });
1052
+ });
1053
+
1054
+ //#endregion
1055
+ //#region ../server/src/internal/documents/document-state.ts
1056
+ const isDocumentCompilationFailed = Schema.is(DocumentCompilationFailed);
1057
+ function makeDocumentState(authored, published, snapshot) {
1058
+ const activeById = /* @__PURE__ */ new Map();
1059
+ for (const source of snapshot.active) {
1060
+ if (activeById.has(source.documentId)) throw new Error(`Document "${source.documentId}" appears more than once in the repository snapshot.`);
1061
+ activeById.set(source.documentId, source);
1062
+ }
1063
+ const authoredById = /* @__PURE__ */ new Map();
1064
+ for (const document of authored.documents) {
1065
+ if (authoredById.has(document.id)) throw new Error(`Compiled document "${document.id}" appears more than once in the authored catalog.`);
1066
+ if (!activeById.has(document.id)) throw new Error(`Compiled document "${document.id}" is missing from the repository snapshot.`);
1067
+ authoredById.set(document.id, document);
1068
+ }
1069
+ for (const documentId of activeById.keys()) if (!authoredById.has(documentId)) throw new Error(`Compiled document "${documentId}" is missing from the repository snapshot.`);
1070
+ return {
1071
+ activeById,
1072
+ authored,
1073
+ authoredById,
1074
+ published,
1075
+ snapshot
1076
+ };
1077
+ }
1078
+ const compileDocumentState = Effect.fn("DocumentState.compile")(function* (engine, snapshot, previous) {
1079
+ return makeDocumentState(yield* engine.compileAll({
1080
+ previous: previous?.authored,
1081
+ sources: authoredSources(snapshot)
1082
+ }), yield* engine.compileAll({
1083
+ previous: previous?.published,
1084
+ sources: publishedSources(snapshot)
1085
+ }), snapshot);
1086
+ });
1087
+ function authoredSources(snapshot) {
1088
+ return snapshot.active.map((source) => ({
1089
+ documentId: source.documentId,
1090
+ draft: source.publishedVersion !== source.version,
1091
+ markdown: source.markdown,
1092
+ sourcePath: source.sourcePath
1093
+ }));
1094
+ }
1095
+ function publishedSources(snapshot) {
1096
+ return snapshot.published.map((source) => ({
1097
+ documentId: source.documentId,
1098
+ draft: false,
1099
+ markdown: source.markdown,
1100
+ publishedAt: source.editedAt,
1101
+ sourcePath: source.sourcePath
1102
+ }));
1103
+ }
1104
+ const compileValidCatalog = Effect.fn("DocumentState.compileValid")(function* (engine, sources, previous) {
1105
+ const result = yield* Effect.result(engine.compileAll({
1106
+ previous,
1107
+ sources
1108
+ }));
1109
+ if (Result.isFailure(result)) return yield* new DocumentValidationFailed({ issues: compilationIssues(result.failure) });
1110
+ const errors = result.success.issues.filter((issue) => issue.severity === "error");
1111
+ if (errors.length > 0) return yield* new DocumentValidationFailed({ issues: errors });
1112
+ return result.success;
1113
+ });
1114
+ const validateDocumentSources = Effect.fn("DocumentState.validate")(function* (engine, sources, sourcePath, previous) {
1115
+ const result = yield* Effect.result(engine.compileAll({
1116
+ previous,
1117
+ sources
1118
+ }));
1119
+ if (Result.isFailure(result)) return DocumentValidationResult.make({
1120
+ issues: compilationIssues(result.failure),
1121
+ valid: false
1122
+ });
1123
+ const catalog = result.success;
1124
+ const document = catalog.documents.find((candidate) => candidate.source.path === sourcePath);
1125
+ const issues = catalog.issues;
1126
+ const validation = {
1127
+ issues,
1128
+ valid: document !== void 0 && !issues.some((issue) => issue.severity === "error")
1129
+ };
1130
+ return DocumentValidationResult.make(Object.assign(validation, document === void 0 ? void 0 : { document }));
1131
+ });
1132
+ function authoringEntries(state, query) {
1133
+ return state.snapshot.active.flatMap((source) => {
1134
+ const document = state.authoredById.get(source.documentId);
1135
+ if (!document) throw new Error(`Compiled document "${source.documentId}" is missing from the repository snapshot.`);
1136
+ if (query.collection !== void 0 && document.collection !== query.collection) return [];
1137
+ if (query.locale !== void 0 && document.locale !== query.locale) return [];
1138
+ return [DocumentSummary.make({
1139
+ ...browseMetadata(state, source),
1140
+ document,
1141
+ sourcePath: source.sourcePath,
1142
+ version: source.version
1143
+ })];
1144
+ });
1145
+ }
1146
+ function deletedEntries(state) {
1147
+ return state.snapshot.deleted.map((source) => DeletedDocument.make({
1148
+ ...source.title ? { title: source.title } : void 0,
1149
+ ...source.path ? { path: source.path } : void 0,
1150
+ ...source.trashBatchId ? { trashBatchId: source.trashBatchId } : void 0,
1151
+ documentId: source.documentId,
1152
+ deletedAt: source.deletedAt,
1153
+ sourcePath: source.sourcePath,
1154
+ version: source.version
1155
+ }));
1156
+ }
1157
+ function entryById(state, documentId) {
1158
+ const source = state.activeById.get(documentId);
1159
+ const document = state.authoredById.get(documentId);
1160
+ return source && document ? DocumentSummary.make({
1161
+ ...browseMetadata(state, source),
1162
+ document,
1163
+ sourcePath: source.sourcePath,
1164
+ version: source.version
1165
+ }) : void 0;
1166
+ }
1167
+ function compilationIssues(error) {
1168
+ if (isDocumentCompilationFailed(error)) return error.issues;
1169
+ return [{
1170
+ code: "markdown.parse",
1171
+ message: errorMessage(error.cause),
1172
+ severity: "error",
1173
+ sourcePath: error.sourcePath
1174
+ }];
1175
+ }
1176
+ function errorMessage(cause) {
1177
+ return cause instanceof Error && cause.message ? cause.message : "Markdown could not be parsed.";
1178
+ }
1179
+ function browseMetadata(state, source) {
1180
+ const live = state.published.documents.find((document) => document.id === source.documentId);
1181
+ const metadata = {};
1182
+ if (source.createdBy) metadata.createdBy = source.createdBy;
1183
+ if (source.editedBy) metadata.editedBy = source.editedBy;
1184
+ if (source.editedAt) metadata.editedAt = source.editedAt;
1185
+ if (source.publishedVersion) metadata.publishedVersion = source.publishedVersion;
1186
+ if (live) metadata.publishedPath = live.path;
1187
+ return metadata;
1188
+ }
1189
+
1190
+ //#endregion
1191
+ //#region ../server/src/internal/documents/document-tree-mutation.ts
1192
+ /** Selects by route boundaries, never by title or a raw prefix such as /about matching /about-us. */
1193
+ function subtreeIds(state, documentId) {
1194
+ const parent = state.authoredById.get(documentId);
1195
+ if (!parent) return [documentId];
1196
+ return state.authored.documents.filter((page) => page.id === parent.id || parent.key !== "/" && page.collection === parent.collection && page.locale === parent.locale && page.key.startsWith(`${parent.key}/`)).map((page) => page.id);
1197
+ }
1198
+ const prepareTreeMutation = Effect.fn("Documents.prepareTreeMutation")(function* (dependencies, state, command) {
1199
+ const { configuration, engine } = dependencies;
1200
+ const input = command.input;
1201
+ const root = command.action === "document.undelete" ? state.snapshot.deleted.find((source) => source.documentId === input.documentId) : state.activeById.get(input.documentId);
1202
+ if (!root) return yield* new DocumentNotFound({ documentId: input.documentId });
1203
+ if (root.version !== input.expectedVersion) return yield* new DocumentVersionConflict({
1204
+ documentId: root.documentId,
1205
+ actualVersion: root.version,
1206
+ expectedVersion: input.expectedVersion
1207
+ });
1208
+ let sources;
1209
+ if (command.action === "document.undelete") {
1210
+ const deleted = state.snapshot.deleted.find((source) => source.documentId === root.documentId);
1211
+ sources = state.snapshot.deleted.filter((source) => source.documentId === root.documentId || deleted && deleted.trashBatchId && source.trashBatchId === deleted.trashBatchId && deleted.path !== void 0 && source.path?.startsWith(`${deleted.path}/`));
1212
+ } else {
1213
+ const ids = new Set(command.action === "document.duplicate" && !command.input.includeChildren ? [root.documentId] : subtreeIds(state, root.documentId));
1214
+ sources = state.snapshot.active.filter((source) => ids.has(source.documentId));
1215
+ }
1216
+ sources = [root, ...sources.filter((source) => source.documentId !== root.documentId)];
1217
+ const plans = [];
1218
+ const parent = state.authoredById.get(root.documentId);
1219
+ for (const source of sources) {
1220
+ if (command.action === "document.remove" || command.action === "document.undelete") {
1221
+ const page = state.authoredById.get(source.documentId);
1222
+ plans.push({
1223
+ ...command.action === "document.remove" ? {
1224
+ trashBatchId: root.version,
1225
+ title: page?.title,
1226
+ path: page?.path
1227
+ } : void 0,
1228
+ action: command.action,
1229
+ documentId: source.documentId,
1230
+ expectedVersion: source.version,
1231
+ markdown: source.markdown,
1232
+ sourcePath: source.sourcePath
1233
+ });
1234
+ continue;
1235
+ }
1236
+ const page = state.authoredById.get(source.documentId);
1237
+ const collection = configuration.types.find((type) => type.collection === page?.collection);
1238
+ if (!page || !parent || !collection) return yield* new DocumentNotFound({ documentId: source.documentId });
1239
+ if (command.action === "document.duplicate" && !collection.canCreate) return yield* invalid("This collection does not allow new pages.");
1240
+ const destination = command.input.location;
1241
+ if (parent.key === "/" && command.action === "document.move") return yield* invalid("The home page cannot be moved.");
1242
+ const rootRelative = documentRelativePath(collection, {
1243
+ document: parent,
1244
+ sourcePath: root.sourcePath
1245
+ }) ?? "";
1246
+ const nextRelative = destination.kind === "home" ? "" : [destination.parent, destination.slug].filter(Boolean).join("/");
1247
+ if (rootRelative && (nextRelative === rootRelative || nextRelative.startsWith(`${rootRelative}/`))) return yield* invalid("Choose a destination outside this page and its subpages.");
1248
+ const relative = documentRelativePath(collection, {
1249
+ document: page,
1250
+ sourcePath: source.sourcePath
1251
+ }) ?? "";
1252
+ const segments = `${nextRelative}${source.documentId === root.documentId ? "" : relative.slice(rootRelative.length)}`.split("/").filter(Boolean);
1253
+ const slug = segments.pop();
1254
+ const location = slug ? Object.assign({
1255
+ kind: "entry",
1256
+ slug: yield* decodeSlug(slug)
1257
+ }, segments.length ? { parent: yield* decodeParent(segments.join("/")) } : void 0) : { kind: "home" };
1258
+ const sourcePath = yield* Effect.fromResult(planDocumentMove(configuration, page, location));
1259
+ const markdown = yield* relocateDocumentMarkdown(configuration, page, location, source.markdown).pipe(Effect.mapError(() => invalid("The page could not be copied to its new URL.")));
1260
+ if (command.action === "document.duplicate") {
1261
+ const documentId = yield* command.nextDocumentId;
1262
+ plans.push({
1263
+ action: "document.create",
1264
+ documentId,
1265
+ markdown,
1266
+ sourcePath
1267
+ });
1268
+ } else plans.push({
1269
+ action: "document.move",
1270
+ documentId: source.documentId,
1271
+ expectedVersion: source.version,
1272
+ markdown,
1273
+ sourcePath,
1274
+ publishedVersion: source.publishedVersion
1275
+ });
1276
+ }
1277
+ const changed = new Set(plans.map((plan) => plan.documentId));
1278
+ const nextSources = authoredSources(state.snapshot).filter((source) => !changed.has(source.documentId));
1279
+ if (command.action !== "document.remove") for (const plan of plans) {
1280
+ if (nextSources.some((source) => source.sourcePath === plan.sourcePath)) return yield* new DocumentPathConflict({ sourcePath: plan.sourcePath });
1281
+ nextSources.push({
1282
+ documentId: plan.documentId,
1283
+ draft: true,
1284
+ markdown: plan.markdown,
1285
+ sourcePath: plan.sourcePath
1286
+ });
1287
+ }
1288
+ if (command.action === "document.move" || command.action === "document.duplicate") {
1289
+ const preliminary = yield* engine.compileAll({
1290
+ sources: nextSources,
1291
+ previous: state.authored
1292
+ });
1293
+ const destinations = /* @__PURE__ */ new Map();
1294
+ for (let index = 0; index < sources.length; index++) {
1295
+ const original = sources[index];
1296
+ const plan = plans[index];
1297
+ const oldPage = original && state.authoredById.get(original.documentId);
1298
+ const newPage = plan && preliminary.documents.find((document) => document.id === plan.documentId);
1299
+ if (oldPage && newPage) destinations.set(oldPage.path, newPage.path);
1300
+ }
1301
+ const originals = command.action === "document.move" ? state.snapshot.active : sources;
1302
+ for (const source of originals) {
1303
+ const page = state.authoredById.get(source.documentId);
1304
+ if (!page) continue;
1305
+ const body = rewriteDocumentRoutes(page, destinations, dependencies.components);
1306
+ if (!body) continue;
1307
+ const index = command.action === "document.duplicate" ? sources.findIndex((item) => item.documentId === source.documentId) : plans.findIndex((plan) => plan.documentId === source.documentId);
1308
+ const existing = index >= 0 ? plans[index] : void 0;
1309
+ const projected = existing && preliminary.documents.find((document) => document.id === existing.documentId);
1310
+ const markdown = yield* renderEditorMarkdown(Object.assign({}, body, { frontmatter: projected?.body.frontmatter ?? body.frontmatter })).pipe(Effect.mapError(() => invalid("Page links could not be updated.")));
1311
+ if (existing) plans[index] = {
1312
+ ...existing,
1313
+ markdown
1314
+ };
1315
+ else plans.push({
1316
+ action: "document.update",
1317
+ documentId: source.documentId,
1318
+ expectedVersion: source.version,
1319
+ sourcePath: source.sourcePath,
1320
+ markdown,
1321
+ publishedVersion: source.publishedVersion
1322
+ });
1323
+ }
1324
+ for (const plan of plans) {
1325
+ const index = nextSources.findIndex((source) => source.documentId === plan.documentId);
1326
+ const updated = {
1327
+ documentId: plan.documentId,
1328
+ sourcePath: plan.sourcePath,
1329
+ markdown: plan.markdown,
1330
+ draft: true
1331
+ };
1332
+ if (index >= 0) nextSources[index] = updated;
1333
+ else nextSources.push(updated);
1334
+ }
1335
+ }
1336
+ const first = plans[0];
1337
+ if (!first) return yield* Effect.die(/* @__PURE__ */ new Error("Empty subtree mutation."));
1338
+ return {
1339
+ authored: yield* compileValidCatalog(engine, nextSources, state.authored),
1340
+ published: command.action === "document.remove" ? yield* compileValidCatalog(engine, publishedSources(state.snapshot).filter((source) => !changed.has(source.documentId)), state.published) : state.published,
1341
+ commitPlan: first,
1342
+ additionalPlans: plans.slice(1)
1343
+ };
1344
+ });
1345
+ const decodeSlug = (value) => Schema.decodeEffect(Slug)(value).pipe(Effect.mapError(() => invalid("Invalid URL ending.")));
1346
+ const decodeParent = (value) => Schema.decodeEffect(RelativeDocumentPath)(value).pipe(Effect.mapError(() => invalid("Invalid parent URL.")));
1347
+ function invalid(message) {
1348
+ return new DocumentValidationFailed({ issues: [{
1349
+ code: "document.invalid",
1350
+ severity: "error",
1351
+ message
1352
+ }] });
1353
+ }
1354
+
1355
+ //#endregion
1356
+ //#region ../server/src/internal/documents/document-lifecycle.ts
1357
+ const PreparedDocumentMutation = Data.taggedEnum();
1358
+ const prepareDocumentMutation = Effect.fn("DocumentLifecycle.prepare")(function* (dependencies, state, command) {
1359
+ const { engine, repository } = dependencies;
1360
+ if (command.action === "document.move" || command.action === "document.remove" || command.action === "document.undelete" || command.action === "document.duplicate") return PreparedDocumentMutation.Commit(yield* prepareTreeMutation(dependencies, state, command));
1361
+ const authored = authoredSources(state.snapshot);
1362
+ const published = publishedSources(state.snapshot);
1363
+ switch (command.action) {
1364
+ case "document.create": {
1365
+ yield* ensurePathAvailable(state, command.sourcePath);
1366
+ const nextAuthored = [...authored, {
1367
+ documentId: command.documentId,
1368
+ draft: true,
1369
+ markdown: command.markdown,
1370
+ sourcePath: command.sourcePath
1371
+ }];
1372
+ return PreparedDocumentMutation.Commit({
1373
+ authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1374
+ published: state.published,
1375
+ commitPlan: {
1376
+ action: command.action,
1377
+ documentId: command.documentId,
1378
+ markdown: command.markdown,
1379
+ sourcePath: command.sourcePath
1380
+ }
1381
+ });
1382
+ }
1383
+ case "document.update": {
1384
+ const current = yield* requireActive(state, command.input.documentId);
1385
+ yield* requireVersion(current, command.input.expectedVersion);
1386
+ const nextAuthored = replaceSource(authored, {
1387
+ documentId: current.documentId,
1388
+ draft: true,
1389
+ markdown: command.input.markdown,
1390
+ sourcePath: current.sourcePath
1391
+ });
1392
+ return PreparedDocumentMutation.Commit({
1393
+ authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1394
+ published: state.published,
1395
+ commitPlan: {
1396
+ action: command.action,
1397
+ documentId: current.documentId,
1398
+ expectedVersion: command.input.expectedVersion,
1399
+ markdown: command.input.markdown,
1400
+ publishedVersion: current.publishedVersion,
1401
+ sourcePath: current.sourcePath
1402
+ }
1403
+ });
1404
+ }
1405
+ case "document.restore": {
1406
+ const current = yield* requireActive(state, command.input.documentId);
1407
+ yield* requireVersion(current, command.input.expectedVersion);
1408
+ const historical = yield* repository.revision(current.documentId, command.input.revisionId);
1409
+ const nextAuthored = replaceSource(authored, {
1410
+ documentId: current.documentId,
1411
+ draft: true,
1412
+ markdown: historical.markdown,
1413
+ sourcePath: current.sourcePath
1414
+ });
1415
+ return PreparedDocumentMutation.Commit({
1416
+ authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1417
+ published: state.published,
1418
+ commitPlan: {
1419
+ action: command.action,
1420
+ documentId: current.documentId,
1421
+ expectedVersion: command.input.expectedVersion,
1422
+ markdown: historical.markdown,
1423
+ publishedVersion: current.publishedVersion,
1424
+ revisionId: command.input.revisionId,
1425
+ sourcePath: current.sourcePath
1426
+ }
1427
+ });
1428
+ }
1429
+ case "document.unpublish": {
1430
+ const current = yield* requireActive(state, command.input.documentId);
1431
+ yield* requireVersion(current, command.input.expectedVersion);
1432
+ return PreparedDocumentMutation.Commit({
1433
+ authored: yield* compileValidCatalog(engine, replaceSource(authored, {
1434
+ documentId: current.documentId,
1435
+ draft: true,
1436
+ markdown: current.markdown,
1437
+ sourcePath: current.sourcePath
1438
+ }), state.authored),
1439
+ published: yield* compileValidCatalog(engine, withoutDocument(published, current.documentId), state.published),
1440
+ commitPlan: {
1441
+ action: command.action,
1442
+ documentId: current.documentId,
1443
+ expectedVersion: current.version,
1444
+ markdown: current.markdown,
1445
+ sourcePath: current.sourcePath,
1446
+ revisionId: current.revisionId
1447
+ }
1448
+ });
1449
+ }
1450
+ case "document.publish": {
1451
+ const current = yield* requireActive(state, command.input.documentId);
1452
+ yield* requireVersion(current, command.input.expectedVersion);
1453
+ if (current.publishedVersion === current.version) {
1454
+ const entry = entryById(state, current.documentId);
1455
+ const result = {
1456
+ action: "document.publish",
1457
+ affectedDocumentIds: [current.documentId],
1458
+ documentId: current.documentId,
1459
+ generation: state.snapshot.generation,
1460
+ revisionId: current.revisionId,
1461
+ sourcePath: current.sourcePath,
1462
+ version: current.version
1463
+ };
1464
+ return PreparedDocumentMutation.Unchanged({ result: DocumentMutationResult.make(Object.assign(result, entry === void 0 ? void 0 : { entry })) });
1465
+ }
1466
+ const source = {
1467
+ documentId: current.documentId,
1468
+ draft: false,
1469
+ markdown: current.markdown,
1470
+ sourcePath: current.sourcePath
1471
+ };
1472
+ return PreparedDocumentMutation.Commit({
1473
+ authored: yield* compileValidCatalog(engine, replaceSource(authored, source), state.authored),
1474
+ published: yield* compileValidCatalog(engine, replaceSource(published, source), state.published),
1475
+ commitPlan: {
1476
+ action: command.action,
1477
+ documentId: current.documentId,
1478
+ expectedVersion: command.input.expectedVersion,
1479
+ markdown: current.markdown,
1480
+ revisionId: current.revisionId,
1481
+ sourcePath: current.sourcePath
1482
+ }
1483
+ });
1484
+ }
1485
+ }
1486
+ });
1487
+ const validateDocumentUpdate = Effect.fn("DocumentLifecycle.validateUpdate")(function* (engine, state, input) {
1488
+ const current = yield* requireActive(state, input.documentId);
1489
+ yield* requireVersion(current, input.expectedVersion);
1490
+ const source = {
1491
+ documentId: current.documentId,
1492
+ draft: true,
1493
+ markdown: input.markdown,
1494
+ sourcePath: current.sourcePath
1495
+ };
1496
+ return yield* validateDocumentSources(engine, replaceSource(authoredSources(state.snapshot), source), current.sourcePath, state.authored);
1497
+ });
1498
+ function requireActive(state, documentId) {
1499
+ const source = state.activeById.get(documentId);
1500
+ return source ? Effect.succeed(source) : Effect.fail(new DocumentNotFound({ documentId }));
1501
+ }
1502
+ function requireVersion(source, expectedVersion) {
1503
+ return source.version === expectedVersion ? Effect.void : Effect.fail(new DocumentVersionConflict({
1504
+ actualVersion: source.version,
1505
+ documentId: source.documentId,
1506
+ expectedVersion
1507
+ }));
1508
+ }
1509
+ function ensurePathAvailable(state, sourcePath, exceptDocumentId) {
1510
+ return state.snapshot.active.some((source) => source.sourcePath === sourcePath && source.documentId !== exceptDocumentId) ? Effect.fail(new DocumentPathConflict({ sourcePath })) : Effect.void;
1511
+ }
1512
+ function replaceSource(sources, source) {
1513
+ return [...sources.filter((candidate) => candidate.documentId !== source.documentId), source];
1514
+ }
1515
+ function withoutDocument(sources, documentId) {
1516
+ return sources.filter((source) => source.documentId !== documentId);
1517
+ }
1518
+
1519
+ //#endregion
1520
+ //#region ../server/src/internal/documents/document-media-references.ts
1521
+ function projectMediaReferences(authored, published, documentId) {
1522
+ const references = [...referencesFromCatalog(authored, documentId), ...referencesFromCatalog(published, documentId)];
1523
+ return [...new Map(references.map((reference) => [referenceIdentity(reference), reference])).values()];
1524
+ }
1525
+ function referencesFromCatalog(catalog, documentId) {
1526
+ return catalog.references.all.filter((reference) => reference.target.kind === "media" && (documentId === void 0 || reference.source.documentId === documentId)).map(toMediaReference);
1527
+ }
1528
+ function toMediaReference(reference) {
1529
+ if (reference.target.kind !== "media") throw new Error("Expected a media reference.");
1530
+ return MediaReference.make({
1531
+ documentId: reference.source.documentId,
1532
+ ...reference.target.mediaId ? { mediaId: reference.target.mediaId } : void 0,
1533
+ nodePath: reference.nodePath,
1534
+ objectKey: MediaObjectKey.make(reference.target.key),
1535
+ path: reference.source.path,
1536
+ sourcePath: reference.source.sourcePath
1537
+ });
1538
+ }
1539
+ function referenceIdentity(reference) {
1540
+ return `${reference.documentId}:${reference.sourcePath}:${reference.nodePath.join(".")}:${reference.objectKey}`;
1541
+ }
1542
+
1543
+ //#endregion
1544
+ //#region ../server/src/internal/documents/content-transfer.ts
1545
+ /** Plan all incoming sources against one document generation, including cross-document references. */
1546
+ const planContentTransfer = Effect.fn("ContentTransfer.plan")(function* (engine, state, input, crypto) {
1547
+ const currentByPath = new Map(state.snapshot.active.map((source) => [source.sourcePath, source]));
1548
+ const seen = /* @__PURE__ */ new Set();
1549
+ const commitPlans = [];
1550
+ const entries = [];
1551
+ const authored = new Map(authoredSources(state.snapshot).map((source) => [source.sourcePath, source]));
1552
+ const published = new Map(publishedSources(state.snapshot).map((source) => [source.sourcePath, source]));
1553
+ const now = yield* DateTime.now;
1554
+ let created = 0;
1555
+ let updated = 0;
1556
+ let publishedCount = 0;
1557
+ let unchanged = 0;
1558
+ for (const source of input.sources) {
1559
+ if (seen.has(source.sourcePath)) return yield* new ContentTransferConflict({ message: `Duplicate source path: ${source.sourcePath}` });
1560
+ seen.add(source.sourcePath);
1561
+ const current = currentByPath.get(source.sourcePath);
1562
+ const shouldPublish = input.publish && source.published;
1563
+ const action = !current ? "create" : current.markdown !== source.markdown ? input.overwrite ? "update" : "conflict" : shouldPublish && current.publishedVersion !== current.version ? "publish" : "unchanged";
1564
+ entries.push({
1565
+ sourcePath: source.sourcePath,
1566
+ action,
1567
+ currentRevisionId: current?.revisionId ?? null
1568
+ });
1569
+ if (action === "create" || action === "update") {
1570
+ const documentId = current?.documentId ?? DocumentId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
1571
+ if (current) {
1572
+ commitPlans.push({
1573
+ action: "document.update",
1574
+ documentId,
1575
+ expectedVersion: current.version,
1576
+ markdown: source.markdown,
1577
+ publish: shouldPublish,
1578
+ publishedVersion: current.publishedVersion,
1579
+ sourcePath: source.sourcePath
1580
+ });
1581
+ updated++;
1582
+ } else {
1583
+ commitPlans.push({
1584
+ action: "document.create",
1585
+ documentId,
1586
+ markdown: source.markdown,
1587
+ publish: shouldPublish,
1588
+ sourcePath: source.sourcePath
1589
+ });
1590
+ created++;
1591
+ }
1592
+ if (shouldPublish) publishedCount++;
1593
+ authored.set(source.sourcePath, {
1594
+ documentId,
1595
+ draft: !shouldPublish,
1596
+ markdown: source.markdown,
1597
+ sourcePath: source.sourcePath
1598
+ });
1599
+ if (shouldPublish) published.set(source.sourcePath, {
1600
+ documentId,
1601
+ draft: false,
1602
+ markdown: source.markdown,
1603
+ publishedAt: now,
1604
+ sourcePath: source.sourcePath
1605
+ });
1606
+ } else if (action === "publish" && current) {
1607
+ commitPlans.push({
1608
+ action: "document.publish",
1609
+ documentId: current.documentId,
1610
+ expectedVersion: current.version,
1611
+ markdown: current.markdown,
1612
+ revisionId: current.revisionId,
1613
+ sourcePath: current.sourcePath
1614
+ });
1615
+ publishedCount++;
1616
+ authored.set(source.sourcePath, {
1617
+ documentId: current.documentId,
1618
+ draft: false,
1619
+ markdown: current.markdown,
1620
+ sourcePath: current.sourcePath
1621
+ });
1622
+ published.set(source.sourcePath, {
1623
+ documentId: current.documentId,
1624
+ draft: false,
1625
+ markdown: current.markdown,
1626
+ publishedAt: now,
1627
+ sourcePath: current.sourcePath
1628
+ });
1629
+ } else if (action === "unchanged") unchanged++;
1630
+ }
1631
+ const firstPath = input.sources[0]?.sourcePath;
1632
+ const authoredValidation = firstPath ? yield* validateDocumentSources(engine, [...authored.values()], firstPath, state.authored) : DocumentValidationResult.make({
1633
+ issues: [],
1634
+ valid: true
1635
+ });
1636
+ const publishedValidation = input.publish && firstPath ? yield* validateDocumentSources(engine, [...published.values()], firstPath, state.published) : DocumentValidationResult.make({
1637
+ issues: [],
1638
+ valid: true
1639
+ });
1640
+ const issues = [...authoredValidation.issues, ...publishedValidation.issues];
1641
+ return {
1642
+ plan: ContentPlan.make({
1643
+ generation: state.snapshot.generation,
1644
+ entries,
1645
+ issues,
1646
+ valid: authoredValidation.valid && !entries.some((entry) => entry.action === "conflict") && !issues.some((issue) => issue.severity === "error")
1647
+ }),
1648
+ authoredSources: [...authored.values()],
1649
+ publishedSources: [...published.values()],
1650
+ commitPlans,
1651
+ counts: {
1652
+ created,
1653
+ updated,
1654
+ published: publishedCount,
1655
+ unchanged
1656
+ }
1657
+ };
1658
+ });
1659
+
1660
+ //#endregion
1661
+ //#region ../server/src/internal/documents/document-mutation.ts
1662
+ /** Commit validated sources and their media references through one generation fence. */
1663
+ const commitApprovedDocumentPlans = Effect.fn("Documents.commitApprovedPlans")(function* (input) {
1664
+ const [plan, ...additionalPlans] = input.plans;
1665
+ return yield* input.repository.commit({
1666
+ activity: input.activity,
1667
+ plan,
1668
+ additionalPlans,
1669
+ expectedGeneration: input.current.snapshot.generation,
1670
+ mediaReferences: input.plans.flatMap((candidate) => projectMediaReferences(input.authored, input.published, candidate.documentId))
1671
+ });
1672
+ });
1673
+ /** Authorize every affected document before the adapter commits the approved plan atomically. */
1674
+ const commitDocumentMutation = Effect.fn("Documents.commitMutation")(function* (accessControl, dependencies, current, command, activity) {
1675
+ if (command.action === "document.move" || command.action === "document.remove" || command.action === "document.duplicate") {
1676
+ const ids = command.action === "document.duplicate" && !command.input.includeChildren ? [command.input.documentId] : subtreeIds(current, command.input.documentId);
1677
+ for (const documentId of ids) yield* accessControl.require({
1678
+ actor: activity.actor,
1679
+ requestId: activity.requestId
1680
+ }, {
1681
+ action: command.action,
1682
+ resource: {
1683
+ kind: "document",
1684
+ documentId
1685
+ }
1686
+ });
1687
+ if (command.action === "document.duplicate") {
1688
+ const source = current.authoredById.get(command.input.documentId);
1689
+ if (source) yield* accessControl.require({
1690
+ actor: activity.actor,
1691
+ requestId: activity.requestId
1692
+ }, {
1693
+ action: "document.create",
1694
+ resource: {
1695
+ kind: "collection",
1696
+ collection: source.collection,
1697
+ locale: source.locale
1698
+ }
1699
+ });
1700
+ }
1701
+ }
1702
+ const prepared = yield* prepareDocumentMutation(dependencies, current, command);
1703
+ if (command.action === "document.move" && Predicate.isTagged("Commit")(prepared)) {
1704
+ for (const plan of prepared.additionalPlans ?? []) if (plan.action === "document.update") yield* accessControl.require({
1705
+ actor: activity.actor,
1706
+ requestId: activity.requestId
1707
+ }, {
1708
+ action: "document.update",
1709
+ resource: {
1710
+ kind: "document",
1711
+ documentId: plan.documentId
1712
+ }
1713
+ });
1714
+ }
1715
+ if (command.action === "document.undelete" && Predicate.isTagged("Commit")(prepared)) for (const plan of [prepared.commitPlan, ...prepared.additionalPlans ?? []]) yield* accessControl.require({
1716
+ actor: activity.actor,
1717
+ requestId: activity.requestId
1718
+ }, {
1719
+ action: "document.undelete",
1720
+ resource: {
1721
+ kind: "document",
1722
+ documentId: plan.documentId
1723
+ }
1724
+ });
1725
+ if (Predicate.isTagged("Unchanged")(prepared)) return {
1726
+ kind: "unchanged",
1727
+ result: prepared.result
1728
+ };
1729
+ const committed = yield* commitApprovedDocumentPlans({
1730
+ repository: dependencies.repository,
1731
+ current,
1732
+ plans: [prepared.commitPlan, ...prepared.additionalPlans ?? []],
1733
+ authored: prepared.authored,
1734
+ published: prepared.published,
1735
+ activity
1736
+ });
1737
+ const next = makeDocumentState(prepared.authored, prepared.published, committed.snapshot);
1738
+ const entry = entryById(next, committed.mutation.documentId);
1739
+ return {
1740
+ kind: "committed",
1741
+ next,
1742
+ result: DocumentMutationResult.make(Object.assign({}, committed.mutation, entry === void 0 ? void 0 : { entry }))
1743
+ };
1744
+ });
1745
+
1746
+ //#endregion
1747
+ //#region ../server/src/internal/documents/document-store.ts
1748
+ var DocumentStore = class extends Context.Service()("@internal/server/DocumentStore") {};
1749
+
1750
+ //#endregion
1751
+ //#region ../server/src/internal/documents/document-failures.ts
1752
+ var DocumentPersistenceError = class extends Schema.TaggedError()("Documents.PersistenceError", {
1753
+ operation: Schema.NonEmptyString,
1754
+ cause: Schema.Defect()
1755
+ }) {};
1756
+ var DocumentGenerationConflict = class extends Schema.TaggedError()("Documents.GenerationConflict", {
1757
+ expectedGeneration: DocumentGeneration,
1758
+ actualGeneration: DocumentGeneration
1759
+ }) {};
1760
+
1761
+ //#endregion
1762
+ //#region ../server/src/internal/cursor.ts
1763
+ function cursorCodec(schema, makeCursor, invalid) {
1764
+ const decodeJson = Schema.decodeUnknownResult(schema);
1765
+ const encodeJson = flow(Schema.encodeResult(schema), Result.getOrThrow);
1766
+ return {
1767
+ decode: (cursor) => {
1768
+ const decoded = Encoding.decodeBase64UrlString(cursor);
1769
+ if (Result.isFailure(decoded)) return Result.fail(invalid());
1770
+ return decodeJson(decoded.success).pipe(Result.mapError(invalid));
1771
+ },
1772
+ encode: (payload) => makeCursor(Encoding.encodeBase64Url(encodeJson(payload)))
1773
+ };
1774
+ }
1775
+
1776
+ //#endregion
1777
+ //#region ../server/src/internal/documents/document-history.ts
1778
+ const CursorPayload = Schema.Struct({
1779
+ createdAt: Schema.DateTimeUtcFromString,
1780
+ revisionId: RevisionId
1781
+ });
1782
+ const CursorJson = Schema.fromJsonString(CursorPayload);
1783
+ const cursors = cursorCodec(CursorJson, (value) => DocumentHistoryCursor.make(value), () => new DocumentHistoryInvalidCursor());
1784
+ function documentHistoryCapability(accessControl, repository) {
1785
+ const requireHistory = (context, documentId) => accessControl.require(context, {
1786
+ action: "document.history",
1787
+ resource: {
1788
+ documentId,
1789
+ kind: "document"
1790
+ }
1791
+ });
1792
+ const read = Effect.fn("Documents.history.read")(function* (context, documentId, revisionId) {
1793
+ yield* requireHistory(context, documentId);
1794
+ return yield* repository.revision(documentId, revisionId);
1795
+ });
1796
+ const list = Effect.fn("Documents.history.list")(function* (context, query) {
1797
+ yield* requireHistory(context, query.documentId);
1798
+ const cursor = query.cursor ? yield* Effect.fromResult(cursors.decode(query.cursor)) : void 0;
1799
+ const page = yield* repository.revisions({
1800
+ cursor,
1801
+ documentId: query.documentId,
1802
+ limit: query.limit ?? 25
1803
+ });
1804
+ const output = {
1805
+ revisions: page.revisions.map(({ markdown: _markdown, ...revision }) => revision),
1806
+ total: page.total
1807
+ };
1808
+ return page.next ? DocumentRevisionPage.make({
1809
+ ...output,
1810
+ nextCursor: cursors.encode(page.next)
1811
+ }) : DocumentRevisionPage.make(output);
1812
+ });
1813
+ return {
1814
+ compare: Effect.fn("Documents.history.compare")(function* (context, input) {
1815
+ yield* requireHistory(context, input.documentId);
1816
+ const from = yield* repository.revision(input.documentId, input.fromRevisionId);
1817
+ const to = yield* repository.revision(input.documentId, input.toRevisionId);
1818
+ const changes = yield* compareLines(from.markdown, to.markdown);
1819
+ return DocumentRevisionComparison.make({
1820
+ changes,
1821
+ from,
1822
+ to
1823
+ });
1824
+ }),
1825
+ list,
1826
+ read
1827
+ };
1828
+ }
1829
+ const MAXIMUM_COMPARISON_LINES = 1e4;
1830
+ const MAXIMUM_EDIT_LENGTH = 2e3;
1831
+ const comparisonTooLarge = () => new DocumentHistoryComparisonTooLarge({
1832
+ maximumEditLength: MAXIMUM_EDIT_LENGTH,
1833
+ maximumLines: MAXIMUM_COMPARISON_LINES
1834
+ });
1835
+ const compareLines = Effect.fn("Documents.history.compareLines")(function* (beforeMarkdown, afterMarkdown) {
1836
+ const before = beforeMarkdown.split("\n");
1837
+ const after = afterMarkdown.split("\n");
1838
+ if (before.length > MAXIMUM_COMPARISON_LINES || after.length > MAXIMUM_COMPARISON_LINES) return yield* comparisonTooLarge();
1839
+ const diff = diffArrays(before, after, {
1840
+ maxEditLength: MAXIMUM_EDIT_LENGTH,
1841
+ oneChangePerToken: true,
1842
+ timeout: 100
1843
+ });
1844
+ if (!diff) return yield* comparisonTooLarge();
1845
+ const changes = [];
1846
+ let beforeLine = 1;
1847
+ let afterLine = 1;
1848
+ for (const change of diff) for (const line of change.value) if (change.removed) {
1849
+ changes.push({
1850
+ beforeLine,
1851
+ kind: "removed",
1852
+ line
1853
+ });
1854
+ beforeLine += 1;
1855
+ } else if (change.added) {
1856
+ changes.push({
1857
+ afterLine,
1858
+ kind: "added",
1859
+ line
1860
+ });
1861
+ afterLine += 1;
1862
+ } else {
1863
+ changes.push({
1864
+ afterLine,
1865
+ beforeLine,
1866
+ kind: "equal",
1867
+ line
1868
+ });
1869
+ beforeLine += 1;
1870
+ afterLine += 1;
1871
+ }
1872
+ return changes;
1873
+ });
1874
+
1875
+ //#endregion
1876
+ //#region ../server/src/internal/documents/document-delivery.ts
1877
+ function documentDelivery(configuration, catalog) {
1878
+ const read = Effect.fn("Documents.delivery.read")(function* (lookup) {
1879
+ const document = findDocument(yield* catalog, lookup);
1880
+ return Option.isSome(document) ? document.value : yield* new PublishedDocumentNotFound({ lookup });
1881
+ });
1882
+ const query = Effect.fn("Documents.delivery.query")(function* (input) {
1883
+ return queryDocuments((yield* catalog).documents, input, {
1884
+ limit: input.limit,
1885
+ offset: input.offset ?? 0
1886
+ });
1887
+ });
1888
+ const navigation = Effect.fn("Documents.delivery.navigation")(function* (input) {
1889
+ return createNavigation((yield* catalog).documents, input, configuration);
1890
+ });
1891
+ const surround = Effect.fn("Documents.delivery.surround")(function* (lookup, input) {
1892
+ const current = yield* catalog;
1893
+ const document = findDocument(current, lookup);
1894
+ if (Option.isNone(document)) return yield* new PublishedDocumentNotFound({ lookup });
1895
+ const surroundQuery = Object.assign({}, { collection: input.collection ?? document.value.collection }, input.locale === void 0 && document.value.locale === void 0 ? void 0 : { locale: input.locale ?? document.value.locale }, input.where === void 0 ? void 0 : { where: input.where }, input.sort === void 0 ? void 0 : { sort: input.sort });
1896
+ const page = queryDocuments(current.documents, surroundQuery);
1897
+ const index = page.items.findIndex((candidate) => candidate.id === document.value.id);
1898
+ return DocumentSurround.make({
1899
+ previous: index > 0 ? page.items[index - 1] ?? null : null,
1900
+ next: index >= 0 ? page.items[index + 1] ?? null : null
1901
+ });
1902
+ });
1903
+ return {
1904
+ diagnostics: catalog.pipe(Effect.map((current) => current.issues)),
1905
+ navigation,
1906
+ query,
1907
+ read,
1908
+ surround
1909
+ };
1910
+ }
1911
+ function findDocument(catalog, lookup) {
1912
+ switch (lookup.kind) {
1913
+ case "id": return Option.fromNullishOr(catalog.documents.find((document) => document.id === lookup.documentId));
1914
+ case "path": return catalog.findByPath(lookup.path);
1915
+ case "key": return catalog.findByKey(lookup.key, lookup.locale).pipe(Option.filter((document) => lookup.collection === void 0 || document.collection === lookup.collection));
1916
+ }
1917
+ }
1918
+ function queryDocuments(documents, query, page) {
1919
+ const filtered = documents.filter((document) => {
1920
+ if (query.collection !== void 0 && document.collection !== query.collection) return false;
1921
+ if (query.locale !== void 0 && document.locale !== query.locale) return false;
1922
+ return Object.entries(query.where ?? {}).every(([field, expected]) => matchesValue(documentField(document, field), expected));
1923
+ });
1924
+ const sorted = query.sort && query.sort.length > 0 ? sortDocuments(filtered, query.sort) : filtered;
1925
+ const items = page ? sorted.slice(page.offset, page.offset + page.limit) : sorted;
1926
+ return PublishedDocumentPage.make({
1927
+ items,
1928
+ total: filtered.length
1929
+ });
1930
+ }
1931
+ function createNavigation(documents, query, configuration) {
1932
+ const locale = query.locale ?? configuration.locales?.defaultLocale;
1933
+ const candidates = documents.filter((document) => (query.collection === void 0 || document.collection === query.collection) && (locale === void 0 || document.locale === locale) && document.data.navigation !== false);
1934
+ const roots = [];
1935
+ const itemsByKey = /* @__PURE__ */ new Map();
1936
+ const orderByItem = /* @__PURE__ */ new Map();
1937
+ for (const document of candidates) {
1938
+ if (document.key === "/") {
1939
+ const item = navigationItem(document);
1940
+ roots.push(item);
1941
+ orderByItem.set(item, Number.NEGATIVE_INFINITY);
1942
+ continue;
1943
+ }
1944
+ const segments = document.key.slice(1).split("/");
1945
+ let parent;
1946
+ for (let index = 0; index < segments.length; index += 1) {
1947
+ const key = PublicPath.make(`/${segments.slice(0, index + 1).join("/")}`);
1948
+ let item = itemsByKey.get(key);
1949
+ if (!item) {
1950
+ item = {
1951
+ children: [],
1952
+ key,
1953
+ title: titleFromSegment(segments[index] ?? "")
1954
+ };
1955
+ itemsByKey.set(key, item);
1956
+ if (parent) parent.children.push(item);
1957
+ else roots.push(item);
1958
+ }
1959
+ if (index === segments.length - 1) {
1960
+ const resolved = navigationItem(document);
1961
+ item.title = resolved.title;
1962
+ item.path = resolved.path;
1963
+ if (resolved.locale !== void 0) item.locale = resolved.locale;
1964
+ else delete item.locale;
1965
+ item.documentId = resolved.documentId;
1966
+ orderByItem.set(item, navigationOrder(document));
1967
+ }
1968
+ parent = item;
1969
+ }
1970
+ }
1971
+ sortNavigation(roots, orderByItem);
1972
+ return roots.map((item) => DocumentNavigationItem.make(item));
1973
+ }
1974
+ function navigationItem(document) {
1975
+ const navigation = document.data.navigation;
1976
+ const configuredTitle = Schema.is(Schema.JsonObject)(navigation) ? navigation.title : void 0;
1977
+ return Object.assign({
1978
+ children: [],
1979
+ documentId: document.id,
1980
+ key: document.key,
1981
+ path: document.path,
1982
+ title: Schema.is(Schema.String)(configuredTitle) && configuredTitle.trim() ? configuredTitle.trim() : document.title
1983
+ }, document.locale === void 0 ? void 0 : { locale: document.locale });
1984
+ }
1985
+ function navigationOrder(document) {
1986
+ const navigation = document.data.navigation;
1987
+ const order = (Schema.is(Schema.JsonObject)(navigation) ? navigation.order : void 0) ?? document.data.order;
1988
+ return Schema.is(Schema.Finite)(order) ? order : Number.POSITIVE_INFINITY;
1989
+ }
1990
+ function sortNavigation(items, orderByItem) {
1991
+ items.sort((left, right) => {
1992
+ return (orderByItem.get(left) ?? Number.POSITIVE_INFINITY) - (orderByItem.get(right) ?? Number.POSITIVE_INFINITY) || left.title.localeCompare(right.title);
1993
+ });
1994
+ items.forEach((item) => sortNavigation(item.children, orderByItem));
1995
+ }
1996
+ function documentField(document, field) {
1997
+ switch (field) {
1998
+ case "collection": return document.collection;
1999
+ case "description": return document.description;
2000
+ case "id": return document.id;
2001
+ case "key": return document.key;
2002
+ case "locale": return document.locale;
2003
+ case "path": return document.path;
2004
+ case "source.path": return document.source.path;
2005
+ case "title": return document.title;
2006
+ default: return nestedValue(document.data, field.startsWith("data.") ? field.slice(5) : field);
2007
+ }
2008
+ }
2009
+ function nestedValue(value, path) {
2010
+ return path.split(".").reduce((current, segment) => {
2011
+ if (!Schema.is(Schema.JsonObject)(current)) return void 0;
2012
+ return current[segment];
2013
+ }, value);
2014
+ }
2015
+ function matchesValue(actual, expected) {
2016
+ const expectedValues = Array.isArray(expected) ? expected : [expected];
2017
+ return (Array.isArray(actual) ? actual : [actual]).some((value) => Schema.is(DocumentQueryScalar)(value) && expectedValues.includes(value));
2018
+ }
2019
+ function sortDocuments(documents, sorts) {
2020
+ return documents.toSorted((left, right) => {
2021
+ for (const sort of sorts) {
2022
+ const comparison = compareValues(documentField(left, sort.field), documentField(right, sort.field));
2023
+ if (comparison !== 0) return sort.direction === "desc" ? -comparison : comparison;
2024
+ }
2025
+ return 0;
2026
+ });
2027
+ }
2028
+ function compareValues(left, right) {
2029
+ if (left === right) return 0;
2030
+ if (left === void 0 || left === null) return 1;
2031
+ if (right === void 0 || right === null) return -1;
2032
+ if (Schema.is(Schema.Finite)(left) && Schema.is(Schema.Finite)(right)) return left - right;
2033
+ const leftText = Schema.is(Schema.String)(left) ? left : JSON.stringify(left) ?? "";
2034
+ const rightText = Schema.is(Schema.String)(right) ? right : JSON.stringify(right) ?? "";
2035
+ return leftText.localeCompare(rightText);
2036
+ }
2037
+ function titleFromSegment(segment) {
2038
+ return segment.replace(/[-_]+/gu, " ").replace(/^./u, (character) => character.toUpperCase());
2039
+ }
2040
+
2041
+ //#endregion
2042
+ //#region ../server/src/internal/documents/documents.ts
2043
+ const isDocumentGenerationConflict = Schema.is(DocumentGenerationConflict);
2044
+ const isDocumentMediaUnavailable = Schema.is(DocumentMediaUnavailable);
2045
+ const CONTENT_MUTATION_BUFFER_CAPACITY = 16;
2046
+ var Documents = class extends Context.Service()("@internal/server/Documents") {};
2047
+ var DocumentPreviews = class extends Context.Service()("@internal/server/DocumentPreviews") {};
2048
+ var PublishedDocuments = class extends Context.Service()("@internal/server/PublishedDocuments") {};
2049
+ var DocumentMaintenance = class extends Context.Service()("@internal/server/internal/DocumentMaintenance") {};
2050
+ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, options) {
2051
+ const repository = yield* DocumentStore;
2052
+ const accessControl = yield* AccessControl;
2053
+ const crypto = yield* Crypto.Crypto;
2054
+ const engine = yield* createDocumentEngine(configuration, options);
2055
+ const state = yield* Ref.make(yield* loadSynchronizedState(repository, engine));
2056
+ const semaphore = yield* Semaphore.make(1);
2057
+ const mutationEvents = yield* Effect.acquireRelease(PubSub.sliding(CONTENT_MUTATION_BUFFER_CAPACITY), PubSub.shutdown);
2058
+ const dependencies = {
2059
+ engine,
2060
+ configuration,
2061
+ repository,
2062
+ components: options.components
2063
+ };
2064
+ const refreshUnlocked = Effect.fn("Documents.refreshUnlocked")(function* (force = false) {
2065
+ const current = yield* Ref.get(state);
2066
+ const snapshot = force ? yield* loadRequired(repository) : yield* repository.load(current.snapshot.generation);
2067
+ if (!snapshot || !generationIsAfter(snapshot.generation, current.snapshot.generation)) return current.snapshot.generation;
2068
+ const refreshed = yield* compileDocumentState(engine, snapshot, current);
2069
+ yield* Ref.set(state, refreshed);
2070
+ return refreshed.snapshot.generation;
2071
+ });
2072
+ const executeUnlocked = Effect.fn("Documents.execute")(function* (command, activity) {
2073
+ return yield* Effect.gen(function* () {
2074
+ yield* refreshUnlocked();
2075
+ const current = yield* Ref.get(state);
2076
+ const outcome = yield* commitDocumentMutation(accessControl, dependencies, current, command, activity);
2077
+ if (outcome.kind === "unchanged") return outcome.result;
2078
+ yield* Ref.set(state, outcome.next);
2079
+ yield* PubSub.publish(mutationEvents, outcome.result);
2080
+ return outcome.result;
2081
+ }).pipe(Effect.tapError((error) => isDocumentGenerationConflict(error) ? refreshUnlocked(true) : Effect.void), Effect.retry({
2082
+ schedule: Schedule.recurs(3),
2083
+ while: isDocumentGenerationConflict
2084
+ }));
2085
+ });
2086
+ const runMutation = Effect.fn("Documents.runMutation")(function* (command, activity, reason) {
2087
+ const resolvedActivity = enrichActivity(activity, reason);
2088
+ return yield* semaphore.withPermits(1)(executeUnlocked(command, resolvedActivity));
2089
+ });
2090
+ const authored = semaphore.withPermits(1)(Effect.gen(function* () {
2091
+ yield* refreshUnlocked();
2092
+ return (yield* Ref.get(state)).authored;
2093
+ })).pipe(Effect.withSpan("Documents.authored"));
2094
+ const browse = Effect.fn("Documents.browse")((query) => semaphore.withPermits(1)(Effect.gen(function* () {
2095
+ yield* refreshUnlocked();
2096
+ return authoringEntries(yield* Ref.get(state), query);
2097
+ })));
2098
+ const create = Effect.fn("Documents.create")(function* (input, activity) {
2099
+ const collection = configuration.types.find((candidate) => candidate.collection === input.collection);
2100
+ if (collection && !collection.canCreate) return yield* new DocumentValidationFailed({ issues: [{
2101
+ code: "document.invalid",
2102
+ field: "collection",
2103
+ message: `Document collection "${input.collection}" does not allow creation.`,
2104
+ severity: "error"
2105
+ }] });
2106
+ const plan = yield* Effect.fromResult(planDocumentCreation(configuration, input));
2107
+ const source = yield* createInitialDocumentSource(plan);
2108
+ const documentId = DocumentId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
2109
+ return yield* runMutation({
2110
+ action: "document.create",
2111
+ documentId,
2112
+ markdown: source.markdown,
2113
+ sourcePath: plan.sourcePath
2114
+ }, activity, input.reason);
2115
+ });
2116
+ const duplicate = Effect.fn("Documents.duplicate")((input, activity) => runMutation({
2117
+ action: "document.duplicate",
2118
+ input,
2119
+ nextDocumentId: crypto.randomUUIDv4.pipe(Effect.orDie, Effect.map((value) => DocumentId.make(value)))
2120
+ }, activity, input.reason));
2121
+ const unpublish = Effect.fn("Documents.unpublish")((input, activity) => runMutation({
2122
+ action: "document.unpublish",
2123
+ input
2124
+ }, activity, input.reason));
2125
+ const move = Effect.fn("Documents.move")((input, activity) => runMutation({
2126
+ action: "document.move",
2127
+ input
2128
+ }, activity, input.reason));
2129
+ const subscribeMutations = Effect.gen(function* () {
2130
+ const subscription = yield* PubSub.subscribe(mutationEvents);
2131
+ const baseline = yield* semaphore.withPermits(1)(Ref.get(state).pipe(Effect.map((current) => current.snapshot.generation)));
2132
+ return {
2133
+ baseline,
2134
+ mutations: Stream.fromSubscription(subscription).pipe(Stream.filter((mutation) => generationIsAfter(mutation.generation, baseline)))
2135
+ };
2136
+ }).pipe(Effect.withSpan("Documents.subscribeMutations"));
2137
+ const publish = Effect.fn("Documents.publish")((input, activity) => runMutation({
2138
+ action: "document.publish",
2139
+ input
2140
+ }, activity, input.reason));
2141
+ const published = semaphore.withPermits(1)(Effect.gen(function* () {
2142
+ yield* refreshUnlocked();
2143
+ return (yield* Ref.get(state)).published;
2144
+ })).pipe(Effect.withSpan("Documents.published"));
2145
+ const read = Effect.fn("Documents.read")((documentId) => semaphore.withPermits(1)(Effect.gen(function* () {
2146
+ yield* refreshUnlocked();
2147
+ return (yield* Ref.get(state)).activeById.get(documentId) || (yield* new DocumentNotFound({ documentId }));
2148
+ })));
2149
+ const refresh = semaphore.withPermits(1)(refreshUnlocked()).pipe(Effect.withSpan("Documents.refresh"));
2150
+ const remove = Effect.fn("Documents.remove")((input, activity) => runMutation({
2151
+ action: "document.remove",
2152
+ input
2153
+ }, activity, input.reason));
2154
+ const restore = Effect.fn("Documents.restore")((input, activity) => runMutation({
2155
+ action: "document.restore",
2156
+ input
2157
+ }, activity, input.reason));
2158
+ const trash = semaphore.withPermits(1)(Effect.gen(function* () {
2159
+ yield* refreshUnlocked();
2160
+ return deletedEntries(yield* Ref.get(state));
2161
+ })).pipe(Effect.withSpan("Documents.trash"));
2162
+ const undelete = Effect.fn("Documents.undelete")((input, activity) => runMutation({
2163
+ action: "document.undelete",
2164
+ input
2165
+ }, activity, input.reason));
2166
+ const update = Effect.fn("Documents.update")((input, activity) => runMutation({
2167
+ action: "document.update",
2168
+ input
2169
+ }, activity, input.reason));
2170
+ const validate = Effect.fn("Documents.validate")((input) => semaphore.withPermits(1)(Effect.gen(function* () {
2171
+ yield* refreshUnlocked();
2172
+ return yield* validateDocumentUpdate(engine, yield* Ref.get(state), input);
2173
+ })));
2174
+ const history = documentHistoryCapability(accessControl, repository);
2175
+ const delivery = documentDelivery(configuration, published);
2176
+ const document = (documentId) => ({
2177
+ documentId,
2178
+ kind: "document"
2179
+ });
2180
+ const requireThen = (context, action, resource, effect) => accessControl.require(context, {
2181
+ action,
2182
+ resource
2183
+ }).pipe(Effect.andThen(effect));
2184
+ const requireContentTransfer = (context) => accessControl.require(context, {
2185
+ action: "document.transfer",
2186
+ resource: { kind: "catalog" }
2187
+ });
2188
+ const contentSnapshot = (context) => requireContentTransfer(context).pipe(Effect.andThen(semaphore.withPermits(1)(Effect.gen(function* () {
2189
+ yield* refreshUnlocked();
2190
+ const current = yield* Ref.get(state);
2191
+ return ContentSnapshot.make({
2192
+ version: 1,
2193
+ sources: current.snapshot.active.map((source) => ({
2194
+ sourcePath: source.sourcePath,
2195
+ markdown: source.markdown,
2196
+ published: source.publishedVersion === source.version
2197
+ })).sort((left, right) => left.sourcePath.localeCompare(right.sourcePath))
2198
+ });
2199
+ }))));
2200
+ const contentPlan = (context, input) => requireContentTransfer(context).pipe(Effect.andThen(semaphore.withPermits(1)(Effect.gen(function* () {
2201
+ yield* refreshUnlocked();
2202
+ return (yield* planContentTransfer(engine, yield* Ref.get(state), input, crypto)).plan;
2203
+ }))));
2204
+ const contentApply = (context, input) => requireContentTransfer(context).pipe(Effect.andThen(semaphore.withPermits(1)(Effect.gen(function* () {
2205
+ yield* refreshUnlocked();
2206
+ const current = yield* Ref.get(state);
2207
+ if (current.snapshot.generation !== input.expectedGeneration) return yield* new ContentTransferConflict({ message: "Content changed since the plan. Run content plan again." });
2208
+ const planned = yield* planContentTransfer(engine, current, input, crypto);
2209
+ if (planned.plan.entries.some((entry) => entry.action === "conflict")) return yield* new ContentTransferConflict({ message: "Destination content differs. Use --overwrite after reviewing the plan." });
2210
+ if (!planned.plan.valid) return yield* new DocumentValidationFailed({ issues: planned.plan.issues });
2211
+ if (planned.commitPlans.length === 0) return ContentApplyResult.make({
2212
+ ...planned.counts,
2213
+ generation: current.snapshot.generation
2214
+ });
2215
+ const authored = yield* compileValidCatalog(engine, planned.authoredSources, current.authored);
2216
+ const publishedCatalog = yield* compileValidCatalog(engine, planned.publishedSources, current.published);
2217
+ const [first, ...additionalPlans] = planned.commitPlans;
2218
+ if (!first) throw new Error("Content import requires a commit plan.");
2219
+ const committed = yield* commitApprovedDocumentPlans({
2220
+ repository,
2221
+ current,
2222
+ plans: [first, ...additionalPlans],
2223
+ authored,
2224
+ published: publishedCatalog,
2225
+ activity: requestActivity$1(context, "CMS content import")
2226
+ }).pipe(Effect.mapError((error) => isDocumentGenerationConflict(error) ? new ContentTransferConflict({ message: "Content changed since the plan. Run content plan again." }) : error));
2227
+ yield* Ref.set(state, yield* compileDocumentState(engine, committed.snapshot, current));
2228
+ yield* PubSub.publish(mutationEvents, committed.mutation);
2229
+ return ContentApplyResult.make({
2230
+ ...planned.counts,
2231
+ generation: committed.snapshot.generation
2232
+ });
2233
+ }))));
2234
+ const documents = Documents.of({
2235
+ contentSnapshot,
2236
+ contentPlan,
2237
+ contentApply,
2238
+ browse: (context, query) => requireThen(context, "document.browse", query.collection ? {
2239
+ collection: query.collection,
2240
+ kind: "collection",
2241
+ locale: query.locale
2242
+ } : { kind: "catalog" }, () => browse(query)),
2243
+ create: (context, input) => requireThen(context, "document.create", {
2244
+ collection: input.collection,
2245
+ kind: "collection",
2246
+ locale: input.locale
2247
+ }, () => create(input, requestActivity$1(context, input.reason))),
2248
+ historyCompare: history.compare,
2249
+ historyList: history.list,
2250
+ historyRead: history.read,
2251
+ duplicate: (context, input) => requireThen(context, "document.duplicate", document(input.documentId), () => duplicate(input, requestActivity$1(context, input.reason))),
2252
+ unpublish: (context, input) => requireThen(context, "document.unpublish", document(input.documentId), () => unpublish(input, requestActivity$1(context, input.reason))),
2253
+ move: (context, input) => requireThen(context, "document.move", document(input.documentId), () => move(input, requestActivity$1(context, input.reason))),
2254
+ publish: (context, input) => requireThen(context, "document.publish", document(input.documentId), () => publish(input, requestActivity$1(context, input.reason))),
2255
+ read: (context, documentId) => requireThen(context, "document.read", document(documentId), () => read(documentId)),
2256
+ remove: (context, input) => requireThen(context, "document.remove", document(input.documentId), () => remove(input, requestActivity$1(context, input.reason))),
2257
+ restore: (context, input) => requireThen(context, "document.restore", document(input.documentId), () => restore(input, requestActivity$1(context, input.reason))),
2258
+ trash: (context) => requireThen(context, "document.trash", { kind: "catalog" }, () => trash),
2259
+ undelete: (context, input) => requireThen(context, "document.undelete", document(input.documentId), () => undelete(input, requestActivity$1(context, input.reason))),
2260
+ update: (context, input) => requireThen(context, "document.update", document(input.documentId), () => update(input, requestActivity$1(context, input.reason))),
2261
+ validate: (context, input) => requireThen(context, "document.validate", document(input.documentId), () => validate(input))
2262
+ });
2263
+ const draftDelivery = documentDelivery(configuration, authored);
2264
+ const previews = DocumentPreviews.of({
2265
+ read: (context, lookup) => requireThen(context, "document.browse", { kind: "catalog" }, () => draftDelivery.read(lookup)),
2266
+ query: (context, query) => requireThen(context, "document.browse", { kind: "catalog" }, () => draftDelivery.query(query)),
2267
+ navigation: (context, query) => requireThen(context, "document.browse", { kind: "catalog" }, () => draftDelivery.navigation(query)),
2268
+ surround: (context, lookup, query) => requireThen(context, "document.browse", { kind: "catalog" }, () => draftDelivery.surround(lookup, query))
2269
+ });
2270
+ const publishedService = PublishedDocuments.of({
2271
+ navigation: delivery.navigation,
2272
+ query: delivery.query,
2273
+ read: delivery.read,
2274
+ snapshot: published.pipe(Effect.map((catalog) => catalog.documents)),
2275
+ surround: delivery.surround,
2276
+ diagnostics: (context) => accessControl.require(context, {
2277
+ action: "document.browse",
2278
+ resource: { kind: "catalog" }
2279
+ }).pipe(Effect.andThen(delivery.diagnostics))
2280
+ });
2281
+ return {
2282
+ documents,
2283
+ maintenance: DocumentMaintenance.of({
2284
+ mediaReferences: (documentId) => semaphore.withPermits(1)(Effect.gen(function* () {
2285
+ yield* refreshUnlocked();
2286
+ const current = yield* Ref.get(state);
2287
+ return projectMediaReferences(current.authored, current.published, documentId);
2288
+ })),
2289
+ publish,
2290
+ read,
2291
+ refresh,
2292
+ subscribeMutations,
2293
+ validate
2294
+ }),
2295
+ previews,
2296
+ publishedDocuments: publishedService
2297
+ };
2298
+ });
2299
+ function documentsLayer(configuration, options) {
2300
+ return Layer.effectContext(makeDocuments(configuration, options).pipe(Effect.map(({ documents, maintenance, previews, publishedDocuments }) => Context.empty().pipe(Context.add(Documents, documents), Context.add(DocumentMaintenance, maintenance), Context.add(DocumentPreviews, previews), Context.add(PublishedDocuments, publishedDocuments)))));
2301
+ }
2302
+ function loadRequired(repository) {
2303
+ return repository.load().pipe(Effect.filterOrFail((snapshot) => snapshot !== void 0, () => new DocumentPersistenceError({
2304
+ cause: /* @__PURE__ */ new Error("DocumentStore returned no initial snapshot."),
2305
+ operation: "DocumentStore.load"
2306
+ })));
2307
+ }
2308
+ function loadSynchronizedState(repository, engine) {
2309
+ return Effect.gen(function* () {
2310
+ const snapshot = yield* loadRequired(repository);
2311
+ const state = yield* compileDocumentState(engine, snapshot);
2312
+ yield* repository.synchronizeMediaReferences({
2313
+ expectedGeneration: snapshot.generation,
2314
+ references: projectMediaReferences(state.authored, state.published)
2315
+ });
2316
+ return state;
2317
+ }).pipe(Effect.retry({
2318
+ schedule: Schedule.recurs(3),
2319
+ while: isDocumentGenerationConflict
2320
+ }), Effect.mapError((error) => isDocumentGenerationConflict(error) || isDocumentMediaUnavailable(error) ? new DocumentPersistenceError({
2321
+ cause: error,
2322
+ operation: "Documents.synchronizeMediaReferences"
2323
+ }) : error));
2324
+ }
2325
+ function generationIsAfter(candidate, current) {
2326
+ return BigInt(candidate) > BigInt(current);
2327
+ }
2328
+ function enrichActivity(activity, reason) {
2329
+ return ActivityContext.make(Object.assign({}, activity, reason === void 0 ? void 0 : { reason }));
2330
+ }
2331
+ function requestActivity$1(context, reason) {
2332
+ return ActivityContext.make(Object.assign({
2333
+ actor: context.actor,
2334
+ requestId: context.requestId
2335
+ }, reason === void 0 ? void 0 : { reason }));
2336
+ }
2337
+
2338
+ //#endregion
2339
+ //#region ../server/src/internal/seo/seo-store.ts
2340
+ var SeoPersistenceError = class extends Schema.TaggedError()("Seo.PersistenceError", {
2341
+ operation: Schema.NonEmptyString,
2342
+ cause: Schema.Defect()
2343
+ }) {};
2344
+ var SeoStore = class extends Context.Service()("@internal/server/SeoStore") {};
2345
+
2346
+ //#endregion
2347
+ //#region ../server/src/internal/seo/seo.ts
2348
+ var Seo = class extends Context.Service()("@internal/server/Seo") {};
2349
+ var PublishedSeo = class extends Context.Service()("@internal/server/PublishedSeo") {};
2350
+ var PreviewSeo = class extends Context.Service()("@internal/server/PreviewSeo") {};
2351
+ var SeoDelivery = class extends Context.Service()("@internal/server/SeoDelivery") {};
2352
+ function makeDefaultSeoOptions(baseUrl) {
2353
+ const url = new URL(baseUrl);
2354
+ return {
2355
+ capabilities: SeoCapabilities.make({
2356
+ generatedSocialImageTemplateIds: [],
2357
+ identityTypes: [
2358
+ "Person",
2359
+ "Organization",
2360
+ "LocalBusiness"
2361
+ ],
2362
+ structuredDataTypes: ["WebPage", "Article"]
2363
+ }),
2364
+ defaults: GlobalSeoSettings.make({
2365
+ canonicalOrigin: SeoCanonicalOrigin.make(url.origin),
2366
+ follow: true,
2367
+ index: true,
2368
+ siteName: url.hostname
2369
+ })
2370
+ };
2371
+ }
2372
+ const seoLayer = (options) => Layer.effectContext(Effect.gen(function* () {
2373
+ const accessControl = yield* AccessControl;
2374
+ const store = yield* SeoStore;
2375
+ const load = Effect.fn("Seo.load")(function* () {
2376
+ return (yield* store.load) ?? (yield* store.initialize(options.defaults));
2377
+ });
2378
+ const state = Effect.fn("Seo.state")(function* () {
2379
+ return makeState(yield* load(), options.capabilities);
2380
+ });
2381
+ const requireAdministrator = (context, action) => accessControl.require(context, {
2382
+ action,
2383
+ resource: { kind: "seo" }
2384
+ });
2385
+ const read = Effect.fn("Seo.read")(function* (context) {
2386
+ yield* requireAdministrator(context, "seo.read");
2387
+ return yield* state();
2388
+ });
2389
+ const save = Effect.fn("Seo.save")(function* (context, input) {
2390
+ yield* requireAdministrator(context, "seo.save");
2391
+ const snapshot = yield* store.save({
2392
+ activity: requestActivity(context, input.reason),
2393
+ expectedVersion: input.expectedVersion,
2394
+ settings: input.settings
2395
+ });
2396
+ return SeoMutationResult.make({
2397
+ action: "seo.save",
2398
+ state: makeState(snapshot, options.capabilities)
2399
+ });
2400
+ });
2401
+ const publish = Effect.fn("Seo.publish")(function* (context, input) {
2402
+ yield* requireAdministrator(context, "seo.publish");
2403
+ const snapshot = yield* load();
2404
+ const confirmationChanges = productionChanges(snapshot.published?.settings ?? options.defaults, snapshot.working.settings);
2405
+ if (confirmationChanges.length > 0 && input.confirmedSiteWideChange !== true) return yield* new SeoConfirmationRequired({ changes: confirmationChanges });
2406
+ const published = yield* store.publish({
2407
+ activity: requestActivity(context, input.reason),
2408
+ expectedVersion: input.expectedVersion
2409
+ });
2410
+ return SeoMutationResult.make({
2411
+ action: "seo.publish",
2412
+ state: makeState(published, options.capabilities)
2413
+ });
2414
+ });
2415
+ const seo = Seo.of({
2416
+ publish,
2417
+ read,
2418
+ save
2419
+ });
2420
+ const publishedSeo = PublishedSeo.of({ read: load().pipe(Effect.map((snapshot) => SeoSnapshot.make({
2421
+ capabilities: options.capabilities,
2422
+ revision: snapshot.published ?? snapshot.working
2423
+ }))) });
2424
+ const previewSeo = PreviewSeo.of({ read: load().pipe(Effect.map((snapshot) => SeoSnapshot.make({
2425
+ capabilities: options.capabilities,
2426
+ revision: snapshot.working
2427
+ }))) });
2428
+ return Context.empty().pipe(Context.add(Seo, seo), Context.add(PublishedSeo, publishedSeo), Context.add(PreviewSeo, previewSeo));
2429
+ }));
2430
+ const seoDeliveryLayer = Layer.effect(SeoDelivery, Effect.gen(function* () {
2431
+ const previews = yield* PreviewSeo;
2432
+ const published = yield* PublishedSeo;
2433
+ const documents = yield* PublishedDocuments;
2434
+ const resolveWith = (document, snapshot) => CompiledDocument.make(Object.assign({}, document, { resolvedSeo: resolveDocumentSeo({
2435
+ capabilities: snapshot.capabilities,
2436
+ document,
2437
+ global: snapshot.revision.settings
2438
+ }) }));
2439
+ const publishedDocument = Effect.fn("SeoDelivery.publishedDocument")((document) => published.read.pipe(Effect.map((snapshot) => resolveWith(document, snapshot))));
2440
+ const previewDocument = Effect.fn("SeoDelivery.previewDocument")((document) => previews.read.pipe(Effect.map((snapshot) => resolveWith(document, snapshot))));
2441
+ const sitemap = Effect.gen(function* () {
2442
+ const snapshot = yield* published.read;
2443
+ const resolved = (yield* documents.snapshot).flatMap((document) => {
2444
+ const resolvedDocument = resolveWith(document, snapshot);
2445
+ return resolvedDocument.resolvedSeo?.index === true && resolvedDocument.source.publishedAt !== void 0 ? [resolvedDocument] : [];
2446
+ });
2447
+ const localesByKey = /* @__PURE__ */ new Map();
2448
+ for (const document of resolved) {
2449
+ if (document.locale === void 0) continue;
2450
+ const alternatives = localesByKey.get(document.key) ?? [];
2451
+ alternatives.push({
2452
+ locale: document.locale,
2453
+ loc: document.path
2454
+ });
2455
+ localesByKey.set(document.key, alternatives);
2456
+ }
2457
+ return resolved.flatMap((document) => {
2458
+ const publishedAt = document.source.publishedAt;
2459
+ if (publishedAt === void 0) return [];
2460
+ const alternatives = localesByKey.get(document.key);
2461
+ const entry = {
2462
+ loc: document.path,
2463
+ lastmod: publishedAt
2464
+ };
2465
+ return [SitemapEntry.make(alternatives && alternatives.length > 1 ? {
2466
+ ...entry,
2467
+ alternatives
2468
+ } : entry)];
2469
+ }).sort((left, right) => left.loc.localeCompare(right.loc));
2470
+ }).pipe(Effect.withSpan("SeoDelivery.sitemap"));
2471
+ return SeoDelivery.of({
2472
+ previewDocument,
2473
+ publishedDocument,
2474
+ sitemap
2475
+ });
2476
+ }));
2477
+ function makeState(snapshot, capabilities) {
2478
+ return SeoState.make(snapshot.published === void 0 ? {
2479
+ capabilities,
2480
+ working: snapshot.working
2481
+ } : {
2482
+ capabilities,
2483
+ published: snapshot.published,
2484
+ working: snapshot.working
2485
+ });
2486
+ }
2487
+ function requestActivity(context, reason) {
2488
+ return {
2489
+ actor: context.actor,
2490
+ requestId: context.requestId,
2491
+ ...reason === void 0 ? void 0 : { reason }
2492
+ };
2493
+ }
2494
+ function productionChanges(previous, next) {
2495
+ const changes = [];
2496
+ if (previous.canonicalOrigin !== next.canonicalOrigin) changes.push("canonicalOrigin");
2497
+ if (previous.index !== next.index || previous.follow !== next.follow) changes.push("indexability");
2498
+ return changes;
2499
+ }
2500
+
2501
+ //#endregion
2502
+ //#region ../server/src/internal/media/media-object-store.ts
2503
+ const mediaObjectPrefix = "media/";
2504
+ var MediaObjectStore = class extends Context.Service()("@internal/server/MediaObjectStore") {};
2505
+
2506
+ //#endregion
2507
+ //#region ../server/src/internal/media/media-failures.ts
2508
+ var MediaPersistenceError = class extends Schema.TaggedError()("Media.PersistenceError", {
2509
+ operation: Schema.NonEmptyString,
2510
+ cause: Schema.Defect()
2511
+ }) {};
2512
+ var MediaStorageError = class extends Schema.TaggedError()("Media.StorageError", {
2513
+ operation: Schema.NonEmptyString,
2514
+ cause: Schema.Defect()
2515
+ }) {};
2516
+
2517
+ //#endregion
2518
+ //#region ../s3/src/internal/client.ts
2519
+ var S3RequestError = class extends Schema.TaggedError()("S3.RequestError", {
2520
+ cause: Schema.Defect(),
2521
+ operation: Schema.NonEmptyString
2522
+ }) {};
2523
+ function openAwsS3MediaObjectClient(options) {
2524
+ const credentials = options.credentials ? {
2525
+ accessKeyId: options.credentials.accessKeyId,
2526
+ secretAccessKey: Redacted.value(options.credentials.secretAccessKey),
2527
+ sessionToken: options.credentials.sessionToken ? Redacted.value(options.credentials.sessionToken) : void 0
2528
+ } : void 0;
2529
+ return makeAwsS3MediaObjectClient(new S3Client({
2530
+ credentials,
2531
+ endpoint: options.endpoint,
2532
+ forcePathStyle: options.forcePathStyle,
2533
+ region: options.region,
2534
+ requestChecksumCalculation: "WHEN_REQUIRED"
2535
+ }));
2536
+ }
2537
+ function makeAwsS3MediaObjectClient(client) {
2538
+ const request = (operation, send) => Effect.tryPromise({
2539
+ catch: (cause) => new S3RequestError({
2540
+ cause,
2541
+ operation
2542
+ }),
2543
+ try: send
2544
+ });
2545
+ return {
2546
+ close: Effect.sync(() => client.destroy()),
2547
+ copyObject: (input) => request("MediaObjectStore.S3.copyObject", (signal) => client.send(new CopyObjectCommand({
2548
+ Bucket: input.bucket,
2549
+ CacheControl: input.cacheControl,
2550
+ ContentType: input.contentType,
2551
+ CopySource: input.copySource,
2552
+ Key: input.key,
2553
+ MetadataDirective: "REPLACE"
2554
+ }), { abortSignal: signal })).pipe(Effect.asVoid),
2555
+ createBucket: (input) => request("MediaObjectStore.S3.createBucket", (signal) => client.send(new CreateBucketCommand({
2556
+ Bucket: input.bucket,
2557
+ CreateBucketConfiguration: input.locationConstraint ? { LocationConstraint: input.locationConstraint } : void 0
2558
+ }), { abortSignal: signal })).pipe(Effect.asVoid),
2559
+ deleteObject: (input) => request("MediaObjectStore.S3.deleteObject", (signal) => client.send(new DeleteObjectCommand({
2560
+ Bucket: input.bucket,
2561
+ Key: input.key
2562
+ }), { abortSignal: signal })).pipe(Effect.asVoid),
2563
+ headBucket: (input) => request("MediaObjectStore.S3.headBucket", (signal) => client.send(new HeadBucketCommand({ Bucket: input.bucket }), { abortSignal: signal })).pipe(Effect.asVoid),
2564
+ headObject: (input) => request("MediaObjectStore.S3.headObject", (signal) => client.send(new HeadObjectCommand({
2565
+ Bucket: input.bucket,
2566
+ Key: input.key
2567
+ }), { abortSignal: signal })).pipe(Effect.map((output) => ({
2568
+ contentLength: output.ContentLength,
2569
+ contentType: output.ContentType
2570
+ }))),
2571
+ listObjects: (input) => request("MediaObjectStore.S3.listObjects", (signal) => client.send(new ListObjectsV2Command({
2572
+ Bucket: input.bucket,
2573
+ ContinuationToken: input.continuationToken,
2574
+ Prefix: input.prefix
2575
+ }), { abortSignal: signal })).pipe(Effect.map((output) => ({
2576
+ isTruncated: output.IsTruncated === true,
2577
+ nextContinuationToken: output.NextContinuationToken,
2578
+ objects: (output.Contents ?? []).map((object) => ({
2579
+ key: object.Key,
2580
+ size: object.Size
2581
+ }))
2582
+ }))),
2583
+ putObject: (input) => request("MediaObjectStore.S3.putObject", (signal) => client.send(new PutObjectCommand({
2584
+ Body: NodeStream.toReadableNever(input.body),
2585
+ Bucket: input.bucket,
2586
+ CacheControl: input.cacheControl,
2587
+ ContentLength: input.contentLength,
2588
+ ContentType: input.contentType,
2589
+ Key: input.key
2590
+ }), { abortSignal: signal })).pipe(Effect.asVoid)
2591
+ };
2592
+ }
2593
+
2594
+ //#endregion
2595
+ //#region ../s3/src/internal/media-object-store.ts
2596
+ const replaceableMediaCacheControl = "public, max-age=0, must-revalidate";
2597
+ const isWriteBodyError = Schema.is(Schema.Union([MediaInvalid, MediaStorageError]));
2598
+ function s3MediaObjectStoreLayer(options) {
2599
+ return Layer.effect(MediaObjectStore, resolveOptions(options).pipe(Effect.flatMap((resolved) => Effect.acquireRelease(Effect.sync(() => openAwsS3MediaObjectClient(resolved)), (client) => client.close).pipe(Effect.tap((client) => provisionBucket(resolved, client)), Effect.map((client) => makeStore(resolved, client))))));
2600
+ }
2601
+ function makeStore(options, client) {
2602
+ return {
2603
+ list: listObjects(options.bucket, client),
2604
+ promote: (input) => promoteObject(options.bucket, input, client),
2605
+ put: (input) => putObject(options.bucket, input, client),
2606
+ remove: (objectKey) => providerRequest("MediaObjectStore.S3.remove", client.deleteObject({
2607
+ bucket: options.bucket,
2608
+ key: objectKey
2609
+ })),
2610
+ stat: (objectKey) => statObject(options.bucket, objectKey, client)
2611
+ };
2612
+ }
2613
+ function promoteObject(bucket, input, client) {
2614
+ if (input.byteSize > 5e9) return Effect.fail(storageError("MediaObjectStore.S3.promote", "The object exceeds the S3 single-copy size limit."));
2615
+ return statObject(bucket, input.sourceObjectKey, client).pipe(Effect.flatMap((source) => Option.isSome(source) && source.value.byteSize === input.byteSize && source.value.mimeType === input.mimeType ? Effect.void : Effect.fail(storageError("MediaObjectStore.S3.promote.source", "The staged object metadata does not match its intent."))), Effect.flatMap(() => providerRequest("MediaObjectStore.S3.promote.copy", client.copyObject({
2616
+ bucket,
2617
+ cacheControl: replaceableMediaCacheControl,
2618
+ contentType: input.mimeType,
2619
+ copySource: copySource(bucket, input.sourceObjectKey),
2620
+ key: input.destinationObjectKey
2621
+ }))), Effect.flatMap(() => statObject(bucket, input.destinationObjectKey, client)), Effect.flatMap((destination) => Option.isSome(destination) && destination.value.byteSize === input.byteSize && destination.value.mimeType === input.mimeType ? Effect.succeed(destination.value) : Effect.fail(storageError("MediaObjectStore.S3.promote.verify", "The promoted object metadata does not match its intent."))));
2622
+ }
2623
+ function copySource(bucket, objectKey) {
2624
+ return `/${encodeURIComponent(bucket)}/${objectKey.split("/").map(encodeURIComponent).join("/")}`;
2625
+ }
2626
+ function putObject(bucket, input, client) {
2627
+ return Effect.gen(function* () {
2628
+ const body = yield* validatedWriteBody(input);
2629
+ yield* client.putObject({
2630
+ body,
2631
+ bucket,
2632
+ cacheControl: replaceableMediaCacheControl,
2633
+ contentLength: input.byteSize,
2634
+ contentType: input.mimeType,
2635
+ key: input.objectKey
2636
+ }).pipe(Effect.catch((failure) => isWriteBodyError(failure.cause) ? Effect.fail(failure.cause) : Effect.fail(providerError("MediaObjectStore.S3.put", failure))));
2637
+ return {
2638
+ byteSize: input.byteSize,
2639
+ mimeType: input.mimeType,
2640
+ objectKey: input.objectKey
2641
+ };
2642
+ });
2643
+ }
2644
+ const validatedWriteBody = Effect.fn("MediaObjectStore.S3.validateWriteBody")(function* (input) {
2645
+ const byteLength = yield* Ref.make(0);
2646
+ return input.body.pipe(Stream.mapEffect((chunk) => Effect.gen(function* () {
2647
+ if ((yield* Ref.updateAndGet(byteLength, (current) => current + chunk.byteLength)) > input.byteSize) return yield* storageError("MediaObjectStore.S3.put.body", "The object body exceeds its declared byte size.");
2648
+ return chunk;
2649
+ })), Stream.concat(Stream.fromEffect(Ref.get(byteLength).pipe(Effect.flatMap((actual) => actual === input.byteSize ? Effect.void : Effect.fail(storageError("MediaObjectStore.S3.put.body", "The object body does not match its declared byte size."))))).pipe(Stream.drain)));
2650
+ });
2651
+ function statObject(bucket, objectKey, client) {
2652
+ return client.headObject({
2653
+ bucket,
2654
+ key: objectKey
2655
+ }).pipe(Effect.matchEffect({
2656
+ onFailure: (failure) => isMissingObject(failure.cause) ? Effect.succeed(Option.none()) : Effect.fail(providerError("MediaObjectStore.S3.stat", failure)),
2657
+ onSuccess: (output) => decodeStoredObject("MediaObjectStore.S3.stat", objectKey, output.contentLength, output.contentType).pipe(Effect.asSome)
2658
+ }));
2659
+ }
2660
+ function listObjects(bucket, client) {
2661
+ return Stream.paginate({ seenTokens: /* @__PURE__ */ new Set() }, (state) => providerRequest("MediaObjectStore.S3.list", client.listObjects({
2662
+ bucket,
2663
+ continuationToken: state.continuationToken,
2664
+ prefix: mediaObjectPrefix
2665
+ })).pipe(Effect.flatMap((page) => Effect.gen(function* () {
2666
+ return [yield* Effect.forEach(page.objects, decodeListedObject), yield* nextListState(state, page)];
2667
+ }))));
2668
+ }
2669
+ function decodeListedObject(object) {
2670
+ if (object.key === void 0) return Effect.fail(storageError("MediaObjectStore.S3.list", "The provider returned object metadata without a key."));
2671
+ return decodeStoredObject("MediaObjectStore.S3.list", object.key, object.size);
2672
+ }
2673
+ function nextListState(state, page) {
2674
+ if (!page.isTruncated) return Effect.succeedNone;
2675
+ const token = page.nextContinuationToken;
2676
+ if (!token || token === state.continuationToken || state.seenTokens.has(token)) return Effect.fail(storageError("MediaObjectStore.S3.list", "The provider returned an invalid continuation token."));
2677
+ return Effect.succeedSome({
2678
+ continuationToken: token,
2679
+ seenTokens: /* @__PURE__ */ new Set([...state.seenTokens, token])
2680
+ });
2681
+ }
2682
+ function decodeStoredObject(operation, objectKey, byteSize, contentType) {
2683
+ const decodedObjectKey = Schema.decodeOption(MediaObjectKey)(objectKey);
2684
+ const decodedByteSize = Schema.decodeUnknownOption(MediaByteSize)(byteSize);
2685
+ if (Option.isNone(decodedObjectKey) || Option.isNone(decodedByteSize)) return Effect.fail(storageError(operation, "The provider returned invalid object metadata."));
2686
+ const decodedMimeType = Schema.decodeUnknownOption(ImageMimeType)(contentType);
2687
+ const storedObject = {
2688
+ byteSize: decodedByteSize.value,
2689
+ objectKey: decodedObjectKey.value
2690
+ };
2691
+ return Effect.succeed(Option.isSome(decodedMimeType) ? {
2692
+ ...storedObject,
2693
+ mimeType: decodedMimeType.value
2694
+ } : storedObject);
2695
+ }
2696
+ function providerRequest(operation, request) {
2697
+ return request.pipe(Effect.mapError((failure) => providerError(operation, failure)));
2698
+ }
2699
+ function provisionBucket(options, client) {
2700
+ if (!options.provisionBucket) return Effect.void;
2701
+ return client.headBucket({ bucket: options.bucket }).pipe(Effect.catch((failure) => isMissingBucket(failure.cause) ? createMissingBucket(options, client) : Effect.fail(providerError("MediaObjectStore.S3.bucket.head", failure))));
2702
+ }
2703
+ function createMissingBucket(options, client) {
2704
+ return client.createBucket({
2705
+ bucket: options.bucket,
2706
+ locationConstraint: options.endpoint || options.region === "us-east-1" ? void 0 : options.region
2707
+ }).pipe(Effect.catch((failure) => isBucketAlreadyOwned(failure.cause) ? providerRequest("MediaObjectStore.S3.bucket.verify", client.headBucket({ bucket: options.bucket })) : Effect.fail(providerError("MediaObjectStore.S3.bucket.create", failure))));
2708
+ }
2709
+ const ProviderFailureDetails = Schema.Struct({
2710
+ $metadata: Schema.optionalKey(Schema.Struct({
2711
+ attempts: Schema.optionalKey(Schema.Finite),
2712
+ httpStatusCode: Schema.optionalKey(Schema.Finite)
2713
+ })),
2714
+ name: Schema.optionalKey(Schema.String)
2715
+ });
2716
+ const decodeProviderFailure = Schema.decodeUnknownOption(ProviderFailureDetails);
2717
+ function providerError(operation, failure) {
2718
+ const details = decodeProviderFailure(failure.cause);
2719
+ const diagnostics = [];
2720
+ if (Option.isSome(details)) {
2721
+ if (details.value.name) diagnostics.push(`name=${details.value.name}`);
2722
+ if (details.value.$metadata?.httpStatusCode !== void 0) diagnostics.push(`status=${details.value.$metadata.httpStatusCode}`);
2723
+ if (details.value.$metadata?.attempts !== void 0) diagnostics.push(`attempts=${details.value.$metadata.attempts}`);
2724
+ }
2725
+ return storageError(operation, `The S3 provider request failed${diagnostics.length > 0 ? ` (${diagnostics.join(", ")}).` : "."}`);
2726
+ }
2727
+ function isMissingObject(cause) {
2728
+ const details = decodeProviderFailure(cause);
2729
+ if (Option.isNone(details)) return false;
2730
+ return details.value.name === "NoSuchKey" || details.value.name === "NotFound";
2731
+ }
2732
+ function isMissingBucket(cause) {
2733
+ const details = decodeProviderFailure(cause);
2734
+ if (Option.isNone(details)) return false;
2735
+ return details.value.name === "NoSuchBucket" || details.value.$metadata?.httpStatusCode === 404;
2736
+ }
2737
+ function isBucketAlreadyOwned(cause) {
2738
+ const details = decodeProviderFailure(cause);
2739
+ return Option.isSome(details) && details.value.name === "BucketAlreadyOwnedByYou";
2740
+ }
2741
+ function resolveOptions(options) {
2742
+ const bucket = options.bucket.trim();
2743
+ const region = options.region.trim();
2744
+ const endpoint = options.endpoint?.trim();
2745
+ if (!bucket || !region || options.endpoint !== void 0 && !endpoint) return Effect.fail(storageError("MediaObjectStore.S3.configure", "The bucket, region, and configured endpoint must be non-empty."));
2746
+ if (options.credentials) {
2747
+ const secretAccessKey = Redacted.value(options.credentials.secretAccessKey);
2748
+ const sessionToken = options.credentials.sessionToken && Redacted.value(options.credentials.sessionToken);
2749
+ if (!options.credentials.accessKeyId.trim() || !secretAccessKey || sessionToken === "") return Effect.fail(storageError("MediaObjectStore.S3.configure", "The configured credentials must be non-empty."));
2750
+ }
2751
+ return Effect.succeed({
2752
+ ...options,
2753
+ bucket,
2754
+ credentials: options.credentials ? {
2755
+ ...options.credentials,
2756
+ accessKeyId: options.credentials.accessKeyId.trim()
2757
+ } : void 0,
2758
+ endpoint,
2759
+ forcePathStyle: options.forcePathStyle ?? false,
2760
+ provisionBucket: options.provisionBucket ?? false,
2761
+ region
2762
+ });
2763
+ }
2764
+ function storageError(operation, message) {
2765
+ return new MediaStorageError({
2766
+ cause: new Error(message),
2767
+ operation
2768
+ });
2769
+ }
2770
+
2771
+ //#endregion
2772
+ //#region ../s3/src/S3MediaObjects.ts
2773
+ const maxS3SingleCopyBytes = 5e9;
2774
+
2775
+ //#endregion
2776
+ export { DocumentRevision as $, StudioBadRequest as A, User as B, DocumentStore as C, CurrentStudioRequest as D, defaultAccessControlLayer as E, studioInternalError as F, UserSelfRemovalDenied as G, UserAlreadyExists as H, InitializationAlreadyCompleted as I, CollaborationServerMessage as J, UserSelfRoleChangeDenied as K, InitializationProofInvalid as L, StudioRequestConflict as M, StudioSchemaError as N, StudioApi as O, StudioInternalError as P, DocumentHistoryInvalidCursor as Q, InitializationState as R, DocumentPersistenceError as S, AccessControl as T, UserNotFound as U, UserAdministrationDenied as V, UserPage as W, StateVector as X, CollaborationTicket as Y, DocumentHistoryComparisonTooLarge as Z, Documents as _, mediaObjectPrefix as a, ActivityEntry as at, cursorCodec as b, Seo as c, ActivityPage as ct, seoDeliveryLayer as d, DocumentMediaUnavailable as dt, ACTIVITY_ACTIONS as et, seoLayer as f, DocumentNotFound as ft, DocumentPreviews as g, PublishedDocumentNotFound as gt, DocumentMaintenance as h, DocumentVersionConflict as ht, MediaObjectStore as i, ActivityCursorInvalid as it, StudioBootstrap as j, StudioAuthentication as k, SeoDelivery as l, ActivityTarget as lt, SeoStore as m, DocumentValidationFailed as mt, s3MediaObjectStoreLayer as n, ActivityContext as nt, PreviewSeo as o, ActivityEntryDraft as ot, SeoPersistenceError as p, DocumentPathConflict as pt, CollaborationClientMessage as q, MediaPersistenceError as r, ActivityCursor as rt, PublishedSeo as s, ActivityId as st, maxS3SingleCopyBytes as t, ActivityAction as tt, makeDefaultSeoOptions as u, ActivityTargetKind as ut, PublishedDocuments as v, renderEditorMarkdown as w, DocumentGenerationConflict as x, documentsLayer as y, CreateUserInput as z };
2777
+ //# sourceMappingURL=S3MediaObjects-CpPSzJto.js.map