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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +72 -1
  2. package/package.json +5 -5
  3. package/src/Authoring.d.ts +13 -1
  4. package/src/{AuthoringSchema-BQ5kbhVB.js → Configuration-DyKo0sLS.js} +595 -367
  5. package/src/Configuration-DyKo0sLS.js.map +1 -0
  6. package/src/{DocumentEngine-Da0kWotL.js → DocumentEngine-DYObwMDY.js} +44 -66
  7. package/src/DocumentEngine-DYObwMDY.js.map +1 -0
  8. package/src/DocumentModel.d.ts +4 -0
  9. package/src/DocumentModel.js.map +1 -1
  10. package/src/{HttpPaths-86qLGB90.js → HttpPaths-BbS21Jy8.js} +4 -2
  11. package/src/{HttpPaths-86qLGB90.js.map → HttpPaths-BbS21Jy8.js.map} +1 -1
  12. package/src/Module.js +27 -76
  13. package/src/Module.js.map +1 -1
  14. package/src/{StudioHttp-BTLLooh0.js → StudioHttp-jR2-bc01.js} +44 -242
  15. package/src/StudioHttp-jR2-bc01.js.map +1 -0
  16. package/src/{connection-ZGtL54qY.js → connection-CBTY-TzB.js} +3 -3
  17. package/src/{connection-ZGtL54qY.js.map → connection-CBTY-TzB.js.map} +1 -1
  18. package/src/{installation-BNKaVjni.js → installation-DeuBiFRE.js} +488 -236
  19. package/src/installation-DeuBiFRE.js.map +1 -0
  20. package/src/internal/authoring-catalog.js +147 -0
  21. package/src/internal/authoring-catalog.js.map +1 -0
  22. package/src/internal/authoring-compiler.js +2 -1
  23. package/src/internal/authoring-compiler.js.map +1 -1
  24. package/src/internal/authoring-metadata.js +67 -48
  25. package/src/internal/authoring-metadata.js.map +1 -1
  26. package/src/internal/configuration.js +5 -6
  27. package/src/internal/configuration.js.map +1 -1
  28. package/src/internal/document-model.js +3 -2
  29. package/src/internal/document-model.js.map +1 -1
  30. package/src/internal/studio-app.js +66 -0
  31. package/src/internal/studio-app.js.map +1 -0
  32. package/src/location-DtYv2aes.js +36 -0
  33. package/src/location-DtYv2aes.js.map +1 -0
  34. package/src/{metadata-Czvi3E4K.js → metadata-DE1Mc2td.js} +2 -2
  35. package/src/{metadata-Czvi3E4K.js.map → metadata-DE1Mc2td.js.map} +1 -1
  36. package/src/{models-Citx8jBi.js → models-BkMgeaOL.js} +252 -3
  37. package/src/models-BkMgeaOL.js.map +1 -0
  38. package/src/{models-DS5ClMsH.js → models-C3witQBK.js} +8 -31
  39. package/src/models-C3witQBK.js.map +1 -0
  40. package/src/runtime/blocks.js +17 -0
  41. package/src/runtime/blocks.js.map +1 -0
  42. package/src/runtime/collaboration.js +9 -4
  43. package/src/runtime/collaboration.js.map +1 -1
  44. package/src/runtime/delivery/DocumentRenderer.js +6 -3
  45. package/src/runtime/delivery/DocumentRenderer.js.map +1 -1
  46. package/src/runtime/delivery/blocks.js +6 -0
  47. package/src/runtime/delivery/blocks.js.map +1 -0
  48. package/src/runtime/delivery/context.js +12 -0
  49. package/src/runtime/delivery/context.js.map +1 -0
  50. package/src/runtime/delivery/document-page.js +5 -3
  51. package/src/runtime/delivery/document-page.js.map +1 -1
  52. package/src/runtime/delivery/document.js +18 -2
  53. package/src/runtime/delivery/document.js.map +1 -1
  54. package/src/runtime/delivery/error.js +11 -0
  55. package/src/runtime/delivery/error.js.map +1 -0
  56. package/src/runtime/delivery/plugin.js +21 -0
  57. package/src/runtime/delivery/plugin.js.map +1 -0
  58. package/src/runtime/delivery/useDocument.js +6 -3
  59. package/src/runtime/delivery/useDocument.js.map +1 -1
  60. package/src/runtime/delivery/useDocumentQuery.js +6 -3
  61. package/src/runtime/delivery/useDocumentQuery.js.map +1 -1
  62. package/src/runtime/installation.js +1 -1
  63. package/src/runtime/plugin.js +3 -3
  64. package/src/runtime/plugin.js.map +1 -1
  65. package/src/runtime/preview/connection.js +1 -1
  66. package/src/runtime/preview/controller.js +29 -10
  67. package/src/runtime/preview/controller.js.map +1 -1
  68. package/src/runtime/preview/overlay.js +65 -0
  69. package/src/runtime/preview/overlay.js.map +1 -0
  70. package/src/runtime/preview/plugin.client.js +21 -34
  71. package/src/runtime/preview/plugin.client.js.map +1 -1
  72. package/src/runtime/preview/preview.css +72 -4
  73. package/src/runtime/preview/store.js +1 -1
  74. package/src/runtime/studio-app.js +5 -1
  75. package/src/runtime/studio-app.js.map +1 -1
  76. package/src/runtime/studio-entry.js +36 -0
  77. package/src/runtime/studio-entry.js.map +1 -0
  78. package/src/runtime/studio-navigation.js +11 -0
  79. package/src/runtime/studio-navigation.js.map +1 -0
  80. package/src/{store-Nl4uxEXx.js → store-CfFFx9Ua.js} +3 -3
  81. package/src/{store-Nl4uxEXx.js.map → store-CfFFx9Ua.js.map} +1 -1
  82. package/studio/CreateDocumentDialog--L9lTfx6.js +256 -0
  83. package/studio/collaboration-DUuu678R.js +4565 -0
  84. package/studio/documentPlanning-BXX04Woe.js +28989 -0
  85. package/studio/dom-to-image-more.min-D6jGGdrW.js +877 -0
  86. package/studio/editor-PexnXkJY.js +30817 -0
  87. package/studio/host.css +3 -0
  88. package/studio/host.js +44179 -0
  89. package/studio/rolldown-runtime-B0aSnxlc.js +20 -0
  90. package/dist/studio/assets/ActivityEntryRow-D--fRvWI.js +0 -1
  91. package/dist/studio/assets/ActivityView-D-XBBUHb.js +0 -1
  92. package/dist/studio/assets/CreateDocumentDialog-CH8UH1QM.js +0 -1
  93. package/dist/studio/assets/DocumentsView-CxRpmAxt.js +0 -34
  94. package/dist/studio/assets/DropdownMenu-BSb1l7fr.js +0 -1
  95. package/dist/studio/assets/Input-DVao38h7.js +0 -3
  96. package/dist/studio/assets/MediaView-BHrPIjxB.js +0 -1
  97. package/dist/studio/assets/Modal-VNrXGa5C.js +0 -1
  98. package/dist/studio/assets/OverviewView-DfP6-mLZ.js +0 -1
  99. package/dist/studio/assets/Select-BJZIs9sY.js +0 -1
  100. package/dist/studio/assets/SettingsView-CerHe-ua.js +0 -1
  101. package/dist/studio/assets/Skeleton-CWJNi3nS.js +0 -1
  102. package/dist/studio/assets/StudioPageHeader-DP9HDAF2.js +0 -1
  103. package/dist/studio/assets/UsersView-cP3REkH2.js +0 -1
  104. package/dist/studio/assets/collaboration-BLnCzfvV.js +0 -4
  105. package/dist/studio/assets/context-dY4fNbn3.js +0 -1
  106. package/dist/studio/assets/editor-CE07wW83.js +0 -137
  107. package/dist/studio/assets/gateway-BAyQgCse.js +0 -10
  108. package/dist/studio/assets/index-CjoaPLhZ.js +0 -28
  109. package/dist/studio/assets/index-s2rPqCmP.css +0 -2
  110. package/dist/studio/assets/render-BkxbK2St.js +0 -83
  111. package/dist/studio/assets/rolldown-runtime-DK3Fl9T5.js +0 -1
  112. package/dist/studio/assets/useMediaLibrary-D7qR7aBf.js +0 -1
  113. package/dist/studio/index.html +0 -76
  114. package/src/AuthoringSchema-BQ5kbhVB.js.map +0 -1
  115. package/src/Configuration-BGYFD2N1.js +0 -150
  116. package/src/Configuration-BGYFD2N1.js.map +0 -1
  117. package/src/DocumentEngine-Da0kWotL.js.map +0 -1
  118. package/src/StudioHttp-BTLLooh0.js.map +0 -1
  119. package/src/installation-BNKaVjni.js.map +0 -1
  120. package/src/internal/component-metadata.js +0 -37
  121. package/src/internal/component-metadata.js.map +0 -1
  122. package/src/models-Citx8jBi.js.map +0 -1
  123. package/src/models-DS5ClMsH.js.map +0 -1
