@phtngyn/cms 0.1.0-beta.0 → 0.1.0-beta.2

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 (181) hide show
  1. package/README.md +260 -6
  2. package/package.json +31 -16
  3. package/src/Authoring.d.ts +13 -1
  4. package/src/Configuration-Br7tE9Qt.d.ts +68 -0
  5. package/src/{AuthoringSchema-BQ5kbhVB.js → Configuration-CA_jvQKG.js} +599 -492
  6. package/src/Configuration-CA_jvQKG.js.map +1 -0
  7. package/src/Configuration.d.ts +2 -44
  8. package/src/Configuration.js +0 -1
  9. package/src/Configuration.js.map +1 -1
  10. package/src/{DocumentEngine-Da0kWotL.js → DocumentEngine-CgiBQU4g.js} +110 -84
  11. package/src/DocumentEngine-CgiBQU4g.js.map +1 -0
  12. package/src/DocumentModel-BB9igGC7.d.ts +34 -0
  13. package/src/DocumentModel.d.ts +2 -30
  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-DxbkU3_5.js +367 -0
  20. package/src/Forms-DxbkU3_5.js.map +1 -0
  21. package/src/Forms.d.ts +35 -0
  22. package/src/Forms.js +13 -0
  23. package/src/Forms.js.map +1 -0
  24. package/src/HttpPaths-c9nLZ9H2.js +21 -0
  25. package/src/HttpPaths-c9nLZ9H2.js.map +1 -0
  26. package/src/I18n-DZK4CkJF.d.ts +21 -0
  27. package/src/I18n.d.ts +2 -0
  28. package/src/I18n.js +1 -0
  29. package/src/Module.js +78 -81
  30. package/src/Module.js.map +1 -1
  31. package/src/S3MediaObjects-JKMAWyVB.js +2664 -0
  32. package/src/S3MediaObjects-JKMAWyVB.js.map +1 -0
  33. package/src/Seed.d.ts +6 -0
  34. package/src/Seed.js +34 -0
  35. package/src/Seed.js.map +1 -0
  36. package/src/Seo-402YumpT.d.ts +198 -0
  37. package/src/{connection-ZGtL54qY.js → connection-B-iz0Y9Y.js} +5 -7
  38. package/src/connection-B-iz0Y9Y.js.map +1 -0
  39. package/src/{installation-BNKaVjni.js → installation-DgF2M1gL.js} +1162 -2147
  40. package/src/installation-DgF2M1gL.js.map +1 -0
  41. package/src/internal/authoring-catalog.js +153 -0
  42. package/src/internal/authoring-catalog.js.map +1 -0
  43. package/src/internal/authoring-compiler.js +2 -2
  44. package/src/internal/authoring-compiler.js.map +1 -1
  45. package/src/internal/authoring-metadata.js +68 -49
  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 +25 -13
  50. package/src/internal/configuration.js.map +1 -1
  51. package/src/internal/document-model.js +66 -20
  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 +66 -0
  58. package/src/internal/studio-app.js.map +1 -0
  59. package/src/location-CFKTeK1K.js +35 -0
  60. package/src/location-CFKTeK1K.js.map +1 -0
  61. package/src/{metadata-Czvi3E4K.js → metadata-BEZPWh-3.js} +3 -72
  62. package/src/metadata-BEZPWh-3.js.map +1 -0
  63. package/src/{models-DS5ClMsH.js → models-BWm1pTAf.js} +9 -31
  64. package/src/models-BWm1pTAf.js.map +1 -0
  65. package/src/models-DwYioyNV.js +726 -0
  66. package/src/models-DwYioyNV.js.map +1 -0
  67. package/src/runtime/blocks.js +30 -0
  68. package/src/runtime/blocks.js.map +1 -0
  69. package/src/runtime/collaboration.js +9 -4
  70. package/src/runtime/collaboration.js.map +1 -1
  71. package/src/runtime/delivery/DocumentRenderer.js +10 -4
  72. package/src/runtime/delivery/DocumentRenderer.js.map +1 -1
  73. package/src/runtime/delivery/blocks.js +8 -0
  74. package/src/runtime/delivery/blocks.js.map +1 -0
  75. package/src/runtime/delivery/context.js +12 -0
  76. package/src/runtime/delivery/context.js.map +1 -0
  77. package/src/runtime/delivery/document-page.js +14 -12
  78. package/src/runtime/delivery/document-page.js.map +1 -1
  79. package/src/runtime/delivery/document.js +21 -3
  80. package/src/runtime/delivery/document.js.map +1 -1
  81. package/src/runtime/delivery/error.js +11 -0
  82. package/src/runtime/delivery/error.js.map +1 -0
  83. package/src/runtime/delivery/plugin.js +41 -0
  84. package/src/runtime/delivery/plugin.js.map +1 -0
  85. package/src/runtime/delivery/types.d.ts +5 -0
  86. package/src/runtime/delivery/useCmsDocumentSeo.d.ts +56 -0
  87. package/src/runtime/delivery/useCmsDocumentSeo.js +99 -0
  88. package/src/runtime/delivery/useCmsDocumentSeo.js.map +1 -0
  89. package/src/runtime/delivery/useDocument.js +5 -2
  90. package/src/runtime/delivery/useDocument.js.map +1 -1
  91. package/src/runtime/delivery/useDocumentQuery.js +5 -2
  92. package/src/runtime/delivery/useDocumentQuery.js.map +1 -1
  93. package/src/runtime/forms/document.js +38 -0
  94. package/src/runtime/forms/document.js.map +1 -0
  95. package/src/runtime/forms/useCmsForm.js +149 -0
  96. package/src/runtime/forms/useCmsForm.js.map +1 -0
  97. package/src/runtime/i18n/translator.js +22 -0
  98. package/src/runtime/i18n/translator.js.map +1 -0
  99. package/src/runtime/i18n/useCmsI18n.d.ts +9 -0
  100. package/src/runtime/i18n/useCmsI18n.js +29 -0
  101. package/src/runtime/i18n/useCmsI18n.js.map +1 -0
  102. package/src/runtime/installation.js +1 -1
  103. package/src/runtime/plugin.js +3 -3
  104. package/src/runtime/plugin.js.map +1 -1
  105. package/src/runtime/preview/comark-document.js +0 -1
  106. package/src/runtime/preview/comark-document.js.map +1 -1
  107. package/src/runtime/preview/connection.js +1 -1
  108. package/src/runtime/preview/controller.js +37 -11
  109. package/src/runtime/preview/controller.js.map +1 -1
  110. package/src/runtime/preview/overlay.js +65 -0
  111. package/src/runtime/preview/overlay.js.map +1 -0
  112. package/src/runtime/preview/plugin.client.js +59 -61
  113. package/src/runtime/preview/plugin.client.js.map +1 -1
  114. package/src/runtime/preview/preview.css +72 -4
  115. package/src/runtime/preview/protocol.js.map +1 -1
  116. package/src/runtime/preview/store.js +1 -1
  117. package/src/runtime/preview/targets.js +0 -1
  118. package/src/runtime/preview/targets.js.map +1 -1
  119. package/src/runtime/sitemap-entries.js +17 -0
  120. package/src/runtime/sitemap-entries.js.map +1 -0
  121. package/src/runtime/sitemap.js +24 -0
  122. package/src/runtime/sitemap.js.map +1 -0
  123. package/src/runtime/studio-app.js +5 -1
  124. package/src/runtime/studio-app.js.map +1 -1
  125. package/src/runtime/studio-entry.js +36 -0
  126. package/src/runtime/studio-entry.js.map +1 -0
  127. package/src/runtime/studio-navigation.js +11 -0
  128. package/src/runtime/studio-navigation.js.map +1 -0
  129. package/src/scalars-B2lgGUnj.js +26 -0
  130. package/src/scalars-B2lgGUnj.js.map +1 -0
  131. package/src/{store-Nl4uxEXx.js → store-DjJs3I7k.js} +12 -7
  132. package/src/store-DjJs3I7k.js.map +1 -0
  133. package/studio/CreateDocumentDialog-CTeIFlPb.js +256 -0
  134. package/studio/collaboration-DcK2F6v9.js +4566 -0
  135. package/studio/documentPlanning-Bfv-d7OU.js +29820 -0
  136. package/studio/dom-to-image-more.min-D6jGGdrW.js +877 -0
  137. package/studio/editor-Bzlh3zXt.js +30827 -0
  138. package/studio/host.css +3 -0
  139. package/studio/host.js +46614 -0
  140. package/studio/rolldown-runtime-B0aSnxlc.js +20 -0
  141. package/dist/studio/assets/ActivityEntryRow-D--fRvWI.js +0 -1
  142. package/dist/studio/assets/ActivityView-D-XBBUHb.js +0 -1
  143. package/dist/studio/assets/CreateDocumentDialog-CH8UH1QM.js +0 -1
  144. package/dist/studio/assets/DocumentsView-CxRpmAxt.js +0 -34
  145. package/dist/studio/assets/DropdownMenu-BSb1l7fr.js +0 -1
  146. package/dist/studio/assets/Input-DVao38h7.js +0 -3
  147. package/dist/studio/assets/MediaView-BHrPIjxB.js +0 -1
  148. package/dist/studio/assets/Modal-VNrXGa5C.js +0 -1
  149. package/dist/studio/assets/OverviewView-DfP6-mLZ.js +0 -1
  150. package/dist/studio/assets/Select-BJZIs9sY.js +0 -1
  151. package/dist/studio/assets/SettingsView-CerHe-ua.js +0 -1
  152. package/dist/studio/assets/Skeleton-CWJNi3nS.js +0 -1
  153. package/dist/studio/assets/StudioPageHeader-DP9HDAF2.js +0 -1
  154. package/dist/studio/assets/UsersView-cP3REkH2.js +0 -1
  155. package/dist/studio/assets/collaboration-BLnCzfvV.js +0 -4
  156. package/dist/studio/assets/context-dY4fNbn3.js +0 -1
  157. package/dist/studio/assets/editor-CE07wW83.js +0 -137
  158. package/dist/studio/assets/gateway-BAyQgCse.js +0 -10
  159. package/dist/studio/assets/index-CjoaPLhZ.js +0 -28
  160. package/dist/studio/assets/index-s2rPqCmP.css +0 -2
  161. package/dist/studio/assets/render-BkxbK2St.js +0 -83
  162. package/dist/studio/assets/rolldown-runtime-DK3Fl9T5.js +0 -1
  163. package/dist/studio/assets/useMediaLibrary-D7qR7aBf.js +0 -1
  164. package/dist/studio/index.html +0 -76
  165. package/src/AuthoringSchema-BQ5kbhVB.js.map +0 -1
  166. package/src/Configuration-BGYFD2N1.js +0 -150
  167. package/src/Configuration-BGYFD2N1.js.map +0 -1
  168. package/src/DocumentEngine-Da0kWotL.js.map +0 -1
  169. package/src/HttpPaths-86qLGB90.js +0 -15
  170. package/src/HttpPaths-86qLGB90.js.map +0 -1
  171. package/src/StudioHttp-BTLLooh0.js +0 -916
  172. package/src/StudioHttp-BTLLooh0.js.map +0 -1
  173. package/src/connection-ZGtL54qY.js.map +0 -1
  174. package/src/installation-BNKaVjni.js.map +0 -1
  175. package/src/internal/component-metadata.js +0 -37
  176. package/src/internal/component-metadata.js.map +0 -1
  177. package/src/metadata-Czvi3E4K.js.map +0 -1
  178. package/src/models-Citx8jBi.js +0 -306
  179. package/src/models-Citx8jBi.js.map +0 -1
  180. package/src/models-DS5ClMsH.js.map +0 -1
  181. package/src/store-Nl4uxEXx.js.map +0 -1
@@ -1,32 +1,30 @@
1
- import { a as CMS_READINESS_PATH, i as CMS_LIVENESS_PATH, l as STUDIO_COLLABORATION_PATH, t as CMS_AUTHENTICATION_PATH } from "./HttpPaths-86qLGB90.js";
2
- import { A as RestoreDocumentInput, B as DocumentVersion, C as MoveDocumentInput, E as PublishedDocumentPage, G as SourcePath, H as PublicPath, L as DocumentGeneration, M as UpdateDocumentInput, R as DocumentHistoryCursor, S as MarkdownNode, U as RevisionId, V as EditorNodeId, _ as DocumentSource, a as DOCUMENT_REVISION_ACTIONS, b as DocumentSurround, c as DocumentImportInput, f as DocumentMutationResult, g as DocumentRevisionAction, h as DocumentQueryScalar, j as UndeleteDocumentInput, k as RemoveDocumentInput, l as DocumentImportReport, n as CmsMarkdownDocument, o as DeletedDocument, p as DocumentNavigationItem, w as PublishDocumentInput, x as DocumentValidationResult, y as DocumentSummary, z as DocumentId } from "./models-Citx8jBi.js";
3
- import { a as normalizePublicPath, i as normalizeDocumentRelativePath, n as DocumentCompilationFailed, o as parseDocumentAddress, r as localizeDocumentPath, s as resolveDocumentAddress, t as createDocumentEngine } from "./DocumentEngine-Da0kWotL.js";
4
- import { $ as DocumentVersionConflict, A as ACTIVITY_ACTIONS, B as ActivityTargetKind, C as CollaborationTicket, D as DocumentRevision, E as DocumentHistoryInvalidCursor, F as ActivityEntry, G as Role, H as AuthenticationRequired, I as ActivityEntryDraft, J as StudioRequestContext, K as StudioActor, L as ActivityId, M as ActivityContext, N as ActivityCursor, O as DocumentRevisionComparison, P as ActivityCursorInvalid, Q as DocumentValidationFailed, R as ActivityPage, S as CollaborationServerMessage, T as DocumentHistoryComparisonTooLarge, U as DOCUMENT_ACTIONS, V as AccessDenied, W as MEDIA_ACTIONS, X as DocumentNotFound, Y as DocumentMediaUnavailable, Z as DocumentPathConflict, _ as UserNotFound, _t as MediaStatus, at as MediaConflict, b as UserSelfRoleChangeDenied, c as StudioInternalError, ct as MediaId, d as InitializationProofInvalid, dt as MediaName, et as PublishedDocumentNotFound, f as InitializationState, ft as MediaNotFound, g as UserAlreadyExists, gt as MediaReferenced, h as UserAdministrationDenied, ht as MediaReference, i as StudioBadRequest, it as MediaCleanupReport, j as ActivityAction, k as DocumentRevisionPage, l as studioInternalError, lt as MediaIntegrityReport, m as User, mt as MediaPage, n as StudioApi, nt as MediaAsset, o as StudioRequestConflict, ot as MediaCursor, p as CreateUserInput, pt as MediaObjectKey, q as StudioEntryDenied, r as StudioAuthentication, rt as MediaByteSize, s as StudioSchemaError, st as MediaFolder, t as CurrentStudioRequest, tt as ImageMimeType, u as InitializationAlreadyCompleted, ut as MediaInvalid, v as UserPage, vt as MediaTooLarge, w as StateVector, x as CollaborationClientMessage, y as UserSelfRemovalDenied, yt as MediaUpload, z as ActivityTarget } from "./StudioHttp-BTLLooh0.js";
5
- import { c as PreviewSession, d as PreviewSessionInvalid, m as RequestId, o as PreviewInvalidation, r as PreviewDraftNotFound, s as PreviewOrigin, u as PreviewSessionId } from "./models-DS5ClMsH.js";
6
- import { a as createEditorNodeId, c as createCmsComarkRenderData, d as toComarkDocument, i as withoutEditorMetadata, o as editorNodeId, r as identifiedAttributes, t as assertCanonicalEditorContent } from "./metadata-Czvi3E4K.js";
7
- import { a as EditorRenderFailed, c as comarkTagForEditorMark, d as encodeEditorImageAttributes, f as normalizeEditorImageLayout, h as nonEmptyStringAttribute, i as EditorDocumentInvalid, l as isEditorMark, m as finiteNumberAttribute, o as comarkToEditor, p as compactJsonAttributes, s as EDITOR_MARK_TYPES, u as EDITOR_IMAGE_LAYOUT_ATTRIBUTE, y as encodeCmsEditorComponentAttributes } from "./AuthoringSchema-BQ5kbhVB.js";
8
- import { Cause, Clock, Context, Crypto, Data, DateTime, Deferred, Duration, Effect, Encoding, Exit, Fiber, Layer, Option, Predicate, PubSub, Pull, Queue, Redacted, Ref, Result, Schedule, Schema, Scope, Semaphore, Stream } from "effect";
9
- import { HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse, Multipart } from "effect/unstable/http";
10
- import { HttpApiBuilder, HttpApiMiddleware } from "effect/unstable/httpapi";
1
+ import { a as EditorNodeId, c as RevisionId, d as SourcePath, i as DocumentVersion, r as DocumentId, t as DocumentGeneration } from "./scalars-B2lgGUnj.js";
2
+ import { i as FormResult, n as FORM_SUBMISSION_MAX_BYTES } from "./Forms-CfcI3tPU.js";
3
+ import { a as encodeCmsEditorComponentAttributes, d as createCmsComarkRenderData, i as validateFormValues, t as documentForms } from "./Forms-DxbkU3_5.js";
4
+ import { A as RemoveDocumentInput, C as MarkdownNode, Ct as MediaTooLarge, G as SeoRevision, H as SeoConfirmationRequired, M as UndeleteDocumentInput, N as UnpublishDocumentInput, P as UpdateDocumentInput, Q as AuthenticationRequired, S as DuplicateDocumentInput, St as MediaStatus, T as PublishDocumentInput, U as SeoMediaUnavailable, Y as SeoVersionConflict, Z as AccessDenied, _ as DocumentSource, _t as MediaObjectKey, a as DOCUMENT_REVISION_ACTIONS, at as ImageMimeType, bt as MediaReference, c as DocumentImportInput, ct as MediaCleanupReport, dt as MediaFolder, f as DocumentMutationResult, ft as MediaId, g as DocumentRevisionAction, gt as MediaNotFound, ht as MediaName, it as StudioRequestContext, j as RestoreDocumentInput, kt as RequestId, lt as MediaConflict, mt as MediaInvalid, n as CmsMarkdownDocument, nt as StudioActor, o as DeletedDocument, ot as MediaAsset, pt as MediaIntegrityReport, rt as StudioEntryDenied, st as MediaByteSize, tt as Role, ut as MediaCursor, vt as MediaPage, w as MoveDocumentInput, wt as MediaUpload, xt as MediaReferenced } from "./models-DwYioyNV.js";
5
+ import { $ as DocumentRevision, A as StudioBadRequest, B as User, C as DocumentStore, D as CurrentStudioRequest, E as defaultAccessControlLayer, F as studioInternalError, G as UserSelfRemovalDenied, H as UserAlreadyExists, I as InitializationAlreadyCompleted, J as CollaborationServerMessage, K as UserSelfRoleChangeDenied, L as InitializationProofInvalid, M as StudioRequestConflict, N as StudioSchemaError, O as StudioApi, P as StudioInternalError, Q as DocumentHistoryInvalidCursor, R as InitializationState, S as DocumentPersistenceError, T as AccessControl, U as UserNotFound, V as UserAdministrationDenied, W as UserPage, X as StateVector, Y as CollaborationTicket, Z as DocumentHistoryComparisonTooLarge, _ as Documents, a as mediaObjectPrefix, at as ActivityEntry, b as cursorCodec, c as Seo, ct as ActivityPage, d as seoDeliveryLayer, dt as DocumentMediaUnavailable, et as ACTIVITY_ACTIONS, f as seoLayer, ft as DocumentNotFound, g as DocumentPreviews, gt as PublishedDocumentNotFound, h as DocumentMaintenance, ht as DocumentVersionConflict, i as MediaObjectStore, it as ActivityCursorInvalid, k as StudioAuthentication, l as SeoDelivery, lt as ActivityTarget, m as SeoStore, mt as DocumentValidationFailed, n as s3MediaObjectStoreLayer, nt as ActivityContext, o as PreviewSeo, ot as ActivityEntryDraft, p as SeoPersistenceError, pt as DocumentPathConflict, q as CollaborationClientMessage, r as MediaPersistenceError, rt as ActivityCursor, s as PublishedSeo, st as ActivityId, tt as ActivityAction, u as makeDefaultSeoOptions, ut as ActivityTargetKind, v as PublishedDocuments, w as renderEditorMarkdown, x as DocumentGenerationConflict, y as documentsLayer, z as CreateUserInput } from "./S3MediaObjects-JKMAWyVB.js";
6
+ import { a as CMS_FORM_SUBMIT_PATH, l as CMS_READINESS_PATH, o as CMS_LIVENESS_PATH, p as STUDIO_COLLABORATION_PATH, t as CMS_AUTHENTICATION_PATH, u as STUDIO_API_PATH } from "./HttpPaths-c9nLZ9H2.js";
7
+ import { a as createEditorNodeId, o as editorNodeId, r as identifiedAttributes, t as assertCanonicalEditorContent } from "./metadata-BEZPWh-3.js";
8
+ import { C as finiteNumberAttribute, S as compactJsonAttributes, _ as comarkTagForEditorMark, b as encodeEditorImageAttributes, g as EDITOR_MARK_TYPES, h as comarkToEditor, u as EditorDocumentInvalid, v as isEditorMark, w as nonEmptyStringAttribute, x as normalizeEditorImageLayout, y as EDITOR_IMAGE_LAYOUT_ATTRIBUTE } from "./Configuration-CA_jvQKG.js";
9
+ import { Cause, Clock, Context, Crypto, Data, DateTime, Deferred, Effect, Encoding, Exit, Fiber, Layer, Option, Predicate, PubSub, Pull, Queue, Redacted, Ref, Result, Schedule, Schema, Scope, Semaphore, Stream, flow } from "effect";
11
10
  import { Extension, Node, getSchema, mergeAttributes } from "@tiptap/core";
12
11
  import { prosemirrorJSONToYDoc, ySyncPluginKey, yXmlFragmentToProseMirrorRootNode } from "@tiptap/y-tiptap";
12
+ import { FindMyWay, HttpEffect, HttpMiddleware, HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse, Multipart } from "effect/unstable/http";
13
+ import { HttpApiBuilder, HttpApiMiddleware } from "effect/unstable/httpapi";
13
14
  import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto";
15
+ import { createAuthMiddleware, getSessionFromCtx, isAPIError } from "better-auth/api";
14
16
  import { betterAuth } from "better-auth";
15
17
  import { createAccessControl } from "better-auth/plugins/access";
16
18
  import { admin } from "better-auth/plugins/admin";
17
19
  import { defaultStatements } from "better-auth/plugins/admin/access";
18
- import { APIError, createAuthMiddleware, getSessionFromCtx, isAPIError } from "better-auth/api";
19
20
  import { Hocuspocus, IncomingMessage, MessageType, shouldSkipStoreHooks } from "@hocuspocus/server";
20
- import { renderMarkdown } from "comark/render";
21
- import { diffArrays } from "diff";
22
21
  import StarterKit from "@tiptap/starter-kit";
23
22
  import * as Y from "yjs";
24
23
  import { Doc, applyUpdate, encodeStateAsUpdate, encodeStateVector } from "yjs";
25
- import { CopyObjectCommand, CreateBucketCommand, DeleteObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
26
- import { Readable } from "node:stream";
24
+ import { RateLimiter } from "effect/unstable/persistence";
27
25
  import { PgClient } from "@effect/sql-pg";
28
26
  import { isDeepStrictEqual } from "node:util";
29
- import { and, asc, count, desc, eq, inArray, lt, lte, ne, or, sql } from "drizzle-orm";
27
+ import { and, asc, count, desc, eq, gt, inArray, lt, lte, ne, or, sql } from "drizzle-orm";
30
28
  import * as PgDrizzle from "drizzle-orm/effect-postgres";
31
29
  import { createInsertSchema, createSelectSchema } from "drizzle-orm/effect-schema";
32
30
  import { alias, bigint, boolean, bytea, check, foreignKey, index, integer, jsonb, pgTable, primaryKey, text, timestamp, unique, uniqueIndex, uuid } from "drizzle-orm/pg-core";
@@ -59,50 +57,6 @@ function activityEntryFromDraft(id, draft, occurredAt) {
59
57
  });
60
58
  }
61
59
 
62
- //#endregion
63
- //#region ../server/src/internal/access-control.ts
64
- const VIEWER_ACTIONS = /* @__PURE__ */ new Set([
65
- "document.browse",
66
- "document.read",
67
- "document.trash",
68
- "document.history",
69
- "media.read",
70
- "media.search",
71
- "media.integrity"
72
- ]);
73
- const EDITOR_ACTIONS = /* @__PURE__ */ new Set([...DOCUMENT_ACTIONS, ...MEDIA_ACTIONS.filter((action) => action !== "media.cleanup")]);
74
- var AccessControl = class extends Context.Service()("@internal/server/AccessControl") {};
75
- const defaultAccessControlLayer = Layer.succeed(AccessControl, AccessControl.of({ require: Effect.fn("AccessControl.require")(function* (context, decision) {
76
- if (isAllowed(context, decision.action)) return;
77
- return yield* new AccessDenied({
78
- action: decision.action,
79
- actorId: context.actor.id,
80
- resource: decision.resource
81
- });
82
- }) }));
83
- function isAllowed(context, action) {
84
- return context.actor.roles.some((role) => {
85
- if (role === "administrator") return true;
86
- return role === "editor" && EDITOR_ACTIONS.has(action) || role === "viewer" && VIEWER_ACTIONS.has(action);
87
- });
88
- }
89
-
90
- //#endregion
91
- //#region ../server/src/internal/cursor.ts
92
- /** Owns the opaque base64url JSON representation shared by keyset cursors. */
93
- function cursorCodec(schema, makeCursor, invalid) {
94
- const decodeJson = Schema.decodeUnknownResult(schema);
95
- const encodeJson = Schema.encodeSync(schema);
96
- return {
97
- decode: (cursor) => {
98
- const decoded = Encoding.decodeBase64UrlString(cursor);
99
- if (Result.isFailure(decoded)) return Result.fail(invalid());
100
- return decodeJson(decoded.success).pipe(Result.mapError(invalid));
101
- },
102
- encode: (payload) => makeCursor(Encoding.encodeBase64Url(encodeJson(payload)))
103
- };
104
- }
105
-
106
60
  //#endregion
107
61
  //#region ../server/src/internal/activity/activity.ts
108
62
  var ActivityUnavailable = class extends Schema.TaggedError()("Activity.Unavailable", {
@@ -110,11 +64,11 @@ var ActivityUnavailable = class extends Schema.TaggedError()("Activity.Unavailab
110
64
  cause: Schema.Defect()
111
65
  }) {};
112
66
  var Activity = class extends Context.Service()("@internal/server/Activity") {};
113
- const CursorPayload$2 = Schema.Struct({
67
+ const CursorPayload$1 = Schema.Struct({
114
68
  id: ActivityId,
115
69
  occurredAt: Schema.DateTimeUtcFromString
116
70
  });
117
- const cursors$2 = cursorCodec(Schema.fromJsonString(CursorPayload$2), (value) => ActivityCursor.make(value), () => new ActivityCursorInvalid());
71
+ const cursors$1 = cursorCodec(Schema.fromJsonString(CursorPayload$1), (value) => ActivityCursor.make(value), () => new ActivityCursorInvalid());
118
72
  const activityLayer = Layer.effect(Activity, Effect.gen(function* () {
119
73
  const accessControl = yield* AccessControl;
120
74
  const storage = yield* ActivityStore;
@@ -123,7 +77,7 @@ const activityLayer = Layer.effect(Activity, Effect.gen(function* () {
123
77
  action: "activity.read",
124
78
  resource: { kind: "activity" }
125
79
  });
126
- const cursor = query.cursor ? yield* Effect.fromResult(cursors$2.decode(query.cursor)) : void 0;
80
+ const cursor = query.cursor ? yield* Effect.fromResult(cursors$1.decode(query.cursor)) : void 0;
127
81
  const page = yield* storage.list({
128
82
  action: query.action,
129
83
  actorId: query.actorId,
@@ -139,7 +93,7 @@ const activityLayer = Layer.effect(Activity, Effect.gen(function* () {
139
93
  };
140
94
  return page.next ? ActivityPage.make({
141
95
  ...output,
142
- nextCursor: cursors$2.encode(page.next)
96
+ nextCursor: cursors$1.encode(page.next)
143
97
  }) : ActivityPage.make(output);
144
98
  });
145
99
  return Activity.of({ list });
@@ -255,7 +209,6 @@ const ROUND = [
255
209
  3204031479,
256
210
  3329325298
257
211
  ];
258
- /** Portable synchronous SHA-256 for deterministic journal fingerprints and secret redaction. */
259
212
  function sha256Hex(value) {
260
213
  const input = new TextEncoder().encode(value);
261
214
  const bitLength = BigInt(input.length) * 8n;
@@ -335,14 +288,14 @@ const PendingChange = ChangeDraft.pipe(Schema.fieldsAssign({
335
288
  updatedAt: Schema.DateTimeUtcFromString
336
289
  }));
337
290
  function changeFingerprint(change) {
338
- const encoded = Schema.encodeSync(ChangeDraft)(change);
291
+ const encoded = Result.getOrThrow(Schema.encodeResult(ChangeDraft)(change));
339
292
  return sha256Hex(JSON.stringify(canonicalJson(encoded)));
340
293
  }
341
- const isJsonObject$5 = Schema.is(Schema.JsonObject);
342
- const decodeJson$2 = Schema.decodeUnknownSync(Schema.Json);
294
+ const isJsonObject$4 = Schema.is(Schema.JsonObject);
295
+ const decodeJson$2 = flow(Schema.decodeUnknownResult(Schema.Json), Result.getOrThrow);
343
296
  function canonicalJson(value) {
344
297
  if (Array.isArray(value)) return value.map(canonicalJson);
345
- if (!isJsonObject$5(value)) return value;
298
+ if (!isJsonObject$4(value)) return value;
346
299
  return Object.fromEntries(Object.keys(value).sort((left, right) => left.localeCompare(right)).map((key) => [key, canonicalJson(decodeJson$2(value[key]))]));
347
300
  }
348
301
 
@@ -350,17 +303,17 @@ function canonicalJson(value) {
350
303
  //#region ../server/src/internal/change-journal/change-journal.ts
351
304
  const ChangeBegin = Data.taggedEnum();
352
305
  function requireUnresolvedChange(result) {
353
- switch (result._tag) {
354
- case "Unresolved": return Effect.succeed(result.change);
355
- case "Recorded": return Effect.fail(new ChangeAlreadyResolved({
356
- changeId: result.entry.id,
306
+ return ChangeBegin.$match(result, {
307
+ Unresolved: ({ change }) => Effect.succeed(change),
308
+ Recorded: ({ entry }) => Effect.fail(new ChangeAlreadyResolved({
309
+ changeId: entry.id,
357
310
  outcome: "applied"
358
- }));
359
- case "Cancelled": return Effect.fail(new ChangeAlreadyResolved({
360
- changeId: result.changeId,
311
+ })),
312
+ Cancelled: ({ changeId }) => Effect.fail(new ChangeAlreadyResolved({
313
+ changeId,
361
314
  outcome: "cancelled"
362
- }));
363
- }
315
+ }))
316
+ });
364
317
  }
365
318
  var ChangeJournal = class extends Context.Service()("@internal/server/ChangeJournal") {};
366
319
  const findPendingChange = Effect.fn("ChangeJournal.findPending")(function* (journal, subject) {
@@ -425,7 +378,7 @@ function initialAdministratorDraft(identity, intent) {
425
378
  action: "user.created",
426
379
  context: identity.context,
427
380
  id: identity.id,
428
- intent: Schema.encodeSync(InitialAdministratorIntent)(intent),
381
+ intent: Result.getOrThrow(Schema.encodeResult(InitialAdministratorIntent)(intent)),
429
382
  scope: "initialization",
430
383
  subject: userEmailSubject(intent.normalizedEmail)
431
384
  });
@@ -567,7 +520,7 @@ function userChangeDraft(identity, intent) {
567
520
  action: userChangeAction(intent),
568
521
  context: identity.context,
569
522
  id: identity.id,
570
- intent: Schema.encodeSync(UserChangeIntent)(intent),
523
+ intent: Result.getOrThrow(Schema.encodeResult(UserChangeIntent)(intent)),
571
524
  scope: "users",
572
525
  subject: userEmailSubject(intent.kind === "create" ? intent.normalizedEmail : intent.email)
573
526
  };
@@ -733,12 +686,22 @@ function appliedResolution(change, intent, userId) {
733
686
  //#region ../auth/src/internal/user.ts
734
687
  const cmsUserCreationChangeField = "cmsCreationChangeId";
735
688
  const cmsUserSessionsRevokedChangeField = "cmsSessionsRevokedChangeId";
689
+ const BetterAuthUser = Schema.Struct({
690
+ banned: Schema.optionalKey(Schema.NullOr(Schema.Boolean)),
691
+ createdAt: Schema.Date,
692
+ email: Schema.String,
693
+ emailVerified: Schema.Boolean,
694
+ id: Schema.String,
695
+ name: Schema.String,
696
+ role: Schema.optionalKey(Schema.NullOr(Schema.String)),
697
+ updatedAt: Schema.Date
698
+ });
736
699
  const BetterAuthUserRecoveryEvidence = Schema.Struct({
737
700
  [cmsUserCreationChangeField]: Schema.optionalKey(Schema.UndefinedOr(Schema.NullOr(ActivityId))),
738
701
  [cmsUserSessionsRevokedChangeField]: Schema.optionalKey(Schema.UndefinedOr(Schema.NullOr(ActivityId)))
739
702
  });
740
- function decodeBetterAuthUser(user) {
741
- return Schema.decodeUnknownEffect(User)({
703
+ function decodeBetterAuthUser(value) {
704
+ return Schema.decodeEffect(BetterAuthUser)(value).pipe(Effect.flatMap((user) => Schema.decodeUnknownEffect(User)({
742
705
  banned: user.banned === true,
743
706
  createdAt: user.createdAt.toISOString(),
744
707
  email: user.email,
@@ -747,7 +710,7 @@ function decodeBetterAuthUser(user) {
747
710
  name: user.name,
748
711
  role: user.role,
749
712
  updatedAt: user.updatedAt.toISOString()
750
- });
713
+ })));
751
714
  }
752
715
  function decodeBetterAuthActor(user) {
753
716
  return Schema.decodeEffect(StudioActor)(betterAuthActorInput(user));
@@ -784,12 +747,12 @@ function betterAuthActivityWriter(storage, crypto) {
784
747
  return yield* storage.append(activityEntryFromDraft(id, draft, yield* DateTime.now));
785
748
  });
786
749
  }
787
- function betterAuthActivityOptions(writeActivity, crypto) {
750
+ function betterAuthActivityOptions(writeActivity, crypto, runPromise) {
788
751
  const capturedActors = /* @__PURE__ */ new WeakMap();
789
- const write = (action, actor) => Effect.runPromise(crypto.randomUUIDv4.pipe(Effect.orDie, Effect.map((requestId) => activityDraft(action, actor, requestId)), Effect.flatMap(writeActivity), Effect.asVoid, Effect.catchCause(() => Effect.logWarning("Authentication Activity could not be recorded.", { action }).pipe(Effect.ignoreCause))));
752
+ const write = (action, actor) => runPromise(crypto.randomUUIDv4.pipe(Effect.orDie, Effect.map((requestId) => activityDraft(action, actor, requestId)), Effect.flatMap(writeActivity), Effect.asVoid, Effect.catchCauseIf((cause) => Cause.interruptors(cause).size === 0, () => Effect.logWarning("Authentication Activity could not be recorded.", { action }).pipe(Effect.ignoreCause))));
790
753
  const before = createAuthMiddleware((context) => {
791
754
  if (context.path !== "/sign-out" && context.path !== "/change-password") return Promise.resolve();
792
- return Effect.runPromise(Effect.tryPromise(() => getSessionFromCtx(context, { disableRefresh: true })).pipe(Effect.orElseSucceed(() => null), Effect.tap((session) => Effect.sync(() => {
755
+ return runPromise(Effect.tryPromise(() => getSessionFromCtx(context, { disableRefresh: true })).pipe(Effect.orElseSucceed(() => null), Effect.tap((session) => Effect.sync(() => {
793
756
  const actor = session?.user ? decodeActor(session.user) : void 0;
794
757
  if (actor) capturedActors.set(context.context, actor);
795
758
  })), Effect.asVoid));
@@ -833,7 +796,149 @@ function activityDraft(action, actor, requestId) {
833
796
  }
834
797
  function decodeActor(user) {
835
798
  const option = decodeBetterAuthActorOption(user);
836
- return option._tag === "Some" ? option.value : void 0;
799
+ return Option.getOrUndefined(option);
800
+ }
801
+
802
+ //#endregion
803
+ //#region ../auth/src/internal/better-auth.ts
804
+ const cmsBetterAuthAccess = createAccessControl(defaultStatements);
805
+ const cmsBetterAuthRoles = {
806
+ administrator: cmsBetterAuthAccess.newRole({
807
+ session: ["list", "revoke"],
808
+ user: [
809
+ "create",
810
+ "list",
811
+ "get",
812
+ "set-role",
813
+ "delete",
814
+ "update"
815
+ ]
816
+ }),
817
+ editor: cmsBetterAuthAccess.newRole({
818
+ session: [],
819
+ user: []
820
+ }),
821
+ viewer: cmsBetterAuthAccess.newRole({
822
+ session: [],
823
+ user: []
824
+ })
825
+ };
826
+ const timestampFields = {
827
+ createdAt: "created_at",
828
+ updatedAt: "updated_at"
829
+ };
830
+ function createCmsBetterAuthOptions(options, activity) {
831
+ return {
832
+ advanced: { database: {
833
+ generateId: "uuid",
834
+ joins: true
835
+ } },
836
+ appName: "CMS Studio",
837
+ basePath: options.basePath ?? "/api/cms/auth",
838
+ baseURL: options.baseURL,
839
+ database: options.database,
840
+ emailAndPassword: {
841
+ disableSignUp: true,
842
+ enabled: true,
843
+ ...activity?.emailAndPassword
844
+ },
845
+ hooks: activity?.hooks,
846
+ user: {
847
+ additionalFields: {
848
+ [cmsUserCreationChangeField]: hiddenChangeField("cms_creation_change_id"),
849
+ [cmsUserSessionsRevokedChangeField]: hiddenChangeField("cms_sessions_revoked_change_id")
850
+ },
851
+ modelName: "auth_users",
852
+ fields: {
853
+ ...timestampFields,
854
+ emailVerified: "email_verified"
855
+ }
856
+ },
857
+ session: {
858
+ cookieCache: {
859
+ enabled: false,
860
+ strategy: "jwt"
861
+ },
862
+ modelName: "auth_sessions",
863
+ fields: {
864
+ ...timestampFields,
865
+ expiresAt: "expires_at",
866
+ ipAddress: "ip_address",
867
+ userAgent: "user_agent",
868
+ userId: "user_id"
869
+ }
870
+ },
871
+ account: {
872
+ modelName: "auth_accounts",
873
+ fields: {
874
+ accessToken: "access_token",
875
+ accessTokenExpiresAt: "access_token_expires_at",
876
+ accountId: "account_id",
877
+ ...timestampFields,
878
+ idToken: "id_token",
879
+ providerId: "provider_id",
880
+ refreshToken: "refresh_token",
881
+ refreshTokenExpiresAt: "refresh_token_expires_at",
882
+ userId: "user_id"
883
+ }
884
+ },
885
+ verification: {
886
+ modelName: "auth_verifications",
887
+ fields: {
888
+ ...timestampFields,
889
+ expiresAt: "expires_at"
890
+ }
891
+ },
892
+ rateLimit: {
893
+ enabled: true,
894
+ storage: "database",
895
+ modelName: "auth_rate_limits",
896
+ fields: { lastRequest: "last_request" }
897
+ },
898
+ plugins: [admin({
899
+ ac: cmsBetterAuthAccess,
900
+ adminRoles: ["administrator"],
901
+ defaultRole: "editor",
902
+ roles: cmsBetterAuthRoles,
903
+ schema: {
904
+ session: { fields: { impersonatedBy: "impersonated_by" } },
905
+ user: { fields: {
906
+ banExpires: "ban_expires",
907
+ banReason: "ban_reason",
908
+ banned: "banned",
909
+ role: "role"
910
+ } }
911
+ }
912
+ })],
913
+ secret: options.secret
914
+ };
915
+ }
916
+ function createCmsBetterAuth(options, activity) {
917
+ return betterAuth(createCmsBetterAuthOptions(options, activity));
918
+ }
919
+ function hiddenChangeField(fieldName) {
920
+ return {
921
+ fieldName,
922
+ input: false,
923
+ required: false,
924
+ returned: false,
925
+ type: "string"
926
+ };
927
+ }
928
+
929
+ //#endregion
930
+ //#region ../auth/src/internal/handler.ts
931
+ function rejectExternalAdminRoutes(handler, basePath) {
932
+ const mountedAt = canonicalBasePath(basePath);
933
+ return (request) => {
934
+ const path = new URL(request.url).pathname;
935
+ const relative = mountedAt === "/" ? path : path === mountedAt ? "/" : path.startsWith(`${mountedAt}/`) ? path.slice(mountedAt.length) : path;
936
+ if (relative === "/admin" || relative.startsWith("/admin/")) return Promise.resolve(new Response(null, { status: 404 }));
937
+ return handler(request);
938
+ };
939
+ }
940
+ function canonicalBasePath(basePath) {
941
+ return (basePath.startsWith("/") ? basePath : `/${basePath}`).replace(/\/+$/u, "") || "/";
837
942
  }
838
943
 
839
944
  //#endregion
@@ -916,9 +1021,24 @@ function matches(code, ...candidates) {
916
1021
  return code !== void 0 && candidates.some((candidate) => candidate.code === code);
917
1022
  }
918
1023
  function apiErrorCode(cause) {
919
- if (!(cause instanceof APIError)) return void 0;
920
- const body = Schema.decodeUnknownOption(Schema.Struct({ code: Schema.String }))(cause.body);
921
- return body._tag === "Some" ? body.value.code : void 0;
1024
+ if (!(cause instanceof Error) || cause.name !== "APIError") return void 0;
1025
+ const apiError = Schema.decodeUnknownOption(Schema.Struct({
1026
+ body: Schema.Struct({ code: Schema.String }),
1027
+ statusCode: Schema.Int
1028
+ }))(cause);
1029
+ return Option.map(apiError, ({ body }) => body.code).pipe(Option.getOrUndefined);
1030
+ }
1031
+
1032
+ //#endregion
1033
+ //#region ../auth/src/internal/provider.ts
1034
+ function providerCall(operation, request, onError) {
1035
+ return Effect.tryPromise({
1036
+ catch: (cause) => onError(operation, cause),
1037
+ try: request
1038
+ }).pipe(Effect.withSpan(operation));
1039
+ }
1040
+ function providerAdapter(auth, operation, onError) {
1041
+ return providerCall(operation, () => auth.$context, onError).pipe(Effect.map((context) => context.internalAdapter));
922
1042
  }
923
1043
 
924
1044
  //#endregion
@@ -927,13 +1047,10 @@ function betterAuthIdentity(auth) {
927
1047
  const authorityHeaders = /* @__PURE__ */ new WeakMap();
928
1048
  const authenticate = Effect.fn("BetterAuth.Authentication.authenticate")(function* (headers) {
929
1049
  const providerHeaders = new globalThis.Headers(headers);
930
- const session = yield* Effect.tryPromise({
931
- catch: () => studioInternalError(),
932
- try: () => auth.api.getSession({
933
- headers: providerHeaders,
934
- query: { disableCookieCache: true }
935
- })
936
- });
1050
+ const session = yield* providerCall("BetterAuth.getSession", () => auth.api.getSession({
1051
+ headers: providerHeaders,
1052
+ query: { disableCookieCache: true }
1053
+ }), () => studioInternalError());
937
1054
  if (!session?.user) return null;
938
1055
  const actor = yield* decodeBetterAuthActor(session.user).pipe(Effect.mapError(() => studioInternalError()));
939
1056
  const authority = issueIdentityAuthority();
@@ -1027,16 +1144,13 @@ function betterAuthIdentity(auth) {
1027
1144
  const apply = (changeId) => applyOperation(auth, "BetterAuth.revokeUserSessions", command, () => auth.api.revokeUserSessions({
1028
1145
  body: { userId: command.userId },
1029
1146
  headers
1030
- })).pipe(Effect.flatMap(() => Effect.tryPromise({
1031
- catch: (cause) => IdentityApplyFailure.Unknown({ error: unavailable$4("BetterAuth.adminUpdateUser.sessionRevocationReceipt", cause) }),
1032
- try: () => auth.api.adminUpdateUser({
1033
- body: {
1034
- data: { [cmsUserSessionsRevokedChangeField]: changeId },
1035
- userId: command.userId
1036
- },
1037
- headers
1038
- })
1039
- })), Effect.as(user));
1147
+ })).pipe(Effect.flatMap(() => providerCall("BetterAuth.adminUpdateUser.sessionRevocationReceipt", () => auth.api.adminUpdateUser({
1148
+ body: {
1149
+ data: { [cmsUserSessionsRevokedChangeField]: changeId },
1150
+ userId: command.userId
1151
+ },
1152
+ headers
1153
+ }), (operation, cause) => IdentityApplyFailure.Unknown({ error: unavailable$4(operation, cause) }))), Effect.as(user));
1040
1154
  return IdentityPreparation.Prepared({
1041
1155
  apply,
1042
1156
  intent,
@@ -1054,22 +1168,13 @@ function betterAuthIdentity(auth) {
1054
1168
  };
1055
1169
  }
1056
1170
  function prepareOperation(auth, operation, request) {
1057
- return Effect.tryPromise({
1058
- catch: (cause) => prepareError(auth.$ERROR_CODES, operation, cause),
1059
- try: request
1060
- });
1171
+ return providerCall(operation, request, (name, cause) => prepareError(auth.$ERROR_CODES, name, cause));
1061
1172
  }
1062
1173
  function prepareUserOperation(auth, operation, userId, request) {
1063
- return Effect.tryPromise({
1064
- catch: (cause) => prepareError(auth.$ERROR_CODES, operation, cause, userId),
1065
- try: request
1066
- });
1174
+ return providerCall(operation, request, (name, cause) => prepareError(auth.$ERROR_CODES, name, cause, userId));
1067
1175
  }
1068
1176
  function applyOperation(auth, operation, command, request) {
1069
- return Effect.tryPromise({
1070
- catch: (cause) => applyError(auth.$ERROR_CODES, operation, command, cause),
1071
- try: request
1072
- });
1177
+ return providerCall(operation, request, (name, cause) => applyError(auth.$ERROR_CODES, name, command, cause));
1073
1178
  }
1074
1179
  function requireAuthority(authorityHeaders, authority) {
1075
1180
  const headers = authorityHeaders.get(authority);
@@ -1107,13 +1212,10 @@ function betterAuthInitialIdentity(auth) {
1107
1212
  });
1108
1213
  const intent = initialAdministratorIntent(input);
1109
1214
  return {
1110
- apply: Effect.tryPromise({
1111
- catch: (cause) => initialIdentityError(auth.$ERROR_CODES, "BetterAuth.initialization.createUser", createInput, cause),
1112
- try: () => auth.api.createUser({ body: {
1113
- data: { emailVerified: true },
1114
- ...createInput
1115
- } })
1116
- }).pipe(Effect.flatMap((result) => decodeBetterAuthUser(result.user).pipe(Effect.mapError((cause) => InitialIdentityFailure.Unknown({ error: initializationUnavailable$1("BetterAuth.initialization.createUser", cause) }))))),
1215
+ apply: providerCall("BetterAuth.initialization.createUser", () => auth.api.createUser({ body: {
1216
+ data: { emailVerified: true },
1217
+ ...createInput
1218
+ } }), (operation, cause) => initialIdentityError(auth.$ERROR_CODES, operation, createInput, cause)).pipe(Effect.flatMap((result) => decodeBetterAuthUser(result.user).pipe(Effect.mapError((cause) => InitialIdentityFailure.Unknown({ error: initializationUnavailable$1("BetterAuth.initialization.createUser", cause) }))))),
1117
1219
  intent
1118
1220
  };
1119
1221
  } });
@@ -1122,1399 +1224,227 @@ function betterAuthInitialIdentity(auth) {
1122
1224
  //#endregion
1123
1225
  //#region ../auth/src/internal/recovery.ts
1124
1226
  function betterAuthRecovery(auth) {
1125
- const userAdapter = Effect.tryPromise({
1126
- catch: (cause) => unavailable$4("BetterAuth.recovery.context", cause),
1127
- try: () => auth.$context
1128
- }).pipe(Effect.map((context) => context.internalAdapter));
1129
- const initialAdapter = Effect.tryPromise({
1130
- catch: (cause) => initializationUnavailable$1("BetterAuth.initialization.recovery.context", cause),
1131
- try: () => auth.$context
1132
- }).pipe(Effect.map((context) => context.internalAdapter));
1227
+ const userAdapter = providerAdapter(auth, "BetterAuth.recovery.context", unavailable$4);
1228
+ const initialAdapter = providerAdapter(auth, "BetterAuth.initialization.recovery.context", initializationUnavailable$1);
1133
1229
  const users = IdentityRecovery.of({ observe: Effect.fn("BetterAuth.Recovery.observeUserChange")(function* (intent) {
1134
1230
  const adapter = yield* userAdapter;
1135
1231
  switch (intent.kind) {
1136
1232
  case "create": {
1137
- const result = yield* Effect.tryPromise({
1138
- catch: (cause) => unavailable$4("BetterAuth.recovery.findUserByEmail", cause),
1139
- try: () => adapter.findUserByEmail(intent.normalizedEmail, { includeAccounts: true })
1140
- });
1233
+ const result = yield* providerCall("BetterAuth.recovery.findUserByEmail", () => adapter.findUserByEmail(intent.normalizedEmail, { includeAccounts: true }), unavailable$4);
1141
1234
  if (!result) return {
1142
1235
  creationChangeId: null,
1143
1236
  credentialReady: false,
1144
1237
  kind: "create",
1145
1238
  user: null
1146
1239
  };
1147
- const user = yield* decodeUser(result.user, "BetterAuth.recovery.findUserByEmail");
1148
- return {
1149
- creationChangeId: yield* decodeBetterAuthUserCreationChangeId(result.user).pipe(Effect.mapError((cause) => unavailable$4("BetterAuth.recovery.creationChangeId", cause))),
1150
- credentialReady: hasCredential(result.accounts),
1151
- kind: "create",
1152
- user
1153
- };
1154
- }
1155
- case "remove": {
1156
- const user = yield* findUser(adapter, intent.userId);
1157
- return {
1158
- kind: "remove",
1159
- sessionFingerprints: yield* findSessionFingerprints(adapter, intent.userId),
1160
- user
1161
- };
1162
- }
1163
- case "change-role": return {
1164
- kind: "change-role",
1165
- user: yield* findUser(adapter, intent.userId)
1166
- };
1167
- case "revoke-sessions": {
1168
- const rawUser = yield* findRawUser(adapter, intent.userId);
1169
- const user = rawUser ? yield* decodeUser(rawUser, "BetterAuth.recovery.findUserById") : null;
1170
- return {
1171
- kind: "revoke-sessions",
1172
- sessionFingerprints: yield* findSessionFingerprints(adapter, intent.userId),
1173
- sessionsRevokedChangeId: rawUser ? yield* decodeBetterAuthUserSessionsRevokedChangeId(rawUser).pipe(Effect.mapError((cause) => unavailable$4("BetterAuth.recovery.sessionsRevokedChangeId", cause))) : null,
1174
- user
1175
- };
1176
- }
1177
- }
1178
- }) });
1179
- return {
1180
- initial: InitialIdentityRecovery.of({ observe: Effect.fn("BetterAuth.Recovery.observeInitialAdministrator")(function* (intent) {
1181
- const adapter = yield* initialAdapter;
1182
- const result = yield* Effect.tryPromise({
1183
- catch: (cause) => initializationUnavailable$1("BetterAuth.recovery.findInitialAdministrator", cause),
1184
- try: () => adapter.findUserByEmail(intent.normalizedEmail, { includeAccounts: true })
1185
- });
1186
- if (!result) return {
1187
- credentialReady: false,
1188
- user: null
1189
- };
1190
- return {
1191
- credentialReady: hasCredential(result.accounts),
1192
- user: yield* decodeBetterAuthUser(result.user).pipe(Effect.mapError((cause) => initializationUnavailable$1("BetterAuth.recovery.findInitialAdministrator", cause)))
1193
- };
1194
- }) }),
1195
- users
1196
- };
1197
- }
1198
- function hasCredential(accounts) {
1199
- return accounts.some((account) => account.providerId === "credential" && (account.password?.length ?? 0) > 0);
1200
- }
1201
- function decodeUser(user, operation) {
1202
- return decodeBetterAuthUser(user).pipe(Effect.mapError((cause) => unavailable$4(operation, cause)));
1203
- }
1204
- function findUser(adapter, userId) {
1205
- return findRawUser(adapter, userId).pipe(Effect.flatMap((user) => user ? decodeUser(user, "BetterAuth.recovery.findUserById") : Effect.succeed(null)));
1206
- }
1207
- function findRawUser(adapter, userId) {
1208
- return Effect.tryPromise({
1209
- catch: (cause) => unavailable$4("BetterAuth.recovery.findUserById", cause),
1210
- try: () => adapter.findUserById(userId)
1211
- });
1212
- }
1213
- function findSessionFingerprints(adapter, userId) {
1214
- return Effect.tryPromise({
1215
- catch: (cause) => unavailable$4("BetterAuth.recovery.listSessions", cause),
1216
- try: () => adapter.listSessions(userId)
1217
- }).pipe(Effect.map((sessions) => sessions.map((session) => sessionFingerprint(session.id))));
1218
- }
1219
-
1220
- //#endregion
1221
- //#region ../auth/src/internal/better-auth.ts
1222
- const cmsBetterAuthAccess = createAccessControl(defaultStatements);
1223
- const cmsBetterAuthRoles = {
1224
- administrator: cmsBetterAuthAccess.newRole({
1225
- session: ["list", "revoke"],
1226
- user: [
1227
- "create",
1228
- "list",
1229
- "get",
1230
- "set-role",
1231
- "delete",
1232
- "update"
1233
- ]
1234
- }),
1235
- editor: cmsBetterAuthAccess.newRole({
1236
- session: [],
1237
- user: []
1238
- }),
1239
- viewer: cmsBetterAuthAccess.newRole({
1240
- session: [],
1241
- user: []
1242
- })
1243
- };
1244
- const timestampFields = {
1245
- createdAt: "created_at",
1246
- updatedAt: "updated_at"
1247
- };
1248
- function createCmsBetterAuthOptions(options, activity) {
1249
- return {
1250
- advanced: { database: {
1251
- generateId: "uuid",
1252
- joins: true
1253
- } },
1254
- appName: "CMS Studio",
1255
- basePath: options.basePath ?? "/api/cms/auth",
1256
- baseURL: options.baseURL,
1257
- database: options.database,
1258
- emailAndPassword: {
1259
- disableSignUp: true,
1260
- enabled: true,
1261
- ...activity?.emailAndPassword
1262
- },
1263
- hooks: activity?.hooks,
1264
- user: {
1265
- additionalFields: {
1266
- [cmsUserCreationChangeField]: hiddenChangeField("cms_creation_change_id"),
1267
- [cmsUserSessionsRevokedChangeField]: hiddenChangeField("cms_sessions_revoked_change_id")
1268
- },
1269
- modelName: "auth_users",
1270
- fields: {
1271
- ...timestampFields,
1272
- emailVerified: "email_verified"
1273
- }
1274
- },
1275
- session: {
1276
- cookieCache: {
1277
- enabled: false,
1278
- strategy: "jwt"
1279
- },
1280
- modelName: "auth_sessions",
1281
- fields: {
1282
- ...timestampFields,
1283
- expiresAt: "expires_at",
1284
- ipAddress: "ip_address",
1285
- userAgent: "user_agent",
1286
- userId: "user_id"
1287
- }
1288
- },
1289
- account: {
1290
- modelName: "auth_accounts",
1291
- fields: {
1292
- accessToken: "access_token",
1293
- accessTokenExpiresAt: "access_token_expires_at",
1294
- accountId: "account_id",
1295
- ...timestampFields,
1296
- idToken: "id_token",
1297
- providerId: "provider_id",
1298
- refreshToken: "refresh_token",
1299
- refreshTokenExpiresAt: "refresh_token_expires_at",
1300
- userId: "user_id"
1301
- }
1302
- },
1303
- verification: {
1304
- modelName: "auth_verifications",
1305
- fields: {
1306
- ...timestampFields,
1307
- expiresAt: "expires_at"
1308
- }
1309
- },
1310
- rateLimit: {
1311
- enabled: true,
1312
- storage: "database",
1313
- modelName: "auth_rate_limits",
1314
- fields: { lastRequest: "last_request" }
1315
- },
1316
- plugins: [admin({
1317
- ac: cmsBetterAuthAccess,
1318
- adminRoles: ["administrator"],
1319
- defaultRole: "editor",
1320
- roles: cmsBetterAuthRoles,
1321
- schema: {
1322
- session: { fields: { impersonatedBy: "impersonated_by" } },
1323
- user: { fields: {
1324
- banExpires: "ban_expires",
1325
- banReason: "ban_reason",
1326
- banned: "banned",
1327
- role: "role"
1328
- } }
1329
- }
1330
- })],
1331
- secret: options.secret
1332
- };
1333
- }
1334
- function createCmsBetterAuth(options, activity) {
1335
- return betterAuth(createCmsBetterAuthOptions(options, activity));
1336
- }
1337
- function betterAuthLayer(options) {
1338
- return Layer.effectContext(Effect.gen(function* () {
1339
- const crypto = yield* Crypto.Crypto;
1340
- const storage = yield* ActivityStore;
1341
- const auth = createCmsBetterAuth(options, betterAuthActivityOptions(betterAuthActivityWriter(storage, crypto), crypto));
1342
- const identity = betterAuthIdentity(auth);
1343
- const recovery = betterAuthRecovery(auth);
1344
- const initialization = betterAuthInitialIdentity(auth);
1345
- const basePath = options.basePath ?? "/api/cms/auth";
1346
- const handler = AuthenticationHandler.of({ handle: rejectExternalAdminRoutes(auth.handler, basePath) });
1347
- return Context.empty().pipe(Context.add(Authentication, identity.authentication), Context.add(IdentityAdministration, identity.administration), Context.add(IdentityRecovery, recovery.users), Context.add(InitialIdentity, initialization), Context.add(InitialIdentityRecovery, recovery.initial), Context.add(AuthenticationHandler, handler));
1348
- }));
1349
- }
1350
- function rejectExternalAdminRoutes(handler, basePath) {
1351
- const mountedAt = canonicalBasePath(basePath);
1352
- return (request) => {
1353
- const path = new URL(request.url).pathname;
1354
- const relative = mountedAt === "/" ? path : path === mountedAt ? "/" : path.startsWith(`${mountedAt}/`) ? path.slice(mountedAt.length) : path;
1355
- if (relative === "/admin" || relative.startsWith("/admin/")) return Promise.resolve(new Response(null, { status: 404 }));
1356
- return handler(request);
1357
- };
1358
- }
1359
- function canonicalBasePath(basePath) {
1360
- return (basePath.startsWith("/") ? basePath : `/${basePath}`).replace(/\/+$/u, "") || "/";
1361
- }
1362
- function hiddenChangeField(fieldName) {
1363
- return {
1364
- fieldName,
1365
- input: false,
1366
- required: false,
1367
- returned: false,
1368
- type: "string"
1369
- };
1370
- }
1371
-
1372
- //#endregion
1373
- //#region ../server/src/adapter/Readiness.ts
1374
- var ReadinessFailed = class extends Schema.TaggedError()("Cms.ReadinessFailed", { cause: Schema.Defect() }) {};
1375
- var Readiness = class extends Context.Service()("@internal/server/Readiness") {};
1376
-
1377
- //#endregion
1378
- //#region ../server/src/internal/working-documents/working-document-model.ts
1379
- const WorkingDocumentGenerationId = Schema.String.check(Schema.isUUID()).pipe(Schema.brand("WorkingDocumentGenerationId"));
1380
- const WorkingDocumentUpdateSequence = Schema.Int.check(Schema.isGreaterThan(0)).pipe(Schema.brand("WorkingDocumentUpdateSequence"));
1381
- const WorkingDocumentHighWatermark = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)).pipe(Schema.brand("WorkingDocumentHighWatermark"));
1382
- var WorkingDocumentPersistenceError = class extends Schema.TaggedError()("WorkingDocuments.PersistenceError", {
1383
- operation: Schema.NonEmptyString,
1384
- cause: Schema.Defect()
1385
- }) {};
1386
- var WorkingDocumentSuperseded = class extends Schema.TaggedError()("WorkingDocuments.Superseded", {
1387
- documentId: DocumentId,
1388
- expectedGeneration: WorkingDocumentGenerationId,
1389
- actualGeneration: Schema.optionalKey(WorkingDocumentGenerationId)
1390
- }) {};
1391
- var WorkingDocumentConflict = class extends Schema.TaggedError()("WorkingDocuments.Conflict", {
1392
- documentId: DocumentId,
1393
- operation: Schema.NonEmptyString,
1394
- message: Schema.NonEmptyString
1395
- }) {};
1396
-
1397
- //#endregion
1398
- //#region ../server/src/internal/working-documents/working-document.ts
1399
- /** Durable identity for one exact recoverable Working Document generation. */
1400
- const WorkingDocumentRef = Schema.Struct({
1401
- documentId: DocumentId,
1402
- generationId: WorkingDocumentGenerationId
1403
- });
1404
- var WorkingDocumentInvalid = class extends Schema.TaggedError()("WorkingDocuments.Invalid", {
1405
- documentId: DocumentId,
1406
- message: Schema.NonEmptyString,
1407
- cause: Schema.Defect()
1408
- }) {};
1409
- var WorkingDocumentUnavailable = class extends Schema.TaggedError()("WorkingDocuments.Unavailable", {
1410
- operation: Schema.NonEmptyString,
1411
- cause: Schema.Defect()
1412
- }) {};
1413
- /** An authored or working baseline committed, but the publish workflow still needs recovery. */
1414
- var WorkingDocumentCheckpointed = class extends Schema.TaggedError()("WorkingDocuments.Checkpointed", {
1415
- checkpointVersion: DocumentVersion,
1416
- documentId: DocumentId,
1417
- result: Schema.optionalKey(DocumentMutationResult),
1418
- stateVector: Schema.Uint8Array,
1419
- cause: Schema.Defect()
1420
- }) {};
1421
- /** Signals that one exact Working Document generation changed or became invalid. */
1422
- const WorkingDocumentChange = Schema.Struct({
1423
- kind: Schema.Literals(["updated", "invalidated"]),
1424
- ref: WorkingDocumentRef
1425
- });
1426
- var WorkingDocuments = class extends Context.Service()("@internal/server/WorkingDocuments") {};
1427
-
1428
- //#endregion
1429
- //#region ../document/src/internal/editor/render.ts
1430
- const renderEditorMarkdown = Effect.fn("EditorDocument.renderMarkdown")((document) => {
1431
- return Effect.tryPromise({
1432
- try: () => renderMarkdown(toComarkDocument(withoutEditorMetadata(document))),
1433
- catch: (cause) => new EditorRenderFailed({ cause })
1434
- });
1435
- });
1436
-
1437
- //#endregion
1438
- //#region ../document/src/DocumentPlanning.ts
1439
- /** Derives one canonical Source path and public path without performing persistence. */
1440
- function planDocumentCreation(configuration, input) {
1441
- return Result.gen(function* () {
1442
- const collection = configuration.types.find((candidate) => candidate.collection === input.collection);
1443
- if (!collection) return yield* invalid$1(`Document collection "${input.collection}" does not exist.`, "collection");
1444
- if (!collection.canCreate) return yield* invalid$1(`Document collection "${input.collection}" does not allow creation.`, "collection");
1445
- const title = input.title.trim();
1446
- if (!title) return yield* invalid$1("A document title is required.", "title");
1447
- const locale = yield* resolveLocale(configuration, input.locale);
1448
- const address = yield* parseDocumentAddress(collection.addressPattern).pipe(Result.mapError(addressInvalid));
1449
- const relativePath = yield* resolveRelativePath(collection, input.location);
1450
- const data = Object.fromEntries(address.tokens.flatMap((token) => {
1451
- if (token.name === "slug" && relativePath) return [[token.name, relativePath.split("/").at(-1) ?? relativePath]];
1452
- return [];
1453
- }));
1454
- const relativeKey = yield* normalizePublicPath(relativePath).pipe(Result.mapError(addressInvalid));
1455
- const key = yield* resolveDocumentAddress(address, relativeKey, data).pipe(Result.mapError(addressInvalid));
1456
- const sourcePath = documentSourcePath(collection, relativePath, locale);
1457
- const path = localizeDocumentPath(key, locale, configuration.locales);
1458
- return Object.assign({
1459
- collection,
1460
- data,
1461
- key,
1462
- path,
1463
- sourcePath,
1464
- title
1465
- }, locale === void 0 ? void 0 : { locale });
1466
- });
1467
- }
1468
- /** Creates the canonical initial editor source, including configured field defaults. */
1469
- const createInitialDocumentSource = Effect.fn("DocumentPlanning.createInitialSource")(function* (plan) {
1470
- const body = CmsMarkdownDocument.make({
1471
- frontmatter: initialFrontmatter(plan),
1472
- meta: {},
1473
- nodes: [[
1474
- "h1",
1475
- {},
1476
- plan.title
1477
- ]]
1478
- });
1479
- return {
1480
- body,
1481
- markdown: yield* renderEditorMarkdown(body)
1482
- };
1483
- });
1484
- /** Derives the canonical destination Source path while preserving the Document's collection and locale. */
1485
- function planDocumentMove(configuration, document, location) {
1486
- return Result.gen(function* () {
1487
- const collection = configuration.types.find((candidate) => candidate.collection === document.collection);
1488
- if (!collection) return yield* invalid$1(`Document collection "${document.collection}" does not exist.`, "documentId");
1489
- return documentSourcePath(collection, yield* resolveRelativePath(collection, location), document.locale);
1490
- });
1491
- }
1492
- function documentSourcePath(collection, relativePath, locale) {
1493
- const sourceName = relativePath ? `${relativePath}.md` : "index.md";
1494
- return SourcePath.make([
1495
- locale,
1496
- collection.directory === "." ? void 0 : collection.directory,
1497
- sourceName
1498
- ].filter((value) => value !== void 0).join("/"));
1499
- }
1500
- function initialFrontmatter(plan) {
1501
- let frontmatter = {
1502
- ...plan.data,
1503
- title: plan.title
1504
- };
1505
- 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);
1506
- return frontmatter;
1507
- }
1508
- function nestedValue$1(value, path) {
1509
- return path.split(".").reduce((current, segment) => isJsonObject$4(current) ? current[segment] : void 0, value);
1510
- }
1511
- function setNestedValue(target, path, value) {
1512
- const [segment, ...rest] = path;
1513
- if (!segment) return target;
1514
- if (rest.length === 0) return Object.assign({}, target, { [segment]: value });
1515
- const current = target[segment];
1516
- return Object.assign({}, target, { [segment]: setNestedValue(isJsonObject$4(current) ? current : {}, rest, value) });
1517
- }
1518
- const isJsonObject$4 = Schema.is(Schema.JsonObject);
1519
- function resolveLocale(configuration, requested) {
1520
- const locales = configuration.locales;
1521
- if (!locales) return requested === void 0 ? Result.succeed(void 0) : invalid$1(`Document locale "${requested}" is not configured.`, "locale");
1522
- const locale = requested ?? locales.defaultLocale;
1523
- return locales.locales.includes(locale) ? Result.succeed(locale) : invalid$1(`Document locale "${locale}" does not exist.`, "locale");
1524
- }
1525
- function resolveRelativePath(collection, location) {
1526
- if (location.kind === "home") return collection.addressPattern === "/:path*" ? Result.succeed("") : invalid$1("Only a root catch-all collection can use the home location.", "location");
1527
- if (location.parent !== void 0 && !collection.hierarchy) return invalid$1(`Document collection "${collection.collection}" does not allow hierarchy.`, "location.parent");
1528
- return normalizeDocumentRelativePath([location.parent, location.slug].filter((value) => value !== void 0).join("/")).pipe(Result.mapError((error) => addressInvalid({
1529
- ...error,
1530
- field: "location"
1531
- })));
1532
- }
1533
- function addressInvalid(error) {
1534
- return new DocumentValidationFailed({ issues: [validationIssue(error.message, error.field)] });
1535
- }
1536
- function invalid$1(message, field) {
1537
- return Result.fail(new DocumentValidationFailed({ issues: [validationIssue(message, field)] }));
1538
- }
1539
- function validationIssue(message, field) {
1540
- return field === void 0 ? {
1541
- code: "document.invalid",
1542
- message,
1543
- severity: "error"
1544
- } : {
1545
- code: "document.invalid",
1546
- field,
1547
- message,
1548
- severity: "error"
1549
- };
1550
- }
1551
-
1552
- //#endregion
1553
- //#region ../server/src/internal/documents/document-state.ts
1554
- const isDocumentCompilationFailed = Schema.is(DocumentCompilationFailed);
1555
- function makeDocumentState(authored, published, snapshot) {
1556
- const activeById = /* @__PURE__ */ new Map();
1557
- for (const source of snapshot.active) {
1558
- if (activeById.has(source.documentId)) throw new Error(`Document "${source.documentId}" appears more than once in the repository snapshot.`);
1559
- activeById.set(source.documentId, source);
1560
- }
1561
- const authoredById = /* @__PURE__ */ new Map();
1562
- for (const document of authored.documents) {
1563
- if (authoredById.has(document.id)) throw new Error(`Compiled document "${document.id}" appears more than once in the authored catalog.`);
1564
- if (!activeById.has(document.id)) throw new Error(`Compiled document "${document.id}" is missing from the repository snapshot.`);
1565
- authoredById.set(document.id, document);
1566
- }
1567
- for (const documentId of activeById.keys()) if (!authoredById.has(documentId)) throw new Error(`Compiled document "${documentId}" is missing from the repository snapshot.`);
1568
- return {
1569
- activeById,
1570
- authored,
1571
- authoredById,
1572
- published,
1573
- snapshot
1574
- };
1575
- }
1576
- const compileDocumentState = Effect.fn("DocumentState.compile")(function* (engine, snapshot, previous) {
1577
- return makeDocumentState(yield* engine.compileAll({
1578
- previous: previous?.authored,
1579
- sources: authoredSources(snapshot)
1580
- }), yield* engine.compileAll({
1581
- previous: previous?.published,
1582
- sources: publishedSources(snapshot)
1583
- }), snapshot);
1584
- });
1585
- function authoredSources(snapshot) {
1586
- return snapshot.active.map((source) => ({
1587
- documentId: source.documentId,
1588
- draft: source.publishedVersion !== source.version,
1589
- markdown: source.markdown,
1590
- sourcePath: source.sourcePath
1591
- }));
1592
- }
1593
- function publishedSources(snapshot) {
1594
- return snapshot.published.map((source) => ({
1595
- documentId: source.documentId,
1596
- draft: false,
1597
- markdown: source.markdown,
1598
- sourcePath: source.sourcePath
1599
- }));
1600
- }
1601
- const compileValidCatalog = Effect.fn("DocumentState.compileValid")(function* (engine, sources, previous) {
1602
- const result = yield* Effect.result(engine.compileAll({
1603
- previous,
1604
- sources
1605
- }));
1606
- if (Result.isFailure(result)) return yield* new DocumentValidationFailed({ issues: compilationIssues(result.failure) });
1607
- const errors = result.success.issues.filter((issue) => issue.severity === "error");
1608
- if (errors.length > 0) return yield* new DocumentValidationFailed({ issues: errors });
1609
- return result.success;
1610
- });
1611
- const validateDocumentSources = Effect.fn("DocumentState.validate")(function* (engine, sources, sourcePath, previous) {
1612
- const result = yield* Effect.result(engine.compileAll({
1613
- previous,
1614
- sources
1615
- }));
1616
- if (Result.isFailure(result)) return DocumentValidationResult.make({
1617
- issues: compilationIssues(result.failure),
1618
- valid: false
1619
- });
1620
- const catalog = result.success;
1621
- const document = catalog.documents.find((candidate) => candidate.source.path === sourcePath);
1622
- const issues = catalog.issues;
1623
- const validation = {
1624
- issues,
1625
- valid: document !== void 0 && !issues.some((issue) => issue.severity === "error")
1626
- };
1627
- return DocumentValidationResult.make(Object.assign(validation, document === void 0 ? void 0 : { document }));
1628
- });
1629
- function authoringEntries(state, query) {
1630
- return state.snapshot.active.flatMap((source) => {
1631
- const document = state.authoredById.get(source.documentId);
1632
- if (!document) throw new Error(`Compiled document "${source.documentId}" is missing from the repository snapshot.`);
1633
- if (query.collection !== void 0 && document.collection !== query.collection) return [];
1634
- if (query.locale !== void 0 && document.locale !== query.locale) return [];
1635
- return [DocumentSummary.make({
1636
- document,
1637
- sourcePath: source.sourcePath,
1638
- version: source.version
1639
- })];
1640
- });
1641
- }
1642
- function deletedEntries(state) {
1643
- return state.snapshot.deleted.map((source) => DeletedDocument.make({
1644
- documentId: source.documentId,
1645
- deletedAt: source.deletedAt,
1646
- sourcePath: source.sourcePath,
1647
- version: source.version
1648
- }));
1649
- }
1650
- function entryById(state, documentId) {
1651
- const source = state.activeById.get(documentId);
1652
- const document = state.authoredById.get(documentId);
1653
- return source && document ? DocumentSummary.make({
1654
- document,
1655
- sourcePath: source.sourcePath,
1656
- version: source.version
1657
- }) : void 0;
1658
- }
1659
- function compilationIssues(error) {
1660
- if (isDocumentCompilationFailed(error)) return error.issues;
1661
- return [{
1662
- code: "markdown.parse",
1663
- message: errorMessage(error.cause),
1664
- severity: "error",
1665
- sourcePath: error.sourcePath
1666
- }];
1667
- }
1668
- function errorMessage(cause) {
1669
- return cause instanceof Error && cause.message ? cause.message : "Markdown could not be parsed.";
1670
- }
1671
-
1672
- //#endregion
1673
- //#region ../server/src/internal/documents/document-lifecycle.ts
1674
- const PreparedDocumentMutation = Data.taggedEnum();
1675
- const prepareDocumentMutation = Effect.fn("DocumentLifecycle.prepare")(function* (dependencies, state, command) {
1676
- const { engine, configuration, repository } = dependencies;
1677
- const authored = authoredSources(state.snapshot);
1678
- const published = publishedSources(state.snapshot);
1679
- switch (command.action) {
1680
- case "document.create": {
1681
- yield* ensurePathAvailable(state, command.sourcePath);
1682
- const nextAuthored = [...authored, {
1683
- documentId: command.documentId,
1684
- draft: true,
1685
- markdown: command.markdown,
1686
- sourcePath: command.sourcePath
1687
- }];
1688
- return PreparedDocumentMutation.Commit({
1689
- authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1690
- published: state.published,
1691
- commitPlan: {
1692
- action: command.action,
1693
- documentId: command.documentId,
1694
- markdown: command.markdown,
1695
- sourcePath: command.sourcePath
1696
- }
1697
- });
1698
- }
1699
- case "document.update": {
1700
- const current = yield* requireActive(state, command.input.documentId);
1701
- yield* requireVersion(current, command.input.expectedVersion);
1702
- const nextAuthored = replaceSource(authored, {
1703
- documentId: current.documentId,
1704
- draft: true,
1705
- markdown: command.input.markdown,
1706
- sourcePath: current.sourcePath
1707
- });
1708
- return PreparedDocumentMutation.Commit({
1709
- authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1710
- published: state.published,
1711
- commitPlan: {
1712
- action: command.action,
1713
- documentId: current.documentId,
1714
- expectedVersion: command.input.expectedVersion,
1715
- markdown: command.input.markdown,
1716
- publishedVersion: current.publishedVersion,
1717
- sourcePath: current.sourcePath
1718
- }
1719
- });
1720
- }
1721
- case "document.move": {
1722
- const current = yield* requireActive(state, command.input.documentId);
1723
- yield* requireVersion(current, command.input.expectedVersion);
1724
- const entry = entryById(state, current.documentId);
1725
- if (!entry) return yield* Effect.die(/* @__PURE__ */ new Error("Active document is missing from the authored catalog."));
1726
- const sourcePath = yield* Effect.fromResult(planDocumentMove(configuration, entry.document, command.input.location));
1727
- yield* ensurePathAvailable(state, sourcePath, current.documentId);
1728
- const nextAuthored = replaceSource(authored, {
1729
- documentId: current.documentId,
1730
- draft: true,
1731
- markdown: current.markdown,
1732
- sourcePath
1733
- });
1734
- return PreparedDocumentMutation.Commit({
1735
- authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1736
- published: state.published,
1737
- commitPlan: {
1738
- action: command.action,
1739
- documentId: current.documentId,
1740
- expectedVersion: command.input.expectedVersion,
1741
- markdown: current.markdown,
1742
- publishedVersion: current.publishedVersion,
1743
- sourcePath
1744
- }
1745
- });
1746
- }
1747
- case "document.remove": {
1748
- const current = yield* requireActive(state, command.input.documentId);
1749
- yield* requireVersion(current, command.input.expectedVersion);
1750
- return PreparedDocumentMutation.Commit({
1751
- authored: yield* compileValidCatalog(engine, withoutDocument(authored, current.documentId), state.authored),
1752
- published: yield* compileValidCatalog(engine, withoutDocument(published, current.documentId), state.published),
1753
- commitPlan: {
1754
- action: command.action,
1755
- documentId: current.documentId,
1756
- expectedVersion: command.input.expectedVersion,
1757
- markdown: current.markdown,
1758
- sourcePath: current.sourcePath
1759
- }
1760
- });
1761
- }
1762
- case "document.restore": {
1763
- const current = yield* requireActive(state, command.input.documentId);
1764
- yield* requireVersion(current, command.input.expectedVersion);
1765
- const historical = yield* repository.revision(current.documentId, command.input.revisionId);
1766
- const nextAuthored = replaceSource(authored, {
1767
- documentId: current.documentId,
1768
- draft: true,
1769
- markdown: historical.markdown,
1770
- sourcePath: current.sourcePath
1771
- });
1772
- return PreparedDocumentMutation.Commit({
1773
- authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1774
- published: state.published,
1775
- commitPlan: {
1776
- action: command.action,
1777
- documentId: current.documentId,
1778
- expectedVersion: command.input.expectedVersion,
1779
- markdown: historical.markdown,
1780
- publishedVersion: current.publishedVersion,
1781
- revisionId: command.input.revisionId,
1782
- sourcePath: current.sourcePath
1783
- }
1784
- });
1785
- }
1786
- case "document.undelete": {
1787
- const deleted = yield* requireDeleted(state, command.input.documentId);
1788
- yield* requireVersion(deleted, command.input.expectedVersion);
1789
- yield* ensurePathAvailable(state, deleted.sourcePath);
1790
- const nextAuthored = [...authored, {
1791
- documentId: deleted.documentId,
1792
- draft: true,
1793
- markdown: deleted.markdown,
1794
- sourcePath: deleted.sourcePath
1795
- }];
1796
- return PreparedDocumentMutation.Commit({
1797
- authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1798
- published: state.published,
1799
- commitPlan: {
1800
- action: command.action,
1801
- documentId: deleted.documentId,
1802
- expectedVersion: command.input.expectedVersion,
1803
- markdown: deleted.markdown,
1804
- sourcePath: deleted.sourcePath
1805
- }
1806
- });
1807
- }
1808
- case "document.publish": {
1809
- const current = yield* requireActive(state, command.input.documentId);
1810
- yield* requireVersion(current, command.input.expectedVersion);
1811
- if (current.publishedVersion === current.version) {
1812
- const entry = entryById(state, current.documentId);
1813
- const result = {
1814
- action: "document.publish",
1815
- documentId: current.documentId,
1816
- generation: state.snapshot.generation,
1817
- revisionId: current.revisionId,
1818
- sourcePath: current.sourcePath,
1819
- version: current.version
1820
- };
1821
- return PreparedDocumentMutation.Unchanged({ result: DocumentMutationResult.make(Object.assign(result, entry === void 0 ? void 0 : { entry })) });
1822
- }
1823
- const source = {
1824
- documentId: current.documentId,
1825
- draft: false,
1826
- markdown: current.markdown,
1827
- sourcePath: current.sourcePath
1828
- };
1829
- return PreparedDocumentMutation.Commit({
1830
- authored: yield* compileValidCatalog(engine, replaceSource(authored, source), state.authored),
1831
- published: yield* compileValidCatalog(engine, replaceSource(published, source), state.published),
1832
- commitPlan: {
1833
- action: command.action,
1834
- documentId: current.documentId,
1835
- expectedVersion: command.input.expectedVersion,
1836
- markdown: current.markdown,
1837
- revisionId: current.revisionId,
1838
- sourcePath: current.sourcePath
1839
- }
1840
- });
1841
- }
1842
- }
1843
- });
1844
- const validateDocumentUpdate = Effect.fn("DocumentLifecycle.validateUpdate")(function* (engine, state, input) {
1845
- const current = yield* requireActive(state, input.documentId);
1846
- yield* requireVersion(current, input.expectedVersion);
1847
- const source = {
1848
- documentId: current.documentId,
1849
- draft: true,
1850
- markdown: input.markdown,
1851
- sourcePath: current.sourcePath
1852
- };
1853
- return yield* validateDocumentSources(engine, replaceSource(authoredSources(state.snapshot), source), current.sourcePath, state.authored);
1854
- });
1855
- function requireActive(state, documentId) {
1856
- const source = state.activeById.get(documentId);
1857
- return source ? Effect.succeed(source) : Effect.fail(new DocumentNotFound({ documentId }));
1858
- }
1859
- function requireDeleted(state, documentId) {
1860
- const source = state.snapshot.deleted.find((candidate) => candidate.documentId === documentId);
1861
- return source ? Effect.succeed(source) : Effect.fail(new DocumentNotFound({ documentId }));
1862
- }
1863
- function requireVersion(source, expectedVersion) {
1864
- return source.version === expectedVersion ? Effect.void : Effect.fail(new DocumentVersionConflict({
1865
- actualVersion: source.version,
1866
- documentId: source.documentId,
1867
- expectedVersion
1868
- }));
1869
- }
1870
- function ensurePathAvailable(state, sourcePath, exceptDocumentId) {
1871
- return state.snapshot.active.some((source) => source.sourcePath === sourcePath && source.documentId !== exceptDocumentId) ? Effect.fail(new DocumentPathConflict({ sourcePath })) : Effect.void;
1872
- }
1873
- function replaceSource(sources, source) {
1874
- return [...sources.filter((candidate) => candidate.documentId !== source.documentId), source];
1875
- }
1876
- function withoutDocument(sources, documentId) {
1877
- return sources.filter((source) => source.documentId !== documentId);
1878
- }
1879
-
1880
- //#endregion
1881
- //#region ../server/src/internal/documents/document-media-references.ts
1882
- /** Projects the durable authored/published media-reference union without reparsing documents. */
1883
- function projectMediaReferences(authored, published, documentId) {
1884
- const references = [...referencesFromCatalog(authored, documentId), ...referencesFromCatalog(published, documentId)];
1885
- return [...new Map(references.map((reference) => [referenceIdentity(reference), reference])).values()];
1886
- }
1887
- function referencesFromCatalog(catalog, documentId) {
1888
- return catalog.references.all.filter((reference) => reference.target.kind === "media" && (documentId === void 0 || reference.source.documentId === documentId)).map(toMediaReference);
1889
- }
1890
- function toMediaReference(reference) {
1891
- if (reference.target.kind !== "media") throw new Error("Expected a media reference.");
1892
- return MediaReference.make({
1893
- documentId: reference.source.documentId,
1894
- nodePath: reference.nodePath,
1895
- objectKey: MediaObjectKey.make(reference.target.key),
1896
- path: reference.source.path,
1897
- sourcePath: reference.source.sourcePath
1898
- });
1899
- }
1900
- function referenceIdentity(reference) {
1901
- return `${reference.documentId}:${reference.sourcePath}:${reference.nodePath.join(".")}:${reference.objectKey}`;
1902
- }
1903
-
1904
- //#endregion
1905
- //#region ../server/src/internal/documents/document-import.ts
1906
- /** Imports configured or migrated Sources through the normal Document lifecycle. */
1907
- const importDocumentSources = Effect.fn("Documents.importSources")(function* (dependencies, input, activity) {
1908
- const report = {
1909
- created: 0,
1910
- preserved: 0,
1911
- published: 0,
1912
- unchanged: 0,
1913
- updated: 0
1914
- };
1915
- for (const source of input.sources) {
1916
- const plan = planDocumentImport(yield* dependencies.current(source.sourcePath), source, input.mode);
1917
- switch (plan.kind) {
1918
- case "create": {
1919
- const created = yield* dependencies.runMutation({
1920
- action: "document.create",
1921
- documentId: yield* dependencies.nextDocumentId,
1922
- markdown: source.markdown,
1923
- sourcePath: source.sourcePath
1924
- }, activity, input.reason);
1925
- report.created += 1;
1926
- if (plan.publish) {
1927
- yield* publish(dependencies, created, input, activity);
1928
- report.published += 1;
1929
- }
1930
- break;
1931
- }
1932
- case "preserve":
1933
- case "unchanged":
1934
- report[plan.kind === "preserve" ? "preserved" : "unchanged"] += 1;
1935
- break;
1936
- case "publish":
1937
- yield* publish(dependencies, plan.current, input, activity);
1938
- report.published += 1;
1939
- break;
1940
- case "update": {
1941
- const updated = yield* dependencies.runMutation({
1942
- action: "document.update",
1943
- input: {
1944
- documentId: plan.current.documentId,
1945
- expectedVersion: plan.current.version,
1946
- markdown: source.markdown,
1947
- reason: input.reason
1948
- }
1949
- }, activity, input.reason);
1950
- report.updated += 1;
1951
- if (plan.publish) {
1952
- yield* publish(dependencies, updated, input, activity);
1953
- report.published += 1;
1954
- }
1955
- break;
1956
- }
1957
- }
1958
- }
1959
- return DocumentImportReport.make(report);
1960
- });
1961
- /** Plans one deterministic import without performing persistence or generating identities. */
1962
- function planDocumentImport(current, source, mode) {
1963
- if (!current) return {
1964
- kind: "create",
1965
- publish: source.published
1966
- };
1967
- if (current.markdown !== source.markdown) return mode === "overwrite" ? {
1968
- current,
1969
- kind: "update",
1970
- publish: source.published
1971
- } : { kind: "preserve" };
1972
- if (source.published && current.publishedVersion !== current.version) return {
1973
- current,
1974
- kind: "publish"
1975
- };
1976
- return { kind: "unchanged" };
1977
- }
1978
- function publish(dependencies, source, input, activity) {
1979
- return dependencies.runMutation({
1980
- action: "document.publish",
1981
- input: {
1982
- documentId: source.documentId,
1983
- expectedVersion: source.version,
1984
- reason: input.reason
1985
- }
1986
- }, activity, input.reason);
1987
- }
1988
-
1989
- //#endregion
1990
- //#region ../server/src/internal/documents/document-store.ts
1991
- var DocumentStore = class extends Context.Service()("@internal/server/DocumentStore") {};
1992
-
1993
- //#endregion
1994
- //#region ../server/src/internal/documents/document-failures.ts
1995
- var DocumentPersistenceError = class extends Schema.TaggedError()("Documents.PersistenceError", {
1996
- operation: Schema.NonEmptyString,
1997
- cause: Schema.Defect()
1998
- }) {};
1999
- var DocumentGenerationConflict = class extends Schema.TaggedError()("Documents.GenerationConflict", {
2000
- expectedGeneration: DocumentGeneration,
2001
- actualGeneration: DocumentGeneration
2002
- }) {};
2003
-
2004
- //#endregion
2005
- //#region ../server/src/internal/documents/document-history.ts
2006
- const CursorPayload$1 = Schema.Struct({
2007
- createdAt: Schema.DateTimeUtcFromString,
2008
- revisionId: RevisionId
2009
- });
2010
- const CursorJson$1 = Schema.fromJsonString(CursorPayload$1);
2011
- const cursors$1 = cursorCodec(CursorJson$1, (value) => DocumentHistoryCursor.make(value), () => new DocumentHistoryInvalidCursor());
2012
- function documentHistoryCapability(accessControl, repository) {
2013
- const requireHistory = (context, documentId) => accessControl.require(context, {
2014
- action: "document.history",
2015
- resource: {
2016
- documentId,
2017
- kind: "document"
2018
- }
2019
- });
2020
- const read = Effect.fn("Documents.history.read")(function* (context, documentId, revisionId) {
2021
- yield* requireHistory(context, documentId);
2022
- return yield* repository.revision(documentId, revisionId);
2023
- });
2024
- const list = Effect.fn("Documents.history.list")(function* (context, query) {
2025
- yield* requireHistory(context, query.documentId);
2026
- const cursor = query.cursor ? yield* Effect.fromResult(cursors$1.decode(query.cursor)) : void 0;
2027
- const page = yield* repository.revisions({
2028
- cursor,
2029
- documentId: query.documentId,
2030
- limit: query.limit ?? 25
2031
- });
2032
- const output = {
2033
- revisions: page.revisions.map(({ markdown: _markdown, ...revision }) => revision),
2034
- total: page.total
2035
- };
2036
- return page.next ? DocumentRevisionPage.make({
2037
- ...output,
2038
- nextCursor: cursors$1.encode(page.next)
2039
- }) : DocumentRevisionPage.make(output);
2040
- });
2041
- return {
2042
- compare: Effect.fn("Documents.history.compare")(function* (context, input) {
2043
- yield* requireHistory(context, input.documentId);
2044
- const from = yield* repository.revision(input.documentId, input.fromRevisionId);
2045
- const to = yield* repository.revision(input.documentId, input.toRevisionId);
2046
- const changes = yield* compareLines(from.markdown, to.markdown);
2047
- return DocumentRevisionComparison.make({
2048
- changes,
2049
- from,
2050
- to
2051
- });
2052
- }),
2053
- list,
2054
- read
2055
- };
2056
- }
2057
- const MAXIMUM_COMPARISON_LINES = 1e4;
2058
- const MAXIMUM_EDIT_LENGTH = 2e3;
2059
- const comparisonTooLarge = () => new DocumentHistoryComparisonTooLarge({
2060
- maximumEditLength: MAXIMUM_EDIT_LENGTH,
2061
- maximumLines: MAXIMUM_COMPARISON_LINES
2062
- });
2063
- const compareLines = Effect.fn("Documents.history.compareLines")(function* (beforeMarkdown, afterMarkdown) {
2064
- const before = beforeMarkdown.split("\n");
2065
- const after = afterMarkdown.split("\n");
2066
- if (before.length > MAXIMUM_COMPARISON_LINES || after.length > MAXIMUM_COMPARISON_LINES) return yield* comparisonTooLarge();
2067
- const diff = diffArrays(before, after, {
2068
- maxEditLength: MAXIMUM_EDIT_LENGTH,
2069
- oneChangePerToken: true,
2070
- timeout: 100
2071
- });
2072
- if (!diff) return yield* comparisonTooLarge();
2073
- const changes = [];
2074
- let beforeLine = 1;
2075
- let afterLine = 1;
2076
- for (const change of diff) for (const line of change.value) if (change.removed) {
2077
- changes.push({
2078
- beforeLine,
2079
- kind: "removed",
2080
- line
2081
- });
2082
- beforeLine += 1;
2083
- } else if (change.added) {
2084
- changes.push({
2085
- afterLine,
2086
- kind: "added",
2087
- line
2088
- });
2089
- afterLine += 1;
2090
- } else {
2091
- changes.push({
2092
- afterLine,
2093
- beforeLine,
2094
- kind: "equal",
2095
- line
2096
- });
2097
- beforeLine += 1;
2098
- afterLine += 1;
2099
- }
2100
- return changes;
2101
- });
2102
-
2103
- //#endregion
2104
- //#region ../server/src/internal/documents/published-documents.ts
2105
- function publishedDocuments(configuration, catalog) {
2106
- const read = Effect.fn("Documents.published.read")(function* (lookup) {
2107
- const document = findPublishedDocument(yield* catalog, lookup);
2108
- return Option.isSome(document) ? document.value : yield* new PublishedDocumentNotFound({ lookup });
2109
- });
2110
- const query = Effect.fn("Documents.published.query")(function* (input) {
2111
- return queryDocuments((yield* catalog).documents, input, {
2112
- limit: input.limit,
2113
- offset: input.offset ?? 0
2114
- });
2115
- });
2116
- const navigation = Effect.fn("Documents.published.navigation")(function* (input) {
2117
- return createNavigation((yield* catalog).documents, input, configuration);
2118
- });
2119
- const surround = Effect.fn("Documents.published.surround")(function* (lookup, input) {
2120
- const current = yield* catalog;
2121
- const document = findPublishedDocument(current, lookup);
2122
- if (Option.isNone(document)) return yield* new PublishedDocumentNotFound({ lookup });
2123
- 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 });
2124
- const page = queryDocuments(current.documents, surroundQuery);
2125
- const index = page.items.findIndex((candidate) => candidate.id === document.value.id);
2126
- return DocumentSurround.make({
2127
- previous: index > 0 ? page.items[index - 1] ?? null : null,
2128
- next: index >= 0 ? page.items[index + 1] ?? null : null
2129
- });
2130
- });
2131
- return {
2132
- diagnostics: catalog.pipe(Effect.map((current) => current.issues)),
2133
- navigation,
2134
- query,
2135
- read,
2136
- surround
2137
- };
2138
- }
2139
- function findPublishedDocument(catalog, lookup) {
2140
- switch (lookup.kind) {
2141
- case "id": return Option.fromNullishOr(catalog.documents.find((document) => document.id === lookup.documentId));
2142
- case "path": return catalog.findByPath(lookup.path);
2143
- case "key": return catalog.findByKey(lookup.key, lookup.locale).pipe(Option.filter((document) => lookup.collection === void 0 || document.collection === lookup.collection));
2144
- }
2145
- }
2146
- function queryDocuments(documents, query, page) {
2147
- const filtered = documents.filter((document) => {
2148
- if (query.collection !== void 0 && document.collection !== query.collection) return false;
2149
- if (query.locale !== void 0 && document.locale !== query.locale) return false;
2150
- return Object.entries(query.where ?? {}).every(([field, expected]) => matchesValue(documentField(document, field), expected));
2151
- });
2152
- const sorted = query.sort && query.sort.length > 0 ? sortDocuments(filtered, query.sort) : filtered;
2153
- const items = page ? sorted.slice(page.offset, page.offset + page.limit) : sorted;
2154
- return PublishedDocumentPage.make({
2155
- items,
2156
- total: filtered.length
2157
- });
2158
- }
2159
- function createNavigation(documents, query, configuration) {
2160
- const locale = query.locale ?? configuration.locales?.defaultLocale;
2161
- const candidates = documents.filter((document) => (query.collection === void 0 || document.collection === query.collection) && (locale === void 0 || document.locale === locale) && document.data.navigation !== false);
2162
- const roots = [];
2163
- const itemsByKey = /* @__PURE__ */ new Map();
2164
- const orderByItem = /* @__PURE__ */ new Map();
2165
- for (const document of candidates) {
2166
- if (document.key === "/") {
2167
- const item = navigationItem(document);
2168
- roots.push(item);
2169
- orderByItem.set(item, Number.NEGATIVE_INFINITY);
2170
- continue;
2171
- }
2172
- const segments = document.key.slice(1).split("/");
2173
- let parent;
2174
- for (let index = 0; index < segments.length; index += 1) {
2175
- const key = PublicPath.make(`/${segments.slice(0, index + 1).join("/")}`);
2176
- let item = itemsByKey.get(key);
2177
- if (!item) {
2178
- item = {
2179
- children: [],
2180
- key,
2181
- title: titleFromSegment(segments[index] ?? "")
1240
+ const user = yield* decodeUser(result.user, "BetterAuth.recovery.findUserByEmail");
1241
+ return {
1242
+ creationChangeId: yield* decodeBetterAuthUserCreationChangeId(result.user).pipe(Effect.mapError((cause) => unavailable$4("BetterAuth.recovery.creationChangeId", cause))),
1243
+ credentialReady: hasCredential(result.accounts),
1244
+ kind: "create",
1245
+ user
1246
+ };
1247
+ }
1248
+ case "remove": {
1249
+ const user = yield* findUser(adapter, intent.userId);
1250
+ return {
1251
+ kind: "remove",
1252
+ sessionFingerprints: yield* findSessionFingerprints(adapter, intent.userId),
1253
+ user
2182
1254
  };
2183
- itemsByKey.set(key, item);
2184
- if (parent) parent.children.push(item);
2185
- else roots.push(item);
2186
1255
  }
2187
- if (index === segments.length - 1) {
2188
- const resolved = navigationItem(document);
2189
- item.title = resolved.title;
2190
- item.path = resolved.path;
2191
- item.locale = resolved.locale;
2192
- item.documentId = resolved.documentId;
2193
- orderByItem.set(item, navigationOrder(document));
1256
+ case "change-role": return {
1257
+ kind: "change-role",
1258
+ user: yield* findUser(adapter, intent.userId)
1259
+ };
1260
+ case "revoke-sessions": {
1261
+ const rawUser = yield* findRawUser(adapter, intent.userId);
1262
+ const user = rawUser ? yield* decodeUser(rawUser, "BetterAuth.recovery.findUserById") : null;
1263
+ return {
1264
+ kind: "revoke-sessions",
1265
+ sessionFingerprints: yield* findSessionFingerprints(adapter, intent.userId),
1266
+ sessionsRevokedChangeId: rawUser ? yield* decodeBetterAuthUserSessionsRevokedChangeId(rawUser).pipe(Effect.mapError((cause) => unavailable$4("BetterAuth.recovery.sessionsRevokedChangeId", cause))) : null,
1267
+ user
1268
+ };
2194
1269
  }
2195
- parent = item;
2196
1270
  }
2197
- }
2198
- sortNavigation(roots, orderByItem);
2199
- return roots.map((item) => DocumentNavigationItem.make(item));
2200
- }
2201
- function navigationItem(document) {
2202
- const navigation = document.data.navigation;
2203
- const configuredTitle = Schema.is(Schema.JsonObject)(navigation) ? navigation.title : void 0;
1271
+ }) });
2204
1272
  return {
2205
- children: [],
2206
- documentId: document.id,
2207
- key: document.key,
2208
- locale: document.locale,
2209
- path: document.path,
2210
- title: Schema.is(Schema.String)(configuredTitle) && configuredTitle.trim() ? configuredTitle.trim() : document.title
1273
+ initial: InitialIdentityRecovery.of({ observe: Effect.fn("BetterAuth.Recovery.observeInitialAdministrator")(function* (intent) {
1274
+ const adapter = yield* initialAdapter;
1275
+ const result = yield* providerCall("BetterAuth.recovery.findInitialAdministrator", () => adapter.findUserByEmail(intent.normalizedEmail, { includeAccounts: true }), initializationUnavailable$1);
1276
+ if (!result) return {
1277
+ credentialReady: false,
1278
+ user: null
1279
+ };
1280
+ return {
1281
+ credentialReady: hasCredential(result.accounts),
1282
+ user: yield* decodeBetterAuthUser(result.user).pipe(Effect.mapError((cause) => initializationUnavailable$1("BetterAuth.recovery.findInitialAdministrator", cause)))
1283
+ };
1284
+ }) }),
1285
+ users
2211
1286
  };
2212
1287
  }
2213
- function navigationOrder(document) {
2214
- const navigation = document.data.navigation;
2215
- const order = (Schema.is(Schema.JsonObject)(navigation) ? navigation.order : void 0) ?? document.data.order;
2216
- return Schema.is(Schema.Finite)(order) ? order : Number.POSITIVE_INFINITY;
2217
- }
2218
- function sortNavigation(items, orderByItem) {
2219
- items.sort((left, right) => {
2220
- return (orderByItem.get(left) ?? Number.POSITIVE_INFINITY) - (orderByItem.get(right) ?? Number.POSITIVE_INFINITY) || left.title.localeCompare(right.title);
2221
- });
2222
- items.forEach((item) => sortNavigation(item.children, orderByItem));
2223
- }
2224
- function documentField(document, field) {
2225
- switch (field) {
2226
- case "collection": return document.collection;
2227
- case "description": return document.description;
2228
- case "id": return document.id;
2229
- case "key": return document.key;
2230
- case "locale": return document.locale;
2231
- case "path": return document.path;
2232
- case "source.path": return document.source.path;
2233
- case "title": return document.title;
2234
- default: return nestedValue(document.data, field.startsWith("data.") ? field.slice(5) : field);
2235
- }
1288
+ function hasCredential(accounts) {
1289
+ return accounts.some((account) => account.providerId === "credential" && (account.password?.length ?? 0) > 0);
2236
1290
  }
2237
- function nestedValue(value, path) {
2238
- return path.split(".").reduce((current, segment) => {
2239
- if (!Schema.is(Schema.JsonObject)(current)) return void 0;
2240
- return current[segment];
2241
- }, value);
2242
- }
2243
- function matchesValue(actual, expected) {
2244
- const expectedValues = Array.isArray(expected) ? expected : [expected];
2245
- return (Array.isArray(actual) ? actual : [actual]).some((value) => Schema.is(DocumentQueryScalar)(value) && expectedValues.includes(value));
2246
- }
2247
- function sortDocuments(documents, sorts) {
2248
- return documents.toSorted((left, right) => {
2249
- for (const sort of sorts) {
2250
- const comparison = compareValues(documentField(left, sort.field), documentField(right, sort.field));
2251
- if (comparison !== 0) return sort.direction === "desc" ? -comparison : comparison;
2252
- }
2253
- return 0;
2254
- });
1291
+ function decodeUser(user, operation) {
1292
+ return decodeBetterAuthUser(user).pipe(Effect.mapError((cause) => unavailable$4(operation, cause)));
1293
+ }
1294
+ function findUser(adapter, userId) {
1295
+ return findRawUser(adapter, userId).pipe(Effect.flatMap((user) => user ? decodeUser(user, "BetterAuth.recovery.findUserById") : Effect.succeed(null)));
2255
1296
  }
2256
- function compareValues(left, right) {
2257
- if (left === right) return 0;
2258
- if (left === void 0 || left === null) return 1;
2259
- if (right === void 0 || right === null) return -1;
2260
- if (Schema.is(Schema.Finite)(left) && Schema.is(Schema.Finite)(right)) return left - right;
2261
- const leftText = Schema.is(Schema.String)(left) ? left : JSON.stringify(left) ?? "";
2262
- const rightText = Schema.is(Schema.String)(right) ? right : JSON.stringify(right) ?? "";
2263
- return leftText.localeCompare(rightText);
1297
+ function findRawUser(adapter, userId) {
1298
+ return providerCall("BetterAuth.recovery.findUserById", () => adapter.findUserById(userId), unavailable$4);
2264
1299
  }
2265
- function titleFromSegment(segment) {
2266
- return segment.replace(/[-_]+/gu, " ").replace(/^./u, (character) => character.toUpperCase());
1300
+ function findSessionFingerprints(adapter, userId) {
1301
+ return providerCall("BetterAuth.recovery.listSessions", () => adapter.listSessions(userId), unavailable$4).pipe(Effect.map((sessions) => sessions.map((session) => sessionFingerprint(session.id))));
2267
1302
  }
2268
1303
 
2269
1304
  //#endregion
2270
- //#region ../server/src/internal/documents/documents.ts
2271
- const isDocumentGenerationConflict$2 = Schema.is(DocumentGenerationConflict);
2272
- const isDocumentMediaUnavailable$1 = Schema.is(DocumentMediaUnavailable);
2273
- const CONTENT_MUTATION_BUFFER_CAPACITY = 16;
2274
- var Documents = class extends Context.Service()("@internal/server/Documents") {};
2275
- var DocumentPreviews = class extends Context.Service()("@internal/server/DocumentPreviews") {};
2276
- var PublishedDocuments = class extends Context.Service()("@internal/server/PublishedDocuments") {};
2277
- var DocumentMaintenance = class extends Context.Service()("@internal/server/internal/DocumentMaintenance") {};
2278
- const makeDocuments = Effect.fn("Documents.make")(function* (configuration, options) {
2279
- const repository = yield* DocumentStore;
2280
- const accessControl = yield* AccessControl;
2281
- const crypto = yield* Crypto.Crypto;
2282
- const engine = yield* createDocumentEngine(configuration, options);
2283
- const state = yield* Ref.make(yield* loadSynchronizedState(repository, engine));
2284
- const semaphore = yield* Semaphore.make(1);
2285
- const mutationEvents = yield* Effect.acquireRelease(PubSub.sliding(CONTENT_MUTATION_BUFFER_CAPACITY), PubSub.shutdown);
2286
- const dependencies = {
2287
- engine,
2288
- configuration,
2289
- repository
2290
- };
2291
- const refreshUnlocked = Effect.fn("Documents.refreshUnlocked")(function* (force = false) {
2292
- const current = yield* Ref.get(state);
2293
- const snapshot = force ? yield* loadRequired(repository) : yield* repository.load(current.snapshot.generation);
2294
- if (!snapshot || !generationIsAfter(snapshot.generation, current.snapshot.generation)) return current.snapshot.generation;
2295
- const refreshed = yield* compileDocumentState(engine, snapshot, current);
2296
- yield* Ref.set(state, refreshed);
2297
- return refreshed.snapshot.generation;
2298
- });
2299
- const executeUnlocked = Effect.fn("Documents.execute")(function* (command, activity) {
2300
- return yield* Effect.gen(function* () {
2301
- yield* refreshUnlocked();
2302
- const current = yield* Ref.get(state);
2303
- const prepared = yield* prepareDocumentMutation(dependencies, current, command);
2304
- if (prepared._tag === "Unchanged") return prepared.result;
2305
- const committed = yield* repository.commit({
2306
- activity,
2307
- plan: prepared.commitPlan,
2308
- expectedGeneration: current.snapshot.generation,
2309
- mediaReferences: projectMediaReferences(prepared.authored, prepared.published, commandDocumentId(command))
2310
- });
2311
- const next = makeDocumentState(prepared.authored, prepared.published, committed.snapshot);
2312
- yield* Ref.set(state, next);
2313
- const entry = entryById(next, committed.mutation.documentId);
2314
- const result = DocumentMutationResult.make(Object.assign({}, committed.mutation, entry === void 0 ? void 0 : { entry }));
2315
- yield* PubSub.publish(mutationEvents, result);
2316
- return result;
2317
- }).pipe(Effect.tapError((error) => isDocumentGenerationConflict$2(error) ? refreshUnlocked(true) : Effect.void), Effect.retry({
2318
- schedule: Schedule.recurs(3),
2319
- while: isDocumentGenerationConflict$2
2320
- }));
2321
- });
2322
- const runMutation = Effect.fn("Documents.runMutation")(function* (command, activity, reason) {
2323
- const resolvedActivity = enrichActivity(activity, reason);
2324
- return yield* semaphore.withPermits(1)(executeUnlocked(command, resolvedActivity));
2325
- });
2326
- const authored = semaphore.withPermits(1)(Effect.gen(function* () {
2327
- yield* refreshUnlocked();
2328
- return (yield* Ref.get(state)).authored;
2329
- })).pipe(Effect.withSpan("Documents.authored"));
2330
- const browse = Effect.fn("Documents.browse")((query) => semaphore.withPermits(1)(Effect.gen(function* () {
2331
- yield* refreshUnlocked();
2332
- return authoringEntries(yield* Ref.get(state), query);
2333
- })));
2334
- const create = Effect.fn("Documents.create")(function* (input, activity) {
2335
- const plan = yield* Effect.fromResult(planDocumentCreation(configuration, input));
2336
- const source = yield* createInitialDocumentSource(plan);
2337
- const documentId = DocumentId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
2338
- return yield* runMutation({
2339
- action: "document.create",
2340
- documentId,
2341
- markdown: source.markdown,
2342
- sourcePath: plan.sourcePath
2343
- }, activity, input.reason);
2344
- });
2345
- const generation = semaphore.withPermits(1)(Effect.gen(function* () {
2346
- yield* refreshUnlocked();
2347
- return (yield* Ref.get(state)).snapshot.generation;
1305
+ //#region ../auth/src/BetterAuth.ts
1306
+ function betterAuthLayer(options) {
1307
+ if (options.basePath !== void 0 && options.basePath !== "/api/cms/auth") throw new Error(`Authentication basePath must be ${CMS_AUTHENTICATION_PATH}.`);
1308
+ return Layer.effectContext(Effect.gen(function* () {
1309
+ const crypto = yield* Crypto.Crypto;
1310
+ const storage = yield* ActivityStore;
1311
+ const runPromise = Effect.runPromiseWith(yield* Effect.context());
1312
+ const auth = createCmsBetterAuth(options, betterAuthActivityOptions(betterAuthActivityWriter(storage, crypto), crypto, runPromise));
1313
+ const identity = betterAuthIdentity(auth);
1314
+ const recovery = betterAuthRecovery(auth);
1315
+ const initialization = betterAuthInitialIdentity(auth);
1316
+ const basePath = options.basePath ?? "/api/cms/auth";
1317
+ const handler = AuthenticationHandler.of({ handle: rejectExternalAdminRoutes(auth.handler, basePath) });
1318
+ return Context.empty().pipe(Context.add(Authentication, identity.authentication), Context.add(IdentityAdministration, identity.administration), Context.add(IdentityRecovery, recovery.users), Context.add(InitialIdentity, initialization), Context.add(InitialIdentityRecovery, recovery.initial), Context.add(AuthenticationHandler, handler));
2348
1319
  }));
2349
- const importSources = (input, activity) => importDocumentSources({
2350
- current: (sourcePath) => semaphore.withPermits(1)(Effect.gen(function* () {
2351
- yield* refreshUnlocked();
2352
- return (yield* Ref.get(state)).snapshot.active.find((candidate) => candidate.sourcePath === sourcePath);
2353
- })),
2354
- nextDocumentId: crypto.randomUUIDv4.pipe(Effect.orDie, Effect.map((value) => DocumentId.make(value))),
2355
- runMutation
2356
- }, input, activity);
2357
- const move = Effect.fn("Documents.move")((input, activity) => runMutation({
2358
- action: "document.move",
2359
- input
2360
- }, activity, input.reason));
2361
- const subscribeMutations = Effect.gen(function* () {
2362
- const subscription = yield* PubSub.subscribe(mutationEvents);
2363
- const baseline = yield* semaphore.withPermits(1)(Ref.get(state).pipe(Effect.map((current) => current.snapshot.generation)));
2364
- return {
2365
- baseline,
2366
- mutations: Stream.fromSubscription(subscription).pipe(Stream.filter((mutation) => generationIsAfter(mutation.generation, baseline)))
2367
- };
2368
- }).pipe(Effect.withSpan("Documents.subscribeMutations"));
2369
- const publish = Effect.fn("Documents.publish")((input, activity) => runMutation({
2370
- action: "document.publish",
2371
- input
2372
- }, activity, input.reason));
2373
- const published = semaphore.withPermits(1)(Effect.gen(function* () {
2374
- yield* refreshUnlocked();
2375
- return (yield* Ref.get(state)).published;
2376
- })).pipe(Effect.withSpan("Documents.published"));
2377
- const read = Effect.fn("Documents.read")((documentId) => semaphore.withPermits(1)(Effect.gen(function* () {
2378
- yield* refreshUnlocked();
2379
- return (yield* Ref.get(state)).activeById.get(documentId) || (yield* new DocumentNotFound({ documentId }));
2380
- })));
2381
- const refresh = semaphore.withPermits(1)(refreshUnlocked()).pipe(Effect.withSpan("Documents.refresh"));
2382
- const remove = Effect.fn("Documents.remove")((input, activity) => runMutation({
2383
- action: "document.remove",
2384
- input
2385
- }, activity, input.reason));
2386
- const restore = Effect.fn("Documents.restore")((input, activity) => runMutation({
2387
- action: "document.restore",
2388
- input
2389
- }, activity, input.reason));
2390
- const trash = semaphore.withPermits(1)(Effect.gen(function* () {
2391
- yield* refreshUnlocked();
2392
- return deletedEntries(yield* Ref.get(state));
2393
- })).pipe(Effect.withSpan("Documents.trash"));
2394
- const undelete = Effect.fn("Documents.undelete")((input, activity) => runMutation({
2395
- action: "document.undelete",
2396
- input
2397
- }, activity, input.reason));
2398
- const update = Effect.fn("Documents.update")((input, activity) => runMutation({
2399
- action: "document.update",
2400
- input
2401
- }, activity, input.reason));
2402
- const validate = Effect.fn("Documents.validate")((input) => semaphore.withPermits(1)(Effect.gen(function* () {
2403
- yield* refreshUnlocked();
2404
- return yield* validateDocumentUpdate(engine, yield* Ref.get(state), input);
2405
- })));
2406
- const history = documentHistoryCapability(accessControl, repository);
2407
- const delivery = publishedDocuments(configuration, published);
2408
- const document = (documentId) => ({
2409
- documentId,
2410
- kind: "document"
2411
- });
2412
- const requireThen = (context, action, resource, effect) => accessControl.require(context, {
2413
- action,
2414
- resource
2415
- }).pipe(Effect.andThen(effect));
2416
- const documents = Documents.of({
2417
- browse: (context, query) => requireThen(context, "document.browse", query.collection ? {
2418
- collection: query.collection,
2419
- kind: "collection",
2420
- locale: query.locale
2421
- } : { kind: "catalog" }, () => browse(query)),
2422
- create: (context, input) => requireThen(context, "document.create", {
2423
- collection: input.collection,
2424
- kind: "collection",
2425
- locale: input.locale
2426
- }, () => create(input, requestActivity(context, input.reason))),
2427
- historyCompare: history.compare,
2428
- historyList: history.list,
2429
- historyRead: history.read,
2430
- move: (context, input) => requireThen(context, "document.move", document(input.documentId), () => move(input, requestActivity(context, input.reason))),
2431
- publish: (context, input) => requireThen(context, "document.publish", document(input.documentId), () => publish(input, requestActivity(context, input.reason))),
2432
- read: (context, documentId) => requireThen(context, "document.read", document(documentId), () => read(documentId)),
2433
- remove: (context, input) => requireThen(context, "document.remove", document(input.documentId), () => remove(input, requestActivity(context, input.reason))),
2434
- restore: (context, input) => requireThen(context, "document.restore", document(input.documentId), () => restore(input, requestActivity(context, input.reason))),
2435
- trash: (context) => requireThen(context, "document.trash", { kind: "catalog" }, () => trash),
2436
- undelete: (context, input) => requireThen(context, "document.undelete", document(input.documentId), () => undelete(input, requestActivity(context, input.reason))),
2437
- update: (context, input) => requireThen(context, "document.update", document(input.documentId), () => update(input, requestActivity(context, input.reason))),
2438
- validate: (context, input) => requireThen(context, "document.validate", document(input.documentId), () => validate(input))
2439
- });
2440
- const previews = DocumentPreviews.of({
2441
- generation,
2442
- read: (lookup) => authored.pipe(Effect.map((catalog) => findPublishedDocument(catalog, lookup)))
2443
- });
2444
- const publishedService = PublishedDocuments.of({
2445
- navigation: delivery.navigation,
2446
- query: delivery.query,
2447
- read: delivery.read,
2448
- surround: delivery.surround,
2449
- diagnostics: (context) => accessControl.require(context, {
2450
- action: "document.browse",
2451
- resource: { kind: "catalog" }
2452
- }).pipe(Effect.andThen(delivery.diagnostics))
2453
- });
2454
- return {
2455
- documents,
2456
- maintenance: DocumentMaintenance.of({
2457
- importSources,
2458
- mediaReferences: (documentId) => semaphore.withPermits(1)(Effect.gen(function* () {
2459
- yield* refreshUnlocked();
2460
- const current = yield* Ref.get(state);
2461
- return projectMediaReferences(current.authored, current.published, documentId);
2462
- })),
2463
- publish,
2464
- read,
2465
- refresh,
2466
- subscribeMutations,
2467
- validate
2468
- }),
2469
- previews,
2470
- publishedDocuments: publishedService
2471
- };
2472
- });
2473
- function documentsLayer(configuration, options) {
2474
- 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)))));
2475
1320
  }
2476
- function loadRequired(repository) {
2477
- return repository.load().pipe(Effect.filterOrFail((snapshot) => snapshot !== void 0, () => new DocumentPersistenceError({
2478
- cause: /* @__PURE__ */ new Error("DocumentStore returned no initial snapshot."),
2479
- operation: "DocumentStore.load"
1321
+
1322
+ //#endregion
1323
+ //#region ../platform-node/src/internal/http/form-ingress.ts
1324
+ function handleFormRequest(request, handle) {
1325
+ return Effect.runPromise(Effect.gen(function* () {
1326
+ const ingress = yield* Effect.gen(function* () {
1327
+ const origin = request.headers.get("origin");
1328
+ if (origin && origin !== new URL(request.url).origin) return Response.json({ error: "Origin rejected." }, { status: 403 });
1329
+ if (request.method !== "POST") return new Response(null, { status: 405 });
1330
+ if (request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") return new Response(null, { status: 415 });
1331
+ const reader = request.body?.getReader();
1332
+ if (!reader) return new Response(null, { status: 400 });
1333
+ const chunks = [];
1334
+ let size = 0;
1335
+ try {
1336
+ while (true) {
1337
+ const chunk = yield* Effect.tryPromise(() => reader.read());
1338
+ if (chunk.done) break;
1339
+ size += chunk.value.byteLength;
1340
+ if (size > 131072) {
1341
+ yield* Effect.tryPromise(() => reader.cancel()).pipe(Effect.ignore);
1342
+ return new Response(null, { status: 413 });
1343
+ }
1344
+ chunks.push(chunk.value);
1345
+ }
1346
+ } finally {
1347
+ reader.releaseLock();
1348
+ }
1349
+ const body = new Uint8Array(size);
1350
+ let offset = 0;
1351
+ for (const chunk of chunks) {
1352
+ body.set(chunk, offset);
1353
+ offset += chunk.byteLength;
1354
+ }
1355
+ return body;
1356
+ }).pipe(Effect.orElseSucceed(() => new Response(null, { status: 400 })));
1357
+ if (ingress instanceof Response) return ingress;
1358
+ return yield* Effect.promise(() => handle(new Request(request.url, {
1359
+ method: "POST",
1360
+ headers: request.headers,
1361
+ body: ingress,
1362
+ signal: request.signal
1363
+ })));
1364
+ }).pipe(Effect.map((response) => {
1365
+ response.headers.set("cache-control", "no-store");
1366
+ return response;
2480
1367
  })));
2481
1368
  }
2482
- function loadSynchronizedState(repository, engine) {
2483
- return Effect.gen(function* () {
2484
- const snapshot = yield* loadRequired(repository);
2485
- const state = yield* compileDocumentState(engine, snapshot);
2486
- yield* repository.synchronizeMediaReferences({
2487
- expectedGeneration: snapshot.generation,
2488
- references: projectMediaReferences(state.authored, state.published)
1369
+
1370
+ //#endregion
1371
+ //#region ../server/src/Readiness.ts
1372
+ var ReadinessFailed = class extends Schema.TaggedError()("Cms.ReadinessFailed", { cause: Schema.Defect() }) {};
1373
+ var Readiness = class extends Context.Service()("@internal/server/Readiness") {};
1374
+ /** A stale unresolved external change makes the installation unavailable until reconciliation resolves it. */
1375
+ const readinessLayer = Layer.effect(Readiness, Effect.gen(function* () {
1376
+ const journal = yield* ChangeJournal;
1377
+ const check = Effect.gen(function* () {
1378
+ const now = yield* DateTime.now;
1379
+ const pending = yield* journal.pending({
1380
+ before: DateTime.subtractDuration(now, "30 seconds"),
1381
+ limit: 1
1382
+ }).pipe(Effect.mapError((cause) => new ReadinessFailed({ cause })));
1383
+ const oldest = pending.changes[0];
1384
+ if (pending.total === 0) return;
1385
+ if (oldest === void 0) return yield* new ReadinessFailed({ cause: /* @__PURE__ */ new Error("The pending change journal returned no change.") });
1386
+ yield* Effect.logWarning("CMS readiness found a stale pending change.", {
1387
+ action: oldest.action,
1388
+ ageMillis: DateTime.toEpochMillis(now) - DateTime.toEpochMillis(oldest.updatedAt),
1389
+ changeId: oldest.id,
1390
+ phase: "pending"
2489
1391
  });
2490
- return state;
2491
- }).pipe(Effect.retry({
2492
- schedule: Schedule.recurs(3),
2493
- while: isDocumentGenerationConflict$2
2494
- }), Effect.mapError((error) => isDocumentGenerationConflict$2(error) || isDocumentMediaUnavailable$1(error) ? new DocumentPersistenceError({
2495
- cause: error,
2496
- operation: "Documents.synchronizeMediaReferences"
2497
- }) : error));
2498
- }
2499
- function commandDocumentId(command) {
2500
- return command.action === "document.create" ? command.documentId : command.input.documentId;
2501
- }
2502
- function generationIsAfter(candidate, current) {
2503
- return BigInt(candidate) > BigInt(current);
2504
- }
2505
- function enrichActivity(activity, reason) {
2506
- return ActivityContext.make(Object.assign({}, activity, reason === void 0 ? void 0 : { reason }));
2507
- }
2508
- function requestActivity(context, reason) {
2509
- return ActivityContext.make(Object.assign({
2510
- actor: context.actor,
2511
- requestId: context.requestId
2512
- }, reason === void 0 ? void 0 : { reason }));
2513
- }
1392
+ return yield* new ReadinessFailed({ cause: /* @__PURE__ */ new Error("A stale change remains pending.") });
1393
+ }).pipe(Effect.withSpan("Installation.readiness"));
1394
+ return Readiness.of({ check });
1395
+ }));
1396
+
1397
+ //#endregion
1398
+ //#region ../server/src/internal/working-documents/working-document-model.ts
1399
+ const WorkingDocumentGenerationId = Schema.String.check(Schema.isUUID()).pipe(Schema.brand("WorkingDocumentGenerationId"));
1400
+ const WorkingDocumentUpdateSequence = Schema.Int.check(Schema.isGreaterThan(0)).pipe(Schema.brand("WorkingDocumentUpdateSequence"));
1401
+ const WorkingDocumentHighWatermark = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)).pipe(Schema.brand("WorkingDocumentHighWatermark"));
1402
+ var WorkingDocumentPersistenceError = class extends Schema.TaggedError()("WorkingDocuments.PersistenceError", {
1403
+ operation: Schema.NonEmptyString,
1404
+ cause: Schema.Defect()
1405
+ }) {};
1406
+ var WorkingDocumentSuperseded = class extends Schema.TaggedError()("WorkingDocuments.Superseded", {
1407
+ documentId: DocumentId,
1408
+ expectedGeneration: WorkingDocumentGenerationId,
1409
+ actualGeneration: Schema.optionalKey(WorkingDocumentGenerationId)
1410
+ }) {};
1411
+ var WorkingDocumentConflict = class extends Schema.TaggedError()("WorkingDocuments.Conflict", {
1412
+ documentId: DocumentId,
1413
+ operation: Schema.NonEmptyString,
1414
+ message: Schema.NonEmptyString
1415
+ }) {};
1416
+
1417
+ //#endregion
1418
+ //#region ../server/src/internal/working-documents/working-document.ts
1419
+ const WorkingDocumentRef = Schema.Struct({
1420
+ documentId: DocumentId,
1421
+ generationId: WorkingDocumentGenerationId
1422
+ });
1423
+ var WorkingDocumentInvalid = class extends Schema.TaggedError()("WorkingDocuments.Invalid", {
1424
+ documentId: DocumentId,
1425
+ message: Schema.NonEmptyString,
1426
+ cause: Schema.Defect()
1427
+ }) {};
1428
+ var WorkingDocumentUnavailable = class extends Schema.TaggedError()("WorkingDocuments.Unavailable", {
1429
+ operation: Schema.NonEmptyString,
1430
+ cause: Schema.Defect()
1431
+ }) {};
1432
+ /** An authored or working baseline committed, but the publish workflow still needs recovery. */
1433
+ var WorkingDocumentCheckpointed = class extends Schema.TaggedError()("WorkingDocuments.Checkpointed", {
1434
+ checkpointVersion: DocumentVersion,
1435
+ documentId: DocumentId,
1436
+ result: Schema.optionalKey(DocumentMutationResult),
1437
+ stateVector: Schema.Uint8Array,
1438
+ cause: Schema.Defect()
1439
+ }) {};
1440
+ const WorkingDocumentChange = Schema.Struct({
1441
+ kind: Schema.Literals(["updated", "invalidated"]),
1442
+ ref: WorkingDocumentRef
1443
+ });
1444
+ var WorkingDocuments = class extends Context.Service()("@internal/server/WorkingDocuments") {};
2514
1445
 
2515
1446
  //#endregion
2516
1447
  //#region ../document/src/internal/editor/identity-extension.ts
2517
- /** Ensures Yjs only observes authorable nodes with final, unique identities. */
2518
1448
  const EditorNodeIdentity = Extension.create({
2519
1449
  name: "cmsNodeIdentity",
2520
1450
  priority: 1e4,
@@ -3063,7 +1993,7 @@ function unwrapFirstParagraph(nodes) {
3063
1993
  }
3064
1994
  function restoreOpaqueNode(value, nodeId) {
3065
1995
  const decoded = Schema.decodeUnknownOption(MarkdownNode)(value);
3066
- if (decoded._tag === "None" || Predicate.isString(decoded.value)) return void 0;
1996
+ if (Option.isNone(decoded) || Predicate.isString(decoded.value)) return void 0;
3067
1997
  const node = structuredClone(decoded.value);
3068
1998
  if (nodeId === void 0) return node;
3069
1999
  if (node[0] === null) return [
@@ -3173,10 +2103,10 @@ const PROPERTIES_NAME = "properties";
3173
2103
  const PROPS_PREFIX = "props:";
3174
2104
  const CMS_PROPS_PREFIX = "cmsProps:";
3175
2105
  const editorSchema$1 = createEditorSchema();
3176
- const decodeEditorNodeId = Schema.decodeUnknownSync(EditorNodeId);
3177
- const decodeJson$1 = Schema.decodeUnknownSync(Schema.Json);
3178
- const decodeJsonObject$1 = Schema.decodeUnknownSync(Schema.JsonObject);
3179
- const decodePath = Schema.decodeUnknownSync(Schema.Array(Schema.String));
2106
+ const decodeEditorNodeId = flow(Schema.decodeUnknownResult(EditorNodeId), Result.getOrThrow);
2107
+ const decodeJson$1 = flow(Schema.decodeUnknownResult(Schema.Json), Result.getOrThrow);
2108
+ const decodeJsonObject$1 = flow(Schema.decodeUnknownResult(Schema.JsonObject), Result.getOrThrow);
2109
+ const decodePath = flow(Schema.decodeUnknownResult(Schema.Array(Schema.String)), Result.getOrThrow);
3180
2110
  function seedCollaborationFields(document, content) {
3181
2111
  const state = extractGranularState(content);
3182
2112
  replaceMap(document.getMap(FRONTMATTER_NAME), flatten(state.frontmatter));
@@ -3220,7 +2150,7 @@ function extractGranularState(content) {
3220
2150
  const nodeId = optionalEditorNodeId(node.attrs?.cmsNodeId);
3221
2151
  if (nodeId === void 0) return;
3222
2152
  const values = Object.assign({}, prefixValues(optionalJsonObject(node.attrs?.props), PROPS_PREFIX), prefixValues(optionalJsonObject(node.attrs?.cmsProps), CMS_PROPS_PREFIX));
3223
- if (Object.keys(values).length > 0) properties.set(nodeId, values);
2153
+ if (Object.hasOwn(node.attrs ?? {}, "props") || Object.hasOwn(node.attrs ?? {}, "cmsProps")) properties.set(nodeId, values);
3224
2154
  });
3225
2155
  return {
3226
2156
  frontmatter,
@@ -3245,7 +2175,7 @@ function flatten(value) {
3245
2175
  }
3246
2176
  function unflatten(value) {
3247
2177
  const result = {};
3248
- for (const [encodedPath, child] of Object.entries(value)) {
2178
+ for (const [encodedPath, child] of Object.entries(value).sort(([left], [right]) => decodePath(JSON.parse(left)).length - decodePath(JSON.parse(right)).length || left.localeCompare(right))) {
3249
2179
  const path = decodePath(JSON.parse(encodedPath));
3250
2180
  if (path.length === 0) continue;
3251
2181
  let current = result;
@@ -3268,10 +2198,10 @@ function mapValues(map) {
3268
2198
  return decodeJsonObject$1(Object.fromEntries([...map.entries()].map(([key, value]) => [key, structuredClone(decodeJson$1(value))])));
3269
2199
  }
3270
2200
  function prefixValues(value, prefix) {
3271
- return decodeJsonObject$1(Object.fromEntries(Object.entries(value).map(([key, child]) => [`${prefix}${key}`, structuredClone(child)])));
2201
+ return decodeJsonObject$1(Object.fromEntries(Object.entries(flatten(encodeLists(value))).map(([key, child]) => [`${prefix}${key}`, structuredClone(child)])));
3272
2202
  }
3273
2203
  function prefixedValues(value, prefix) {
3274
- return decodeJsonObject$1(Object.fromEntries(Object.entries(value).filter(([key]) => key.startsWith(prefix)).map(([key, child]) => [key.slice(prefix.length), structuredClone(child)])));
2204
+ return decodeJsonObject$1(decodeLists(unflatten(Object.fromEntries(Object.entries(value).filter(([key]) => key.startsWith(prefix)).map(([key, child]) => [key.slice(prefix.length).startsWith("[") ? key.slice(prefix.length) : JSON.stringify([key.slice(prefix.length)]), structuredClone(child)])))));
3275
2205
  }
3276
2206
  function optionalJsonObject(value) {
3277
2207
  return value === null || value === void 0 ? {} : structuredClone(decodeJsonObject$1(value));
@@ -3291,6 +2221,33 @@ function define(target, key, value) {
3291
2221
  writable: true
3292
2222
  });
3293
2223
  }
2224
+ const listMarker = "\0cmsList";
2225
+ function encodeLists(value) {
2226
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => {
2227
+ if (Array.isArray(child) && child.every((item) => isJsonObject(item) && Predicate.isString(item.id))) return [key, {
2228
+ [listMarker]: true,
2229
+ items: Object.fromEntries(child.flatMap((item, order) => isJsonObject(item) && Predicate.isString(item.id) ? [[item.id, {
2230
+ order,
2231
+ value: encodeLists(item)
2232
+ }]] : []))
2233
+ }];
2234
+ return [key, isJsonObject(child) ? encodeLists(child) : child];
2235
+ }));
2236
+ }
2237
+ function decodeLists(value) {
2238
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => {
2239
+ if (!isJsonObject(child)) return [key, child];
2240
+ if (child[listMarker] === true) return [key, (isJsonObject(child.items) ? Object.entries(child.items) : []).flatMap(([id, item]) => {
2241
+ if (!isJsonObject(item) || !isJsonObject(item.value) || item.value.id !== id || !Predicate.isNumber(item.order)) return [];
2242
+ return [{
2243
+ id,
2244
+ order: item.order,
2245
+ value: decodeLists(item.value)
2246
+ }];
2247
+ }).sort((left, right) => left.order - right.order || left.id.localeCompare(right.id)).map((item) => item.value)];
2248
+ return [key, decodeLists(child)];
2249
+ }));
2250
+ }
3294
2251
 
3295
2252
  //#endregion
3296
2253
  //#region ../document/src/internal/collaboration/document.ts
@@ -3299,9 +2256,9 @@ const METADATA_NAME = "cms";
3299
2256
  const META_KEY = "meta";
3300
2257
  const SOURCE_PATH_KEY = "sourcePath";
3301
2258
  const editorSchema = createEditorSchema();
3302
- const decodeJson = Schema.decodeUnknownSync(Schema.Json);
3303
- const decodeJsonObject = Schema.decodeUnknownSync(Schema.JsonObject);
3304
- const decodeSourcePath = Schema.decodeUnknownSync(SourcePath);
2259
+ const decodeJson = flow(Schema.decodeUnknownResult(Schema.Json), Result.getOrThrow);
2260
+ const decodeJsonObject = flow(Schema.decodeUnknownResult(Schema.JsonObject), Result.getOrThrow);
2261
+ const decodeSourcePath = flow(Schema.decodeUnknownResult(SourcePath), Result.getOrThrow);
3305
2262
  function seedCollaborationDocument(sourcePath, seed, meta) {
3306
2263
  const document = prosemirrorJSONToYDoc(editorSchema, seed, BODY_NAME);
3307
2264
  try {
@@ -3398,7 +2355,6 @@ function sameBytes$1(left, right) {
3398
2355
 
3399
2356
  //#endregion
3400
2357
  //#region ../document/src/CollaborationDocument.ts
3401
- /** Creates the initial collaborative state from the canonical editor projection. */
3402
2358
  const createCollaborationDocument = Effect.fn("CollaborationDocument.create")(function* (input) {
3403
2359
  const seed = yield* openEditorDocument(input.body);
3404
2360
  return yield* Effect.try({
@@ -3406,7 +2362,6 @@ const createCollaborationDocument = Effect.fn("CollaborationDocument.create")(fu
3406
2362
  catch: (cause) => invalidCollaboration("Could not create the collaborative editor document.", cause)
3407
2363
  });
3408
2364
  });
3409
- /** Renders collaborative state through the canonical editor-to-Markdown encoder. */
3410
2365
  const renderCollaborationDocument = Effect.fn("CollaborationDocument.render")(function* (document) {
3411
2366
  const inspected = yield* inspectCollaborationDocument(document);
3412
2367
  return {
@@ -3425,31 +2380,26 @@ const captureCollaborationDocument = Effect.fn("CollaborationDocument.capture")(
3425
2380
  catch: (cause) => invalidCollaboration("Could not capture the collaborative editor document.", cause)
3426
2381
  });
3427
2382
  });
3428
- /** Restores a validated, caller-owned document from a full collaboration snapshot. */
3429
2383
  const restoreCollaborationDocument = Effect.fn("CollaborationDocument.restore")(function* (snapshot) {
3430
2384
  return yield* validateRestoredDocument(yield* Effect.try({
3431
2385
  try: () => restoreCollaborationSnapshot(snapshot),
3432
2386
  catch: (cause) => invalidCollaboration("Could not restore the collaborative editor document.", cause)
3433
2387
  }));
3434
2388
  });
3435
- /** Restores a validated document when only its authoritative full-state update is persisted. */
3436
2389
  const restoreCollaborationState = Effect.fn("CollaborationDocument.restoreState")(function* (state) {
3437
2390
  return yield* validateRestoredDocument(yield* Effect.try({
3438
2391
  try: () => restoreCollaborationUpdate(state),
3439
2392
  catch: (cause) => invalidCollaboration("Could not restore the collaborative editor document.", cause)
3440
2393
  }));
3441
2394
  });
3442
- /** Applies one encoded collaboration update while translating corrupt data to a typed failure. */
3443
2395
  const applyCollaborationUpdate = Effect.fn("CollaborationDocument.applyUpdate")((document, update) => Effect.try({
3444
2396
  try: () => applyCollaborationStateUpdate(document, update),
3445
2397
  catch: (cause) => invalidCollaboration("Could not apply the collaborative document update.", cause)
3446
2398
  }));
3447
- /** Captures the current state vector and an optional differential update. */
3448
2399
  const synchronizeCollaborationState = Effect.fn("CollaborationDocument.synchronizeState")((document, stateVector) => Effect.try({
3449
2400
  try: () => synchronizeCollaborationStateValue(document, stateVector),
3450
2401
  catch: (cause) => invalidCollaboration("Could not synchronize the collaborative document state.", cause)
3451
2402
  }));
3452
- /** Releases a collaboration document owned by a scoped workflow. */
3453
2403
  function destroyCollaborationDocument(document) {
3454
2404
  return Effect.sync(() => document.destroy());
3455
2405
  }
@@ -3550,6 +2500,25 @@ function sameJson(left, right) {
3550
2500
  return JSON.stringify(left) === JSON.stringify(right);
3551
2501
  }
3552
2502
 
2503
+ //#endregion
2504
+ //#region ../server/src/internal/working-documents/working-document-source.ts
2505
+ const validatedWorkingDocument = Effect.fn("WorkingDocuments.validateSource")(function* (source, validate) {
2506
+ const validation = yield* validate(UpdateDocumentInput.make({
2507
+ documentId: source.documentId,
2508
+ expectedVersion: source.version,
2509
+ markdown: source.markdown
2510
+ }));
2511
+ if (!validation.valid || !validation.document) return yield* new WorkingDocumentInvalid({
2512
+ cause: validation.issues,
2513
+ documentId: source.documentId,
2514
+ message: "Authored document cannot initialize a Working Document."
2515
+ });
2516
+ return validation.document;
2517
+ });
2518
+ const newWorkingSnapshot = Effect.fn("WorkingDocuments.newSnapshot")(function* (source, document, generationId) {
2519
+ return yield* createWorkingSnapshot(source, document, yield* generationId(source.documentId));
2520
+ });
2521
+
3553
2522
  //#endregion
3554
2523
  //#region ../server/src/internal/working-documents/working-document-reconciliation.ts
3555
2524
  const isDocumentNotFound$3 = Schema.is(DocumentNotFound);
@@ -3564,18 +2533,6 @@ const startWorkingDocumentReconciliation = Effect.fn("WorkingDocuments.startReco
3564
2533
  })
3565
2534
  }).pipe(Effect.asVoid);
3566
2535
  const currentSource = (documentId) => options.documents.read(documentId).pipe(Effect.asSome, Effect.catchIf(isDocumentNotFound$3, () => Effect.succeedNone));
3567
- const validatedDocument = Effect.fn("WorkingDocuments.reconcile.validateSource")(function* (source) {
3568
- const validation = yield* options.documents.validate(UpdateDocumentInput.make({
3569
- documentId: source.documentId,
3570
- expectedVersion: source.version,
3571
- markdown: source.markdown
3572
- }));
3573
- if (!validation.valid || !validation.document) return yield* invalidSource(source.documentId, validation);
3574
- return validation.document;
3575
- });
3576
- const newSnapshot = Effect.fn("WorkingDocuments.reconcile.newSnapshot")(function* (source, document) {
3577
- return yield* createWorkingSnapshot(source, document, yield* options.generationId(source.documentId));
3578
- });
3579
2536
  const rotateDirty = Effect.fn("WorkingDocuments.reconcile.rotateDirty")(function* (snapshot) {
3580
2537
  const generationId = yield* options.generationId(snapshot.documentId);
3581
2538
  const seed = yield* useWorkingSnapshot(snapshot, (document) => captureCollaborationDocument(document).pipe(Effect.map((captured) => rotatedSeed(snapshot, generationId, captured))));
@@ -3611,10 +2568,10 @@ const startWorkingDocumentReconciliation = Effect.fn("WorkingDocuments.startReco
3611
2568
  yield* publishChange(loaded.value);
3612
2569
  return;
3613
2570
  }
3614
- const document = yield* validatedDocument(source.value);
2571
+ const document = yield* validatedWorkingDocument(source.value, options.documents.validate);
3615
2572
  yield* options.store.replace({
3616
2573
  expectedGeneration: loaded.value.generationId,
3617
- snapshot: yield* newSnapshot(source.value, document)
2574
+ snapshot: yield* newWorkingSnapshot(source.value, document, options.generationId)
3618
2575
  });
3619
2576
  yield* publishChange(loaded.value);
3620
2577
  }));
@@ -3644,6 +2601,7 @@ const startWorkingDocumentReconciliation = Effect.fn("WorkingDocuments.startReco
3644
2601
  return [true, {
3645
2602
  ...current,
3646
2603
  mutation: result,
2604
+ sweepRequired: current.sweepRequired || (result.affectedDocumentIds?.length ?? 0) > 1,
3647
2605
  target: result.generation
3648
2606
  }];
3649
2607
  })) yield* Queue.offer(wakeups, void 0);
@@ -3699,13 +2657,6 @@ function rotatedSeed(snapshot, generationId, captured) {
3699
2657
  state: Uint8Array.from(captured.state)
3700
2658
  };
3701
2659
  }
3702
- function invalidSource(documentId, validation) {
3703
- return Effect.fail(new WorkingDocumentInvalid({
3704
- cause: validation.issues,
3705
- documentId,
3706
- message: "Authored document cannot initialize a Working Document."
3707
- }));
3708
- }
3709
2660
  function generationAtOrBefore(candidate, current) {
3710
2661
  return BigInt(candidate) <= BigInt(current);
3711
2662
  }
@@ -3713,6 +2664,28 @@ function generationsAreAdjacent(current, candidate) {
3713
2664
  return BigInt(candidate) === BigInt(current) + 1n;
3714
2665
  }
3715
2666
 
2667
+ //#endregion
2668
+ //#region ../server/src/internal/working-documents/document-locks.ts
2669
+ /** Retain a lock only while an operation is running or waiting for that document. */
2670
+ function documentSynchronization() {
2671
+ const locks = /* @__PURE__ */ new Map();
2672
+ return (documentId, effect) => Effect.acquireUseRelease(Effect.sync(() => {
2673
+ let lock = locks.get(documentId);
2674
+ if (!lock) {
2675
+ lock = {
2676
+ semaphore: Semaphore.makeUnsafe(1),
2677
+ users: 0
2678
+ };
2679
+ locks.set(documentId, lock);
2680
+ }
2681
+ lock.users += 1;
2682
+ return lock;
2683
+ }), (lock) => lock.semaphore.withPermit(effect), (lock) => Effect.sync(() => {
2684
+ lock.users -= 1;
2685
+ if (lock.users === 0 && locks.get(documentId) === lock) locks.delete(documentId);
2686
+ }));
2687
+ }
2688
+
3716
2689
  //#endregion
3717
2690
  //#region ../server/src/internal/working-documents/working-documents.ts
3718
2691
  function workingDocumentsLayer(options = {}) {
@@ -3726,31 +2699,11 @@ const makeWorkingDocuments = Effect.fn("WorkingDocuments.make")(function* (optio
3726
2699
  const store = yield* WorkingDocumentStore;
3727
2700
  const changeSource = yield* WorkingDocumentChanges;
3728
2701
  const changes = yield* Effect.acquireRelease(PubSub.bounded(64), PubSub.shutdown);
3729
- const locks = /* @__PURE__ */ new Map();
2702
+ const synchronized = documentSynchronization();
3730
2703
  const compactAfterUpdates = validCompactionLimit(options.compactAfterUpdates);
3731
- const synchronized = (documentId, effect) => Effect.suspend(() => {
3732
- let semaphore = locks.get(documentId);
3733
- if (!semaphore) {
3734
- semaphore = Semaphore.makeUnsafe(1);
3735
- locks.set(documentId, semaphore);
3736
- }
3737
- return semaphore.withPermit(effect);
3738
- });
3739
2704
  const generationId = (documentId) => crypto.randomUUIDv4.pipe(Effect.map((uuid) => WorkingDocumentGenerationId.make(uuid)), Effect.mapError((cause) => invalid(documentId, "Could not create a working document generation.", cause)));
3740
2705
  const attemptId = (documentId) => crypto.randomUUIDv4.pipe(Effect.map((uuid) => WorkingDocumentPublishAttemptId.make(uuid)), Effect.mapError((cause) => invalid(documentId, "Could not create a publish attempt.", cause)));
3741
2706
  const markdownHash = (documentId, markdown) => crypto.digest("SHA-256", new TextEncoder().encode(markdown)).pipe(Effect.map(hex), Effect.mapError((cause) => invalid(documentId, "Could not fingerprint Working Document Markdown.", cause)));
3742
- const validatedDocument = Effect.fn("WorkingDocuments.validateSource")(function* (source) {
3743
- const validation = yield* maintenance.validate(UpdateDocumentInput.make({
3744
- documentId: source.documentId,
3745
- expectedVersion: source.version,
3746
- markdown: source.markdown
3747
- }));
3748
- if (!validation.valid || !validation.document) return yield* invalid(source.documentId, "Authored document cannot initialize a Working Document.", validation.issues);
3749
- return validation.document;
3750
- });
3751
- const newSnapshot = Effect.fn("WorkingDocuments.newSnapshot")(function* (source, document) {
3752
- return yield* createWorkingSnapshot(source, document, yield* generationId(source.documentId));
3753
- });
3754
2707
  const compactSnapshot = Effect.fn("WorkingDocuments.compactSnapshot")(function* (snapshot) {
3755
2708
  const lastUpdate = snapshot.updates.at(-1);
3756
2709
  if (!lastUpdate) return false;
@@ -3830,21 +2783,21 @@ const makeWorkingDocuments = Effect.fn("WorkingDocuments.make")(function* (optio
3830
2783
  const ensureOpenSnapshot = Effect.fn("WorkingDocuments.ensureOpenSnapshot")(function* (source) {
3831
2784
  const loaded = yield* store.load(source.documentId);
3832
2785
  if (Option.isNone(loaded)) {
3833
- const document = yield* validatedDocument(source);
3834
- const initialized = yield* store.initialize(yield* newSnapshot(source, document));
2786
+ const document = yield* validatedWorkingDocument(source, maintenance.validate);
2787
+ const initialized = yield* store.initialize(yield* newWorkingSnapshot(source, document, generationId));
3835
2788
  if (initialized.savedVersion === source.version) return initialized;
3836
2789
  if (yield* workingSnapshotIsDirty(initialized)) return initialized;
3837
2790
  return yield* store.replace({
3838
2791
  expectedGeneration: initialized.generationId,
3839
- snapshot: yield* newSnapshot(source, document)
2792
+ snapshot: yield* newWorkingSnapshot(source, document, generationId)
3840
2793
  });
3841
2794
  }
3842
2795
  if (loaded.value.savedVersion === source.version) return loaded.value;
3843
2796
  if (yield* workingSnapshotIsDirty(loaded.value)) return loaded.value;
3844
- const document = yield* validatedDocument(source);
2797
+ const document = yield* validatedWorkingDocument(source, maintenance.validate);
3845
2798
  return yield* store.replace({
3846
2799
  expectedGeneration: loaded.value.generationId,
3847
- snapshot: yield* newSnapshot(source, document)
2800
+ snapshot: yield* newWorkingSnapshot(source, document, generationId)
3848
2801
  });
3849
2802
  });
3850
2803
  const open = (context, documentId) => synchronized(documentId, Effect.gen(function* () {
@@ -4068,7 +3021,7 @@ function closeHocuspocus(hocuspocus, operations) {
4068
3021
  const store = hocuspocus.debouncer.executeNow(`onStoreDocument-${document.name}`);
4069
3022
  return store === void 0 ? [] : [store];
4070
3023
  });
4071
- yield* Effect.promise(() => Promise.allSettled(stores));
3024
+ const storeResults = yield* Effect.promise(() => Promise.allSettled(stores));
4072
3025
  yield* Effect.promise(operations.drain);
4073
3026
  const cleanupResults = yield* Effect.promise(() => Promise.allSettled(documents.map((document) => document.saveMutex.runExclusive(() => Promise.resolve()).then(() => hocuspocus.unloadDocument(document)))));
4074
3027
  const destroyResult = yield* Effect.result(Effect.tryPromise({
@@ -4076,6 +3029,8 @@ function closeHocuspocus(hocuspocus, operations) {
4076
3029
  catch: (cause) => gatewayError("close", cause)
4077
3030
  }).pipe(Effect.asVoid));
4078
3031
  const cleanupFailure = cleanupResults.find((result) => result.status === "rejected");
3032
+ const storeFailure = storeResults.find((result) => result.status === "rejected");
3033
+ if (storeFailure?.status === "rejected") return yield* gatewayError("close", storeFailure.reason);
4079
3034
  if (cleanupFailure?.status === "rejected") return yield* gatewayError("close", cleanupFailure.reason);
4080
3035
  if (Result.isFailure(destroyResult)) return yield* destroyResult.failure;
4081
3036
  });
@@ -4089,7 +3044,6 @@ function gatewayError(operation, cause) {
4089
3044
 
4090
3045
  //#endregion
4091
3046
  //#region ../platform-node/src/internal/realtime/keyed-operations.ts
4092
- /** Serializes each room independently and retains poisoned writes for shutdown reporting. */
4093
3047
  function createKeyedOperations(options) {
4094
3048
  const pending = /* @__PURE__ */ new Map();
4095
3049
  const failures = /* @__PURE__ */ new Map();
@@ -4183,7 +3137,6 @@ function stableStringHash(value) {
4183
3137
  //#region ../platform-node/src/internal/realtime/wake-dispatcher.ts
4184
3138
  const DEFAULT_CAPACITY = 1024;
4185
3139
  const DEFAULT_CONCURRENCY = 32;
4186
- /** Bounded wake scheduling: duplicate rooms coalesce and unrelated rooms run concurrently. */
4187
3140
  const runStudioRealtimeWakeDispatcher = Effect.fn("StudioRealtimeWakeDispatcher.run")(function* (wakes, handle, options = {}) {
4188
3141
  const queue = yield* Queue.dropping(options.capacity ?? DEFAULT_CAPACITY);
4189
3142
  const queued = /* @__PURE__ */ new Set();
@@ -4396,7 +3349,7 @@ const makeStudioRealtimeRoomHost = Effect.fn("StudioRealtimeRoomHost.make")(func
4396
3349
  },
4397
3350
  onStateless: (payload) => runTracked(Effect.gen(function* () {
4398
3351
  const decoded = yield* Effect.option(decodeClientMessage(payload.payload));
4399
- if (decoded._tag === "None" || decoded.value.type !== "checkpoint") return;
3352
+ if (Option.isNone(decoded) || decoded.value.type !== "checkpoint") return;
4400
3353
  const name = yield* decodeRoomName(payload.documentName).pipe(Effect.mapError((cause) => roomHostError("checkpoint", cause)));
4401
3354
  const stateVector = Y.encodeStateVector(payload.document);
4402
3355
  yield* Effect.tryPromise({
@@ -4529,7 +3482,9 @@ function decodeBytes(value, tokenInvalid) {
4529
3482
  try: () => {
4530
3483
  const normalized = value.replaceAll("-", "+").replaceAll("_", "/");
4531
3484
  const binary = atob(normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "="));
4532
- return Uint8Array.from(binary, (character) => character.codePointAt(0) ?? 0);
3485
+ const bytes = Uint8Array.from(binary, (character) => character.codePointAt(0) ?? 0);
3486
+ if (encodeBytes(bytes) !== value) throw new Error("Noncanonical base64url encoding");
3487
+ return bytes;
4533
3488
  },
4534
3489
  catch: tokenInvalid
4535
3490
  });
@@ -4671,7 +3626,7 @@ const makeStudioRealtimeGateway = Effect.gen(function* () {
4671
3626
  onStateless: (payload) => {
4672
3627
  return runTracked(Effect.gen(function* () {
4673
3628
  const decoded = yield* Effect.option(decodeClientMessage(payload.payload));
4674
- if (decoded._tag === "None") return;
3629
+ if (Option.isNone(decoded)) return;
4675
3630
  const request = decoded.value;
4676
3631
  const context = authenticatedContext(payload.connection.context, payload.documentName);
4677
3632
  if (request.type === "checkpoint") return;
@@ -4881,11 +3836,10 @@ function httpProtocol(protocol) {
4881
3836
  * StudioRealtimeRoomHost synchronizes durable Yjs state across replicas. Presence
4882
3837
  * and stateless broadcasts remain process-local until an ephemeral bus is added.
4883
3838
  */
4884
- const studioRealtimeGatewayLayer = Layer.effect(StudioRealtimeGateway, Effect.acquireRelease(makeStudioRealtimeGateway, (gateway) => gateway.close.pipe(Effect.catchCause((cause) => Effect.logError("Studio realtime gateway shutdown failed.", cause)))));
3839
+ const studioRealtimeGatewayLayer = Layer.effect(StudioRealtimeGateway, Effect.acquireRelease(makeStudioRealtimeGateway, (gateway) => gateway.close.pipe(Effect.tapError((error) => Effect.logError("Studio realtime gateway shutdown failed.", error)), Effect.orDie)));
4885
3840
 
4886
3841
  //#endregion
4887
3842
  //#region ../platform-node/src/internal/realtime/websocket.ts
4888
- /** Hides Hocuspocus connection bookkeeping from framework WebSocket adapters. */
4889
3843
  function studioRealtimeWebSocketBridge(gateway) {
4890
3844
  const connections = /* @__PURE__ */ new WeakMap();
4891
3845
  const release = (socket) => {
@@ -4910,21 +3864,25 @@ function studioRealtimeWebSocketBridge(gateway) {
4910
3864
  //#endregion
4911
3865
  //#region ../platform-node/src/internal/installation/host.ts
4912
3866
  var Installation = class extends Context.Service()("@internal/platform-node/Installation") {};
4913
- function installationHttpLayer(bootstrap, application, cleanup) {
3867
+ function installationHttpLayer(bootstrap, cleanup) {
4914
3868
  return Layer.effect(Installation, Effect.gen(function* () {
4915
- const outerContext = yield* Effect.context();
4916
3869
  const cleanupContext = yield* Effect.context();
4917
- const memoMap = Context.getUnsafe(outerContext, Layer.CurrentMemoMap);
4918
- const webHandler = yield* Effect.acquireRelease(Effect.sync(() => HttpRouter.toWebHandler(application, { memoMap })), ({ dispose }) => Effect.promise(dispose));
4919
- return yield* makeInstallation(bootstrap, webHandler.handler, (context) => cleanup(context).pipe(Effect.provide(cleanupContext)));
3870
+ const routerConfig = yield* HttpRouter.RouterConfig;
3871
+ const router = yield* HttpRouter.HttpRouter;
3872
+ const handle = HttpEffect.toWebHandlerWith(yield* Effect.context())(router.asHttpEffect(), HttpMiddleware.logger);
3873
+ return yield* makeInstallation(bootstrap, handle, (context) => cleanup(context).pipe(Effect.provide(cleanupContext)), routerConfig);
4920
3874
  }));
4921
3875
  }
4922
- const makeInstallation = Effect.fn("Installation.make")(function* (bootstrap, handle, cleanup) {
3876
+ const makeInstallation = Effect.fn("Installation.make")(function* (bootstrap, handle, cleanup, routerConfig) {
4923
3877
  const authentication = yield* AuthenticationHandler;
4924
3878
  const crypto = yield* Crypto.Crypto;
4925
3879
  const realtime = yield* StudioRealtimeGateway;
4926
3880
  const roomHost = yield* StudioRealtimeRoomHost;
4927
- const health = (yield* Readiness).check.pipe(Effect.andThen(roomHost.check), Effect.match({
3881
+ const readiness = yield* Readiness;
3882
+ const protectedRoutes = FindMyWay.make(routerConfig);
3883
+ protectedRoutes.all(CMS_FORM_SUBMIT_PATH, "form");
3884
+ protectedRoutes.all(`${STUDIO_API_PATH}/preview/*`, "preview");
3885
+ const health = readiness.check.pipe(Effect.andThen(roomHost.check), Effect.match({
4928
3886
  onFailure: () => ({
4929
3887
  live: true,
4930
3888
  ready: false
@@ -4950,12 +3908,18 @@ const makeInstallation = Effect.fn("Installation.make")(function* (bootstrap, ha
4950
3908
  cleanupMedia,
4951
3909
  fetch: (request) => {
4952
3910
  const pathname = new URL(request.url).pathname;
3911
+ const protection = protectedRoutes.find(request.method, pathname)?.handler;
3912
+ if (protection === "form") return handleFormRequest(request, handle);
4953
3913
  if (pathname === "/api/cms/auth" || pathname.startsWith(`${"/api/cms/auth"}/`)) return authentication.handle(request);
4954
3914
  if (pathname === CMS_LIVENESS_PATH) return Promise.resolve(healthResponse({
4955
3915
  live: true,
4956
3916
  ready: true
4957
3917
  }, false));
4958
3918
  if (pathname === CMS_READINESS_PATH) return runPromise(health).then((status) => healthResponse(status, true));
3919
+ if (protection === "preview") return handle(request).then((response) => {
3920
+ response.headers.set("cache-control", "private, no-store");
3921
+ return response;
3922
+ });
4959
3923
  return handle(request);
4960
3924
  },
4961
3925
  health,
@@ -5000,21 +3964,6 @@ const startInstallation = Effect.fn("CmsInstallation.start")(function* (options)
5000
3964
  };
5001
3965
  });
5002
3966
 
5003
- //#endregion
5004
- //#region ../server/src/internal/media/media-object-store.ts
5005
- var MediaObjectStore = class extends Context.Service()("@internal/server/MediaObjectStore") {};
5006
-
5007
- //#endregion
5008
- //#region ../server/src/internal/media/media-failures.ts
5009
- var MediaPersistenceError = class extends Schema.TaggedError()("Media.PersistenceError", {
5010
- operation: Schema.NonEmptyString,
5011
- cause: Schema.Defect()
5012
- }) {};
5013
- var MediaStorageError = class extends Schema.TaggedError()("Media.StorageError", {
5014
- operation: Schema.NonEmptyString,
5015
- cause: Schema.Defect()
5016
- }) {};
5017
-
5018
3967
  //#endregion
5019
3968
  //#region ../server/src/internal/media/media-change.ts
5020
3969
  const MediaUploadIntent = Schema.Struct({
@@ -5177,7 +4126,7 @@ function makeMediaChange(input) {
5177
4126
  action: input.action,
5178
4127
  context: input.change.context,
5179
4128
  id: input.change.id,
5180
- intent: Schema.encodeSync(MediaChangeIntent)(input.intent),
4129
+ intent: Result.getOrThrow(Schema.encodeResult(MediaChangeIntent)(input.intent)),
5181
4130
  scope: "media",
5182
4131
  subject: input.subject,
5183
4132
  target: input.target
@@ -5186,55 +4135,43 @@ function makeMediaChange(input) {
5186
4135
 
5187
4136
  //#endregion
5188
4137
  //#region ../server/src/internal/media/media-reconciliation.ts
5189
- function recoverPendingMedia(store, objectStore, operations, recoveryAge, options = {}) {
4138
+ const recoveryBatchSize = 100;
4139
+ function recoverPendingMedia(store, objectStore, operations, recoveryAge, cursor = {}) {
5190
4140
  return Effect.gen(function* () {
5191
4141
  const before = DateTime.subtractDuration(yield* DateTime.now, recoveryAge);
5192
- const maxBatches = options.maxBatches ?? Number.POSITIVE_INFINITY;
5193
- const recoverBatch = Effect.gen(function* () {
5194
- const pending = yield* store.pending({
5195
- before,
5196
- limit: 100
5197
- });
5198
- for (const record of pending) {
5199
- if (record.status === "deleting") {
4142
+ const pending = yield* store.pending({
4143
+ after: cursor.media,
4144
+ before,
4145
+ limit: recoveryBatchSize
4146
+ });
4147
+ for (const record of pending) {
4148
+ if (record.status === "deleting") {
4149
+ yield* objectStore.remove(record.objectKey);
4150
+ yield* store.finishDelete(record.id);
4151
+ continue;
4152
+ }
4153
+ const stored = yield* objectStore.stat(record.objectKey);
4154
+ if (Option.isSome(stored) && stored.value.byteSize === record.byteSize && (stored.value.mimeType === void 0 || stored.value.mimeType === record.mimeType)) yield* store.activate(record.id);
4155
+ else {
4156
+ if (Option.isSome(stored)) {
5200
4157
  yield* objectStore.remove(record.objectKey);
5201
- yield* store.finishDelete(record.id);
5202
- continue;
5203
- }
5204
- const stored = yield* objectStore.stat(record.objectKey);
5205
- if (Option.isSome(stored) && stored.value.byteSize === record.byteSize && (stored.value.mimeType === void 0 || stored.value.mimeType === record.mimeType)) yield* store.activate(record.id);
5206
- else {
5207
- if (Option.isSome(stored)) {
5208
- yield* objectStore.remove(record.objectKey);
5209
- const remaining = yield* objectStore.stat(record.objectKey);
5210
- if (Option.isSome(remaining)) continue;
5211
- }
5212
- yield* store.abandonUpload(record.id);
4158
+ const remaining = yield* objectStore.stat(record.objectKey);
4159
+ if (Option.isSome(remaining)) continue;
5213
4160
  }
4161
+ yield* store.abandonUpload(record.id);
5214
4162
  }
5215
- return pending.length < 100;
5216
- });
5217
- let mediaBatch = 0;
5218
- let mediaComplete = false;
5219
- while (!mediaComplete && mediaBatch < maxBatches) {
5220
- mediaComplete = yield* recoverBatch;
5221
- mediaBatch += 1;
5222
4163
  }
5223
- const recoverReplacementBatch = Effect.gen(function* () {
5224
- const replacements = yield* store.pendingReplacements({
5225
- before,
5226
- limit: 100
5227
- });
5228
- yield* Effect.forEach(replacements, (replacement) => recoverReplacement(store, objectStore, replacement).pipe(Effect.catchTag("Media.Conflict", () => Effect.void)), { discard: true });
5229
- return replacements.length < 100;
4164
+ const replacements = yield* store.pendingReplacements({
4165
+ after: cursor.replacements,
4166
+ before,
4167
+ limit: recoveryBatchSize
5230
4168
  });
5231
- let replacementBatch = 0;
5232
- let replacementsComplete = false;
5233
- while (!replacementsComplete && replacementBatch < maxBatches) {
5234
- replacementsComplete = yield* recoverReplacementBatch;
5235
- replacementBatch += 1;
5236
- }
4169
+ yield* Effect.forEach(replacements, (replacement) => recoverReplacement(store, objectStore, replacement).pipe(Effect.catchTag("Media.Conflict", () => Effect.void)), { discard: true });
5237
4170
  yield* recoverPendingMediaCleanup(operations, objectStore, before);
4171
+ return {
4172
+ media: pending.length < recoveryBatchSize ? void 0 : pending.at(-1)?.id,
4173
+ replacements: replacements.length < recoveryBatchSize ? void 0 : replacements.at(-1)?.mediaId
4174
+ };
5238
4175
  });
5239
4176
  }
5240
4177
  function recoverPendingMediaCleanup(operations, objectStore, before) {
@@ -5422,10 +4359,14 @@ const makeMediaLibrary = Effect.fn("MediaLibrary.make")(function* (options) {
5422
4359
  const store = yield* MediaStore;
5423
4360
  const baseUrl = options.publicBaseUrl.replace(/\/+$/gu, "");
5424
4361
  const pendingRecoveryAge = options.pendingRecoveryAge ?? "5 minutes";
5425
- yield* recoverPendingMedia(store, objectStore, operations, pendingRecoveryAge);
4362
+ const recoveryCursor = yield* Ref.make(yield* recoverPendingMedia(store, objectStore, operations, pendingRecoveryAge));
5426
4363
  const mutations = yield* Semaphore.make(1);
5427
4364
  const recoveryInterval = options.pendingRecoveryInterval ?? MEDIA_OPERATION_RECOVERY_INTERVAL;
5428
- if (recoveryInterval !== false) yield* mutations.withPermits(1)(recoverPendingMedia(store, objectStore, operations, pendingRecoveryAge, { maxBatches: 1 })).pipe(Effect.catch((error) => Effect.logWarning("Pending media change recovery sweep failed.", { errorTag: error._tag }))).pipe(Effect.repeat(Schedule.spaced(recoveryInterval)), Effect.delay(recoveryInterval), Effect.forkScoped({ startImmediately: true }));
4365
+ if (recoveryInterval !== false) yield* mutations.withPermits(1)(Effect.gen(function* () {
4366
+ const cursor = yield* Ref.get(recoveryCursor);
4367
+ const next = yield* recoverPendingMedia(store, objectStore, operations, pendingRecoveryAge, cursor);
4368
+ yield* Ref.set(recoveryCursor, next);
4369
+ })).pipe(Effect.catch((error) => Effect.logWarning("Pending media change recovery sweep failed.", { errorTag: error._tag }))).pipe(Effect.repeat(Schedule.spaced(recoveryInterval)), Effect.delay(recoveryInterval), Effect.forkScoped({ startImmediately: true }));
5429
4370
  const toAsset = (record) => MediaAsset.make({
5430
4371
  byteSize: record.byteSize,
5431
4372
  createdAt: record.createdAt,
@@ -5494,7 +4435,7 @@ const makeMediaLibrary = Effect.fn("MediaLibrary.make")(function* (options) {
5494
4435
  return yield* Effect.scoped(prepareUpload(input).pipe(Effect.flatMap((prepared) => mutations.withPermits(1)(Effect.gen(function* () {
5495
4436
  const id = MediaId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
5496
4437
  const operationId = ActivityId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
5497
- const objectKey = MediaObjectKey.make(`media/${id}.${imageExtension(prepared.mimeType)}`);
4438
+ const objectKey = MediaObjectKey.make(`${mediaObjectPrefix}${id}.${imageExtension(prepared.mimeType)}`);
5498
4439
  const now = yield* DateTime.now;
5499
4440
  const reserved = yield* store.reserve({
5500
4441
  byteSize: input.metadata.declaredByteSize,
@@ -5542,7 +4483,7 @@ const makeMediaLibrary = Effect.fn("MediaLibrary.make")(function* (options) {
5542
4483
  name: input.metadata.name,
5543
4484
  objectKey: current.value.objectKey,
5544
4485
  staged: false,
5545
- stagingObjectKey: MediaObjectKey.make(`media/replacements/${operationId}.${imageExtension(prepared.mimeType)}`),
4486
+ stagingObjectKey: MediaObjectKey.make(`${mediaObjectPrefix}replacements/${operationId}.${imageExtension(prepared.mimeType)}`),
5546
4487
  updatedAt: timestamp
5547
4488
  };
5548
4489
  const pendingReplacement = yield* store.beginReplace(replacement, {
@@ -5673,62 +4614,34 @@ function activityFailure(operation) {
5673
4614
  }
5674
4615
 
5675
4616
  //#endregion
5676
- //#region ../server/src/internal/preview/preview-sessions.ts
5677
- const DEFAULT_LIFETIME_MILLIS = 3e5;
5678
- const MAXIMUM_TOKEN_LENGTH = 4096;
5679
- const PreviewSessionClaims = Schema.Struct({
5680
- actorId: StudioActor.fields.id,
5681
- editorOrigin: PreviewOrigin,
5682
- expiresAt: Schema.Int.check(Schema.isGreaterThan(0)),
5683
- previewOrigin: PreviewOrigin,
5684
- sessionId: PreviewSessionId
5685
- });
5686
- var PreviewSessionConfigurationInvalid = class extends Schema.TaggedError()("Preview.SessionConfigurationInvalid", { message: Schema.NonEmptyString }) {};
5687
- var PreviewSessions = class extends Context.Service()("@internal/server/PreviewSessions") {};
5688
- function previewSessionsLayer(secret, options) {
5689
- return Layer.effect(PreviewSessions, makePreviewSessions(secret, options));
5690
- }
5691
- const makePreviewSessions = Effect.fn("PreviewSessions.make")(function* (secret, options) {
5692
- const lifetimeMillis = options.lifetimeMillis ?? DEFAULT_LIFETIME_MILLIS;
5693
- if (!Number.isSafeInteger(lifetimeMillis) || lifetimeMillis <= 0) return yield* new PreviewSessionConfigurationInvalid({ message: "The preview session lifetime must be positive." });
5694
- const crypto = yield* Crypto.Crypto;
5695
- const tokens = yield* makeSignedTokenCodec(secret, {
5696
- configuration: () => new PreviewSessionConfigurationInvalid({ message: "The preview session secret must be at least 32 bytes and usable for signing." }),
5697
- invalid: () => new PreviewSessionInvalid({ reason: "invalid" })
5698
- });
5699
- const issue = Effect.fn("PreviewSessions.issue")(function* (actorId, editorOrigin, previewOrigin) {
5700
- if (!options.allowedPreviewOrigins.has(previewOrigin)) return yield* invalidSession("origin");
5701
- const expiresAt = (yield* Clock.currentTimeMillis) + lifetimeMillis;
5702
- const claims = PreviewSessionClaims.make({
5703
- actorId,
5704
- editorOrigin,
5705
- expiresAt,
5706
- previewOrigin,
5707
- sessionId: PreviewSessionId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie))
5708
- });
5709
- const token = yield* Schema.encodeEffect(Schema.fromJsonString(PreviewSessionClaims))(claims).pipe(Effect.mapError(() => new PreviewSessionInvalid({ reason: "invalid" })), Effect.flatMap(tokens.sign), Effect.mapError(() => new PreviewSessionInvalid({ reason: "invalid" })));
5710
- return PreviewSession.make({
5711
- editorOrigin,
5712
- expiresAt: DateTime.makeUnsafe(expiresAt),
5713
- previewOrigin,
5714
- sessionId: claims.sessionId,
5715
- token
5716
- });
5717
- });
5718
- const verify = Effect.fn("PreviewSessions.verify")(function* (token, previewOrigin) {
5719
- const payload = yield* tokens.verify(token, MAXIMUM_TOKEN_LENGTH).pipe(Effect.mapError(() => new PreviewSessionInvalid({ reason: "invalid" })));
5720
- const claims = yield* Schema.decodeEffect(Schema.fromJsonString(PreviewSessionClaims))(payload).pipe(Effect.mapError(() => new PreviewSessionInvalid({ reason: "invalid" })));
5721
- if (claims.previewOrigin !== previewOrigin) return yield* invalidSession("origin");
5722
- if (claims.expiresAt <= (yield* Clock.currentTimeMillis)) return yield* invalidSession("expired");
5723
- return claims;
5724
- });
5725
- return PreviewSessions.of({
5726
- issue,
5727
- verify
4617
+ //#region ../server/src/ProductModules.ts
4618
+ function productModulesLayer(options) {
4619
+ const accessControl = defaultAccessControlLayer;
4620
+ const seo = seoLayer(options.bootstrap.seo ?? makeDefaultSeoOptions(options.baseUrl)).pipe(Layer.provide(accessControl));
4621
+ const authoredDocuments = documentsLayer(options.bootstrap.documents, {
4622
+ ...options.documentEngine,
4623
+ components: options.documentEngine.components ?? [],
4624
+ mediaBaseUrl: options.documentEngine.mediaBaseUrl ?? "https://media.invalid"
4625
+ }).pipe(Layer.provide(accessControl));
4626
+ const documentAccess = Layer.merge(authoredDocuments, accessControl);
4627
+ const activity = activityLayer.pipe(Layer.provide(accessControl));
4628
+ const workingDocuments = workingDocumentsLayer(options.workingDocuments).pipe(Layer.provide(documentAccess));
4629
+ const seoDelivery = seoDeliveryLayer.pipe(Layer.provide(Layer.merge(documentAccess, seo)));
4630
+ return Layer.mergeAll(activity, documentAccess, workingDocuments, seo, seoDelivery, readinessLayer);
4631
+ }
4632
+ function productModulesWithMediaLayer(options) {
4633
+ const products = productModulesLayer({
4634
+ ...options,
4635
+ documentEngine: {
4636
+ ...options.documentEngine,
4637
+ mediaBaseUrl: options.documentEngine.mediaBaseUrl ?? options.mediaPublicBaseUrl
4638
+ }
5728
4639
  });
5729
- });
5730
- function invalidSession(reason) {
5731
- return Effect.fail(new PreviewSessionInvalid({ reason }));
4640
+ const media = mediaLibraryLayer({
4641
+ maxUploadBytes: options.bootstrap.media.maxUploadBytes,
4642
+ publicBaseUrl: options.mediaPublicBaseUrl
4643
+ }).pipe(Layer.provide(products));
4644
+ return Layer.merge(products, media);
5732
4645
  }
5733
4646
 
5734
4647
  //#endregion
@@ -5831,20 +4744,21 @@ const reconcileStored = Effect.fn("Initialization.reconcileStored")(function* (s
5831
4744
  id: state.claim.changeId
5832
4745
  }, state.claim.intent);
5833
4746
  const begin = yield* journal.begin(draft).pipe(Effect.mapError((cause) => unavailable$3("Initialization.recover", cause)));
5834
- switch (begin._tag) {
5835
- case "Recorded":
5836
- yield* store.complete(state.claim.changeId);
5837
- return yield* store.read;
5838
- case "Cancelled":
5839
- yield* store.reset(state.claim.changeId);
5840
- return yield* store.read;
5841
- case "Unresolved": {
5842
- const resolution = yield* reconcileInitialAdministrator(journal, begin.change, recovery.observe).pipe(Effect.mapError((cause) => unavailable$3("Initialization.recover", cause)));
5843
- if (resolution.kind === "applied") yield* store.complete(state.claim.changeId);
5844
- if (resolution.kind === "cancelled") yield* store.reset(state.claim.changeId);
5845
- return yield* store.read;
5846
- }
4747
+ if (Predicate.isTagged("Recorded")(begin)) {
4748
+ yield* store.complete(state.claim.changeId);
4749
+ return yield* store.read;
4750
+ }
4751
+ if (Predicate.isTagged("Cancelled")(begin)) {
4752
+ yield* store.reset(state.claim.changeId);
4753
+ return yield* store.read;
5847
4754
  }
4755
+ if (Predicate.isTagged("Unresolved")(begin)) {
4756
+ const resolution = yield* reconcileInitialAdministrator(journal, begin.change, recovery.observe).pipe(Effect.mapError((cause) => unavailable$3("Initialization.recover", cause)));
4757
+ if (resolution.kind === "applied") yield* store.complete(state.claim.changeId);
4758
+ if (resolution.kind === "cancelled") yield* store.reset(state.claim.changeId);
4759
+ return yield* store.read;
4760
+ }
4761
+ return yield* store.read;
5848
4762
  });
5849
4763
  function completeBestEffort(store, changeId) {
5850
4764
  return store.complete(changeId).pipe(Effect.catch((error) => Effect.logWarning("Initialization completion remains recoverable.", {
@@ -6045,6 +4959,69 @@ function targetError(operation) {
6045
4959
  return (error) => Schema.is(UserAlreadyExists)(error) ? unavailable$2(operation)(error) : error;
6046
4960
  }
6047
4961
 
4962
+ //#endregion
4963
+ //#region ../server/src/Forms.ts
4964
+ /** Only the currently published definition can authorize a submission. Never trust client rules. */
4965
+ const submitForm = Effect.fn("Forms.submit")(function* (input, components, handlers) {
4966
+ const document = yield* (yield* PublishedDocuments).read({
4967
+ kind: "id",
4968
+ documentId: input.documentId
4969
+ }).pipe(Effect.orElseSucceed(() => void 0));
4970
+ if (!document) return FormResult.make({
4971
+ status: "unavailable",
4972
+ errors: {}
4973
+ });
4974
+ const form = documentForms(document, components).find((candidate) => JSON.stringify(candidate.nodePath) === JSON.stringify(input.nodePath));
4975
+ if (!form || form.revision !== input.revision) return FormResult.make({
4976
+ status: "stale",
4977
+ errors: {}
4978
+ });
4979
+ const handler = Object.hasOwn(handlers, form.name) ? handlers[form.name] : void 0;
4980
+ if (!handler) return FormResult.make({
4981
+ status: "unavailable",
4982
+ errors: {}
4983
+ });
4984
+ const fields = form.steps.flatMap((step) => step.fields);
4985
+ const errors = validateFormValues(fields, input.values);
4986
+ if (Object.keys(input.values).some((id) => !fields.some((field) => field.id === id))) errors._form = "The submission contains unknown answers.";
4987
+ if (Object.keys(errors).length) return FormResult.make({
4988
+ status: "invalid",
4989
+ errors
4990
+ });
4991
+ const values = Object.fromEntries(fields.map((field) => [field.id, normalizedAnswer(Object.hasOwn(input.values, field.id) ? input.values[field.id] : void 0)]));
4992
+ return yield* Effect.tryPromise(() => Promise.resolve(handler({
4993
+ form,
4994
+ values
4995
+ }))).pipe(Effect.as(FormResult.make({
4996
+ status: "accepted",
4997
+ errors: {}
4998
+ })), Effect.orElseSucceed(() => FormResult.make({
4999
+ status: "failed",
5000
+ errors: {}
5001
+ })));
5002
+ });
5003
+ function normalizedAnswer(value) {
5004
+ return value === void 0 || Predicate.isString(value) && !value.trim() ? null : value;
5005
+ }
5006
+
5007
+ //#endregion
5008
+ //#region ../platform-node/src/internal/http/forms.ts
5009
+ function formsApiLayer(components, handlers) {
5010
+ return HttpApiBuilder.group(StudioApi, "forms", (group) => Effect.gen(function* () {
5011
+ const documents = yield* PublishedDocuments;
5012
+ const limiter = yield* RateLimiter.RateLimiter;
5013
+ return group.handle("submit", ({ payload }) => limiter.consume({
5014
+ key: "cms-forms",
5015
+ limit: 60,
5016
+ window: "1 minute",
5017
+ onExceeded: "fail"
5018
+ }).pipe(Effect.andThen(submitForm(payload, components, handlers).pipe(Effect.provideService(PublishedDocuments, documents))), Effect.orElseSucceed(() => FormResult.make({
5019
+ status: "unavailable",
5020
+ errors: {}
5021
+ }))));
5022
+ })).pipe(Layer.provide(RateLimiter.layer.pipe(Layer.provide(RateLimiter.layerStoreMemory))));
5023
+ }
5024
+
6048
5025
  //#endregion
6049
5026
  //#region ../platform-node/src/internal/http/errors.ts
6050
5027
  const studioSchemaErrorLayer = HttpApiMiddleware.layerSchemaErrorTransform(StudioSchemaError, (error) => Effect.fail(mapStudioSchemaError(error.kind)));
@@ -6065,7 +5042,7 @@ function mapUnexpectedError(effect) {
6065
5042
  //#region ../platform-node/src/internal/http/collaboration.ts
6066
5043
  const PublicTicketError = Schema.Union([AccessDenied, DocumentNotFound]);
6067
5044
  const isPublicTicketError = Schema.is(PublicTicketError);
6068
- const collaborationApiLayer = HttpApiBuilder.group(StudioApi, "collaboration", (handlers) => Effect.gen(function* () {
5045
+ const collaborationApiLayer = (authoringRevision) => HttpApiBuilder.group(StudioApi, "collaboration", (handlers) => Effect.gen(function* () {
6069
5046
  const accessControl = yield* AccessControl;
6070
5047
  const documents = yield* WorkingDocuments;
6071
5048
  const tickets = yield* StudioRealtimeTickets;
@@ -6090,6 +5067,7 @@ const collaborationApiLayer = HttpApiBuilder.group(StudioApi, "collaboration", (
6090
5067
  ref: opened.ref
6091
5068
  });
6092
5069
  return CollaborationTicket.make({
5070
+ authoringRevision,
6093
5071
  authoredStateVector: encodeRealtimeStateVector(opened.authoredStateVector),
6094
5072
  documentName: roomName,
6095
5073
  source: opened.source,
@@ -6124,7 +5102,13 @@ const documentsApiLayer = HttpApiBuilder.group(StudioApi, "documents", (handlers
6124
5102
  }))).pipe(mapMutationError)).handle("validate", ({ params, payload }) => withStudioRequest((context) => documents.validate(context, UpdateDocumentInput.make({
6125
5103
  documentId: params.documentId,
6126
5104
  ...payload
6127
- }))).pipe(preservePublicErrors((error) => isAccessDenied$1(error) || isDocumentNotFound$1(error) || isDocumentVersionConflict(error)))).handle("move", ({ params, payload }) => withStudioRequest((context) => documents.move(context, MoveDocumentInput.make({
5105
+ }))).pipe(preservePublicErrors((error) => isAccessDenied$1(error) || isDocumentNotFound$1(error) || isDocumentVersionConflict(error)))).handle("duplicate", ({ params, payload }) => withStudioRequest((context) => documents.duplicate(context, DuplicateDocumentInput.make({
5106
+ documentId: params.documentId,
5107
+ ...payload
5108
+ }))).pipe(mapMutationError)).handle("unpublish", ({ params, payload }) => withStudioRequest((context) => documents.unpublish(context, UnpublishDocumentInput.make({
5109
+ documentId: params.documentId,
5110
+ ...payload
5111
+ }))).pipe(mapMutationError)).handle("move", ({ params, payload }) => withStudioRequest((context) => documents.move(context, MoveDocumentInput.make({
6128
5112
  documentId: params.documentId,
6129
5113
  ...payload
6130
5114
  }))).pipe(mapMutationError)).handle("publish", ({ params, payload }) => withStudioRequest((context) => documents.publish(context, PublishDocumentInput.make({
@@ -6219,7 +5203,7 @@ function decodeMediaUpload(payload, query, maxUploadBytes) {
6219
5203
  }
6220
5204
  function mapMultipartError(error) {
6221
5205
  if (isMediaInvalid(error)) return error;
6222
- return error.reason._tag === "InternalError" ? studioInternalError() : invalidMultipart();
5206
+ return Predicate.isTagged("InternalError")(error.reason) ? studioInternalError() : invalidMultipart();
6223
5207
  }
6224
5208
  function invalidMultipart() {
6225
5209
  return new MediaInvalid({ message: "Upload exactly one file whose body matches its declared size." });
@@ -6227,43 +5211,20 @@ function invalidMultipart() {
6227
5211
 
6228
5212
  //#endregion
6229
5213
  //#region ../platform-node/src/internal/http/preview.ts
6230
- const isPreviewSessionInvalid = Schema.is(PreviewSessionInvalid);
6231
- const isPreviewResolvePublicError = Schema.is(Schema.Union([PreviewSessionInvalid, PreviewDraftNotFound]));
6232
- function previewApiLayers(options = {}) {
6233
- const sessions = HttpApiBuilder.group(StudioApi, "previewSessions", (handlers) => Effect.gen(function* () {
6234
- const previewSessions = yield* PreviewSessions;
6235
- return handlers.handle("issue", ({ payload }) => Effect.gen(function* () {
6236
- const context = yield* CurrentStudioRequest;
6237
- const origin = yield* requestOrigin;
6238
- return yield* previewSessions.issue(context.actor.id, origin, payload.previewOrigin);
6239
- }).pipe(preservePublicErrors(isPreviewSessionInvalid)));
6240
- }));
6241
- const delivery = HttpApiBuilder.group(StudioApi, "previewDelivery", (handlers) => Effect.gen(function* () {
6242
- const documents = yield* DocumentPreviews;
6243
- const previewSessions = yield* PreviewSessions;
6244
- const verify = (token) => requestOrigin.pipe(Effect.flatMap((origin) => previewSessions.verify(token, origin)));
6245
- return handlers.handle("resolve", ({ payload }) => Effect.gen(function* () {
6246
- yield* verify(payload.token);
6247
- const document = yield* documents.read(payload.lookup);
6248
- return Option.isSome(document) ? document.value : yield* new PreviewDraftNotFound({ lookup: payload.lookup });
6249
- }).pipe(preservePublicErrors(isPreviewResolvePublicError))).handle("invalidations", ({ payload }) => Effect.gen(function* () {
6250
- const claims = yield* verify(payload.token);
6251
- const now = yield* Clock.currentTimeMillis;
6252
- const expiresIn = Math.max(0, claims.expiresAt - now);
6253
- return generationInvalidations(documents, options.invalidationInterval ?? "1 second").pipe(Stream.interruptWhen(Effect.sleep(Duration.millis(expiresIn))));
6254
- }).pipe(preservePublicErrors(isPreviewSessionInvalid)));
6255
- }));
6256
- return Layer.merge(sessions, delivery);
6257
- }
6258
- function generationInvalidations(documents, interval) {
6259
- return Stream.make(0).pipe(Stream.concat(Stream.fromSchedule(Schedule.spaced(interval))), Stream.mapEffect(() => documents.generation), Stream.changes, Stream.drop(1), Stream.map((generation) => PreviewInvalidation.make({
6260
- generation,
6261
- type: "document-invalidated"
6262
- })), Stream.catch(() => Stream.empty));
6263
- }
6264
- const requestOrigin = HttpServerRequest.HttpServerRequest.pipe(Effect.flatMap((request) => {
6265
- const origin = request.headers.origin;
6266
- return origin === void 0 ? Effect.fail(new PreviewSessionInvalid({ reason: "origin" })) : Schema.decodeEffect(PreviewOrigin)(origin).pipe(Effect.mapError(() => new PreviewSessionInvalid({ reason: "origin" })));
5214
+ const publicError = Schema.is(Schema.Union([AccessDenied, PublishedDocumentNotFound]));
5215
+ const denied = Schema.is(AccessDenied);
5216
+ const previewApiLayer = HttpApiBuilder.group(StudioApi, "previewDelivery", (handlers) => Effect.gen(function* () {
5217
+ const documents = yield* DocumentPreviews;
5218
+ const accessControl = yield* AccessControl;
5219
+ const seo = yield* PreviewSeo;
5220
+ const seoDelivery = yield* SeoDelivery;
5221
+ return handlers.handle("read", ({ payload }) => CurrentStudioRequest.pipe(Effect.flatMap((context) => documents.read(context, payload).pipe(Effect.flatMap((document) => seoDelivery.previewDocument(document)))), preservePublicErrors(publicError))).handle("query", ({ payload }) => CurrentStudioRequest.pipe(Effect.flatMap((context) => documents.query(context, payload).pipe(Effect.flatMap((page) => Effect.forEach(page.items, (document) => seoDelivery.previewDocument(document)).pipe(Effect.map((items) => Object.assign({}, page, { items })))))), preservePublicErrors(denied))).handle("navigation", ({ payload }) => CurrentStudioRequest.pipe(Effect.flatMap((context) => documents.navigation(context, payload)), preservePublicErrors(denied))).handle("surround", ({ payload }) => CurrentStudioRequest.pipe(Effect.flatMap((context) => documents.surround(context, payload.lookup, payload.query).pipe(Effect.flatMap((surround) => Effect.all({
5222
+ previous: surround.previous ? seoDelivery.previewDocument(surround.previous) : Effect.succeed(null),
5223
+ next: surround.next ? seoDelivery.previewDocument(surround.next) : Effect.succeed(null)
5224
+ })))), preservePublicErrors(publicError))).handle("seo", () => CurrentStudioRequest.pipe(Effect.flatMap((context) => accessControl.require(context, {
5225
+ action: "document.browse",
5226
+ resource: { kind: "catalog" }
5227
+ }).pipe(Effect.andThen(seo.read))), preservePublicErrors(denied)));
6267
5228
  }));
6268
5229
 
6269
5230
  //#endregion
@@ -6271,7 +5232,15 @@ const requestOrigin = HttpServerRequest.HttpServerRequest.pipe(Effect.flatMap((r
6271
5232
  const isPublishedDocumentNotFound = Schema.is(PublishedDocumentNotFound);
6272
5233
  const deliveryApiLayer = Layer.merge(HttpApiBuilder.group(StudioApi, "delivery", (handlers) => Effect.gen(function* () {
6273
5234
  const documents = yield* PublishedDocuments;
6274
- return handlers.handle("read", ({ payload }) => documents.read(payload).pipe(preservePublicErrors(isPublishedDocumentNotFound))).handle("query", ({ payload }) => documents.query(payload).pipe(mapUnexpectedError)).handle("navigation", ({ payload }) => documents.navigation(payload).pipe(mapUnexpectedError)).handle("surround", ({ payload }) => documents.surround(payload.lookup, payload.query).pipe(preservePublicErrors(isPublishedDocumentNotFound)));
5235
+ const seo = yield* PublishedSeo;
5236
+ const seoDelivery = yield* SeoDelivery;
5237
+ return handlers.handle("read", ({ payload }) => documents.read(payload).pipe(Effect.flatMap(seoDelivery.publishedDocument), preservePublicErrors(isPublishedDocumentNotFound))).handle("query", ({ payload }) => documents.query(payload).pipe(Effect.flatMap((page) => Effect.forEach(page.items, seoDelivery.publishedDocument).pipe(Effect.map((items) => ({
5238
+ items,
5239
+ total: page.total
5240
+ })))), mapUnexpectedError)).handle("navigation", ({ payload }) => documents.navigation(payload).pipe(mapUnexpectedError)).handle("surround", ({ payload }) => documents.surround(payload.lookup, payload.query).pipe(Effect.flatMap((surround) => Effect.all({
5241
+ previous: surround.previous ? seoDelivery.publishedDocument(surround.previous) : Effect.succeed(null),
5242
+ next: surround.next ? seoDelivery.publishedDocument(surround.next) : Effect.succeed(null)
5243
+ })), preservePublicErrors(isPublishedDocumentNotFound))).handle("seo", () => seo.read.pipe(mapUnexpectedError)).handle("sitemap", () => seoDelivery.sitemap.pipe(mapUnexpectedError));
6275
5244
  })), HttpApiBuilder.group(StudioApi, "diagnostics", (handlers) => Effect.gen(function* () {
6276
5245
  const documents = yield* PublishedDocuments;
6277
5246
  return handlers.handle("documents", () => CurrentStudioRequest.pipe(Effect.flatMap((context) => documents.diagnostics(context)), preservePublicErrors(Schema.is(AccessDenied))));
@@ -6287,6 +5256,12 @@ const isHistoryPublicError = Schema.is(Schema.Union([
6287
5256
  DocumentNotFound
6288
5257
  ]));
6289
5258
  const isInitializationPublicError = Schema.is(Schema.Union([InitializationAlreadyCompleted, InitializationProofInvalid]));
5259
+ const isSeoPublicError = Schema.is(Schema.Union([
5260
+ AccessDenied,
5261
+ SeoConfirmationRequired,
5262
+ SeoMediaUnavailable,
5263
+ SeoVersionConflict
5264
+ ]));
6290
5265
  const administrationApiLayer = Layer.mergeAll(HttpApiBuilder.group(StudioApi, "initialization", (handlers) => Effect.gen(function* () {
6291
5266
  const initialization = yield* Initialization;
6292
5267
  return handlers.handle("status", () => initialization.status.pipe(mapUnexpectedError)).handle("claim", ({ payload }) => initialization.claim(payload).pipe(preservePublicErrors(isInitializationPublicError)));
@@ -6296,6 +5271,9 @@ const administrationApiLayer = Layer.mergeAll(HttpApiBuilder.group(StudioApi, "i
6296
5271
  })), HttpApiBuilder.group(StudioApi, "activity", (handlers) => Effect.gen(function* () {
6297
5272
  const activity = yield* Activity;
6298
5273
  return handlers.handle("list", ({ query }) => withStudioRequest((context) => activity.list(context, query)).pipe(preservePublicErrors(isActivityPublicError)));
5274
+ })), HttpApiBuilder.group(StudioApi, "seo", (handlers) => Effect.gen(function* () {
5275
+ const seo = yield* Seo;
5276
+ return handlers.handle("read", () => withStudioRequest((context) => seo.read(context)).pipe(preservePublicErrors(isSeoPublicError))).handle("save", ({ payload }) => withStudioRequest((context) => seo.save(context, payload)).pipe(preservePublicErrors(isSeoPublicError))).handle("publish", ({ payload }) => withStudioRequest((context) => seo.publish(context, payload)).pipe(preservePublicErrors(isSeoPublicError)));
6299
5277
  })));
6300
5278
 
6301
5279
  //#endregion
@@ -6321,43 +5299,14 @@ const userAdministrationHttpLayer = HttpApiBuilder.group(StudioApi, "users", (ha
6321
5299
 
6322
5300
  //#endregion
6323
5301
  //#region ../platform-node/src/internal/http/api.ts
6324
- function studioApiLayer(bootstrap) {
5302
+ function studioApiLayer(bootstrap, authoringRevision, components = [], formHandlers = {}) {
6325
5303
  const configuration = HttpApiBuilder.group(StudioApi, "configuration", (handlers) => handlers.handle("bootstrap", () => Effect.succeed(bootstrap)));
6326
- return authenticatedRouterHandlers(Layer.mergeAll(configuration, documentsApiLayer, mediaApiLayer(bootstrap.media.enabled, bootstrap.media.maxUploadBytes), collaborationApiLayer, deliveryApiLayer, previewApiLayers(), administrationApiLayer, userAdministrationHttpLayer).pipe(Layer.provideMerge(studioSchemaErrorLayer)));
5304
+ return authenticatedRouterHandlers(Layer.mergeAll(configuration, formsApiLayer(components, formHandlers), documentsApiLayer, mediaApiLayer(bootstrap.media.enabled, bootstrap.media.maxUploadBytes), collaborationApiLayer(authoringRevision), deliveryApiLayer, previewApiLayer, administrationApiLayer, userAdministrationHttpLayer).pipe(Layer.provideMerge(studioSchemaErrorLayer)));
6327
5305
  }
6328
5306
  function authenticatedRouterHandlers(handlers) {
6329
5307
  return handlers;
6330
5308
  }
6331
5309
 
6332
- //#endregion
6333
- //#region ../platform-node/src/internal/installation/server-modules.ts
6334
- function productModulesLayer(options) {
6335
- const accessControl = defaultAccessControlLayer;
6336
- const authoredDocuments = documentsLayer(options.bootstrap.documents, {
6337
- ...options.documentEngine,
6338
- components: options.documentEngine.components ?? options.bootstrap.components,
6339
- mediaBaseUrl: options.documentEngine.mediaBaseUrl ?? "https://media.invalid"
6340
- }).pipe(Layer.provide(accessControl));
6341
- const documentAccess = Layer.merge(authoredDocuments, accessControl);
6342
- const activity = activityLayer.pipe(Layer.provide(accessControl));
6343
- const workingDocuments = workingDocumentsLayer(options.workingDocuments).pipe(Layer.provide(documentAccess));
6344
- return Layer.mergeAll(activity, documentAccess, workingDocuments);
6345
- }
6346
- function productModulesWithMediaLayer(options) {
6347
- const products = productModulesLayer({
6348
- ...options,
6349
- documentEngine: {
6350
- ...options.documentEngine,
6351
- mediaBaseUrl: options.documentEngine.mediaBaseUrl ?? options.mediaPublicBaseUrl
6352
- }
6353
- });
6354
- const media = mediaLibraryLayer({
6355
- maxUploadBytes: options.bootstrap.media.maxUploadBytes,
6356
- publicBaseUrl: options.mediaPublicBaseUrl
6357
- }).pipe(Layer.provide(products));
6358
- return Layer.merge(products, media);
6359
- }
6360
-
6361
5310
  //#endregion
6362
5311
  //#region ../platform-node/src/internal/installation/error.ts
6363
5312
  var CmsInstallationError = class extends Schema.TaggedError()("Cms.InstallationError", {
@@ -6374,8 +5323,12 @@ function makeInstallationLayer(validated) {
6374
5323
  const options = validated.options;
6375
5324
  const core = options.composition.core;
6376
5325
  const productOptions = {
5326
+ baseUrl: options.baseUrl,
6377
5327
  bootstrap: options.bootstrap,
6378
- documentEngine: { documentModel: options.documentModel },
5328
+ documentEngine: {
5329
+ documentModel: options.documentModel,
5330
+ components: options.authoring.components
5331
+ },
6379
5332
  workingDocuments: options.workingDocuments
6380
5333
  };
6381
5334
  const enabledProductModules = (media) => productModulesWithMediaLayer({
@@ -6384,7 +5337,6 @@ function makeInstallationLayer(validated) {
6384
5337
  }).pipe(Layer.provide(Layer.merge(core, media.objects)));
6385
5338
  const productModules = validated.media.enabled ? enabledProductModules(validated.media.configuration) : productModulesLayer(productOptions).pipe(Layer.provide(core));
6386
5339
  const authentication = studioAuthenticationLayer.pipe(Layer.provide(core));
6387
- const previewSessions = previewSessionsLayer(options.studioSecret, { allowedPreviewOrigins: options.allowedPreviewOrigins }).pipe(Layer.provide(core));
6388
5340
  const initialization = initializationLayer({
6389
5341
  initialSources: options.initialSources,
6390
5342
  initializationToken: options.initializationToken
@@ -6393,10 +5345,10 @@ function makeInstallationLayer(validated) {
6393
5345
  const tickets = studioRealtimeTicketsLayer(options.studioSecret).pipe(Layer.provide(core));
6394
5346
  const roomHost = studioRealtimeRoomHostLayer().pipe(Layer.provide(productModules));
6395
5347
  const gateway = studioRealtimeGatewayLayer.pipe(Layer.provide(Layer.mergeAll(productModules, roomHost, tickets)));
6396
- const httpDependencies = Layer.mergeAll(core, productModules, gateway, initialization, previewSessions, roomHost, users).pipe(Layer.provideMerge(authentication), Layer.provideMerge(tickets));
6397
- const handlers = studioApiLayer(options.bootstrap).pipe(Layer.provide(httpDependencies));
6398
- const httpApi = HttpApiBuilder.layer(StudioApi).pipe(Layer.provide(handlers), Layer.provide(HttpServer.layerServices));
6399
- return validated.media.enabled ? installationHttpLayer(options.bootstrap, httpApi, (context) => Effect.flatMap(MediaLibrary, (library) => library.cleanup(context))).pipe(Layer.provide(httpDependencies)) : installationHttpLayer(options.bootstrap, httpApi, () => Effect.succeed(MediaCleanupReport.make({ removedObjectKeys: [] }))).pipe(Layer.provide(httpDependencies));
5348
+ const httpDependencies = Layer.mergeAll(core, productModules, gateway, initialization, roomHost, users).pipe(Layer.provideMerge(authentication), Layer.provideMerge(tickets));
5349
+ const handlers = studioApiLayer(options.bootstrap, options.authoring.revision, options.authoring.components, options.formHandlers).pipe(Layer.provide(httpDependencies));
5350
+ const httpApi = HttpApiBuilder.layer(StudioApi).pipe(Layer.provide(handlers), Layer.provide(HttpServer.layerServices), Layer.provideMerge(HttpRouter.layer));
5351
+ return validated.media.enabled ? installationHttpLayer(options.bootstrap, (context) => Effect.flatMap(MediaLibrary, (library) => library.cleanup(context))).pipe(Layer.provide(Layer.merge(httpDependencies, httpApi))) : installationHttpLayer(options.bootstrap, () => Effect.succeed(MediaCleanupReport.make({ removedObjectKeys: [] }))).pipe(Layer.provide(Layer.merge(httpDependencies, httpApi)));
6400
5352
  }
6401
5353
  function validateInstallationOptions(options) {
6402
5354
  if (options.bootstrap.media.enabled) {
@@ -6434,7 +5386,6 @@ function mapStartupError(layer, operation) {
6434
5386
 
6435
5387
  //#endregion
6436
5388
  //#region ../platform-node/src/Installation.ts
6437
- /** Starts auth, persistence, Studio realtime, delivery, health, and maintenance as one deep module. */
6438
5389
  const installCms$1 = Effect.fn("CmsInstallation.install")(function* (options) {
6439
5390
  return yield* startInstallation({
6440
5391
  cleanup: options.bootstrap.media.enabled ? options.cleanup : void 0,
@@ -6443,7 +5394,9 @@ const installCms$1 = Effect.fn("CmsInstallation.install")(function* (options) {
6443
5394
  });
6444
5395
  function createCmsInstallationLayer(options) {
6445
5396
  return installationLayer({
6446
- allowedPreviewOrigins: /* @__PURE__ */ new Set([options.baseUrl]),
5397
+ baseUrl: options.baseUrl,
5398
+ formHandlers: options.formHandlers,
5399
+ authoring: options.authoring,
6447
5400
  bootstrap: options.bootstrap,
6448
5401
  composition: options.composition,
6449
5402
  studioSecret: options.studioSecret,
@@ -6453,262 +5406,6 @@ function createCmsInstallationLayer(options) {
6453
5406
  });
6454
5407
  }
6455
5408
 
6456
- //#endregion
6457
- //#region ../s3/src/internal/client.ts
6458
- var S3RequestError = class extends Schema.TaggedError()("S3.RequestError", {
6459
- cause: Schema.Defect(),
6460
- operation: Schema.NonEmptyString
6461
- }) {};
6462
- function openAwsS3MediaObjectClient(options) {
6463
- const credentials = options.credentials ? {
6464
- accessKeyId: options.credentials.accessKeyId,
6465
- secretAccessKey: Redacted.value(options.credentials.secretAccessKey),
6466
- sessionToken: options.credentials.sessionToken ? Redacted.value(options.credentials.sessionToken) : void 0
6467
- } : void 0;
6468
- return makeAwsS3MediaObjectClient(new S3Client({
6469
- credentials,
6470
- endpoint: options.endpoint,
6471
- forcePathStyle: options.forcePathStyle,
6472
- region: options.region
6473
- }));
6474
- }
6475
- /** Wraps the official client; exported only through the package-private path for transport-level tests. */
6476
- function makeAwsS3MediaObjectClient(client) {
6477
- const request = (operation, send) => Effect.tryPromise({
6478
- catch: (cause) => new S3RequestError({
6479
- cause,
6480
- operation
6481
- }),
6482
- try: send
6483
- });
6484
- return {
6485
- close: Effect.sync(() => client.destroy()),
6486
- copyObject: (input) => request("MediaObjectStore.S3.copyObject", (signal) => client.send(new CopyObjectCommand({
6487
- Bucket: input.bucket,
6488
- CacheControl: input.cacheControl,
6489
- ContentType: input.contentType,
6490
- CopySource: input.copySource,
6491
- Key: input.key,
6492
- MetadataDirective: "REPLACE"
6493
- }), { abortSignal: signal })).pipe(Effect.asVoid),
6494
- createBucket: (input) => request("MediaObjectStore.S3.createBucket", (signal) => client.send(new CreateBucketCommand({
6495
- Bucket: input.bucket,
6496
- CreateBucketConfiguration: input.locationConstraint ? { LocationConstraint: input.locationConstraint } : void 0
6497
- }), { abortSignal: signal })).pipe(Effect.asVoid),
6498
- deleteObject: (input) => request("MediaObjectStore.S3.deleteObject", (signal) => client.send(new DeleteObjectCommand({
6499
- Bucket: input.bucket,
6500
- Key: input.key
6501
- }), { abortSignal: signal })).pipe(Effect.asVoid),
6502
- headBucket: (input) => request("MediaObjectStore.S3.headBucket", (signal) => client.send(new HeadBucketCommand({ Bucket: input.bucket }), { abortSignal: signal })).pipe(Effect.asVoid),
6503
- headObject: (input) => request("MediaObjectStore.S3.headObject", (signal) => client.send(new HeadObjectCommand({
6504
- Bucket: input.bucket,
6505
- Key: input.key
6506
- }), { abortSignal: signal })).pipe(Effect.map((output) => ({
6507
- contentLength: output.ContentLength,
6508
- contentType: output.ContentType
6509
- }))),
6510
- listObjects: (input) => request("MediaObjectStore.S3.listObjects", (signal) => client.send(new ListObjectsV2Command({
6511
- Bucket: input.bucket,
6512
- ContinuationToken: input.continuationToken,
6513
- Prefix: input.prefix
6514
- }), { abortSignal: signal })).pipe(Effect.map((output) => ({
6515
- isTruncated: output.IsTruncated === true,
6516
- nextContinuationToken: output.NextContinuationToken,
6517
- objects: (output.Contents ?? []).map((object) => ({
6518
- key: object.Key,
6519
- size: object.Size
6520
- }))
6521
- }))),
6522
- putObject: (input) => request("MediaObjectStore.S3.putObject", (signal) => client.send(new PutObjectCommand({
6523
- Body: Readable.from(input.body),
6524
- Bucket: input.bucket,
6525
- CacheControl: input.cacheControl,
6526
- ContentLength: input.contentLength,
6527
- ContentType: input.contentType,
6528
- Key: input.key
6529
- }), { abortSignal: signal })).pipe(Effect.asVoid)
6530
- };
6531
- }
6532
-
6533
- //#endregion
6534
- //#region ../s3/src/internal/media-object-store.ts
6535
- const mediaPrefix = "media/";
6536
- const replaceableCacheControl = "public, max-age=0, must-revalidate";
6537
- const isWriteBodyError = Schema.is(Schema.Union([MediaInvalid, MediaStorageError]));
6538
- /** Owns and closes the official AWS client for the lifetime of the layer. */
6539
- function s3MediaObjectStoreLayer(options) {
6540
- 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))))));
6541
- }
6542
- function makeStore(options, client) {
6543
- return {
6544
- list: listObjects(options.bucket, client),
6545
- promote: (input) => promoteObject(options.bucket, input, client),
6546
- put: (input) => putObject(options.bucket, input, client),
6547
- remove: (objectKey) => providerRequest("MediaObjectStore.S3.remove", client.deleteObject({
6548
- bucket: options.bucket,
6549
- key: objectKey
6550
- })),
6551
- stat: (objectKey) => statObject(options.bucket, objectKey, client)
6552
- };
6553
- }
6554
- function promoteObject(bucket, input, client) {
6555
- 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({
6556
- bucket,
6557
- cacheControl: replaceableCacheControl,
6558
- contentType: input.mimeType,
6559
- copySource: copySource(bucket, input.sourceObjectKey),
6560
- key: input.destinationObjectKey
6561
- }))), 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."))));
6562
- }
6563
- function copySource(bucket, objectKey) {
6564
- return `/${encodeURIComponent(bucket)}/${objectKey.split("/").map(encodeURIComponent).join("/")}`;
6565
- }
6566
- function putObject(bucket, input, client) {
6567
- return Effect.gen(function* () {
6568
- const body = yield* validatedWriteBody(input);
6569
- const iterable = yield* Stream.toAsyncIterableEffect(body);
6570
- yield* client.putObject({
6571
- body: iterable,
6572
- bucket,
6573
- cacheControl: replaceableCacheControl,
6574
- contentLength: input.byteSize,
6575
- contentType: input.mimeType,
6576
- key: input.objectKey
6577
- }).pipe(Effect.catch((failure) => isWriteBodyError(failure.cause) ? Effect.fail(failure.cause) : Effect.fail(providerError("MediaObjectStore.S3.put", failure))));
6578
- return {
6579
- byteSize: input.byteSize,
6580
- mimeType: input.mimeType,
6581
- objectKey: input.objectKey
6582
- };
6583
- });
6584
- }
6585
- const validatedWriteBody = Effect.fn("MediaObjectStore.S3.validateWriteBody")(function* (input) {
6586
- const byteLength = yield* Ref.make(0);
6587
- return input.body.pipe(Stream.mapEffect((chunk) => Effect.gen(function* () {
6588
- 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.");
6589
- return chunk;
6590
- })), 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)));
6591
- });
6592
- function statObject(bucket, objectKey, client) {
6593
- return client.headObject({
6594
- bucket,
6595
- key: objectKey
6596
- }).pipe(Effect.matchEffect({
6597
- onFailure: (failure) => isMissingObject(failure.cause) ? Effect.succeed(Option.none()) : Effect.fail(providerError("MediaObjectStore.S3.stat", failure)),
6598
- onSuccess: (output) => decodeStoredObject("MediaObjectStore.S3.stat", objectKey, output.contentLength, output.contentType).pipe(Effect.asSome)
6599
- }));
6600
- }
6601
- function listObjects(bucket, client) {
6602
- return Stream.paginate({ seenTokens: /* @__PURE__ */ new Set() }, (state) => providerRequest("MediaObjectStore.S3.list", client.listObjects({
6603
- bucket,
6604
- continuationToken: state.continuationToken,
6605
- prefix: mediaPrefix
6606
- })).pipe(Effect.flatMap((page) => Effect.gen(function* () {
6607
- return [yield* Effect.forEach(page.objects, decodeListedObject), yield* nextListState(state, page)];
6608
- }))));
6609
- }
6610
- function decodeListedObject(object) {
6611
- if (object.key === void 0) return Effect.fail(storageError("MediaObjectStore.S3.list", "The provider returned object metadata without a key."));
6612
- return decodeStoredObject("MediaObjectStore.S3.list", object.key, object.size);
6613
- }
6614
- function nextListState(state, page) {
6615
- if (!page.isTruncated) return Effect.succeedNone;
6616
- const token = page.nextContinuationToken;
6617
- if (!token || token === state.continuationToken || state.seenTokens.has(token)) return Effect.fail(storageError("MediaObjectStore.S3.list", "The provider returned an invalid continuation token."));
6618
- return Effect.succeedSome({
6619
- continuationToken: token,
6620
- seenTokens: /* @__PURE__ */ new Set([...state.seenTokens, token])
6621
- });
6622
- }
6623
- function decodeStoredObject(operation, objectKey, byteSize, contentType) {
6624
- const decodedObjectKey = Schema.decodeOption(MediaObjectKey)(objectKey);
6625
- const decodedByteSize = Schema.decodeUnknownOption(MediaByteSize)(byteSize);
6626
- if (Option.isNone(decodedObjectKey) || Option.isNone(decodedByteSize)) return Effect.fail(storageError(operation, "The provider returned invalid object metadata."));
6627
- const decodedMimeType = Schema.decodeUnknownOption(ImageMimeType)(contentType);
6628
- const storedObject = {
6629
- byteSize: decodedByteSize.value,
6630
- objectKey: decodedObjectKey.value
6631
- };
6632
- return Effect.succeed(Option.isSome(decodedMimeType) ? {
6633
- ...storedObject,
6634
- mimeType: decodedMimeType.value
6635
- } : storedObject);
6636
- }
6637
- function providerRequest(operation, request) {
6638
- return request.pipe(Effect.mapError((failure) => providerError(operation, failure)));
6639
- }
6640
- function provisionBucket(options, client) {
6641
- if (!options.provisionBucket) return Effect.void;
6642
- return client.headBucket({ bucket: options.bucket }).pipe(Effect.catch((failure) => isMissingBucket(failure.cause) ? createMissingBucket(options, client) : Effect.fail(providerError("MediaObjectStore.S3.bucket.head", failure))));
6643
- }
6644
- function createMissingBucket(options, client) {
6645
- return client.createBucket({
6646
- bucket: options.bucket,
6647
- locationConstraint: options.endpoint || options.region === "us-east-1" ? void 0 : options.region
6648
- }).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))));
6649
- }
6650
- const ProviderFailureDetails = Schema.Struct({
6651
- $metadata: Schema.optionalKey(Schema.Struct({
6652
- attempts: Schema.optionalKey(Schema.Finite),
6653
- httpStatusCode: Schema.optionalKey(Schema.Finite)
6654
- })),
6655
- name: Schema.optionalKey(Schema.String)
6656
- });
6657
- const decodeProviderFailure = Schema.decodeUnknownOption(ProviderFailureDetails);
6658
- function providerError(operation, failure) {
6659
- const details = decodeProviderFailure(failure.cause);
6660
- const diagnostics = [];
6661
- if (Option.isSome(details)) {
6662
- if (details.value.name) diagnostics.push(`name=${details.value.name}`);
6663
- if (details.value.$metadata?.httpStatusCode !== void 0) diagnostics.push(`status=${details.value.$metadata.httpStatusCode}`);
6664
- if (details.value.$metadata?.attempts !== void 0) diagnostics.push(`attempts=${details.value.$metadata.attempts}`);
6665
- }
6666
- return storageError(operation, `The S3 provider request failed${diagnostics.length > 0 ? ` (${diagnostics.join(", ")}).` : "."}`);
6667
- }
6668
- function isMissingObject(cause) {
6669
- const details = decodeProviderFailure(cause);
6670
- if (Option.isNone(details)) return false;
6671
- return details.value.name === "NoSuchKey" || details.value.name === "NotFound";
6672
- }
6673
- function isMissingBucket(cause) {
6674
- const details = decodeProviderFailure(cause);
6675
- if (Option.isNone(details)) return false;
6676
- return details.value.name === "NoSuchBucket" || details.value.$metadata?.httpStatusCode === 404;
6677
- }
6678
- function isBucketAlreadyOwned(cause) {
6679
- const details = decodeProviderFailure(cause);
6680
- return Option.isSome(details) && details.value.name === "BucketAlreadyOwnedByYou";
6681
- }
6682
- function resolveOptions(options) {
6683
- const bucket = options.bucket.trim();
6684
- const region = options.region.trim();
6685
- const endpoint = options.endpoint?.trim();
6686
- 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."));
6687
- if (options.credentials) {
6688
- const secretAccessKey = Redacted.value(options.credentials.secretAccessKey);
6689
- const sessionToken = options.credentials.sessionToken && Redacted.value(options.credentials.sessionToken);
6690
- if (!options.credentials.accessKeyId.trim() || !secretAccessKey || sessionToken === "") return Effect.fail(storageError("MediaObjectStore.S3.configure", "The configured credentials must be non-empty."));
6691
- }
6692
- return Effect.succeed({
6693
- ...options,
6694
- bucket,
6695
- credentials: options.credentials ? {
6696
- ...options.credentials,
6697
- accessKeyId: options.credentials.accessKeyId.trim()
6698
- } : void 0,
6699
- endpoint,
6700
- forcePathStyle: options.forcePathStyle ?? false,
6701
- provisionBucket: options.provisionBucket ?? false,
6702
- region
6703
- });
6704
- }
6705
- function storageError(operation, message) {
6706
- return new MediaStorageError({
6707
- cause: new Error(message),
6708
- operation
6709
- });
6710
- }
6711
-
6712
5409
  //#endregion
6713
5410
  //#region ../sql-pg/src/internal/database/table-helpers.ts
6714
5411
  const transactionTimestamp = sql`transaction_timestamp()`;
@@ -6722,6 +5419,7 @@ const ACTIVITY_TARGET_KINDS = [
6722
5419
  "document",
6723
5420
  "media",
6724
5421
  "media-library",
5422
+ "seo",
6725
5423
  "user"
6726
5424
  ];
6727
5425
  const IDENTIFIED_ACTIVITY_TARGET_KINDS = [
@@ -6748,8 +5446,8 @@ const activityEntries = pgTable("activity_entries", {
6748
5446
  }, (table) => [
6749
5447
  check("activity_entries_action_check", isOneOf(table.action, ACTIVITY_ACTIONS)),
6750
5448
  check("activity_entries_target_kind_check", isOneOf(table.targetKind, ACTIVITY_TARGET_KINDS)),
6751
- check("activity_entries_target_check", sql`(${table.targetKind} = 'media-library' AND ${table.targetId} IS NULL)
6752
- OR (${table.targetKind} <> 'media-library' AND ${table.targetId} IS NOT NULL)`),
5449
+ check("activity_entries_target_check", sql`(${table.targetKind} in ('media-library', 'seo') AND ${table.targetId} IS NULL)
5450
+ OR (${table.targetKind} not in ('media-library', 'seo') AND ${table.targetId} IS NOT NULL)`),
6753
5451
  check("activity_entries_actor_roles_check", sql`jsonb_typeof(${table.actorRoles}) = 'array'`),
6754
5452
  check("activity_entries_details_check", sql`jsonb_typeof(${table.details}) = 'object'`),
6755
5453
  index("activity_entries_history").on(table.occurredAt.desc(), table.id.desc()),
@@ -6840,27 +5538,30 @@ function activityEntryValues(entry) {
6840
5538
  };
6841
5539
  }
6842
5540
  function activityEntriesEqual(left, right) {
6843
- return isDeepStrictEqual(Schema.encodeSync(ActivityEntry)(left), Schema.encodeSync(ActivityEntry)(right));
5541
+ return isDeepStrictEqual(Result.getOrThrow(Schema.encodeResult(ActivityEntry)(left)), Result.getOrThrow(Schema.encodeResult(ActivityEntry)(right)));
6844
5542
  }
6845
5543
  function decodeActivityTarget(kind, id) {
6846
- const value = kind === "document" ? {
5544
+ if (kind === "document") return Schema.decodeUnknownEffect(ActivityTarget)({
6847
5545
  documentId: id,
6848
5546
  kind
6849
- } : kind === "media" ? {
5547
+ });
5548
+ if (kind === "media") return Schema.decodeUnknownEffect(ActivityTarget)({
6850
5549
  kind,
6851
5550
  mediaId: id
6852
- } : kind === "user" ? {
5551
+ });
5552
+ if (kind === "user") return Schema.decodeUnknownEffect(ActivityTarget)({
6853
5553
  kind,
6854
5554
  userId: id
6855
- } : { kind };
6856
- return Schema.decodeUnknownEffect(ActivityTarget)(value);
5555
+ });
5556
+ return Schema.decodeEffect(ActivityTarget)({ kind });
6857
5557
  }
6858
5558
  function activityTargetId(entry) {
6859
5559
  switch (entry.target.kind) {
6860
5560
  case "document": return entry.target.documentId;
6861
5561
  case "media": return entry.target.mediaId;
6862
5562
  case "user": return entry.target.userId;
6863
- case "media-library": return;
5563
+ case "media-library":
5564
+ case "seo": return;
6864
5565
  }
6865
5566
  }
6866
5567
  function activityPage(entries, limit, total) {
@@ -7159,7 +5860,8 @@ function targetColumns(target) {
7159
5860
  targetId: target.userId,
7160
5861
  targetKind: target.kind
7161
5862
  };
7162
- case "media-library": return { targetKind: target.kind };
5863
+ case "media-library":
5864
+ case "seo": return { targetKind: target.kind };
7163
5865
  }
7164
5866
  }
7165
5867
  function decodeOptionalTarget(kind, id) {
@@ -7167,7 +5869,7 @@ function decodeOptionalTarget(kind, id) {
7167
5869
  return decodeActivityTarget(kind, id).pipe(Effect.mapError(() => new ChangeMismatch({})));
7168
5870
  }
7169
5871
  function draftEqual(change, draft) {
7170
- return isDeepStrictEqual(Schema.encodeSync(ChangeDraft)(draftFromChange(change)), Schema.encodeSync(ChangeDraft)(draft));
5872
+ return isDeepStrictEqual(Result.getOrThrow(Schema.encodeResult(ChangeDraft)(draftFromChange(change))), Result.getOrThrow(Schema.encodeResult(ChangeDraft)(draft)));
7171
5873
  }
7172
5874
  function draftFromChange(change) {
7173
5875
  const value = {
@@ -7184,7 +5886,7 @@ function draftFromChange(change) {
7184
5886
  });
7185
5887
  }
7186
5888
  function changeEqual(left, right) {
7187
- return isDeepStrictEqual(Schema.encodeSync(PendingChange)(left), Schema.encodeSync(PendingChange)(right));
5889
+ return isDeepStrictEqual(Result.getOrThrow(Schema.encodeResult(PendingChange)(left)), Result.getOrThrow(Schema.encodeResult(PendingChange)(right)));
7188
5890
  }
7189
5891
  function boundedLimit(limit) {
7190
5892
  return Math.min(100, Math.max(0, Math.trunc(limit)));
@@ -7285,6 +5987,33 @@ const embeddedMigrations = [
7285
5987
  folderMillis: 178875937e4,
7286
5988
  hash: "e442485fa6c2d611e3dae508a12d245481bc5ec8f2dd4f5d853910df34484787",
7287
5989
  name: "20260907053610_fancy_lady_deathstrike"
5990
+ },
5991
+ {
5992
+ sql: ["ALTER TABLE \"activity_entries\" DROP CONSTRAINT \"activity_entries_action_check\", ADD CONSTRAINT \"activity_entries_action_check\" CHECK (\"action\" in ('document.created', 'document.updated', 'document.moved', 'document.trashed', 'document.revision.restored', 'document.untrashed', 'document.published', 'document.unpublished', 'media.uploaded', 'media.replaced', 'media.removed', 'media.cleaned', 'user.created', 'user.removed', 'user.role.changed', 'user.sessions.revoked', 'authentication.signed-in', 'authentication.signed-out', 'authentication.password.changed', 'authentication.password.reset'));", "\nALTER TABLE \"pending_changes\" DROP CONSTRAINT \"pending_changes_action_check\", ADD CONSTRAINT \"pending_changes_action_check\" CHECK (\"action\" in ('document.created', 'document.updated', 'document.moved', 'document.trashed', 'document.revision.restored', 'document.untrashed', 'document.published', 'document.unpublished', 'media.uploaded', 'media.replaced', 'media.removed', 'media.cleaned', 'user.created', 'user.removed', 'user.role.changed', 'user.sessions.revoked', 'authentication.signed-in', 'authentication.signed-out', 'authentication.password.changed', 'authentication.password.reset'));"],
5993
+ bps: true,
5994
+ folderMillis: 1788866265e3,
5995
+ hash: "24c56a37796caf8d76c0a97128a382b31e71808aa5b0ca7e5bac96795f9e16c7",
5996
+ name: "20260908111745_classy_johnny_storm"
5997
+ },
5998
+ {
5999
+ sql: [
6000
+ "CREATE TABLE \"seo_media_references\" (\n \"id\" uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n \"scope\" text NOT NULL,\n \"media_id\" uuid NOT NULL,\n CONSTRAINT \"seo_media_references_scope_media_unique\" UNIQUE(\"scope\",\"media_id\"),\n CONSTRAINT \"seo_media_references_scope_check\" CHECK (\"scope\" in ('working', 'published'))\n);\n",
6001
+ "\nCREATE TABLE \"seo_revisions\" (\n \"id\" uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n \"settings\" jsonb NOT NULL,\n \"actor_id\" text NOT NULL,\n \"actor_email\" text NOT NULL,\n \"actor_name\" text NOT NULL,\n \"actor_roles\" jsonb NOT NULL,\n \"request_id\" uuid NOT NULL,\n \"reason\" text,\n \"created_at\" timestamp with time zone DEFAULT transaction_timestamp() NOT NULL,\n CONSTRAINT \"seo_revisions_actor_roles_check\" CHECK (jsonb_typeof(\"actor_roles\") = 'array'),\n CONSTRAINT \"seo_revisions_settings_check\" CHECK (jsonb_typeof(\"settings\") = 'object')\n);\n",
6002
+ "\nCREATE TABLE \"seo_state\" (\n \"singleton\" boolean PRIMARY KEY DEFAULT true,\n \"working_revision_id\" uuid NOT NULL,\n \"published_revision_id\" uuid NOT NULL,\n CONSTRAINT \"seo_state_singleton_check\" CHECK (\"singleton\")\n);\n",
6003
+ "\nCREATE INDEX \"seo_media_references_media\" ON \"seo_media_references\" (\"media_id\");",
6004
+ "\nCREATE INDEX \"seo_revisions_history\" ON \"seo_revisions\" (\"created_at\" DESC NULLS LAST,\"id\" DESC NULLS LAST);",
6005
+ "\nALTER TABLE \"seo_media_references\" ADD CONSTRAINT \"seo_media_references_media_id_media_assets_id_fkey\" FOREIGN KEY (\"media_id\") REFERENCES \"media_assets\"(\"id\") ON DELETE RESTRICT;",
6006
+ "\nALTER TABLE \"seo_state\" ADD CONSTRAINT \"seo_state_working_revision_fkey\" FOREIGN KEY (\"working_revision_id\") REFERENCES \"seo_revisions\"(\"id\") ON DELETE RESTRICT;",
6007
+ "\nALTER TABLE \"seo_state\" ADD CONSTRAINT \"seo_state_published_revision_fkey\" FOREIGN KEY (\"published_revision_id\") REFERENCES \"seo_revisions\"(\"id\") ON DELETE RESTRICT;",
6008
+ "\nALTER TABLE \"activity_entries\" DROP CONSTRAINT \"activity_entries_action_check\", ADD CONSTRAINT \"activity_entries_action_check\" CHECK (\"action\" in ('document.created', 'document.updated', 'document.moved', 'document.trashed', 'document.revision.restored', 'document.untrashed', 'document.published', 'document.unpublished', 'media.uploaded', 'media.replaced', 'media.removed', 'media.cleaned', 'seo.saved', 'seo.published', 'user.created', 'user.removed', 'user.role.changed', 'user.sessions.revoked', 'authentication.signed-in', 'authentication.signed-out', 'authentication.password.changed', 'authentication.password.reset'));",
6009
+ "\nALTER TABLE \"activity_entries\" DROP CONSTRAINT \"activity_entries_target_kind_check\", ADD CONSTRAINT \"activity_entries_target_kind_check\" CHECK (\"target_kind\" in ('document', 'media', 'media-library', 'seo', 'user'));",
6010
+ "\nALTER TABLE \"activity_entries\" DROP CONSTRAINT \"activity_entries_target_check\", ADD CONSTRAINT \"activity_entries_target_check\" CHECK ((\"target_kind\" in ('media-library', 'seo') AND \"target_id\" IS NULL)\n OR (\"target_kind\" not in ('media-library', 'seo') AND \"target_id\" IS NOT NULL));",
6011
+ "\nALTER TABLE \"pending_changes\" DROP CONSTRAINT \"pending_changes_action_check\", ADD CONSTRAINT \"pending_changes_action_check\" CHECK (\"action\" in ('document.created', 'document.updated', 'document.moved', 'document.trashed', 'document.revision.restored', 'document.untrashed', 'document.published', 'document.unpublished', 'media.uploaded', 'media.replaced', 'media.removed', 'media.cleaned', 'seo.saved', 'seo.published', 'user.created', 'user.removed', 'user.role.changed', 'user.sessions.revoked', 'authentication.signed-in', 'authentication.signed-out', 'authentication.password.changed', 'authentication.password.reset'));"
6012
+ ],
6013
+ bps: true,
6014
+ folderMillis: 1789233351e3,
6015
+ hash: "8e6a6abf5385ab3ecf8eb083c1923ac9c74d72aa71bd1d444b7402d3e7e77000",
6016
+ name: "20260912171551_charming_valkyrie"
7288
6017
  }
7289
6018
  ];
7290
6019
 
@@ -7383,12 +6112,16 @@ const initializationState = pgTable("initialization_state", {
7383
6112
 
7384
6113
  //#endregion
7385
6114
  //#region ../sql-pg/src/internal/database/migrations.ts
7386
- /** Applies generated CMS migrations and initializes required application state. */
6115
+ const MIGRATION_LOCK_KEY = "cms:database-migrations:v1";
7387
6116
  const migrateCmsDatabase = Effect.fn("CmsDatabase.migrate")(function* () {
6117
+ const postgres = yield* PgClient.PgClient;
7388
6118
  const database = yield* PgDrizzle.makeWithDefaults();
7389
- yield* migrate(embeddedMigrations, database._.session, "__drizzle_migrations");
7390
- yield* database.insert(cmsState).values({}).onConflictDoNothing({ target: cmsState.singleton });
7391
- yield* database.insert(initializationState).values({}).onConflictDoNothing({ target: initializationState.singleton });
6119
+ yield* postgres.withTransaction(Effect.gen(function* () {
6120
+ yield* postgres.unsafe("select pg_advisory_xact_lock(hashtextextended($1, 0))", [MIGRATION_LOCK_KEY]);
6121
+ yield* migrate(embeddedMigrations, database._.session, "__drizzle_migrations");
6122
+ yield* database.insert(cmsState).values({}).onConflictDoNothing({ target: cmsState.singleton });
6123
+ yield* database.insert(initializationState).values({}).onConflictDoNothing({ target: initializationState.singleton });
6124
+ }));
7392
6125
  });
7393
6126
  const cmsDatabaseMigrationLayer = Layer.effectDiscard(migrateCmsDatabase());
7394
6127
 
@@ -7425,7 +6158,11 @@ const applyDocumentCommitPlan = Effect.fn("DocumentStore.Drizzle.applyCommitPlan
7425
6158
  return;
7426
6159
  }
7427
6160
  case "document.remove": {
7428
- const revisionId = yield* appendRevision(transaction, plan, activity);
6161
+ const metadata = {};
6162
+ if (plan.trashBatchId) metadata.trashBatchId = plan.trashBatchId;
6163
+ if (plan.title) metadata.title = plan.title;
6164
+ if (plan.path) metadata.path = plan.path;
6165
+ const revisionId = yield* appendRevision(transaction, plan, activity, metadata);
7429
6166
  yield* transaction.update(documentsTable).set({
7430
6167
  deletedAt: transactionTimestamp,
7431
6168
  updatedAt: transactionTimestamp
@@ -7454,6 +6191,9 @@ const applyDocumentCommitPlan = Effect.fn("DocumentStore.Drizzle.applyCommitPlan
7454
6191
  }).where(eq(documentHeads.documentId, plan.documentId));
7455
6192
  return;
7456
6193
  }
6194
+ case "document.unpublish":
6195
+ yield* transaction.update(documentHeads).set({ publishedRevisionId: null }).where(eq(documentHeads.documentId, plan.documentId));
6196
+ return;
7457
6197
  case "document.publish":
7458
6198
  yield* transaction.update(documentHeads).set({ publishedRevisionId: plan.revisionId }).where(eq(documentHeads.documentId, plan.documentId));
7459
6199
  return;
@@ -7485,7 +6225,7 @@ function mapActivePathConflict(sourcePath) {
7485
6225
  const causeError = Cause$1.findErrorOption(error.cause);
7486
6226
  if (Option$1.isNone(causeError) || !SqlError.isSqlError(causeError.value)) return error;
7487
6227
  const reason = causeError.value.reason;
7488
- return reason._tag === "UniqueViolation" && reason.constraint === "documents_active_path" ? new DocumentPathConflict({ sourcePath }) : error;
6228
+ return Predicate.isTagged("UniqueViolation")(reason) && reason.constraint === "documents_active_path" ? new DocumentPathConflict({ sourcePath }) : error;
7489
6229
  }));
7490
6230
  }
7491
6231
  function updateAuthoredHead(transaction, documentId, revisionId) {
@@ -7505,6 +6245,7 @@ function activityActionForDocumentPlan(action) {
7505
6245
  case "document.remove": return "document.trashed";
7506
6246
  case "document.restore": return "document.revision.restored";
7507
6247
  case "document.undelete": return "document.untrashed";
6248
+ case "document.unpublish": return "document.unpublished";
7508
6249
  case "document.publish": return "document.published";
7509
6250
  }
7510
6251
  }
@@ -7567,6 +6308,7 @@ const DocumentMediaReferenceInsert = createInsertSchema(documentMediaReferences,
7567
6308
  //#region ../sql-pg/src/internal/documents/records.ts
7568
6309
  const DocumentSnapshotRow = Schema.Struct({
7569
6310
  authored: Schema.NullOr(DocumentRevisionSelect),
6311
+ created: Schema.optionalKey(Schema.NullOr(DocumentRevisionSelect)),
7570
6312
  document: DocumentSelect,
7571
6313
  head: Schema.NullOr(DocumentHeadSelect),
7572
6314
  published: Schema.NullOr(DocumentRevisionSelect)
@@ -7583,11 +6325,18 @@ const decodeDocumentSnapshot = Effect.fn("DocumentStore.Drizzle.decodeSnapshot")
7583
6325
  if (documentIds.has(row.document.id)) return yield* documentPersistenceError("DocumentStore.Drizzle.decodeSnapshot", /* @__PURE__ */ new Error(`Documents persistence returned duplicate document "${row.document.id}".`));
7584
6326
  documentIds.add(row.document.id);
7585
6327
  yield* validateSnapshotRow(row.document, row.head, row.authored, row.published);
7586
- const authored = sourceRecord(row.authored, row.head.publishedRevisionId ?? void 0);
6328
+ const authored = Object.assign({}, sourceRecord(row.authored, row.head.publishedRevisionId ?? void 0), row.created ? { createdBy: revisionActor(row.created) } : void 0);
7587
6329
  if (row.document.deletedAt === null) {
7588
6330
  active.push(authored);
7589
6331
  if (row.published) published.push(sourceRecord(row.published, row.published.id));
7590
- } else deleted.push(Object.assign({}, authored, { deletedAt: row.document.deletedAt }));
6332
+ } else {
6333
+ const details = yield* Schema.decodeEffect(Schema.Struct({
6334
+ title: DeletedDocument.fields.title,
6335
+ path: DeletedDocument.fields.path,
6336
+ trashBatchId: DeletedDocument.fields.trashBatchId
6337
+ }))(row.authored.activityDetails).pipe(Effect.mapError((cause) => documentPersistenceError("DocumentStore.decodeTrash", cause)));
6338
+ deleted.push(Object.assign({}, authored, details, { deletedAt: row.document.deletedAt }));
6339
+ }
7591
6340
  }
7592
6341
  active.sort(compareSources);
7593
6342
  deleted.sort(compareSources);
@@ -7643,6 +6392,8 @@ function validateSnapshotRow(document, head, authored, published) {
7643
6392
  }
7644
6393
  function sourceRecord(revision, publishedRevisionId) {
7645
6394
  const record = {
6395
+ editedAt: revision.createdAt,
6396
+ editedBy: revisionActor(revision),
7646
6397
  documentId: revision.documentId,
7647
6398
  markdown: revision.markdown,
7648
6399
  revisionId: revision.id,
@@ -7661,6 +6412,14 @@ function documentPersistenceError(operation, cause) {
7661
6412
  operation
7662
6413
  });
7663
6414
  }
6415
+ function revisionActor(revision) {
6416
+ return {
6417
+ id: revision.actorId,
6418
+ name: revision.actorName,
6419
+ email: revision.actorEmail,
6420
+ roles: revision.actorRoles
6421
+ };
6422
+ }
7664
6423
 
7665
6424
  //#endregion
7666
6425
  //#region ../sql-pg/src/internal/database/media-reference-lock.ts
@@ -7766,6 +6525,7 @@ function validMediaObjectKey(column) {
7766
6525
  //#endregion
7767
6526
  //#region ../sql-pg/src/internal/documents/document-store.ts
7768
6527
  const authoredRevision = alias(documentRevisions, "authored_revision");
6528
+ const createdRevision = alias(documentRevisions, "created_revision");
7769
6529
  const publishedRevision = alias(documentRevisions, "published_revision");
7770
6530
  const RepositoryDomainError = Schema.Union([
7771
6531
  DocumentGenerationConflict,
@@ -7779,7 +6539,7 @@ const isRepositoryDomainError = Schema.is(RepositoryDomainError);
7779
6539
  const isDocumentNotFound = Schema.is(DocumentNotFound);
7780
6540
  const isDocumentMediaUnavailable = Schema.is(DocumentMediaUnavailable);
7781
6541
  const isDocumentPersistenceError = Schema.is(DocumentPersistenceError);
7782
- const systemActivity = ActivityContext.make({
6542
+ const systemActivity$1 = ActivityContext.make({
7783
6543
  actor: {
7784
6544
  email: "system@cms.invalid",
7785
6545
  id: "system",
@@ -7800,19 +6560,26 @@ const documentStoreLayer = Layer.effect(DocumentStore, Effect.gen(function* () {
7800
6560
  });
7801
6561
  }
7802
6562
  const state = yield* Schema.decodeUnknownEffect(CmsStateSelect)(stateRow);
7803
- const references = yield* referencesForDocument(input.plan.documentId, input.mediaReferences);
7804
- const objectKeys = references.map((reference) => reference.objectKey);
7805
- yield* lockMediaReferenceKeys(transaction, objectKeys);
7806
- yield* rejectDeletingMedia(transaction, references);
7807
- const activity = input.activity ?? systemActivity;
7808
- yield* applyDocumentCommitPlan(transaction, input.plan, activity);
7809
- yield* replaceDocumentMediaReferences(transaction, input.plan.documentId, references);
7810
- const snapshot = yield* readSnapshot(transaction, state.generation);
6563
+ const plans = [input.plan, ...input.additionalPlans ?? []];
6564
+ const references = plans.flatMap((plan) => input.mediaReferences.filter((reference) => reference.documentId === plan.documentId));
6565
+ if (references.length !== input.mediaReferences.length) return yield* persistenceFailure$3("DocumentStore.commit", /* @__PURE__ */ new Error("Unexpected media reference owner."));
6566
+ yield* lockMediaReferenceKeys(transaction, references.map((reference) => reference.objectKey));
6567
+ yield* rejectUnavailableMedia(transaction, references);
6568
+ const activity = input.activity ?? systemActivity$1;
6569
+ for (const plan of plans) {
6570
+ yield* applyDocumentCommitPlan(transaction, plan, activity);
6571
+ yield* replaceDocumentMediaReferences(transaction, plan.documentId, references.filter((reference) => reference.documentId === plan.documentId));
6572
+ }
6573
+ const snapshot = yield* readSnapshot$1(transaction, state.generation);
6574
+ for (const plan of plans) {
6575
+ const affected = yield* sourceForMutation(snapshot, plan.documentId, plan.action === "document.remove");
6576
+ yield* recordDocumentActivity(transaction, affected, plan, activity);
6577
+ }
7811
6578
  const source = yield* sourceForMutation(snapshot, input.plan.documentId, input.plan.action === "document.remove");
7812
- yield* recordDocumentActivity(transaction, source, input.plan, activity);
7813
6579
  return {
7814
6580
  mutation: DocumentMutationResult.make({
7815
6581
  action: input.plan.action,
6582
+ affectedDocumentIds: plans.map((plan) => plan.documentId),
7816
6583
  documentId: source.documentId,
7817
6584
  generation: snapshot.generation,
7818
6585
  revisionId: source.revisionId,
@@ -7825,15 +6592,15 @@ const documentStoreLayer = Layer.effect(DocumentStore, Effect.gen(function* () {
7825
6592
  const load = Effect.fn("DocumentStore.Drizzle.load")((after) => database.transaction((transaction) => Effect.gen(function* () {
7826
6593
  const generation = yield* readGeneration(transaction);
7827
6594
  if (after === generation.toString()) return void 0;
7828
- return yield* readSnapshot(transaction, generation);
6595
+ return yield* readSnapshot$1(transaction, generation);
7829
6596
  }), {
7830
6597
  accessMode: "read only",
7831
6598
  isolationLevel: "repeatable read"
7832
- }).pipe(mapPersistenceFailure$2("DocumentStore.Drizzle.load")));
6599
+ }).pipe(mapPersistenceFailure$3("DocumentStore.Drizzle.load")));
7833
6600
  const synchronizeMediaReferences = Effect.fn("DocumentStore.Drizzle.synchronizeMediaReferences")((input) => database.transaction((transaction) => Effect.gen(function* () {
7834
6601
  const stateRows = yield* transaction.select().from(cmsState).where(eq(cmsState.singleton, true)).limit(2).for("update");
7835
6602
  const stateRow = stateRows[0];
7836
- if (stateRows.length !== 1 || !stateRow) return yield* persistenceFailure$2("DocumentStore.Drizzle.synchronizeMediaReferences", /* @__PURE__ */ new Error(`Expected exactly one document generation row; received ${stateRows.length}.`));
6603
+ if (stateRows.length !== 1 || !stateRow) return yield* persistenceFailure$3("DocumentStore.Drizzle.synchronizeMediaReferences", /* @__PURE__ */ new Error(`Expected exactly one document generation row; received ${stateRows.length}.`));
7837
6604
  const state = yield* Schema.decodeUnknownEffect(CmsStateSelect)(stateRow);
7838
6605
  if (state.generation.toString() !== input.expectedGeneration) return yield* new DocumentGenerationConflict({
7839
6606
  actualGeneration: DocumentGeneration.make(String(state.generation)),
@@ -7841,7 +6608,7 @@ const documentStoreLayer = Layer.effect(DocumentStore, Effect.gen(function* () {
7841
6608
  });
7842
6609
  const references = uniqueMediaReferences(input.references);
7843
6610
  yield* lockMediaReferenceKeys(transaction, references.map((reference) => reference.objectKey));
7844
- yield* rejectDeletingMedia(transaction, references);
6611
+ yield* rejectUnavailableMedia(transaction, references);
7845
6612
  yield* transaction.delete(documentMediaReferences);
7846
6613
  if (references.length > 0) yield* transaction.insert(documentMediaReferences).values(references.map(referenceValues));
7847
6614
  })).pipe(mapSynchronizationFailure("DocumentStore.Drizzle.synchronizeMediaReferences")));
@@ -7857,12 +6624,12 @@ const documentStoreLayer = Layer.effect(DocumentStore, Effect.gen(function* () {
7857
6624
  const cursor = query.cursor ? or(lt(documentRevisions.createdAt, DateTime.formatIso(query.cursor.createdAt)), and(eq(documentRevisions.createdAt, DateTime.formatIso(query.cursor.createdAt)), lt(documentRevisions.id, query.cursor.revisionId))) : void 0;
7858
6625
  const rows = yield* transaction.select().from(documentRevisions).where(and(eq(documentRevisions.documentId, query.documentId), cursor)).orderBy(desc(documentRevisions.createdAt), desc(documentRevisions.id)).limit(query.limit + 1);
7859
6626
  const total = (yield* transaction.select({ value: count() }).from(documentRevisions).where(eq(documentRevisions.documentId, query.documentId)))[0]?.value;
7860
- if (total === void 0) return yield* persistenceFailure$2("DocumentStore.Drizzle.revisions", /* @__PURE__ */ new Error("Documents persistence did not return a history total."));
6627
+ if (total === void 0) return yield* persistenceFailure$3("DocumentStore.Drizzle.revisions", /* @__PURE__ */ new Error("Documents persistence did not return a history total."));
7861
6628
  return revisionPage(yield* Effect.forEach(rows, decodeDocumentRevision), query.limit, total);
7862
6629
  }), {
7863
6630
  accessMode: "read only",
7864
6631
  isolationLevel: "repeatable read"
7865
- }).pipe(mapPersistenceFailure$2("DocumentStore.Drizzle.revisions")));
6632
+ }).pipe(mapPersistenceFailure$3("DocumentStore.Drizzle.revisions")));
7866
6633
  return DocumentStore.of({
7867
6634
  commit,
7868
6635
  load,
@@ -7901,27 +6668,28 @@ const recordDocumentActivity = Effect.fn("DocumentStore.Drizzle.recordActivity")
7901
6668
  targetId: source.documentId,
7902
6669
  targetKind: "document"
7903
6670
  };
7904
- if (plan.action !== "document.publish") values.id = source.revisionId;
6671
+ if (plan.action !== "document.publish" && plan.action !== "document.unpublish") values.id = source.revisionId;
7905
6672
  yield* database.insert(activityEntries).values(values);
7906
6673
  });
7907
- const readSnapshot = Effect.fn("DocumentStore.Drizzle.readSnapshot")(function* (database, generation) {
6674
+ const readSnapshot$1 = Effect.fn("DocumentStore.Drizzle.readSnapshot")(function* (database, generation) {
7908
6675
  const rows = yield* database.select({
7909
6676
  authored: authoredRevision,
6677
+ created: createdRevision,
7910
6678
  document: documentsTable,
7911
6679
  head: documentHeads,
7912
6680
  published: publishedRevision
7913
- }).from(documentsTable).leftJoin(documentHeads, eq(documentHeads.documentId, documentsTable.id)).leftJoin(authoredRevision, and(eq(authoredRevision.documentId, documentHeads.documentId), eq(authoredRevision.id, documentHeads.authoredRevisionId))).leftJoin(publishedRevision, and(eq(publishedRevision.documentId, documentHeads.documentId), eq(publishedRevision.id, documentHeads.publishedRevisionId)));
6681
+ }).from(documentsTable).leftJoin(createdRevision, and(eq(createdRevision.documentId, documentsTable.id), eq(createdRevision.action, "document.create"))).leftJoin(documentHeads, eq(documentHeads.documentId, documentsTable.id)).leftJoin(authoredRevision, and(eq(authoredRevision.documentId, documentHeads.documentId), eq(authoredRevision.id, documentHeads.authoredRevisionId))).leftJoin(publishedRevision, and(eq(publishedRevision.documentId, documentHeads.documentId), eq(publishedRevision.id, documentHeads.publishedRevisionId)));
7914
6682
  return yield* decodeDocumentSnapshot(rows, generation);
7915
6683
  });
7916
6684
  const readGeneration = Effect.fn("DocumentStore.Drizzle.readGeneration")(function* (database) {
7917
6685
  const rows = yield* database.select().from(cmsState);
7918
6686
  const row = rows[0];
7919
- if (rows.length !== 1 || !row) return yield* persistenceFailure$2("DocumentStore.Drizzle.readGeneration", /* @__PURE__ */ new Error(`Expected exactly one document generation row; received ${rows.length}.`));
6687
+ if (rows.length !== 1 || !row) return yield* persistenceFailure$3("DocumentStore.Drizzle.readGeneration", /* @__PURE__ */ new Error(`Expected exactly one document generation row; received ${rows.length}.`));
7920
6688
  return (yield* Schema.decodeUnknownEffect(CmsStateSelect)(row)).generation;
7921
6689
  });
7922
6690
  const sourceForMutation = Effect.fn("DocumentStore.Drizzle.sourceForMutation")(function* (snapshot, documentId, deleted) {
7923
6691
  const source = (deleted ? snapshot.deleted : snapshot.active).find((candidate) => candidate.documentId === documentId);
7924
- if (!source) return yield* persistenceFailure$2("DocumentStore.Drizzle.sourceForMutation", /* @__PURE__ */ new Error(`Committed document "${documentId}" is missing from its resulting snapshot.`));
6692
+ if (!source) return yield* persistenceFailure$3("DocumentStore.Drizzle.sourceForMutation", /* @__PURE__ */ new Error(`Committed document "${documentId}" is missing from its resulting snapshot.`));
7925
6693
  return source;
7926
6694
  });
7927
6695
  function mapCommitFailure(operation) {
@@ -7933,18 +6701,27 @@ function mapSynchronizationFailure(operation) {
7933
6701
  return documentPersistenceError(operation, cause);
7934
6702
  }));
7935
6703
  }
7936
- const referencesForDocument = Effect.fn("DocumentStore.Drizzle.referencesForDocument")(function* (documentId, references) {
7937
- if (references.some((reference) => reference.documentId !== documentId)) return yield* persistenceFailure$2("DocumentStore.Drizzle.referencesForDocument", /* @__PURE__ */ new Error(`The media-reference projection does not belong exclusively to document "${documentId}".`));
7938
- return uniqueMediaReferences(references);
7939
- });
7940
- const rejectDeletingMedia = Effect.fn("DocumentStore.Drizzle.rejectDeletingMedia")(function* (transaction, references) {
6704
+ const rejectUnavailableMedia = Effect.fn("DocumentStore.Drizzle.rejectUnavailableMedia")(function* (transaction, references) {
7941
6705
  const keys = [...new Set(references.map((reference) => reference.objectKey))];
6706
+ const mediaIds = [...new Set(references.flatMap((reference) => reference.mediaId ? [reference.mediaId] : []))];
7942
6707
  if (keys.length === 0) return;
7943
- const rows = yield* transaction.select({ objectKey: mediaAssets.objectKey }).from(mediaAssets).where(and(inArray(mediaAssets.objectKey, keys), eq(mediaAssets.status, "deleting")));
7944
- const unavailable = yield* Schema.decodeEffect(Schema.Array(MediaObjectKey))(rows.map((row) => row.objectKey));
6708
+ if (mediaIds.length > 0) {
6709
+ const identityRows = yield* transaction.select({ objectKey: mediaAssets.objectKey }).from(mediaAssets).where(inArray(mediaAssets.id, mediaIds));
6710
+ yield* lockMediaReferenceKeys(transaction, yield* Schema.decodeEffect(Schema.Array(MediaObjectKey))(identityRows.map((row) => row.objectKey)));
6711
+ }
6712
+ const rows = yield* transaction.select({
6713
+ id: mediaAssets.id,
6714
+ objectKey: mediaAssets.objectKey,
6715
+ status: mediaAssets.status
6716
+ }).from(mediaAssets).where(mediaIds.length > 0 ? or(inArray(mediaAssets.objectKey, keys), inArray(mediaAssets.id, mediaIds)) : inArray(mediaAssets.objectKey, keys));
6717
+ const deletingKeys = new Set(yield* Schema.decodeEffect(Schema.Array(MediaObjectKey))(rows.filter((row) => row.status === "deleting").map((row) => row.objectKey)));
6718
+ const unavailable = references.filter((reference) => {
6719
+ if (!reference.mediaId) return deletingKeys.has(reference.objectKey);
6720
+ const asset = rows.find((row) => row.id === reference.mediaId);
6721
+ return !asset || asset.status !== "ready" || asset.objectKey !== reference.objectKey;
6722
+ });
7945
6723
  if (unavailable.length === 0) return;
7946
- const unavailableSet = new Set(unavailable);
7947
- const affected = references.filter((reference) => unavailableSet.has(reference.objectKey)).sort((left, right) => left.documentId === right.documentId ? left.objectKey.localeCompare(right.objectKey) : left.documentId.localeCompare(right.documentId));
6724
+ const affected = unavailable.sort((left, right) => left.documentId === right.documentId ? left.objectKey.localeCompare(right.objectKey) : left.documentId.localeCompare(right.documentId));
7948
6725
  const first = affected[0];
7949
6726
  if (!first) return;
7950
6727
  return yield* new DocumentMediaUnavailable({
@@ -7968,7 +6745,7 @@ function referenceValues(reference) {
7968
6745
  sourcePath: reference.sourcePath
7969
6746
  };
7970
6747
  }
7971
- function mapPersistenceFailure$2(operation) {
6748
+ function mapPersistenceFailure$3(operation) {
7972
6749
  return (effect) => effect.pipe(Effect.mapError((cause) => isDocumentPersistenceError(cause) ? cause : documentPersistenceError(operation, cause)));
7973
6750
  }
7974
6751
  function mapRevisionFailure(operation) {
@@ -7977,7 +6754,7 @@ function mapRevisionFailure(operation) {
7977
6754
  return documentPersistenceError(operation, cause);
7978
6755
  }));
7979
6756
  }
7980
- function persistenceFailure$2(operation, cause) {
6757
+ function persistenceFailure$3(operation, cause) {
7981
6758
  return Effect.fail(documentPersistenceError(operation, cause));
7982
6759
  }
7983
6760
 
@@ -8040,7 +6817,7 @@ const initializationStoreLayer = Layer.effect(InitializationStore, Effect.gen(fu
8040
6817
  const locks = yield* PostgresAdvisoryLock;
8041
6818
  const claim = Effect.fn("InitializationStore.Postgres.claim")((value) => database.update(initializationState).set({
8042
6819
  changeId: value.changeId,
8043
- claim: Schema.encodeSync(InitializationClaim)(value),
6820
+ claim: Result.getOrThrow(Schema.encodeResult(InitializationClaim)(value)),
8044
6821
  state: "claiming"
8045
6822
  }).where(and(eq(initializationState.singleton, true), eq(initializationState.state, "uninitialized"))).returning({ singleton: initializationState.singleton }).pipe(Effect.map((rows) => rows.length === 1), mapUnavailable("InitializationStore.Postgres.claim")));
8046
6823
  const complete = Effect.fn("InitializationStore.Postgres.complete")((changeId) => transition(database, changeId, "ready"));
@@ -8092,6 +6869,53 @@ function mapUnavailable(operation) {
8092
6869
  })));
8093
6870
  }
8094
6871
 
6872
+ //#endregion
6873
+ //#region ../sql-pg/src/internal/seo/tables.ts
6874
+ const seoRevisions = pgTable("seo_revisions", {
6875
+ id: uuid().defaultRandom().primaryKey(),
6876
+ settings: jsonb().$type().notNull(),
6877
+ actorId: text("actor_id").notNull(),
6878
+ actorEmail: text("actor_email").notNull(),
6879
+ actorName: text("actor_name").notNull(),
6880
+ actorRoles: jsonb("actor_roles").$type().notNull(),
6881
+ requestId: uuid("request_id").notNull(),
6882
+ reason: text(),
6883
+ createdAt: timestamp("created_at", {
6884
+ mode: "string",
6885
+ withTimezone: true
6886
+ }).default(transactionTimestamp).notNull()
6887
+ }, (table) => [
6888
+ check("seo_revisions_actor_roles_check", sql`jsonb_typeof(${table.actorRoles}) = 'array'`),
6889
+ check("seo_revisions_settings_check", sql`jsonb_typeof(${table.settings}) = 'object'`),
6890
+ index("seo_revisions_history").on(table.createdAt.desc(), table.id.desc())
6891
+ ]);
6892
+ const seoState = pgTable("seo_state", {
6893
+ singleton: boolean().default(true).primaryKey(),
6894
+ workingRevisionId: uuid("working_revision_id").notNull(),
6895
+ publishedRevisionId: uuid("published_revision_id").notNull()
6896
+ }, (table) => [
6897
+ check("seo_state_singleton_check", sql`${table.singleton}`),
6898
+ foreignKey({
6899
+ columns: [table.workingRevisionId],
6900
+ foreignColumns: [seoRevisions.id],
6901
+ name: "seo_state_working_revision_fkey"
6902
+ }).onDelete("restrict"),
6903
+ foreignKey({
6904
+ columns: [table.publishedRevisionId],
6905
+ foreignColumns: [seoRevisions.id],
6906
+ name: "seo_state_published_revision_fkey"
6907
+ }).onDelete("restrict")
6908
+ ]);
6909
+ const seoMediaReferences = pgTable("seo_media_references", {
6910
+ id: uuid().defaultRandom().primaryKey(),
6911
+ scope: text({ enum: ["working", "published"] }).notNull(),
6912
+ mediaId: uuid("media_id").notNull().references(() => mediaAssets.id, { onDelete: "restrict" })
6913
+ }, (table) => [
6914
+ check("seo_media_references_scope_check", sql`${table.scope} in ('working', 'published')`),
6915
+ unique("seo_media_references_scope_media_unique").on(table.scope, table.mediaId),
6916
+ index("seo_media_references_media").on(table.mediaId)
6917
+ ]);
6918
+
8095
6919
  //#endregion
8096
6920
  //#region ../sql-pg/src/internal/media/row-schema.ts
8097
6921
  const mediaRefinements = {
@@ -8159,14 +6983,14 @@ const isMediaReferenced = Schema.is(MediaReferenced);
8159
6983
  const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
8160
6984
  const database = yield* PgDrizzle.makeWithDefaults();
8161
6985
  const reserve = Effect.fn("MediaStore.Drizzle.reserve")((record, operation) => database.transaction((transaction) => Effect.gen(function* () {
8162
- if (record.status !== "uploading") return yield* persistenceFailure$1("MediaStore.Drizzle.reserve", /* @__PURE__ */ new Error(`A media reservation must start as uploading, received "${record.status}".`));
6986
+ if (record.status !== "uploading") return yield* persistenceFailure$2("MediaStore.Drizzle.reserve", /* @__PURE__ */ new Error(`A media reservation must start as uploading, received "${record.status}".`));
8163
6987
  yield* beginChange(transaction, mediaUploadChange(record, operation)).pipe(Effect.flatMap(requireUnresolvedChange));
8164
6988
  const rows = yield* transaction.insert(mediaAssets).values(recordValues(record)).onConflictDoNothing().returning();
8165
6989
  if (rows[0]) return yield* decodeMediaRecord(rows[0]);
8166
6990
  const existing = yield* rowById(transaction, record.id);
8167
6991
  if (existing && existing.status !== "deleting" && isSameReservation(existing, record)) return existing;
8168
- return yield* persistenceFailure$1("MediaStore.Drizzle.reserve", /* @__PURE__ */ new Error(`Media reservation "${record.id}" conflicts with durable media state.`));
8169
- })).pipe(mapPersistenceFailure$1("MediaStore.Drizzle.reserve")));
6992
+ return yield* persistenceFailure$2("MediaStore.Drizzle.reserve", /* @__PURE__ */ new Error(`Media reservation "${record.id}" conflicts with durable media state.`));
6993
+ })).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.reserve")));
8170
6994
  const activate = Effect.fn("MediaStore.Drizzle.activate")((id) => database.transaction((transaction) => Effect.gen(function* () {
8171
6995
  const existing = yield* lockedRowById(transaction, id);
8172
6996
  if (existing?.status === "ready") return existing;
@@ -8184,11 +7008,11 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
8184
7008
  kind: "applied"
8185
7009
  });
8186
7010
  return activated;
8187
- })).pipe(mapPersistenceFailure$1("MediaStore.Drizzle.activate")));
7011
+ })).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.activate")));
8188
7012
  const find = Effect.fn("MediaStore.Drizzle.find")((id) => Effect.gen(function* () {
8189
7013
  const rows = yield* database.select().from(mediaAssets).where(and(eq(mediaAssets.id, id), eq(mediaAssets.status, "ready"))).limit(1);
8190
7014
  return rows[0] ? Option.some(yield* decodeMediaRecord(rows[0])) : Option.none();
8191
- }).pipe(mapPersistenceFailure$1("MediaStore.Drizzle.find")));
7015
+ }).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.find")));
8192
7016
  const beginDelete = Effect.fn("MediaStore.Drizzle.beginDelete")((id, operation) => database.transaction((transaction) => Effect.gen(function* () {
8193
7017
  const existing = yield* lockedRowById(transaction, id);
8194
7018
  if (!existing) return yield* new MediaNotFound({ mediaId: id });
@@ -8201,7 +7025,13 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
8201
7025
  yield* lockMediaReferenceKeys(transaction, [existing.objectKey]);
8202
7026
  const referenceRows = yield* transaction.select().from(documentMediaReferences).where(eq(documentMediaReferences.objectKey, existing.objectKey)).orderBy(asc(documentMediaReferences.documentId), asc(documentMediaReferences.sourcePath), asc(documentMediaReferences.nodePath));
8203
7027
  const references = yield* decodeMediaReferences(referenceRows);
8204
- if (references.length > 0) return yield* new MediaReferenced({
7028
+ const seoReferenceRows = yield* transaction.select({ scope: seoMediaReferences.scope }).from(seoMediaReferences).where(eq(seoMediaReferences.mediaId, id)).orderBy(asc(seoMediaReferences.scope));
7029
+ const seoScopes = yield* Schema.decodeEffect(Schema.Array(Schema.Literals(["working", "published"])))(seoReferenceRows.map((row) => row.scope)).pipe(Effect.mapError((cause) => mediaPersistenceError("MediaStore.Drizzle.beginDelete", cause)));
7030
+ if (references.length > 0 || seoScopes.length > 0) return yield* new MediaReferenced(seoScopes.length > 0 ? {
7031
+ mediaId: id,
7032
+ references,
7033
+ seoScopes
7034
+ } : {
8205
7035
  mediaId: id,
8206
7036
  references
8207
7037
  });
@@ -8222,7 +7052,7 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
8222
7052
  const existing = yield* replacementByMediaId(transaction, replacement.mediaId);
8223
7053
  if (existing && isSameReplacement(existing, replacement)) return existing;
8224
7054
  return yield* new MediaConflict({ mediaId: replacement.mediaId });
8225
- })).pipe(mapStoreFailure$1("MediaStore.Drizzle.beginReplace")));
7055
+ })).pipe(mapStoreFailure$2("MediaStore.Drizzle.beginReplace")));
8226
7056
  const markReplaceStaged = Effect.fn("MediaStore.Drizzle.markReplaceStaged")((replacement) => database.transaction((transaction) => Effect.gen(function* () {
8227
7057
  yield* lockedRowById(transaction, replacement.mediaId);
8228
7058
  const existing = yield* replacementByMediaId(transaction, replacement.mediaId);
@@ -8230,14 +7060,14 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
8230
7060
  if (existing.staged) return existing;
8231
7061
  const rows = yield* transaction.update(mediaReplacements).set({ staged: true }).where(and(eq(mediaReplacements.mediaId, replacement.mediaId), eq(mediaReplacements.stagingObjectKey, replacement.stagingObjectKey))).returning();
8232
7062
  if (rows[0]) return yield* decodeMediaReplacement(rows[0]);
8233
- return yield* persistenceFailure$1("MediaStore.Drizzle.markReplaceStaged", /* @__PURE__ */ new Error(`Media replacement "${replacement.mediaId}" lost its durable intent.`));
8234
- })).pipe(mapStoreFailure$1("MediaStore.Drizzle.markReplaceStaged")));
7063
+ return yield* persistenceFailure$2("MediaStore.Drizzle.markReplaceStaged", /* @__PURE__ */ new Error(`Media replacement "${replacement.mediaId}" lost its durable intent.`));
7064
+ })).pipe(mapStoreFailure$2("MediaStore.Drizzle.markReplaceStaged")));
8235
7065
  const finishReplace = Effect.fn("MediaStore.Drizzle.finishReplace")((replacement) => database.transaction((transaction) => Effect.gen(function* () {
8236
7066
  const asset = yield* lockedRowById(transaction, replacement.mediaId);
8237
7067
  const pendingReplacement = yield* replacementByMediaId(transaction, replacement.mediaId);
8238
7068
  if (!pendingReplacement) {
8239
7069
  if (asset && isCompletedReplacement(asset, replacement)) return asset;
8240
- return yield* persistenceFailure$1("MediaStore.Drizzle.finishReplace", /* @__PURE__ */ new Error(`Media replacement "${replacement.mediaId}" cannot be replayed from durable state.`));
7070
+ return yield* persistenceFailure$2("MediaStore.Drizzle.finishReplace", /* @__PURE__ */ new Error(`Media replacement "${replacement.mediaId}" cannot be replayed from durable state.`));
8241
7071
  }
8242
7072
  if (!isSameReplacement(pendingReplacement, replacement)) return yield* new MediaConflict({ mediaId: replacement.mediaId });
8243
7073
  if (!asset || asset.status !== "ready" || asset.objectKey !== replacement.objectKey || asset.mimeType !== replacement.mimeType) return yield* new MediaConflict({ mediaId: replacement.mediaId });
@@ -8250,7 +7080,7 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
8250
7080
  updatedAt: transactionTimestamp
8251
7081
  }).where(and(eq(mediaAssets.id, replacement.mediaId), eq(mediaAssets.status, "ready"))).returning())[0];
8252
7082
  if (!replaced) return yield* transitionFailure("MediaStore.Drizzle.finishReplace", replacement.mediaId, asset.status);
8253
- if (!(yield* transaction.delete(mediaReplacements).where(and(eq(mediaReplacements.mediaId, replacement.mediaId), eq(mediaReplacements.stagingObjectKey, replacement.stagingObjectKey))).returning({ mediaId: mediaReplacements.mediaId }))[0]) return yield* persistenceFailure$1("MediaStore.Drizzle.finishReplace", /* @__PURE__ */ new Error(`Media replacement "${replacement.mediaId}" lost its durable intent.`));
7083
+ if (!(yield* transaction.delete(mediaReplacements).where(and(eq(mediaReplacements.mediaId, replacement.mediaId), eq(mediaReplacements.stagingObjectKey, replacement.stagingObjectKey))).returning({ mediaId: mediaReplacements.mediaId }))[0]) return yield* persistenceFailure$2("MediaStore.Drizzle.finishReplace", /* @__PURE__ */ new Error(`Media replacement "${replacement.mediaId}" lost its durable intent.`));
8254
7084
  const decoded = yield* decodeMediaRecord(replaced);
8255
7085
  const intent = yield* decodeMediaChangeIntent(change);
8256
7086
  yield* resolveChange(transaction, change, {
@@ -8258,7 +7088,7 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
8258
7088
  kind: "applied"
8259
7089
  });
8260
7090
  return decoded;
8261
- })).pipe(mapStoreFailure$1("MediaStore.Drizzle.finishReplace")));
7091
+ })).pipe(mapStoreFailure$2("MediaStore.Drizzle.finishReplace")));
8262
7092
  const cancelReplace = Effect.fn("MediaStore.Drizzle.cancelReplace")((replacement) => database.transaction((transaction) => Effect.gen(function* () {
8263
7093
  yield* lockedRowById(transaction, replacement.mediaId);
8264
7094
  const existing = yield* replacementByMediaId(transaction, replacement.mediaId);
@@ -8267,7 +7097,7 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
8267
7097
  const change = yield* requirePendingChange(transaction, mediaSubject(replacement.mediaId), "media.replaced");
8268
7098
  if (!(yield* transaction.delete(mediaReplacements).where(and(eq(mediaReplacements.mediaId, replacement.mediaId), eq(mediaReplacements.staged, replacement.staged), eq(mediaReplacements.stagingObjectKey, replacement.stagingObjectKey))).returning({ mediaId: mediaReplacements.mediaId }))[0]) return yield* new MediaConflict({ mediaId: replacement.mediaId });
8269
7099
  yield* resolveChange(transaction, change, { kind: "cancelled" });
8270
- })).pipe(mapStoreFailure$1("MediaStore.Drizzle.cancelReplace")));
7100
+ })).pipe(mapStoreFailure$2("MediaStore.Drizzle.cancelReplace")));
8271
7101
  const finishDelete = Effect.fn("MediaStore.Drizzle.finishDelete")((id) => database.transaction((transaction) => Effect.gen(function* () {
8272
7102
  const existing = yield* lockedRowById(transaction, id);
8273
7103
  if (!existing) return;
@@ -8279,7 +7109,7 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
8279
7109
  details: mediaChangeDetails(intent),
8280
7110
  kind: "applied"
8281
7111
  });
8282
- })).pipe(mapPersistenceFailure$1("MediaStore.Drizzle.finishDelete")));
7112
+ })).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.finishDelete")));
8283
7113
  const abandonUpload = Effect.fn("MediaStore.Drizzle.abandonUpload")((id) => database.transaction((transaction) => Effect.gen(function* () {
8284
7114
  const existing = yield* lockedRowById(transaction, id);
8285
7115
  if (!existing) return;
@@ -8287,15 +7117,15 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
8287
7117
  const change = yield* requirePendingChange(transaction, mediaSubject(id), "media.uploaded");
8288
7118
  if (!(yield* transaction.delete(mediaAssets).where(and(eq(mediaAssets.id, id), eq(mediaAssets.status, "uploading"))).returning({ id: mediaAssets.id }))[0]) return yield* transitionFailure("MediaStore.Drizzle.abandonUpload", id, existing.status);
8289
7119
  yield* resolveChange(transaction, change, { kind: "cancelled" });
8290
- })).pipe(mapPersistenceFailure$1("MediaStore.Drizzle.abandonUpload")));
7120
+ })).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.abandonUpload")));
8291
7121
  const pending = Effect.fn("MediaStore.Drizzle.pending")((query) => Effect.gen(function* () {
8292
- const rows = yield* database.select().from(mediaAssets).where(and(ne(mediaAssets.status, "ready"), lte(mediaAssets.updatedAt, DateTime.formatIso(query.before)))).orderBy(asc(mediaAssets.updatedAt), asc(mediaAssets.id)).limit(query.limit);
7122
+ const rows = yield* database.select().from(mediaAssets).where(and(ne(mediaAssets.status, "ready"), lte(mediaAssets.updatedAt, DateTime.formatIso(query.before)), query.after === void 0 ? void 0 : gt(mediaAssets.id, query.after))).orderBy(asc(mediaAssets.id)).limit(query.limit);
8293
7123
  return yield* Effect.forEach(rows, decodeMediaRecord);
8294
- }).pipe(mapPersistenceFailure$1("MediaStore.Drizzle.pending")));
7124
+ }).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.pending")));
8295
7125
  const pendingReplacements = Effect.fn("MediaStore.Drizzle.pendingReplacements")((query) => Effect.gen(function* () {
8296
- const rows = yield* database.select().from(mediaReplacements).where(lte(mediaReplacements.updatedAt, DateTime.formatIso(query.before))).orderBy(asc(mediaReplacements.updatedAt), asc(mediaReplacements.mediaId)).limit(query.limit);
7126
+ const rows = yield* database.select().from(mediaReplacements).where(and(lte(mediaReplacements.updatedAt, DateTime.formatIso(query.before)), query.after === void 0 ? void 0 : gt(mediaReplacements.mediaId, query.after))).orderBy(asc(mediaReplacements.mediaId)).limit(query.limit);
8297
7127
  return yield* Effect.forEach(rows, decodeMediaReplacement);
8298
- }).pipe(mapPersistenceFailure$1("MediaStore.Drizzle.pendingReplacements")));
7128
+ }).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.pendingReplacements")));
8299
7129
  const inspectReplacement = Effect.fn("MediaStore.Drizzle.inspectReplacement")((replacement) => database.transaction((transaction) => Effect.gen(function* () {
8300
7130
  const asset = yield* lockedRowById(transaction, replacement.mediaId);
8301
7131
  const pending = yield* replacementByMediaId(transaction, replacement.mediaId);
@@ -8304,17 +7134,17 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
8304
7134
  return yield* new MediaConflict({ mediaId: replacement.mediaId });
8305
7135
  }
8306
7136
  return asset && isCompletedReplacement(asset, replacement) ? "completed" : "absent";
8307
- })).pipe(mapStoreFailure$1("MediaStore.Drizzle.inspectReplacement")));
7137
+ })).pipe(mapStoreFailure$2("MediaStore.Drizzle.inspectReplacement")));
8308
7138
  const inventory = Effect.gen(function* () {
8309
7139
  const rows = yield* database.select().from(mediaAssets).where(eq(mediaAssets.status, "ready")).orderBy(asc(mediaAssets.objectKey));
8310
7140
  return yield* Effect.forEach(rows, decodeMediaRecord);
8311
- }).pipe(mapPersistenceFailure$1("MediaStore.Drizzle.inventory"), Effect.withSpan("MediaStore.Drizzle.inventory"));
7141
+ }).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.inventory"), Effect.withSpan("MediaStore.Drizzle.inventory"));
8312
7142
  const knownObjectKeys = Effect.gen(function* () {
8313
7143
  const assetRows = yield* database.select({ objectKey: mediaAssets.objectKey }).from(mediaAssets).orderBy(asc(mediaAssets.objectKey));
8314
7144
  const replacementRows = yield* database.select({ objectKey: mediaReplacements.stagingObjectKey }).from(mediaReplacements).orderBy(asc(mediaReplacements.stagingObjectKey));
8315
7145
  const objectKeys = yield* Schema.decodeEffect(Schema.Array(MediaObjectKey))([...assetRows, ...replacementRows].map((row) => row.objectKey));
8316
7146
  return new Set(objectKeys);
8317
- }).pipe(mapPersistenceFailure$1("MediaStore.Drizzle.knownObjectKeys"), Effect.withSpan("MediaStore.Drizzle.knownObjectKeys"));
7147
+ }).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.knownObjectKeys"), Effect.withSpan("MediaStore.Drizzle.knownObjectKeys"));
8318
7148
  const search = Effect.fn("MediaStore.Drizzle.search")((query) => database.transaction((transaction) => Effect.gen(function* () {
8319
7149
  const normalizedSearch = query.search?.trim();
8320
7150
  const pattern = normalizedSearch ? `%${escapeLike(normalizedSearch)}%` : void 0;
@@ -8323,14 +7153,14 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
8323
7153
  const rows = yield* transaction.select().from(mediaAssets).where(and(filters, cursorFilter)).orderBy(desc(mediaAssets.createdAt), desc(mediaAssets.id)).limit(query.limit + 1);
8324
7154
  const folderRows = yield* transaction.selectDistinct({ folder: mediaAssets.folder }).from(mediaAssets).where(and(eq(mediaAssets.status, "ready"), ne(mediaAssets.folder, ""))).orderBy(asc(mediaAssets.folder));
8325
7155
  const total = (yield* transaction.select({ value: count() }).from(mediaAssets).where(filters))[0]?.value;
8326
- if (total === void 0) return yield* persistenceFailure$1("MediaStore.Drizzle.search", /* @__PURE__ */ new Error("Media persistence did not return a search total."));
7156
+ if (total === void 0) return yield* persistenceFailure$2("MediaStore.Drizzle.search", /* @__PURE__ */ new Error("Media persistence did not return a search total."));
8327
7157
  const decoded = yield* Effect.forEach(rows, decodeMediaRecord);
8328
7158
  const folders = yield* Schema.decodeEffect(Schema.Array(MediaFolder))(folderRows.map((row) => row.folder)).pipe(Effect.mapError((cause) => mediaPersistenceError("MediaStore.Drizzle.search", cause)));
8329
7159
  return mediaPage(decoded, query.limit, folders, total);
8330
7160
  }), {
8331
7161
  accessMode: "read only",
8332
7162
  isolationLevel: "repeatable read"
8333
- }).pipe(mapPersistenceFailure$1("MediaStore.Drizzle.search")));
7163
+ }).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.search")));
8334
7164
  return MediaStore.of({
8335
7165
  abandonUpload,
8336
7166
  activate,
@@ -8415,45 +7245,230 @@ const replacementByMediaId = Effect.fn("MediaStore.Drizzle.replacementByMediaId"
8415
7245
  return rows[0] ? yield* decodeMediaReplacement(rows[0]) : void 0;
8416
7246
  });
8417
7247
  function transitionFailure(operation, id, actualStatus) {
8418
- return persistenceFailure$1(operation, /* @__PURE__ */ new Error(actualStatus === void 0 ? `Media record "${id}" does not exist.` : `Media record "${id}" cannot transition from "${actualStatus}".`));
7248
+ return persistenceFailure$2(operation, /* @__PURE__ */ new Error(actualStatus === void 0 ? `Media record "${id}" does not exist.` : `Media record "${id}" cannot transition from "${actualStatus}".`));
8419
7249
  }
8420
- function mapPersistenceFailure$1(operation) {
7250
+ function mapPersistenceFailure$2(operation) {
8421
7251
  return (effect) => effect.pipe(Effect.mapError((cause) => isMediaPersistenceError(cause) ? cause : mediaPersistenceError(operation, cause)));
8422
7252
  }
8423
- function mapStoreFailure$1(operation) {
7253
+ function mapStoreFailure$2(operation) {
8424
7254
  return (effect) => effect.pipe(Effect.mapError((cause) => isMediaConflict(cause) || isMediaPersistenceError(cause) ? cause : mediaPersistenceError(operation, cause)));
8425
7255
  }
8426
7256
  function mapBeginDeleteFailure(operation) {
8427
7257
  return (effect) => effect.pipe(Effect.mapError((cause) => isMediaConflict(cause) || isMediaNotFound(cause) || isMediaPersistenceError(cause) || isMediaReferenced(cause) ? cause : mediaPersistenceError(operation, cause)));
8428
7258
  }
8429
- function persistenceFailure$1(operation, cause) {
7259
+ function persistenceFailure$2(operation, cause) {
8430
7260
  return Effect.fail(mediaPersistenceError(operation, cause));
8431
7261
  }
8432
7262
 
8433
7263
  //#endregion
8434
- //#region ../sql-pg/src/internal/readiness.ts
8435
- const postgresReadinessLayer = Layer.effect(Readiness, Effect.gen(function* () {
8436
- const postgres = yield* PgClient.PgClient;
8437
- const journal = yield* ChangeJournal;
8438
- const check = Effect.gen(function* () {
8439
- yield* postgres.unsafe("select 1");
8440
- const now = yield* DateTime.now;
8441
- const pending = yield* journal.pending({
8442
- before: DateTime.subtractDuration(now, "30 seconds"),
8443
- limit: 1
8444
- });
8445
- const oldest = pending.changes[0];
8446
- if (pending.total === 0 || oldest === void 0) return;
8447
- yield* Effect.logWarning("CMS readiness found a stale pending change.", {
8448
- action: oldest.action,
8449
- ageMillis: DateTime.toEpochMillis(now) - DateTime.toEpochMillis(oldest.updatedAt),
8450
- changeId: oldest.id,
8451
- phase: "pending"
7264
+ //#region ../sql-pg/src/internal/seo/seo-store.ts
7265
+ const SeoStoreDomainError = Schema.Union([
7266
+ SeoMediaUnavailable,
7267
+ SeoPersistenceError,
7268
+ SeoVersionConflict
7269
+ ]);
7270
+ const isSeoStoreDomainError = Schema.is(SeoStoreDomainError);
7271
+ const isSeoPersistenceError = Schema.is(SeoPersistenceError);
7272
+ const systemActivity = {
7273
+ actor: {
7274
+ email: "system@cms.invalid",
7275
+ id: "system",
7276
+ name: "CMS System",
7277
+ roles: ["system"]
7278
+ },
7279
+ requestId: RequestId.make("00000000-0000-4000-8000-000000000000")
7280
+ };
7281
+ const seoStoreLayer = Layer.effect(SeoStore, Effect.gen(function* () {
7282
+ const database = yield* PgDrizzle.makeWithDefaults();
7283
+ const initialize = Effect.fn("SeoStore.Drizzle.initialize")((defaults) => database.transaction((transaction) => Effect.gen(function* () {
7284
+ yield* transaction.execute(sql`SELECT pg_advisory_xact_lock(hashtext('cms:seo:singleton:v1'))`);
7285
+ const current = yield* readSnapshot(transaction);
7286
+ if (current) return current;
7287
+ yield* ensureMediaAvailable(transaction, defaults).pipe(Effect.mapError((cause) => new SeoPersistenceError({
7288
+ cause,
7289
+ operation: "SeoStore.Drizzle.initializeDefaults"
7290
+ })));
7291
+ const revision = yield* insertRevision(transaction, defaults, systemActivity);
7292
+ yield* transaction.insert(seoState).values({
7293
+ publishedRevisionId: revision.revisionId,
7294
+ workingRevisionId: revision.revisionId
8452
7295
  });
8453
- return yield* new ReadinessFailed({ cause: /* @__PURE__ */ new Error("A stale change remains pending.") });
8454
- }).pipe(Effect.mapError((cause) => Schema.is(ReadinessFailed)(cause) ? cause : new ReadinessFailed({ cause })), Effect.withSpan("Installation.readiness"));
8455
- return Readiness.of({ check });
7296
+ yield* replaceMediaReferences(transaction, "working", defaults);
7297
+ yield* replaceMediaReferences(transaction, "published", defaults);
7298
+ return {
7299
+ published: revision,
7300
+ working: revision
7301
+ };
7302
+ })).pipe(mapStoreFailure$1("SeoStore.Drizzle.initialize")));
7303
+ const load = database.transaction((transaction) => readSnapshot(transaction), {
7304
+ accessMode: "read only",
7305
+ isolationLevel: "repeatable read"
7306
+ }).pipe(mapPersistenceFailure$1("SeoStore.Drizzle.load"));
7307
+ const save = Effect.fn("SeoStore.Drizzle.save")((input) => database.transaction((transaction) => Effect.gen(function* () {
7308
+ const current = yield* lockedSnapshot(transaction);
7309
+ yield* requireVersion(current, input.expectedVersion);
7310
+ yield* ensureMediaAvailable(transaction, input.settings);
7311
+ const revision = yield* insertRevision(transaction, input.settings, input.activity);
7312
+ yield* transaction.update(seoState).set({ workingRevisionId: revision.revisionId }).where(eq(seoState.singleton, true));
7313
+ yield* replaceMediaReferences(transaction, "working", input.settings);
7314
+ yield* recordActivity(transaction, "seo.saved", input.activity, {
7315
+ previousVersion: current.working.version,
7316
+ version: revision.version
7317
+ }, ActivityId.make(revision.revisionId));
7318
+ return {
7319
+ ...current,
7320
+ working: revision
7321
+ };
7322
+ })).pipe(mapStoreFailure$1("SeoStore.Drizzle.save")));
7323
+ const publish = Effect.fn("SeoStore.Drizzle.publish")((input) => database.transaction((transaction) => Effect.gen(function* () {
7324
+ const current = yield* lockedSnapshot(transaction);
7325
+ yield* requireVersion(current, input.expectedVersion);
7326
+ yield* ensureMediaAvailable(transaction, current.working.settings);
7327
+ yield* transaction.update(seoState).set({ publishedRevisionId: current.working.revisionId }).where(eq(seoState.singleton, true));
7328
+ yield* replaceMediaReferences(transaction, "published", current.working.settings);
7329
+ yield* recordActivity(transaction, "seo.published", input.activity, { version: current.working.version });
7330
+ return {
7331
+ ...current,
7332
+ published: current.working
7333
+ };
7334
+ })).pipe(mapStoreFailure$1("SeoStore.Drizzle.publish")));
7335
+ return SeoStore.of({
7336
+ initialize,
7337
+ load,
7338
+ publish,
7339
+ save
7340
+ });
8456
7341
  }));
7342
+ const readSnapshot = Effect.fn("SeoStore.Drizzle.readSnapshot")(function* (database) {
7343
+ const state = (yield* database.select().from(seoState).limit(1))[0];
7344
+ if (!state) return void 0;
7345
+ const revisionRows = yield* database.select().from(seoRevisions).where(inArray(seoRevisions.id, [state.workingRevisionId, state.publishedRevisionId]));
7346
+ const revisions = yield* Effect.forEach(revisionRows, decodeRevision);
7347
+ const working = revisions.find((revision) => revision.revisionId === state.workingRevisionId);
7348
+ const published = revisions.find((revision) => revision.revisionId === state.publishedRevisionId);
7349
+ if (!working || !published) return yield* persistenceFailure$1("SeoStore.Drizzle.readSnapshot", /* @__PURE__ */ new Error("SEO state points to a missing revision."));
7350
+ return {
7351
+ published,
7352
+ working
7353
+ };
7354
+ });
7355
+ const lockedSnapshot = Effect.fn("SeoStore.Drizzle.lockSnapshot")(function* (transaction) {
7356
+ if (!(yield* transaction.select().from(seoState).where(eq(seoState.singleton, true)).for("update").limit(1))[0]) return yield* persistenceFailure$1("SeoStore.Drizzle.lockSnapshot", /* @__PURE__ */ new Error("SEO state is not initialized."));
7357
+ return (yield* readSnapshot(transaction)) ?? (yield* persistenceFailure$1("SeoStore.Drizzle.lockSnapshot", /* @__PURE__ */ new Error("SEO state disappeared.")));
7358
+ });
7359
+ const insertRevision = Effect.fn("SeoStore.Drizzle.insertRevision")(function* (transaction, settings, activity) {
7360
+ const row = (yield* transaction.insert(seoRevisions).values({
7361
+ actorEmail: activity.actor.email,
7362
+ actorId: activity.actor.id,
7363
+ actorName: activity.actor.name,
7364
+ actorRoles: activity.actor.roles,
7365
+ reason: activity.reason,
7366
+ requestId: activity.requestId,
7367
+ settings
7368
+ }).returning())[0];
7369
+ return row ? yield* decodeRevision(row) : yield* persistenceFailure$1("SeoStore.Drizzle.insertRevision", /* @__PURE__ */ new Error("No revision row returned."));
7370
+ });
7371
+ const decodeRevision = Effect.fn("SeoStore.Drizzle.decodeRevision")(function* (row) {
7372
+ return yield* Schema.decodeEffect(SeoRevision)({
7373
+ editedAt: row.createdAt,
7374
+ editedBy: {
7375
+ email: row.actorEmail,
7376
+ id: row.actorId,
7377
+ name: row.actorName,
7378
+ roles: row.actorRoles
7379
+ },
7380
+ revisionId: row.id,
7381
+ settings: row.settings,
7382
+ version: row.id
7383
+ }).pipe(Effect.mapError((cause) => new SeoPersistenceError({
7384
+ cause,
7385
+ operation: "SeoStore.Drizzle.decodeRevision"
7386
+ })));
7387
+ });
7388
+ function requireVersion(snapshot, expectedVersion) {
7389
+ return snapshot.working.version === expectedVersion ? Effect.void : Effect.fail(new SeoVersionConflict({
7390
+ actualVersion: snapshot.working.version,
7391
+ expectedVersion
7392
+ }));
7393
+ }
7394
+ const ensureMediaAvailable = Effect.fn("SeoStore.Drizzle.ensureMediaAvailable")(function* (transaction, settings) {
7395
+ const mediaIds = settingsMediaIds(settings);
7396
+ if (mediaIds.length === 0) return;
7397
+ const initialRows = yield* transaction.select({
7398
+ id: mediaAssets.id,
7399
+ objectKey: mediaAssets.objectKey
7400
+ }).from(mediaAssets).where(inArray(mediaAssets.id, mediaIds));
7401
+ const objectKeys = yield* Schema.decodeEffect(Schema.Array(MediaObjectKey))(initialRows.map((row) => row.objectKey));
7402
+ yield* lockMediaReferenceKeys(transaction, objectKeys);
7403
+ const readyRows = yield* transaction.select({
7404
+ id: mediaAssets.id,
7405
+ objectKey: mediaAssets.objectKey
7406
+ }).from(mediaAssets).where(and(inArray(mediaAssets.id, mediaIds), eq(mediaAssets.status, "ready")));
7407
+ const unavailable = settingsMediaImages(settings).find((image) => {
7408
+ const asset = readyRows.find((row) => row.id === image.mediaId);
7409
+ return !asset || mediaObjectKey(image.url) !== asset.objectKey;
7410
+ });
7411
+ if (unavailable) return yield* new SeoMediaUnavailable({ mediaId: unavailable.mediaId });
7412
+ });
7413
+ const replaceMediaReferences = Effect.fn("SeoStore.Drizzle.replaceMediaReferences")(function* (transaction, scope, settings) {
7414
+ yield* transaction.delete(seoMediaReferences).where(eq(seoMediaReferences.scope, scope));
7415
+ const mediaIds = settingsMediaIds(settings);
7416
+ if (mediaIds.length > 0) yield* transaction.insert(seoMediaReferences).values(mediaIds.map((mediaId) => ({
7417
+ mediaId,
7418
+ scope
7419
+ })));
7420
+ });
7421
+ function settingsMediaIds(settings) {
7422
+ return [...new Set(settingsMediaImages(settings).map((image) => image.mediaId))];
7423
+ }
7424
+ function settingsMediaImages(settings) {
7425
+ const images = [settings.socialImage];
7426
+ const identity = settings.identity;
7427
+ if (identity?.type === "Person") images.push(identity.image);
7428
+ if (identity?.type === "Organization") images.push(identity.logo);
7429
+ if (identity?.type === "LocalBusiness") images.push(identity.logo, identity.image);
7430
+ return images.filter((image) => image !== void 0);
7431
+ }
7432
+ function mediaObjectKey(url) {
7433
+ try {
7434
+ const encodedKey = new URL(url).pathname.match(/(?:^|\/)(media\/[^/]+)$/u)?.[1];
7435
+ return encodedKey ? encodedKey.split("/").map(decodeURIComponent).join("/") : void 0;
7436
+ } catch {
7437
+ return;
7438
+ }
7439
+ }
7440
+ const recordActivity = Effect.fn("SeoStore.Drizzle.recordActivity")(function* (transaction, action, context, details, id) {
7441
+ yield* transaction.insert(activityEntries).values({
7442
+ action,
7443
+ actorEmail: context.actor.email,
7444
+ actorId: context.actor.id,
7445
+ actorName: context.actor.name,
7446
+ actorRoles: context.actor.roles,
7447
+ details,
7448
+ id,
7449
+ reason: context.reason,
7450
+ requestId: context.requestId,
7451
+ targetKind: "seo"
7452
+ });
7453
+ });
7454
+ function mapStoreFailure$1(operation) {
7455
+ return (effect) => effect.pipe(Effect.mapError((cause) => isSeoStoreDomainError(cause) ? cause : new SeoPersistenceError({
7456
+ cause,
7457
+ operation
7458
+ })));
7459
+ }
7460
+ function mapPersistenceFailure$1(operation) {
7461
+ return (effect) => effect.pipe(Effect.mapError((cause) => isSeoPersistenceError(cause) ? cause : new SeoPersistenceError({
7462
+ cause,
7463
+ operation
7464
+ })));
7465
+ }
7466
+ function persistenceFailure$1(operation, cause) {
7467
+ return Effect.fail(new SeoPersistenceError({
7468
+ cause,
7469
+ operation
7470
+ }));
7471
+ }
8457
7472
 
8458
7473
  //#endregion
8459
7474
  //#region ../sql-pg/src/internal/user-administration/user-command-fence.ts
@@ -8471,7 +7486,7 @@ const postgresUserCommandFenceLayer = Layer.effect(UserCommandFence, PostgresAdv
8471
7486
  const WORKING_DOCUMENT_CHANGES_CHANNEL = "cms_working_documents";
8472
7487
  const WorkingDocumentChangePayload = Schema.fromJsonString(WorkingDocumentChange);
8473
7488
  function workingDocumentChangePayload(change) {
8474
- return Schema.encodeSync(WorkingDocumentChangePayload)(change);
7489
+ return Result.getOrThrow(Schema.encodeResult(WorkingDocumentChangePayload)(change));
8475
7490
  }
8476
7491
  const decodeWorkingDocumentChangePayload = Schema.decodeUnknownEffect(WorkingDocumentChangePayload);
8477
7492
 
@@ -8506,12 +7521,13 @@ const makePostgresChanges = Effect.fn("WorkingDocumentChanges.makePostgres")(fun
8506
7521
  const postgres = Context.get(context, PgClient.PgClient);
8507
7522
  const heartbeats = /* @__PURE__ */ new Map();
8508
7523
  let heartbeat = 0;
8509
- const listener = yield* postgres.listen(WORKING_DOCUMENT_CHANGES_CHANNEL).pipe(Stream.runForEach((payload) => {
8510
- const waiting = heartbeats.get(payload);
7524
+ const notifications = yield* postgres.listen(WORKING_DOCUMENT_CHANGES_CHANNEL).pipe(Effect.mapError((cause) => changesError("listen", cause)));
7525
+ const listener = yield* Stream.fromQueue(notifications).pipe(Stream.runForEach((payload) => {
7526
+ const waiting = heartbeats.get(payload.payload);
8511
7527
  if (waiting !== void 0) return Deferred.succeed(waiting, void 0).pipe(Effect.asVoid);
8512
- if (payload.startsWith("__cms_probe__:")) return Effect.void;
8513
- return decodeWorkingDocumentChangePayload(payload).pipe(Effect.flatMap((change) => PubSub.publish(changes, change)), Effect.asVoid, Effect.catchCause((cause) => Effect.logWarning("Ignored an invalid Working Document change.", Cause.pretty(cause))));
8514
- }), Effect.mapError((cause) => changesError("listen", cause))).pipe(Effect.forkScoped({ startImmediately: true }));
7528
+ if (payload.payload.startsWith("__cms_probe__:")) return Effect.void;
7529
+ return decodeWorkingDocumentChangePayload(payload.payload).pipe(Effect.flatMap((change) => PubSub.publish(changes, change)), Effect.asVoid, Effect.catchCause((cause) => Effect.logWarning("Ignored an invalid Working Document change.", Cause.pretty(cause))));
7530
+ }), Effect.catchCause((cause) => Effect.fail(changesError("listen", cause)))).pipe(Effect.forkScoped({ startImmediately: true }));
8515
7531
  const pulse = Effect.gen(function* () {
8516
7532
  heartbeat += 1;
8517
7533
  const payload = `__cms_probe__:${session}:${heartbeat}`;
@@ -9069,14 +8085,11 @@ function postgresLayer(options) {
9069
8085
  });
9070
8086
  const migrations = cmsDatabaseMigrationLayer.pipe(Layer.provide(database));
9071
8087
  const locks = postgresAdvisoryLockLayer(options);
9072
- const stores = Layer.mergeAll(postgresActivityStoreLayer, changeJournalLayer, documentStoreLayer, mediaStoreLayer, initializationStoreLayer.pipe(Layer.provide(locks)), postgresUserCommandFenceLayer.pipe(Layer.provide(locks)), postgresWorkingDocumentChangesLayer(options), workingDocumentStoreLayer).pipe(Layer.provide(migrations), Layer.provide(database));
9073
- const readiness = postgresReadinessLayer.pipe(Layer.provide(stores), Layer.provide(database));
9074
- return Layer.merge(stores, readiness);
8088
+ return Layer.mergeAll(postgresActivityStoreLayer, changeJournalLayer, documentStoreLayer, mediaStoreLayer, seoStoreLayer, initializationStoreLayer.pipe(Layer.provide(locks)), postgresUserCommandFenceLayer.pipe(Layer.provide(locks)), postgresWorkingDocumentChangesLayer(options), workingDocumentStoreLayer).pipe(Layer.provide(migrations), Layer.provide(database));
9075
8089
  }
9076
8090
 
9077
8091
  //#endregion
9078
8092
  //#region src/runtime/installation.ts
9079
- /** Composes the private providers selected by the Nuxt product adapter. */
9080
8093
  const installCms = Effect.fn("NuxtCmsInstallation.install")(function* (options) {
9081
8094
  const crypto = NodeCrypto.layer;
9082
8095
  const postgres = postgresLayer({ url: Redacted.make(options.databaseUrl) });
@@ -9088,6 +8101,8 @@ const installCms = Effect.fn("NuxtCmsInstallation.install")(function* (options)
9088
8101
  const core = Layer.mergeAll(crypto, postgres, identity);
9089
8102
  return yield* installCms$1({
9090
8103
  baseUrl: options.baseUrl,
8104
+ formHandlers: options.formHandlers,
8105
+ authoring: options.authoring,
9091
8106
  bootstrap: options.bootstrap,
9092
8107
  cleanup: options.cleanup.enabled ? {
9093
8108
  initialDelayMillis: options.cleanup.initialDelayMillis,
@@ -9125,4 +8140,4 @@ function toS3MediaObjectStoreOptions(options) {
9125
8140
 
9126
8141
  //#endregion
9127
8142
  export { toS3MediaObjectStoreOptions as n, installCms as t };
9128
- //# sourceMappingURL=installation-BNKaVjni.js.map
8143
+ //# sourceMappingURL=installation-DgF2M1gL.js.map