@@ -1,16 +1,16 @@
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 { c as STUDIO_API_PATH, d as STUDIO_COLLABORATION_PATH, i as CMS_LIVENESS_PATH, s as CMS_READINESS_PATH, t as CMS_AUTHENTICATION_PATH } from "./HttpPaths-BbS21Jy8.js";
2
+ import { $ as MediaName, A as RemoveDocumentInput, B as StudioActor, C as MarkdownNode, Ct as Slug, D as PublishedDocumentPage, F as AccessDenied, G as MediaByteSize, H as StudioRequestContext, I as AuthenticationRequired, J as MediaCursor, K as MediaCleanupReport, L as DOCUMENT_ACTIONS, M as UndeleteDocumentInput, N as UnpublishDocumentInput, P as UpdateDocumentInput, Q as MediaInvalid, R as MEDIA_ACTIONS, S as DuplicateDocumentInput, T as PublishDocumentInput, U as ImageMimeType, V as StudioEntryDenied, W as MediaAsset, X as MediaId, Y as MediaFolder, Z as MediaIntegrityReport, _ as DocumentSource, _t as DocumentVersion, a as DOCUMENT_REVISION_ACTIONS, at as MediaReferenced, b as DocumentSurround, bt as RelativeDocumentPath, c as DocumentImportInput, ct as MediaUpload, et as MediaNotFound, f as DocumentMutationResult, g as DocumentRevisionAction, gt as DocumentId, h as DocumentQueryScalar, ht as DocumentHistoryCursor, it as MediaReference, j as RestoreDocumentInput, l as DocumentImportReport, mt as DocumentGeneration, n as CmsMarkdownDocument, nt as MediaPage, o as DeletedDocument, ot as MediaStatus, p as DocumentNavigationItem, pt as RequestId, q as MediaConflict, st as MediaTooLarge, tt as MediaObjectKey, vt as EditorNodeId, w as MoveDocumentInput, wt as SourcePath, x as DocumentValidationResult, xt as RevisionId, y as DocumentSummary, yt as PublicPath, z as Role } from "./models-BkMgeaOL.js";
3
+ import { a as normalizeDocumentRelativePath, c as resolveDocumentAddress, i as localizeDocumentPath, n as referenceTarget, o as normalizePublicPath, r as DocumentCompilationFailed, s as parseDocumentAddress, t as createDocumentEngine } from "./DocumentEngine-DYObwMDY.js";
4
+ import { A as ACTIVITY_ACTIONS, B as ActivityTargetKind, C as CollaborationTicket, D as DocumentRevision, E as DocumentHistoryInvalidCursor, F as ActivityEntry, G as DocumentVersionConflict, H as DocumentNotFound, I as ActivityEntryDraft, K as PublishedDocumentNotFound, L as ActivityId, M as ActivityContext, N as ActivityCursor, O as DocumentRevisionComparison, P as ActivityCursorInvalid, R as ActivityPage, S as CollaborationServerMessage, T as DocumentHistoryComparisonTooLarge, U as DocumentPathConflict, V as DocumentMediaUnavailable, W as DocumentValidationFailed, _ as UserNotFound, b as UserSelfRoleChangeDenied, c as StudioInternalError, d as InitializationProofInvalid, f as InitializationState, g as UserAlreadyExists, h as UserAdministrationDenied, i as StudioBadRequest, j as ActivityAction, k as DocumentRevisionPage, l as studioInternalError, m as User, n as StudioApi, o as StudioRequestConflict, p as CreateUserInput, r as StudioAuthentication, s as StudioSchemaError, t as CurrentStudioRequest, u as InitializationAlreadyCompleted, v as UserPage, w as StateVector, x as CollaborationClientMessage, y as UserSelfRemovalDenied, z as ActivityTarget } from "./StudioHttp-jR2-bc01.js";
5
+ import { a as createEditorNodeId, c as createCmsComarkRenderData, d as toComarkDocument, i as withoutEditorMetadata, o as editorNodeId, r as identifiedAttributes, t as assertCanonicalEditorContent, u as mutableJsonObject } from "./metadata-DE1Mc2td.js";
6
+ import { C as finiteNumberAttribute, D as projectCmsEditorComponentAttributes, S as compactJsonAttributes, T as encodeCmsEditorComponentAttributes, _ as comarkTagForEditorMark, b as encodeEditorImageAttributes, d as EditorRenderFailed, 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-DyKo0sLS.js";
7
+ 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 } from "effect";
11
8
  import { Extension, Node, getSchema, mergeAttributes } from "@tiptap/core";
12
9
  import { prosemirrorJSONToYDoc, ySyncPluginKey, yXmlFragmentToProseMirrorRootNode } from "@tiptap/y-tiptap";
13
- import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto";
10
+ import { NodeStream } from "@effect/platform-node-shared";
11
+ import { HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse, Multipart } from "effect/unstable/http";
12
+ import { HttpApiBuilder, HttpApiMiddleware } from "effect/unstable/httpapi";
13
+ import * as NodeCrypto$1 from "@effect/platform-node-shared/NodeCrypto";
14
14
  import { betterAuth } from "better-auth";
15
15
  import { createAccessControl } from "better-auth/plugins/access";
16
16
  import { admin } from "better-auth/plugins/admin";
@@ -23,7 +23,6 @@ import StarterKit from "@tiptap/starter-kit";
23
23
  import * as Y from "yjs";
24
24
  import { Doc, applyUpdate, encodeStateAsUpdate, encodeStateVector } from "yjs";
25
25
  import { CopyObjectCommand, CreateBucketCommand, DeleteObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
26
- import { Readable } from "node:stream";
27
26
  import { PgClient } from "@effect/sql-pg";
28
27
  import { isDeepStrictEqual } from "node:util";
29
28
  import { and, asc, count, desc, eq, inArray, lt, lte, ne, or, sql } from "drizzle-orm";
@@ -1434,16 +1433,65 @@ const renderEditorMarkdown = Effect.fn("EditorDocument.renderMarkdown")((documen
1434
1433
  });
1435
1434
  });
1436
1435
 
1436
+ //#endregion
1437
+ //#region ../document/src/internal/document-engine/relocate-links.ts
1438
+ /** Rewrites only semantic page links, preserving bindings and unrelated content. */
1439
+ function rewriteDocumentRoutes(document, destinations, components = []) {
1440
+ let changed = false;
1441
+ function link(value) {
1442
+ const target = referenceTarget("a", /* @__PURE__ */ new Map([["href", value]]), document.path, void 0);
1443
+ if (target?.kind !== "document" || !Predicate.isString(value)) return value;
1444
+ const destination = destinations.get(target.path);
1445
+ const sourceMoved = destinations.has(document.path);
1446
+ if (!destination && (!sourceMoved || value.startsWith("/"))) return value;
1447
+ const suffix = value.match(/[?#].*$/u)?.[0] ?? "";
1448
+ const next = `${destination ?? target.path}${suffix}`;
1449
+ if (next !== value) changed = true;
1450
+ return next;
1451
+ }
1452
+ function properties(fields, values) {
1453
+ const result = mutableJsonObject(values);
1454
+ for (const field of fields) {
1455
+ const value = values[field.name] === void 0 ? field.defaultValue : values[field.name];
1456
+ if (field.input === "link") {
1457
+ const next = link(value);
1458
+ if (next !== void 0 && next !== value) result[field.name] = next;
1459
+ } else if (field.fields) {
1460
+ const next = Array.isArray(value) ? value.map((item) => Schema.is(Schema.JsonObject)(item) ? properties(field.fields ?? [], item) : item) : Schema.is(Schema.JsonObject)(value) ? properties(field.fields, value) : value;
1461
+ if (next !== void 0 && JSON.stringify(next) !== JSON.stringify(value)) result[field.name] = next;
1462
+ }
1463
+ }
1464
+ return result;
1465
+ }
1466
+ function visit(node, renderData) {
1467
+ if (Predicate.isString(node) || node[0] === null) return node;
1468
+ const [tag, attributes, ...children] = node;
1469
+ const projection = projectCmsEditorComponentAttributes(attributes, renderData);
1470
+ let props = properties(components.find((component) => component.name.toLowerCase() === tag.toLowerCase())?.authoring.props ?? [], projection.props);
1471
+ if (tag === "a") {
1472
+ const href = link(projection.props.href);
1473
+ if (href !== void 0) props = Object.assign({}, props, { href });
1474
+ }
1475
+ return [
1476
+ tag,
1477
+ JSON.stringify(props) === JSON.stringify(projection.props) ? attributes : encodeCmsEditorComponentAttributes(props, projection.bindings),
1478
+ ...children.map((child) => visit(child, projection.renderData))
1479
+ ];
1480
+ }
1481
+ const nodes = document.body.nodes.map((node) => visit(node, createCmsComarkRenderData(document.body)));
1482
+ return changed ? Object.assign({}, document.body, { nodes }) : void 0;
1483
+ }
1484
+
1437
1485
  //#endregion
1438
1486
  //#region ../document/src/DocumentPlanning.ts
1439
1487
  /** Derives one canonical Source path and public path without performing persistence. */
1440
1488
  function planDocumentCreation(configuration, input) {
1441
1489
  return Result.gen(function* () {
1442
1490
  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");
1491
+ if (!collection) return yield* invalid$2(`Document collection "${input.collection}" does not exist.`, "collection");
1492
+ if (!collection.canCreate) return yield* invalid$2(`Document collection "${input.collection}" does not allow creation.`, "collection");
1445
1493
  const title = input.title.trim();
1446
- if (!title) return yield* invalid$1("A document title is required.", "title");
1494
+ if (!title) return yield* invalid$2("A document title is required.", "title");
1447
1495
  const locale = yield* resolveLocale(configuration, input.locale);
1448
1496
  const address = yield* parseDocumentAddress(collection.addressPattern).pipe(Result.mapError(addressInvalid));
1449
1497
  const relativePath = yield* resolveRelativePath(collection, input.location);
@@ -1485,10 +1533,18 @@ const createInitialDocumentSource = Effect.fn("DocumentPlanning.createInitialSou
1485
1533
  function planDocumentMove(configuration, document, location) {
1486
1534
  return Result.gen(function* () {
1487
1535
  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");
1536
+ if (!collection) return yield* invalid$2(`Document collection "${document.collection}" does not exist.`, "documentId");
1489
1537
  return documentSourcePath(collection, yield* resolveRelativePath(collection, location), document.locale);
1490
1538
  });
1491
1539
  }
1540
+ /** Derives a collection-relative hierarchy path from a canonical document summary. */
1541
+ function documentRelativePath(collection, entry) {
1542
+ let sourcePath = entry.sourcePath;
1543
+ if (entry.document.locale && sourcePath.startsWith(`${entry.document.locale}/`)) sourcePath = sourcePath.slice(entry.document.locale.length + 1);
1544
+ if (collection.directory !== "." && sourcePath.startsWith(`${collection.directory}/`)) sourcePath = sourcePath.slice(collection.directory.length + 1);
1545
+ sourcePath = sourcePath.replace(/\.(?:md|mdc)$/iu, "");
1546
+ return sourcePath.toLocaleLowerCase() === "index" ? void 0 : sourcePath.replace(/\/index$/iu, "");
1547
+ }
1492
1548
  function documentSourcePath(collection, relativePath, locale) {
1493
1549
  const sourceName = relativePath ? `${relativePath}.md` : "index.md";
1494
1550
  return SourcePath.make([
@@ -1518,13 +1574,13 @@ function setNestedValue(target, path, value) {
1518
1574
  const isJsonObject$4 = Schema.is(Schema.JsonObject);
1519
1575
  function resolveLocale(configuration, requested) {
1520
1576
  const locales = configuration.locales;
1521
- if (!locales) return requested === void 0 ? Result.succeed(void 0) : invalid$1(`Document locale "${requested}" is not configured.`, "locale");
1577
+ if (!locales) return requested === void 0 ? Result.succeed(void 0) : invalid$2(`Document locale "${requested}" is not configured.`, "locale");
1522
1578
  const locale = requested ?? locales.defaultLocale;
1523
- return locales.locales.includes(locale) ? Result.succeed(locale) : invalid$1(`Document locale "${locale}" does not exist.`, "locale");
1579
+ return locales.locales.includes(locale) ? Result.succeed(locale) : invalid$2(`Document locale "${locale}" does not exist.`, "locale");
1524
1580
  }
1525
1581
  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");
1582
+ if (location.kind === "home") return collection.addressPattern === "/:path*" ? Result.succeed("") : invalid$2("Only a root catch-all collection can use the home location.", "location");
1583
+ if (location.parent !== void 0 && !collection.hierarchy) return invalid$2(`Document collection "${collection.collection}" does not allow hierarchy.`, "location.parent");
1528
1584
  return normalizeDocumentRelativePath([location.parent, location.slug].filter((value) => value !== void 0).join("/")).pipe(Result.mapError((error) => addressInvalid({
1529
1585
  ...error,
1530
1586
  field: "location"
@@ -1533,7 +1589,7 @@ function resolveRelativePath(collection, location) {
1533
1589
  function addressInvalid(error) {
1534
1590
  return new DocumentValidationFailed({ issues: [validationIssue(error.message, error.field)] });
1535
1591
  }
1536
- function invalid$1(message, field) {
1592
+ function invalid$2(message, field) {
1537
1593
  return Result.fail(new DocumentValidationFailed({ issues: [validationIssue(message, field)] }));
1538
1594
  }
1539
1595
  function validationIssue(message, field) {
@@ -1548,6 +1604,18 @@ function validationIssue(message, field) {
1548
1604
  severity: "error"
1549
1605
  };
1550
1606
  }
1607
+ /** Updates route-owned frontmatter when relocating a page in a slug-addressed collection. */
1608
+ const relocateDocumentMarkdown = Effect.fn("DocumentPlanning.relocateMarkdown")(function* (configuration, document, location, markdown) {
1609
+ const collection = configuration.types.find((type) => type.collection === document.collection);
1610
+ if (!collection || !collection.addressPattern.includes(":slug") || location.kind === "home") return markdown;
1611
+ return yield* renderEditorMarkdown({
1612
+ ...document.body,
1613
+ frontmatter: {
1614
+ ...document.body.frontmatter,
1615
+ slug: location.slug
1616
+ }
1617
+ });
1618
+ });
1551
1619
 
1552
1620
  //#endregion
1553
1621
  //#region ../server/src/internal/documents/document-state.ts
@@ -1633,6 +1701,7 @@ function authoringEntries(state, query) {
1633
1701
  if (query.collection !== void 0 && document.collection !== query.collection) return [];
1634
1702
  if (query.locale !== void 0 && document.locale !== query.locale) return [];
1635
1703
  return [DocumentSummary.make({
1704
+ ...browseMetadata(state, source),
1636
1705
  document,
1637
1706
  sourcePath: source.sourcePath,
1638
1707
  version: source.version
@@ -1641,6 +1710,9 @@ function authoringEntries(state, query) {
1641
1710
  }
1642
1711
  function deletedEntries(state) {
1643
1712
  return state.snapshot.deleted.map((source) => DeletedDocument.make({
1713
+ ...source.title ? { title: source.title } : void 0,
1714
+ ...source.path ? { path: source.path } : void 0,
1715
+ ...source.trashBatchId ? { trashBatchId: source.trashBatchId } : void 0,
1644
1716
  documentId: source.documentId,
1645
1717
  deletedAt: source.deletedAt,
1646
1718
  sourcePath: source.sourcePath,
@@ -1651,6 +1723,7 @@ function entryById(state, documentId) {
1651
1723
  const source = state.activeById.get(documentId);
1652
1724
  const document = state.authoredById.get(documentId);
1653
1725
  return source && document ? DocumentSummary.make({
1726
+ ...browseMetadata(state, source),
1654
1727
  document,
1655
1728
  sourcePath: source.sourcePath,
1656
1729
  version: source.version
@@ -1668,12 +1741,189 @@ function compilationIssues(error) {
1668
1741
  function errorMessage(cause) {
1669
1742
  return cause instanceof Error && cause.message ? cause.message : "Markdown could not be parsed.";
1670
1743
  }
1744
+ function browseMetadata(state, source) {
1745
+ const live = state.published.documents.find((document) => document.id === source.documentId);
1746
+ const metadata = {};
1747
+ if (source.createdBy) metadata.createdBy = source.createdBy;
1748
+ if (source.editedBy) metadata.editedBy = source.editedBy;
1749
+ if (source.editedAt) metadata.editedAt = source.editedAt;
1750
+ if (source.publishedVersion) metadata.publishedVersion = source.publishedVersion;
1751
+ if (live) metadata.publishedPath = live.path;
1752
+ return metadata;
1753
+ }
1754
+
1755
+ //#endregion
1756
+ //#region ../server/src/internal/documents/document-tree-mutation.ts
1757
+ /** Selects by route boundaries, never by title or a raw prefix such as /about matching /about-us. */
1758
+ function subtreeIds(state, documentId) {
1759
+ const parent = state.authoredById.get(documentId);
1760
+ if (!parent) return [documentId];
1761
+ return state.authored.documents.filter((page) => page.id === parent.id || parent.key !== "/" && page.collection === parent.collection && page.locale === parent.locale && page.key.startsWith(`${parent.key}/`)).map((page) => page.id);
1762
+ }
1763
+ const prepareTreeMutation = Effect.fn("Documents.prepareTreeMutation")(function* (dependencies, state, command) {
1764
+ const { configuration, engine } = dependencies;
1765
+ const input = command.input;
1766
+ const root = command.action === "document.undelete" ? state.snapshot.deleted.find((source) => source.documentId === input.documentId) : state.activeById.get(input.documentId);
1767
+ if (!root) return yield* new DocumentNotFound({ documentId: input.documentId });
1768
+ if (root.version !== input.expectedVersion) return yield* new DocumentVersionConflict({
1769
+ documentId: root.documentId,
1770
+ actualVersion: root.version,
1771
+ expectedVersion: input.expectedVersion
1772
+ });
1773
+ let sources;
1774
+ if (command.action === "document.undelete") {
1775
+ const deleted = state.snapshot.deleted.find((source) => source.documentId === root.documentId);
1776
+ const prefix = root.sourcePath.replace(/(?:\/index)?\.(?:md|mdc)$/iu, "");
1777
+ sources = state.snapshot.deleted.filter((source) => source.documentId === root.documentId || deleted && deleted.trashBatchId && source.trashBatchId === deleted.trashBatchId && source.sourcePath.startsWith(`${prefix}/`));
1778
+ } else {
1779
+ const ids = new Set(command.action === "document.duplicate" && !command.input.includeChildren ? [root.documentId] : subtreeIds(state, root.documentId));
1780
+ sources = state.snapshot.active.filter((source) => ids.has(source.documentId));
1781
+ }
1782
+ sources = [root, ...sources.filter((source) => source.documentId !== root.documentId)];
1783
+ const plans = [];
1784
+ const parent = state.authoredById.get(root.documentId);
1785
+ for (const source of sources) {
1786
+ if (command.action === "document.remove" || command.action === "document.undelete") {
1787
+ const page = state.authoredById.get(source.documentId);
1788
+ plans.push({
1789
+ ...command.action === "document.remove" ? {
1790
+ trashBatchId: root.version,
1791
+ title: page?.title,
1792
+ path: page?.path
1793
+ } : void 0,
1794
+ action: command.action,
1795
+ documentId: source.documentId,
1796
+ expectedVersion: source.version,
1797
+ markdown: source.markdown,
1798
+ sourcePath: source.sourcePath
1799
+ });
1800
+ continue;
1801
+ }
1802
+ const page = state.authoredById.get(source.documentId);
1803
+ const collection = configuration.types.find((type) => type.collection === page?.collection);
1804
+ if (!page || !parent || !collection) return yield* new DocumentNotFound({ documentId: source.documentId });
1805
+ if (command.action === "document.duplicate" && !collection.canCreate) return yield* invalid$1("This collection does not allow new pages.");
1806
+ const destination = command.input.location;
1807
+ if (parent.key === "/" && command.action === "document.move") return yield* invalid$1("The home page cannot be moved.");
1808
+ const rootRelative = documentRelativePath(collection, {
1809
+ document: parent,
1810
+ sourcePath: root.sourcePath
1811
+ }) ?? "";
1812
+ const nextRelative = destination.kind === "home" ? "" : [destination.parent, destination.slug].filter(Boolean).join("/");
1813
+ if (rootRelative && (nextRelative === rootRelative || nextRelative.startsWith(`${rootRelative}/`))) return yield* invalid$1("Choose a destination outside this page and its subpages.");
1814
+ const relative = documentRelativePath(collection, {
1815
+ document: page,
1816
+ sourcePath: source.sourcePath
1817
+ }) ?? "";
1818
+ const segments = `${nextRelative}${source.documentId === root.documentId ? "" : relative.slice(rootRelative.length)}`.split("/").filter(Boolean);
1819
+ const slug = segments.pop();
1820
+ const location = slug ? Object.assign({
1821
+ kind: "entry",
1822
+ slug: yield* decodeSlug(slug)
1823
+ }, segments.length ? { parent: yield* decodeParent(segments.join("/")) } : void 0) : { kind: "home" };
1824
+ const sourcePath = yield* Effect.fromResult(planDocumentMove(configuration, page, location));
1825
+ const markdown = yield* relocateDocumentMarkdown(configuration, page, location, source.markdown).pipe(Effect.mapError(() => invalid$1("The page could not be copied to its new URL.")));
1826
+ if (command.action === "document.duplicate") {
1827
+ const documentId = yield* command.nextDocumentId;
1828
+ plans.push({
1829
+ action: "document.create",
1830
+ documentId,
1831
+ markdown,
1832
+ sourcePath
1833
+ });
1834
+ } else plans.push({
1835
+ action: "document.move",
1836
+ documentId: source.documentId,
1837
+ expectedVersion: source.version,
1838
+ markdown,
1839
+ sourcePath,
1840
+ publishedVersion: source.publishedVersion
1841
+ });
1842
+ }
1843
+ const changed = new Set(plans.map((plan) => plan.documentId));
1844
+ const nextSources = authoredSources(state.snapshot).filter((source) => !changed.has(source.documentId));
1845
+ if (command.action !== "document.remove") for (const plan of plans) {
1846
+ if (nextSources.some((source) => source.sourcePath === plan.sourcePath)) return yield* new DocumentPathConflict({ sourcePath: plan.sourcePath });
1847
+ nextSources.push({
1848
+ documentId: plan.documentId,
1849
+ draft: true,
1850
+ markdown: plan.markdown,
1851
+ sourcePath: plan.sourcePath
1852
+ });
1853
+ }
1854
+ if (command.action === "document.move" || command.action === "document.duplicate") {
1855
+ const preliminary = yield* engine.compileAll({
1856
+ sources: nextSources,
1857
+ previous: state.authored
1858
+ });
1859
+ const destinations = /* @__PURE__ */ new Map();
1860
+ for (let index = 0; index < sources.length; index++) {
1861
+ const original = sources[index];
1862
+ const plan = plans[index];
1863
+ const oldPage = original && state.authoredById.get(original.documentId);
1864
+ const newPage = plan && preliminary.documents.find((document) => document.id === plan.documentId);
1865
+ if (oldPage && newPage) destinations.set(oldPage.path, newPage.path);
1866
+ }
1867
+ const originals = command.action === "document.move" ? state.snapshot.active : sources;
1868
+ for (const source of originals) {
1869
+ const page = state.authoredById.get(source.documentId);
1870
+ if (!page) continue;
1871
+ const body = rewriteDocumentRoutes(page, destinations, dependencies.components);
1872
+ if (!body) continue;
1873
+ const index = command.action === "document.duplicate" ? sources.findIndex((item) => item.documentId === source.documentId) : plans.findIndex((plan) => plan.documentId === source.documentId);
1874
+ const existing = index >= 0 ? plans[index] : void 0;
1875
+ const projected = existing && preliminary.documents.find((document) => document.id === existing.documentId);
1876
+ const markdown = yield* renderEditorMarkdown(Object.assign({}, body, { frontmatter: projected?.body.frontmatter ?? body.frontmatter })).pipe(Effect.mapError(() => invalid$1("Page links could not be updated.")));
1877
+ if (existing) plans[index] = {
1878
+ ...existing,
1879
+ markdown
1880
+ };
1881
+ else plans.push({
1882
+ action: "document.update",
1883
+ documentId: source.documentId,
1884
+ expectedVersion: source.version,
1885
+ sourcePath: source.sourcePath,
1886
+ markdown,
1887
+ publishedVersion: source.publishedVersion
1888
+ });
1889
+ }
1890
+ for (const plan of plans) {
1891
+ const index = nextSources.findIndex((source) => source.documentId === plan.documentId);
1892
+ const updated = {
1893
+ documentId: plan.documentId,
1894
+ sourcePath: plan.sourcePath,
1895
+ markdown: plan.markdown,
1896
+ draft: true
1897
+ };
1898
+ if (index >= 0) nextSources[index] = updated;
1899
+ else nextSources.push(updated);
1900
+ }
1901
+ }
1902
+ const first = plans[0];
1903
+ if (!first) return yield* Effect.die(/* @__PURE__ */ new Error("Empty subtree mutation."));
1904
+ return {
1905
+ authored: yield* compileValidCatalog(engine, nextSources, state.authored),
1906
+ published: command.action === "document.remove" ? yield* compileValidCatalog(engine, publishedSources(state.snapshot).filter((source) => !changed.has(source.documentId)), state.published) : state.published,
1907
+ commitPlan: first,
1908
+ additionalPlans: plans.slice(1)
1909
+ };
1910
+ });
1911
+ const decodeSlug = (value) => Schema.decodeEffect(Slug)(value).pipe(Effect.mapError(() => invalid$1("Invalid URL ending.")));
1912
+ const decodeParent = (value) => Schema.decodeEffect(RelativeDocumentPath)(value).pipe(Effect.mapError(() => invalid$1("Invalid parent URL.")));
1913
+ function invalid$1(message) {
1914
+ return new DocumentValidationFailed({ issues: [{
1915
+ code: "document.invalid",
1916
+ severity: "error",
1917
+ message
1918
+ }] });
1919
+ }
1671
1920
 
1672
1921
  //#endregion
1673
1922
  //#region ../server/src/internal/documents/document-lifecycle.ts
1674
1923
  const PreparedDocumentMutation = Data.taggedEnum();
1675
1924
  const prepareDocumentMutation = Effect.fn("DocumentLifecycle.prepare")(function* (dependencies, state, command) {
1676
- const { engine, configuration, repository } = dependencies;
1925
+ const { engine, repository } = dependencies;
1926
+ if (command.action === "document.move" || command.action === "document.remove" || command.action === "document.undelete" || command.action === "document.duplicate") return PreparedDocumentMutation.Commit(yield* prepareTreeMutation(dependencies, state, command));
1677
1927
  const authored = authoredSources(state.snapshot);
1678
1928
  const published = publishedSources(state.snapshot);
1679
1929
  switch (command.action) {
@@ -1718,47 +1968,6 @@ const prepareDocumentMutation = Effect.fn("DocumentLifecycle.prepare")(function*
1718
1968
  }
1719
1969
  });
1720
1970
  }
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
1971
  case "document.restore": {
1763
1972
  const current = yield* requireActive(state, command.input.documentId);
1764
1973
  yield* requireVersion(current, command.input.expectedVersion);
@@ -1783,25 +1992,24 @@ const prepareDocumentMutation = Effect.fn("DocumentLifecycle.prepare")(function*
1783
1992
  }
1784
1993
  });
1785
1994
  }
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
- }];
1995
+ case "document.unpublish": {
1996
+ const current = yield* requireActive(state, command.input.documentId);
1997
+ yield* requireVersion(current, command.input.expectedVersion);
1796
1998
  return PreparedDocumentMutation.Commit({
1797
- authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1798
- published: state.published,
1999
+ authored: yield* compileValidCatalog(engine, replaceSource(authored, {
2000
+ documentId: current.documentId,
2001
+ draft: true,
2002
+ markdown: current.markdown,
2003
+ sourcePath: current.sourcePath
2004
+ }), state.authored),
2005
+ published: yield* compileValidCatalog(engine, withoutDocument(published, current.documentId), state.published),
1799
2006
  commitPlan: {
1800
2007
  action: command.action,
1801
- documentId: deleted.documentId,
1802
- expectedVersion: command.input.expectedVersion,
1803
- markdown: deleted.markdown,
1804
- sourcePath: deleted.sourcePath
2008
+ documentId: current.documentId,
2009
+ expectedVersion: current.version,
2010
+ markdown: current.markdown,
2011
+ sourcePath: current.sourcePath,
2012
+ revisionId: current.revisionId
1805
2013
  }
1806
2014
  });
1807
2015
  }
@@ -1812,6 +2020,7 @@ const prepareDocumentMutation = Effect.fn("DocumentLifecycle.prepare")(function*
1812
2020
  const entry = entryById(state, current.documentId);
1813
2021
  const result = {
1814
2022
  action: "document.publish",
2023
+ affectedDocumentIds: [current.documentId],
1815
2024
  documentId: current.documentId,
1816
2025
  generation: state.snapshot.generation,
1817
2026
  revisionId: current.revisionId,
@@ -1856,10 +2065,6 @@ function requireActive(state, documentId) {
1856
2065
  const source = state.activeById.get(documentId);
1857
2066
  return source ? Effect.succeed(source) : Effect.fail(new DocumentNotFound({ documentId }));
1858
2067
  }
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
2068
  function requireVersion(source, expectedVersion) {
1864
2069
  return source.version === expectedVersion ? Effect.void : Effect.fail(new DocumentVersionConflict({
1865
2070
  actualVersion: source.version,
@@ -2101,24 +2306,24 @@ const compareLines = Effect.fn("Documents.history.compareLines")(function* (befo
2101
2306
  });
2102
2307
 
2103
2308
  //#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);
2309
+ //#region ../server/src/internal/documents/document-delivery.ts
2310
+ function documentDelivery(configuration, catalog) {
2311
+ const read = Effect.fn("Documents.delivery.read")(function* (lookup) {
2312
+ const document = findDocument(yield* catalog, lookup);
2108
2313
  return Option.isSome(document) ? document.value : yield* new PublishedDocumentNotFound({ lookup });
2109
2314
  });
2110
- const query = Effect.fn("Documents.published.query")(function* (input) {
2315
+ const query = Effect.fn("Documents.delivery.query")(function* (input) {
2111
2316
  return queryDocuments((yield* catalog).documents, input, {
2112
2317
  limit: input.limit,
2113
2318
  offset: input.offset ?? 0
2114
2319
  });
2115
2320
  });
2116
- const navigation = Effect.fn("Documents.published.navigation")(function* (input) {
2321
+ const navigation = Effect.fn("Documents.delivery.navigation")(function* (input) {
2117
2322
  return createNavigation((yield* catalog).documents, input, configuration);
2118
2323
  });
2119
- const surround = Effect.fn("Documents.published.surround")(function* (lookup, input) {
2324
+ const surround = Effect.fn("Documents.delivery.surround")(function* (lookup, input) {
2120
2325
  const current = yield* catalog;
2121
- const document = findPublishedDocument(current, lookup);
2326
+ const document = findDocument(current, lookup);
2122
2327
  if (Option.isNone(document)) return yield* new PublishedDocumentNotFound({ lookup });
2123
2328
  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
2329
  const page = queryDocuments(current.documents, surroundQuery);
@@ -2136,7 +2341,7 @@ function publishedDocuments(configuration, catalog) {
2136
2341
  surround
2137
2342
  };
2138
2343
  }
2139
- function findPublishedDocument(catalog, lookup) {
2344
+ function findDocument(catalog, lookup) {
2140
2345
  switch (lookup.kind) {
2141
2346
  case "id": return Option.fromNullishOr(catalog.documents.find((document) => document.id === lookup.documentId));
2142
2347
  case "path": return catalog.findByPath(lookup.path);
@@ -2188,7 +2393,8 @@ function createNavigation(documents, query, configuration) {
2188
2393
  const resolved = navigationItem(document);
2189
2394
  item.title = resolved.title;
2190
2395
  item.path = resolved.path;
2191
- item.locale = resolved.locale;
2396
+ if (resolved.locale !== void 0) item.locale = resolved.locale;
2397
+ else delete item.locale;
2192
2398
  item.documentId = resolved.documentId;
2193
2399
  orderByItem.set(item, navigationOrder(document));
2194
2400
  }
@@ -2201,14 +2407,13 @@ function createNavigation(documents, query, configuration) {
2201
2407
  function navigationItem(document) {
2202
2408
  const navigation = document.data.navigation;
2203
2409
  const configuredTitle = Schema.is(Schema.JsonObject)(navigation) ? navigation.title : void 0;
2204
- return {
2410
+ return Object.assign({
2205
2411
  children: [],
2206
2412
  documentId: document.id,
2207
2413
  key: document.key,
2208
- locale: document.locale,
2209
2414
  path: document.path,
2210
2415
  title: Schema.is(Schema.String)(configuredTitle) && configuredTitle.trim() ? configuredTitle.trim() : document.title
2211
- };
2416
+ }, document.locale === void 0 ? void 0 : { locale: document.locale });
2212
2417
  }
2213
2418
  function navigationOrder(document) {
2214
2419
  const navigation = document.data.navigation;
@@ -2286,7 +2491,8 @@ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, opti
2286
2491
  const dependencies = {
2287
2492
  engine,
2288
2493
  configuration,
2289
- repository
2494
+ repository,
2495
+ components: options.components
2290
2496
  };
2291
2497
  const refreshUnlocked = Effect.fn("Documents.refreshUnlocked")(function* (force = false) {
2292
2498
  const current = yield* Ref.get(state);
@@ -2300,13 +2506,63 @@ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, opti
2300
2506
  return yield* Effect.gen(function* () {
2301
2507
  yield* refreshUnlocked();
2302
2508
  const current = yield* Ref.get(state);
2509
+ if (command.action === "document.move" || command.action === "document.remove" || command.action === "document.duplicate") {
2510
+ const ids = command.action === "document.duplicate" && !command.input.includeChildren ? [command.input.documentId] : subtreeIds(current, command.input.documentId);
2511
+ for (const documentId of ids) yield* accessControl.require({
2512
+ actor: activity.actor,
2513
+ requestId: activity.requestId
2514
+ }, {
2515
+ action: command.action,
2516
+ resource: {
2517
+ kind: "document",
2518
+ documentId
2519
+ }
2520
+ });
2521
+ if (command.action === "document.duplicate") {
2522
+ const source = current.authoredById.get(command.input.documentId);
2523
+ if (source) yield* accessControl.require({
2524
+ actor: activity.actor,
2525
+ requestId: activity.requestId
2526
+ }, {
2527
+ action: "document.create",
2528
+ resource: {
2529
+ kind: "collection",
2530
+ collection: source.collection,
2531
+ locale: source.locale
2532
+ }
2533
+ });
2534
+ }
2535
+ }
2303
2536
  const prepared = yield* prepareDocumentMutation(dependencies, current, command);
2537
+ if (command.action === "document.move" && prepared._tag === "Commit") {
2538
+ for (const plan of prepared.additionalPlans ?? []) if (plan.action === "document.update") yield* accessControl.require({
2539
+ actor: activity.actor,
2540
+ requestId: activity.requestId
2541
+ }, {
2542
+ action: "document.update",
2543
+ resource: {
2544
+ kind: "document",
2545
+ documentId: plan.documentId
2546
+ }
2547
+ });
2548
+ }
2549
+ if (command.action === "document.undelete" && prepared._tag === "Commit") for (const plan of [prepared.commitPlan, ...prepared.additionalPlans ?? []]) yield* accessControl.require({
2550
+ actor: activity.actor,
2551
+ requestId: activity.requestId
2552
+ }, {
2553
+ action: "document.undelete",
2554
+ resource: {
2555
+ kind: "document",
2556
+ documentId: plan.documentId
2557
+ }
2558
+ });
2304
2559
  if (prepared._tag === "Unchanged") return prepared.result;
2305
2560
  const committed = yield* repository.commit({
2306
2561
  activity,
2307
2562
  plan: prepared.commitPlan,
2563
+ additionalPlans: prepared.additionalPlans,
2308
2564
  expectedGeneration: current.snapshot.generation,
2309
- mediaReferences: projectMediaReferences(prepared.authored, prepared.published, commandDocumentId(command))
2565
+ mediaReferences: [prepared.commitPlan, ...prepared.additionalPlans ?? []].flatMap((plan) => projectMediaReferences(prepared.authored, prepared.published, plan.documentId))
2310
2566
  });
2311
2567
  const next = makeDocumentState(prepared.authored, prepared.published, committed.snapshot);
2312
2568
  yield* Ref.set(state, next);
@@ -2342,10 +2598,6 @@ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, opti
2342
2598
  sourcePath: plan.sourcePath
2343
2599
  }, activity, input.reason);
2344
2600
  });
2345
- const generation = semaphore.withPermits(1)(Effect.gen(function* () {
2346
- yield* refreshUnlocked();
2347
- return (yield* Ref.get(state)).snapshot.generation;
2348
- }));
2349
2601
  const importSources = (input, activity) => importDocumentSources({
2350
2602
  current: (sourcePath) => semaphore.withPermits(1)(Effect.gen(function* () {
2351
2603
  yield* refreshUnlocked();
@@ -2354,6 +2606,15 @@ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, opti
2354
2606
  nextDocumentId: crypto.randomUUIDv4.pipe(Effect.orDie, Effect.map((value) => DocumentId.make(value))),
2355
2607
  runMutation
2356
2608
  }, input, activity);
2609
+ const duplicate = Effect.fn("Documents.duplicate")((input, activity) => runMutation({
2610
+ action: "document.duplicate",
2611
+ input,
2612
+ nextDocumentId: crypto.randomUUIDv4.pipe(Effect.orDie, Effect.map((value) => DocumentId.make(value)))
2613
+ }, activity, input.reason));
2614
+ const unpublish = Effect.fn("Documents.unpublish")((input, activity) => runMutation({
2615
+ action: "document.unpublish",
2616
+ input
2617
+ }, activity, input.reason));
2357
2618
  const move = Effect.fn("Documents.move")((input, activity) => runMutation({
2358
2619
  action: "document.move",
2359
2620
  input
@@ -2404,7 +2665,7 @@ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, opti
2404
2665
  return yield* validateDocumentUpdate(engine, yield* Ref.get(state), input);
2405
2666
  })));
2406
2667
  const history = documentHistoryCapability(accessControl, repository);
2407
- const delivery = publishedDocuments(configuration, published);
2668
+ const delivery = documentDelivery(configuration, published);
2408
2669
  const document = (documentId) => ({
2409
2670
  documentId,
2410
2671
  kind: "document"
@@ -2427,6 +2688,8 @@ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, opti
2427
2688
  historyCompare: history.compare,
2428
2689
  historyList: history.list,
2429
2690
  historyRead: history.read,
2691
+ duplicate: (context, input) => requireThen(context, "document.duplicate", document(input.documentId), () => duplicate(input, requestActivity(context, input.reason))),
2692
+ unpublish: (context, input) => requireThen(context, "document.unpublish", document(input.documentId), () => unpublish(input, requestActivity(context, input.reason))),
2430
2693
  move: (context, input) => requireThen(context, "document.move", document(input.documentId), () => move(input, requestActivity(context, input.reason))),
2431
2694
  publish: (context, input) => requireThen(context, "document.publish", document(input.documentId), () => publish(input, requestActivity(context, input.reason))),
2432
2695
  read: (context, documentId) => requireThen(context, "document.read", document(documentId), () => read(documentId)),
@@ -2437,9 +2700,12 @@ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, opti
2437
2700
  update: (context, input) => requireThen(context, "document.update", document(input.documentId), () => update(input, requestActivity(context, input.reason))),
2438
2701
  validate: (context, input) => requireThen(context, "document.validate", document(input.documentId), () => validate(input))
2439
2702
  });
2703
+ const draftDelivery = documentDelivery(configuration, authored);
2440
2704
  const previews = DocumentPreviews.of({
2441
- generation,
2442
- read: (lookup) => authored.pipe(Effect.map((catalog) => findPublishedDocument(catalog, lookup)))
2705
+ read: (context, lookup) => requireThen(context, "document.browse", { kind: "catalog" }, () => draftDelivery.read(lookup)),
2706
+ query: (context, query) => requireThen(context, "document.browse", { kind: "catalog" }, () => draftDelivery.query(query)),
2707
+ navigation: (context, query) => requireThen(context, "document.browse", { kind: "catalog" }, () => draftDelivery.navigation(query)),
2708
+ surround: (context, lookup, query) => requireThen(context, "document.browse", { kind: "catalog" }, () => draftDelivery.surround(lookup, query))
2443
2709
  });
2444
2710
  const publishedService = PublishedDocuments.of({
2445
2711
  navigation: delivery.navigation,
@@ -2496,9 +2762,6 @@ function loadSynchronizedState(repository, engine) {
2496
2762
  operation: "Documents.synchronizeMediaReferences"
2497
2763
  }) : error));
2498
2764
  }
2499
- function commandDocumentId(command) {
2500
- return command.action === "document.create" ? command.documentId : command.input.documentId;
2501
- }
2502
2765
  function generationIsAfter(candidate, current) {
2503
2766
  return BigInt(candidate) > BigInt(current);
2504
2767
  }
@@ -3220,7 +3483,7 @@ function extractGranularState(content) {
3220
3483
  const nodeId = optionalEditorNodeId(node.attrs?.cmsNodeId);
3221
3484
  if (nodeId === void 0) return;
3222
3485
  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);
3486
+ if (Object.hasOwn(node.attrs ?? {}, "props") || Object.hasOwn(node.attrs ?? {}, "cmsProps")) properties.set(nodeId, values);
3224
3487
  });
3225
3488
  return {
3226
3489
  frontmatter,
@@ -3245,7 +3508,7 @@ function flatten(value) {
3245
3508
  }
3246
3509
  function unflatten(value) {
3247
3510
  const result = {};
3248
- for (const [encodedPath, child] of Object.entries(value)) {
3511
+ 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
3512
  const path = decodePath(JSON.parse(encodedPath));
3250
3513
  if (path.length === 0) continue;
3251
3514
  let current = result;
@@ -3268,10 +3531,10 @@ function mapValues(map) {
3268
3531
  return decodeJsonObject$1(Object.fromEntries([...map.entries()].map(([key, value]) => [key, structuredClone(decodeJson$1(value))])));
3269
3532
  }
3270
3533
  function prefixValues(value, prefix) {
3271
- return decodeJsonObject$1(Object.fromEntries(Object.entries(value).map(([key, child]) => [`${prefix}${key}`, structuredClone(child)])));
3534
+ return decodeJsonObject$1(Object.fromEntries(Object.entries(flatten(encodeLists(value))).map(([key, child]) => [`${prefix}${key}`, structuredClone(child)])));
3272
3535
  }
3273
3536
  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)])));
3537
+ 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
3538
  }
3276
3539
  function optionalJsonObject(value) {
3277
3540
  return value === null || value === void 0 ? {} : structuredClone(decodeJsonObject$1(value));
@@ -3291,6 +3554,33 @@ function define(target, key, value) {
3291
3554
  writable: true
3292
3555
  });
3293
3556
  }
3557
+ const listMarker = "\0cmsList";
3558
+ function encodeLists(value) {
3559
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => {
3560
+ if (Array.isArray(child) && child.every((item) => isJsonObject(item) && Predicate.isString(item.id))) return [key, {
3561
+ [listMarker]: true,
3562
+ items: Object.fromEntries(child.flatMap((item, order) => isJsonObject(item) && Predicate.isString(item.id) ? [[item.id, {
3563
+ order,
3564
+ value: encodeLists(item)
3565
+ }]] : []))
3566
+ }];
3567
+ return [key, isJsonObject(child) ? encodeLists(child) : child];
3568
+ }));
3569
+ }
3570
+ function decodeLists(value) {
3571
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => {
3572
+ if (!isJsonObject(child)) return [key, child];
3573
+ if (child[listMarker] === true) return [key, (isJsonObject(child.items) ? Object.entries(child.items) : []).flatMap(([id, item]) => {
3574
+ if (!isJsonObject(item) || !isJsonObject(item.value) || item.value.id !== id || !Predicate.isNumber(item.order)) return [];
3575
+ return [{
3576
+ id,
3577
+ order: item.order,
3578
+ value: decodeLists(item.value)
3579
+ }];
3580
+ }).sort((left, right) => left.order - right.order || left.id.localeCompare(right.id)).map((item) => item.value)];
3581
+ return [key, decodeLists(child)];
3582
+ }));
3583
+ }
3294
3584
 
3295
3585
  //#endregion
3296
3586
  //#region ../document/src/internal/collaboration/document.ts
@@ -3644,6 +3934,7 @@ const startWorkingDocumentReconciliation = Effect.fn("WorkingDocuments.startReco
3644
3934
  return [true, {
3645
3935
  ...current,
3646
3936
  mutation: result,
3937
+ sweepRequired: current.sweepRequired || (result.affectedDocumentIds?.length ?? 0) > 1,
3647
3938
  target: result.generation
3648
3939
  }];
3649
3940
  })) yield* Queue.offer(wakeups, void 0);
@@ -4956,6 +5247,10 @@ const makeInstallation = Effect.fn("Installation.make")(function* (bootstrap, ha
4956
5247
  ready: true
4957
5248
  }, false));
4958
5249
  if (pathname === CMS_READINESS_PATH) return runPromise(health).then((status) => healthResponse(status, true));
5250
+ if (pathname.startsWith(`${STUDIO_API_PATH}/preview/`)) return handle(request).then((response) => {
5251
+ response.headers.set("cache-control", "private, no-store");
5252
+ return response;
5253
+ });
4959
5254
  return handle(request);
4960
5255
  },
4961
5256
  health,
@@ -5672,65 +5967,6 @@ function activityFailure(operation) {
5672
5967
  });
5673
5968
  }
5674
5969
 
5675
- //#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
5728
- });
5729
- });
5730
- function invalidSession(reason) {
5731
- return Effect.fail(new PreviewSessionInvalid({ reason }));
5732
- }
5733
-
5734
5970
  //#endregion
5735
5971
  //#region ../server/src/internal/initialization/initialization.ts
5736
5972
  const InitializationUnavailableError = InitializationUnavailable;
@@ -6065,7 +6301,7 @@ function mapUnexpectedError(effect) {
6065
6301
  //#region ../platform-node/src/internal/http/collaboration.ts
6066
6302
  const PublicTicketError = Schema.Union([AccessDenied, DocumentNotFound]);
6067
6303
  const isPublicTicketError = Schema.is(PublicTicketError);
6068
- const collaborationApiLayer = HttpApiBuilder.group(StudioApi, "collaboration", (handlers) => Effect.gen(function* () {
6304
+ const collaborationApiLayer = (authoringRevision) => HttpApiBuilder.group(StudioApi, "collaboration", (handlers) => Effect.gen(function* () {
6069
6305
  const accessControl = yield* AccessControl;
6070
6306
  const documents = yield* WorkingDocuments;
6071
6307
  const tickets = yield* StudioRealtimeTickets;
@@ -6090,6 +6326,7 @@ const collaborationApiLayer = HttpApiBuilder.group(StudioApi, "collaboration", (
6090
6326
  ref: opened.ref
6091
6327
  });
6092
6328
  return CollaborationTicket.make({
6329
+ authoringRevision,
6093
6330
  authoredStateVector: encodeRealtimeStateVector(opened.authoredStateVector),
6094
6331
  documentName: roomName,
6095
6332
  source: opened.source,
@@ -6124,7 +6361,13 @@ const documentsApiLayer = HttpApiBuilder.group(StudioApi, "documents", (handlers
6124
6361
  }))).pipe(mapMutationError)).handle("validate", ({ params, payload }) => withStudioRequest((context) => documents.validate(context, UpdateDocumentInput.make({
6125
6362
  documentId: params.documentId,
6126
6363
  ...payload
6127
- }))).pipe(preservePublicErrors((error) => isAccessDenied$1(error) || isDocumentNotFound$1(error) || isDocumentVersionConflict(error)))).handle("move", ({ params, payload }) => withStudioRequest((context) => documents.move(context, MoveDocumentInput.make({
6364
+ }))).pipe(preservePublicErrors((error) => isAccessDenied$1(error) || isDocumentNotFound$1(error) || isDocumentVersionConflict(error)))).handle("duplicate", ({ params, payload }) => withStudioRequest((context) => documents.duplicate(context, DuplicateDocumentInput.make({
6365
+ documentId: params.documentId,
6366
+ ...payload
6367
+ }))).pipe(mapMutationError)).handle("unpublish", ({ params, payload }) => withStudioRequest((context) => documents.unpublish(context, UnpublishDocumentInput.make({
6368
+ documentId: params.documentId,
6369
+ ...payload
6370
+ }))).pipe(mapMutationError)).handle("move", ({ params, payload }) => withStudioRequest((context) => documents.move(context, MoveDocumentInput.make({
6128
6371
  documentId: params.documentId,
6129
6372
  ...payload
6130
6373
  }))).pipe(mapMutationError)).handle("publish", ({ params, payload }) => withStudioRequest((context) => documents.publish(context, PublishDocumentInput.make({
@@ -6227,43 +6470,11 @@ function invalidMultipart() {
6227
6470
 
6228
6471
  //#endregion
6229
6472
  //#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" })));
6473
+ const publicError = Schema.is(Schema.Union([AccessDenied, PublishedDocumentNotFound]));
6474
+ const denied = Schema.is(AccessDenied);
6475
+ const previewApiLayer = HttpApiBuilder.group(StudioApi, "previewDelivery", (handlers) => Effect.gen(function* () {
6476
+ const documents = yield* DocumentPreviews;
6477
+ return handlers.handle("read", ({ payload }) => CurrentStudioRequest.pipe(Effect.flatMap((context) => documents.read(context, payload)), preservePublicErrors(publicError))).handle("query", ({ payload }) => CurrentStudioRequest.pipe(Effect.flatMap((context) => documents.query(context, payload)), 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)), preservePublicErrors(publicError)));
6267
6478
  }));
6268
6479
 
6269
6480
  //#endregion
@@ -6321,9 +6532,9 @@ const userAdministrationHttpLayer = HttpApiBuilder.group(StudioApi, "users", (ha
6321
6532
 
6322
6533
  //#endregion
6323
6534
  //#region ../platform-node/src/internal/http/api.ts
6324
- function studioApiLayer(bootstrap) {
6535
+ function studioApiLayer(bootstrap, authoringRevision) {
6325
6536
  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)));
6537
+ return authenticatedRouterHandlers(Layer.mergeAll(configuration, documentsApiLayer, mediaApiLayer(bootstrap.media.enabled, bootstrap.media.maxUploadBytes), collaborationApiLayer(authoringRevision), deliveryApiLayer, previewApiLayer, administrationApiLayer, userAdministrationHttpLayer).pipe(Layer.provideMerge(studioSchemaErrorLayer)));
6327
6538
  }
6328
6539
  function authenticatedRouterHandlers(handlers) {
6329
6540
  return handlers;
@@ -6335,7 +6546,7 @@ function productModulesLayer(options) {
6335
6546
  const accessControl = defaultAccessControlLayer;
6336
6547
  const authoredDocuments = documentsLayer(options.bootstrap.documents, {
6337
6548
  ...options.documentEngine,
6338
- components: options.documentEngine.components ?? options.bootstrap.components,
6549
+ components: options.documentEngine.components ?? [],
6339
6550
  mediaBaseUrl: options.documentEngine.mediaBaseUrl ?? "https://media.invalid"
6340
6551
  }).pipe(Layer.provide(accessControl));
6341
6552
  const documentAccess = Layer.merge(authoredDocuments, accessControl);
@@ -6375,7 +6586,10 @@ function makeInstallationLayer(validated) {
6375
6586
  const core = options.composition.core;
6376
6587
  const productOptions = {
6377
6588
  bootstrap: options.bootstrap,
6378
- documentEngine: { documentModel: options.documentModel },
6589
+ documentEngine: {
6590
+ documentModel: options.documentModel,
6591
+ components: options.authoring.components
6592
+ },
6379
6593
  workingDocuments: options.workingDocuments
6380
6594
  };
6381
6595
  const enabledProductModules = (media) => productModulesWithMediaLayer({
@@ -6384,7 +6598,6 @@ function makeInstallationLayer(validated) {
6384
6598
  }).pipe(Layer.provide(Layer.merge(core, media.objects)));
6385
6599
  const productModules = validated.media.enabled ? enabledProductModules(validated.media.configuration) : productModulesLayer(productOptions).pipe(Layer.provide(core));
6386
6600
  const authentication = studioAuthenticationLayer.pipe(Layer.provide(core));
6387
- const previewSessions = previewSessionsLayer(options.studioSecret, { allowedPreviewOrigins: options.allowedPreviewOrigins }).pipe(Layer.provide(core));
6388
6601
  const initialization = initializationLayer({
6389
6602
  initialSources: options.initialSources,
6390
6603
  initializationToken: options.initializationToken
@@ -6393,8 +6606,8 @@ function makeInstallationLayer(validated) {
6393
6606
  const tickets = studioRealtimeTicketsLayer(options.studioSecret).pipe(Layer.provide(core));
6394
6607
  const roomHost = studioRealtimeRoomHostLayer().pipe(Layer.provide(productModules));
6395
6608
  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));
6609
+ const httpDependencies = Layer.mergeAll(core, productModules, gateway, initialization, roomHost, users).pipe(Layer.provideMerge(authentication), Layer.provideMerge(tickets));
6610
+ const handlers = studioApiLayer(options.bootstrap, options.authoring.revision).pipe(Layer.provide(httpDependencies));
6398
6611
  const httpApi = HttpApiBuilder.layer(StudioApi).pipe(Layer.provide(handlers), Layer.provide(HttpServer.layerServices));
6399
6612
  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));
6400
6613
  }
@@ -6443,7 +6656,7 @@ const installCms$1 = Effect.fn("CmsInstallation.install")(function* (options) {
6443
6656
  });
6444
6657
  function createCmsInstallationLayer(options) {
6445
6658
  return installationLayer({
6446
- allowedPreviewOrigins: /* @__PURE__ */ new Set([options.baseUrl]),
6659
+ authoring: options.authoring,
6447
6660
  bootstrap: options.bootstrap,
6448
6661
  composition: options.composition,
6449
6662
  studioSecret: options.studioSecret,
@@ -6520,7 +6733,7 @@ function makeAwsS3MediaObjectClient(client) {
6520
6733
  }))
6521
6734
  }))),
6522
6735
  putObject: (input) => request("MediaObjectStore.S3.putObject", (signal) => client.send(new PutObjectCommand({
6523
- Body: Readable.from(input.body),
6736
+ Body: NodeStream.toReadableNever(Stream.fromAsyncIterable(input.body, (cause) => cause)),
6524
6737
  Bucket: input.bucket,
6525
6738
  CacheControl: input.cacheControl,
6526
6739
  ContentLength: input.contentLength,
@@ -7285,6 +7498,13 @@ const embeddedMigrations = [
7285
7498
  folderMillis: 178875937e4,
7286
7499
  hash: "e442485fa6c2d611e3dae508a12d245481bc5ec8f2dd4f5d853910df34484787",
7287
7500
  name: "20260907053610_fancy_lady_deathstrike"
7501
+ },
7502
+ {
7503
+ 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'));"],
7504
+ bps: true,
7505
+ folderMillis: 1788866265e3,
7506
+ hash: "24c56a37796caf8d76c0a97128a382b31e71808aa5b0ca7e5bac96795f9e16c7",
7507
+ name: "20260908111745_classy_johnny_storm"
7288
7508
  }
7289
7509
  ];
7290
7510
 
@@ -7425,7 +7645,11 @@ const applyDocumentCommitPlan = Effect.fn("DocumentStore.Drizzle.applyCommitPlan
7425
7645
  return;
7426
7646
  }
7427
7647
  case "document.remove": {
7428
- const revisionId = yield* appendRevision(transaction, plan, activity);
7648
+ const metadata = {};
7649
+ if (plan.trashBatchId) metadata.trashBatchId = plan.trashBatchId;
7650
+ if (plan.title) metadata.title = plan.title;
7651
+ if (plan.path) metadata.path = plan.path;
7652
+ const revisionId = yield* appendRevision(transaction, plan, activity, metadata);
7429
7653
  yield* transaction.update(documentsTable).set({
7430
7654
  deletedAt: transactionTimestamp,
7431
7655
  updatedAt: transactionTimestamp
@@ -7454,6 +7678,9 @@ const applyDocumentCommitPlan = Effect.fn("DocumentStore.Drizzle.applyCommitPlan
7454
7678
  }).where(eq(documentHeads.documentId, plan.documentId));
7455
7679
  return;
7456
7680
  }
7681
+ case "document.unpublish":
7682
+ yield* transaction.update(documentHeads).set({ publishedRevisionId: null }).where(eq(documentHeads.documentId, plan.documentId));
7683
+ return;
7457
7684
  case "document.publish":
7458
7685
  yield* transaction.update(documentHeads).set({ publishedRevisionId: plan.revisionId }).where(eq(documentHeads.documentId, plan.documentId));
7459
7686
  return;
@@ -7505,6 +7732,7 @@ function activityActionForDocumentPlan(action) {
7505
7732
  case "document.remove": return "document.trashed";
7506
7733
  case "document.restore": return "document.revision.restored";
7507
7734
  case "document.undelete": return "document.untrashed";
7735
+ case "document.unpublish": return "document.unpublished";
7508
7736
  case "document.publish": return "document.published";
7509
7737
  }
7510
7738
  }
@@ -7567,6 +7795,7 @@ const DocumentMediaReferenceInsert = createInsertSchema(documentMediaReferences,
7567
7795
  //#region ../sql-pg/src/internal/documents/records.ts
7568
7796
  const DocumentSnapshotRow = Schema.Struct({
7569
7797
  authored: Schema.NullOr(DocumentRevisionSelect),
7798
+ created: Schema.optionalKey(Schema.NullOr(DocumentRevisionSelect)),
7570
7799
  document: DocumentSelect,
7571
7800
  head: Schema.NullOr(DocumentHeadSelect),
7572
7801
  published: Schema.NullOr(DocumentRevisionSelect)
@@ -7583,11 +7812,18 @@ const decodeDocumentSnapshot = Effect.fn("DocumentStore.Drizzle.decodeSnapshot")
7583
7812
  if (documentIds.has(row.document.id)) return yield* documentPersistenceError("DocumentStore.Drizzle.decodeSnapshot", /* @__PURE__ */ new Error(`Documents persistence returned duplicate document "${row.document.id}".`));
7584
7813
  documentIds.add(row.document.id);
7585
7814
  yield* validateSnapshotRow(row.document, row.head, row.authored, row.published);
7586
- const authored = sourceRecord(row.authored, row.head.publishedRevisionId ?? void 0);
7815
+ const authored = Object.assign({}, sourceRecord(row.authored, row.head.publishedRevisionId ?? void 0), row.created ? { createdBy: revisionActor(row.created) } : void 0);
7587
7816
  if (row.document.deletedAt === null) {
7588
7817
  active.push(authored);
7589
7818
  if (row.published) published.push(sourceRecord(row.published, row.published.id));
7590
- } else deleted.push(Object.assign({}, authored, { deletedAt: row.document.deletedAt }));
7819
+ } else {
7820
+ const details = yield* Schema.decodeEffect(Schema.Struct({
7821
+ title: DeletedDocument.fields.title,
7822
+ path: DeletedDocument.fields.path,
7823
+ trashBatchId: DeletedDocument.fields.trashBatchId
7824
+ }))(row.authored.activityDetails).pipe(Effect.mapError((cause) => documentPersistenceError("DocumentStore.decodeTrash", cause)));
7825
+ deleted.push(Object.assign({}, authored, details, { deletedAt: row.document.deletedAt }));
7826
+ }
7591
7827
  }
7592
7828
  active.sort(compareSources);
7593
7829
  deleted.sort(compareSources);
@@ -7643,6 +7879,8 @@ function validateSnapshotRow(document, head, authored, published) {
7643
7879
  }
7644
7880
  function sourceRecord(revision, publishedRevisionId) {
7645
7881
  const record = {
7882
+ editedAt: revision.createdAt,
7883
+ editedBy: revisionActor(revision),
7646
7884
  documentId: revision.documentId,
7647
7885
  markdown: revision.markdown,
7648
7886
  revisionId: revision.id,
@@ -7661,6 +7899,14 @@ function documentPersistenceError(operation, cause) {
7661
7899
  operation
7662
7900
  });
7663
7901
  }
7902
+ function revisionActor(revision) {
7903
+ return {
7904
+ id: revision.actorId,
7905
+ name: revision.actorName,
7906
+ email: revision.actorEmail,
7907
+ roles: revision.actorRoles
7908
+ };
7909
+ }
7664
7910
 
7665
7911
  //#endregion
7666
7912
  //#region ../sql-pg/src/internal/database/media-reference-lock.ts
@@ -7766,6 +8012,7 @@ function validMediaObjectKey(column) {
7766
8012
  //#endregion
7767
8013
  //#region ../sql-pg/src/internal/documents/document-store.ts
7768
8014
  const authoredRevision = alias(documentRevisions, "authored_revision");
8015
+ const createdRevision = alias(documentRevisions, "created_revision");
7769
8016
  const publishedRevision = alias(documentRevisions, "published_revision");
7770
8017
  const RepositoryDomainError = Schema.Union([
7771
8018
  DocumentGenerationConflict,
@@ -7800,19 +8047,26 @@ const documentStoreLayer = Layer.effect(DocumentStore, Effect.gen(function* () {
7800
8047
  });
7801
8048
  }
7802
8049
  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);
8050
+ const plans = [input.plan, ...input.additionalPlans ?? []];
8051
+ const references = plans.flatMap((plan) => input.mediaReferences.filter((reference) => reference.documentId === plan.documentId));
8052
+ if (references.length !== input.mediaReferences.length) return yield* persistenceFailure$2("DocumentStore.commit", /* @__PURE__ */ new Error("Unexpected media reference owner."));
8053
+ yield* lockMediaReferenceKeys(transaction, references.map((reference) => reference.objectKey));
7806
8054
  yield* rejectDeletingMedia(transaction, references);
7807
8055
  const activity = input.activity ?? systemActivity;
7808
- yield* applyDocumentCommitPlan(transaction, input.plan, activity);
7809
- yield* replaceDocumentMediaReferences(transaction, input.plan.documentId, references);
8056
+ for (const plan of plans) {
8057
+ yield* applyDocumentCommitPlan(transaction, plan, activity);
8058
+ yield* replaceDocumentMediaReferences(transaction, plan.documentId, references.filter((reference) => reference.documentId === plan.documentId));
8059
+ }
7810
8060
  const snapshot = yield* readSnapshot(transaction, state.generation);
8061
+ for (const plan of plans) {
8062
+ const affected = yield* sourceForMutation(snapshot, plan.documentId, plan.action === "document.remove");
8063
+ yield* recordDocumentActivity(transaction, affected, plan, activity);
8064
+ }
7811
8065
  const source = yield* sourceForMutation(snapshot, input.plan.documentId, input.plan.action === "document.remove");
7812
- yield* recordDocumentActivity(transaction, source, input.plan, activity);
7813
8066
  return {
7814
8067
  mutation: DocumentMutationResult.make({
7815
8068
  action: input.plan.action,
8069
+ affectedDocumentIds: plans.map((plan) => plan.documentId),
7816
8070
  documentId: source.documentId,
7817
8071
  generation: snapshot.generation,
7818
8072
  revisionId: source.revisionId,
@@ -7901,16 +8155,17 @@ const recordDocumentActivity = Effect.fn("DocumentStore.Drizzle.recordActivity")
7901
8155
  targetId: source.documentId,
7902
8156
  targetKind: "document"
7903
8157
  };
7904
- if (plan.action !== "document.publish") values.id = source.revisionId;
8158
+ if (plan.action !== "document.publish" && plan.action !== "document.unpublish") values.id = source.revisionId;
7905
8159
  yield* database.insert(activityEntries).values(values);
7906
8160
  });
7907
8161
  const readSnapshot = Effect.fn("DocumentStore.Drizzle.readSnapshot")(function* (database, generation) {
7908
8162
  const rows = yield* database.select({
7909
8163
  authored: authoredRevision,
8164
+ created: createdRevision,
7910
8165
  document: documentsTable,
7911
8166
  head: documentHeads,
7912
8167
  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)));
8168
+ }).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
8169
  return yield* decodeDocumentSnapshot(rows, generation);
7915
8170
  });
7916
8171
  const readGeneration = Effect.fn("DocumentStore.Drizzle.readGeneration")(function* (database) {
@@ -7933,10 +8188,6 @@ function mapSynchronizationFailure(operation) {
7933
8188
  return documentPersistenceError(operation, cause);
7934
8189
  }));
7935
8190
  }
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
8191
  const rejectDeletingMedia = Effect.fn("DocumentStore.Drizzle.rejectDeletingMedia")(function* (transaction, references) {
7941
8192
  const keys = [...new Set(references.map((reference) => reference.objectKey))];
7942
8193
  if (keys.length === 0) return;
@@ -9078,7 +9329,7 @@ function postgresLayer(options) {
9078
9329
  //#region src/runtime/installation.ts
9079
9330
  /** Composes the private providers selected by the Nuxt product adapter. */
9080
9331
  const installCms = Effect.fn("NuxtCmsInstallation.install")(function* (options) {
9081
- const crypto = NodeCrypto.layer;
9332
+ const crypto = NodeCrypto$1.layer;
9082
9333
  const postgres = postgresLayer({ url: Redacted.make(options.databaseUrl) });
9083
9334
  const identity = Layer.unwrap(Effect.acquireRelease(Effect.sync(() => new Pool({ connectionString: options.databaseUrl })), (pool) => Effect.promise(() => pool.end()).pipe(Effect.asVoid)).pipe(Effect.map((pool) => betterAuthLayer({
9084
9335
  baseURL: options.baseUrl,
@@ -9088,6 +9339,7 @@ const installCms = Effect.fn("NuxtCmsInstallation.install")(function* (options)
9088
9339
  const core = Layer.mergeAll(crypto, postgres, identity);
9089
9340
  return yield* installCms$1({
9090
9341
  baseUrl: options.baseUrl,
9342
+ authoring: options.authoring,
9091
9343
  bootstrap: options.bootstrap,
9092
9344
  cleanup: options.cleanup.enabled ? {
9093
9345
  initialDelayMillis: options.cleanup.initialDelayMillis,
@@ -9125,4 +9377,4 @@ function toS3MediaObjectStoreOptions(options) {
9125
9377
 
9126
9378
  //#endregion
9127
9379
  export { toS3MediaObjectStoreOptions as n, installCms as t };
9128
- //# sourceMappingURL=installation-BNKaVjni.js.map
9380
+ //# sourceMappingURL=installation-DeuBiFRE.js.map