@phtngyn/cms 0.2.0-beta.3 → 0.2.0-beta.5

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 (121) hide show
  1. package/README.md +31 -13
  2. package/package.json +7 -7
  3. package/src/ApplicationTypes.d.ts +3 -3
  4. package/src/Authoring.d.ts +11 -3
  5. package/src/{Configuration-BU-UyGoH.js → Configuration-JQ1qyaU0.js} +347 -189
  6. package/src/Configuration-JQ1qyaU0.js.map +1 -0
  7. package/src/Configuration.d.ts +1 -1
  8. package/src/Content.d.ts +7 -6
  9. package/src/Content.js +106 -47
  10. package/src/Content.js.map +1 -1
  11. package/src/ContentTransfer-BxwswWn3.js +86 -0
  12. package/src/ContentTransfer-BxwswWn3.js.map +1 -0
  13. package/src/{ContentTransfer-CRQet3QJ.d.ts → ContentTransfer-Z9FHyLez.d.ts} +33 -9
  14. package/src/{DocumentEngine-ClhFXW9M.js → DocumentEngine-j6Vi7nQo.js} +279 -178
  15. package/src/DocumentEngine-j6Vi7nQo.js.map +1 -0
  16. package/src/DocumentModel.d.ts +9 -2
  17. package/src/DocumentModel.js +17 -4
  18. package/src/DocumentModel.js.map +1 -1
  19. package/src/{Forms-DoB56hfe.js → Forms-CX-hwmJF.js} +18 -11
  20. package/src/Forms-CX-hwmJF.js.map +1 -0
  21. package/src/{Forms-BbkE7QfC.js → Forms-CfNPTiCr.js} +2 -2
  22. package/src/{Forms-BbkE7QfC.js.map → Forms-CfNPTiCr.js.map} +1 -1
  23. package/src/Media-j1bTritO.js +112 -0
  24. package/src/Media-j1bTritO.js.map +1 -0
  25. package/src/Module.js +0 -4
  26. package/src/Module.js.map +1 -1
  27. package/src/References-CCPDAPve.js +45 -0
  28. package/src/References-CCPDAPve.js.map +1 -0
  29. package/src/{S3MediaObjects-CR3p1WTg.js → S3MediaObjects-BRuPQbxl.js} +454 -516
  30. package/src/S3MediaObjects-BRuPQbxl.js.map +1 -0
  31. package/src/cli/content.js +6 -5
  32. package/src/cli/content.js.map +1 -1
  33. package/src/cli.js +16 -3
  34. package/src/cli.js.map +1 -1
  35. package/src/{connection-D5S9Iph-.js → connection-BP80q4d-.js} +2 -2
  36. package/src/{connection-D5S9Iph-.js.map → connection-BP80q4d-.js.map} +1 -1
  37. package/src/{dev-lifecycle-D3f9-upa.js → dev-lifecycle-DdBluSiR.js} +26 -6
  38. package/src/dev-lifecycle-DdBluSiR.js.map +1 -0
  39. package/src/{installation-fZtNxtoW.js → installation-BoG8E3nJ.js} +7982 -7155
  40. package/src/installation-BoG8E3nJ.js.map +1 -0
  41. package/src/internal/authoring-catalog.js +2 -2
  42. package/src/internal/authoring-compiler.js +35 -9
  43. package/src/internal/authoring-compiler.js.map +1 -1
  44. package/src/internal/authoring-metadata.js +14 -5
  45. package/src/internal/authoring-metadata.js.map +1 -1
  46. package/src/internal/configuration.js +27 -3
  47. package/src/internal/configuration.js.map +1 -1
  48. package/src/internal/dev-lifecycle.js +2 -2
  49. package/src/internal/document-model.js +134 -43
  50. package/src/internal/document-model.js.map +1 -1
  51. package/src/internal/field-metadata.js +2 -1
  52. package/src/internal/field-metadata.js.map +1 -1
  53. package/src/internal/form-capabilities.js +1 -1
  54. package/src/{location-CPX7oj0o.js → location-CMNGO3lU.js} +2 -2
  55. package/src/{location-CPX7oj0o.js.map → location-CMNGO3lU.js.map} +1 -1
  56. package/src/{metadata-BEZPWh-3.js → metadata-UzZmjwxM.js} +2 -2
  57. package/src/{metadata-BEZPWh-3.js.map → metadata-UzZmjwxM.js.map} +1 -1
  58. package/src/{models-C9kF9JlQ.js → models-5j67LMqW.js} +4 -4
  59. package/src/{models-C9kF9JlQ.js.map → models-5j67LMqW.js.map} +1 -1
  60. package/src/{models-ChRxBChy.js → models-VmGmG19-.js} +42 -130
  61. package/src/models-VmGmG19-.js.map +1 -0
  62. package/src/runtime/delivery/DocumentRenderer.js +5 -1
  63. package/src/runtime/delivery/DocumentRenderer.js.map +1 -1
  64. package/src/runtime/delivery/context.js +1 -1
  65. package/src/runtime/delivery/document-page.js +1 -5
  66. package/src/runtime/delivery/document-page.js.map +1 -1
  67. package/src/runtime/delivery/plugin.js +34 -5
  68. package/src/runtime/delivery/plugin.js.map +1 -1
  69. package/src/runtime/delivery/preview.d.ts +1 -1
  70. package/src/runtime/delivery/types.d.ts +1 -1
  71. package/src/runtime/delivery/useCmsDocumentSeo.d.ts +2 -1
  72. package/src/runtime/delivery/useCmsDocumentSeo.js +5 -4
  73. package/src/runtime/delivery/useCmsDocumentSeo.js.map +1 -1
  74. package/src/runtime/forms/document.js +2 -2
  75. package/src/runtime/forms/useCmsForm.js +2 -2
  76. package/src/runtime/installation.js +1 -1
  77. package/src/runtime/plugin.js +1 -1
  78. package/src/runtime/preview/CmsStudioPreviewMarker.js +18 -4
  79. package/src/runtime/preview/CmsStudioPreviewMarker.js.map +1 -1
  80. package/src/runtime/preview/connection.js +1 -1
  81. package/src/runtime/preview/controller.js +65 -74
  82. package/src/runtime/preview/controller.js.map +1 -1
  83. package/src/runtime/preview/overlay.js +85 -28
  84. package/src/runtime/preview/overlay.js.map +1 -1
  85. package/src/runtime/preview/plugin.client.js +4 -4
  86. package/src/runtime/preview/preview.css +50 -36
  87. package/src/runtime/preview/protocol.d.ts +11 -5
  88. package/src/runtime/preview/protocol.js +17 -5
  89. package/src/runtime/preview/protocol.js.map +1 -1
  90. package/src/runtime/preview/regions.js +142 -0
  91. package/src/runtime/preview/regions.js.map +1 -0
  92. package/src/runtime/preview/store.js +1 -1
  93. package/src/runtime/sitemap.js +9 -10
  94. package/src/runtime/sitemap.js.map +1 -1
  95. package/src/{scalars-B2lgGUnj.js → scalars-CVZqT3L0.js} +2 -6
  96. package/src/scalars-CVZqT3L0.js.map +1 -0
  97. package/src/{store-CCsjHaZm.js → store-PKKB8xa2.js} +49 -66
  98. package/src/store-PKKB8xa2.js.map +1 -0
  99. package/src/{types-DutFRtaG.d.ts → types-DZ0IV-Ow.d.ts} +14 -3
  100. package/studio/CreateDocumentDialog-vQ8hDs2C.js +253 -0
  101. package/studio/{documentPlanning-CDb31mnl.js → documentPlanning-Bx3W9KkD.js} +12054 -12085
  102. package/studio/{editor-Bzlh3zXt.js → editor-P2rB7Csp.js} +11228 -11927
  103. package/studio/host.css +1 -1
  104. package/studio/host.js +23595 -24903
  105. package/studio/rolldown-runtime-Dy4uBu1J.js +11 -0
  106. package/src/Configuration-BU-UyGoH.js.map +0 -1
  107. package/src/ContentTransfer-BhF9G56s.js +0 -57
  108. package/src/ContentTransfer-BhF9G56s.js.map +0 -1
  109. package/src/DocumentEngine-ClhFXW9M.js.map +0 -1
  110. package/src/Forms-DoB56hfe.js.map +0 -1
  111. package/src/S3MediaObjects-CR3p1WTg.js.map +0 -1
  112. package/src/dev-lifecycle-D3f9-upa.js.map +0 -1
  113. package/src/installation-fZtNxtoW.js.map +0 -1
  114. package/src/models-ChRxBChy.js.map +0 -1
  115. package/src/runtime/preview/targets.js +0 -101
  116. package/src/runtime/preview/targets.js.map +0 -1
  117. package/src/scalars-B2lgGUnj.js.map +0 -1
  118. package/src/store-CCsjHaZm.js.map +0 -1
  119. package/studio/CreateDocumentDialog-DJ8c0WZb.js +0 -256
  120. package/studio/dom-to-image-more.min-D6jGGdrW.js +0 -877
  121. package/studio/rolldown-runtime-B0aSnxlc.js +0 -20
@@ -1,12 +1,13 @@
1
- import { c as RevisionId, d as SourcePath, f as nonNegativeInt, i as DocumentVersion, n as DocumentHistoryCursor, o as PublicPath, r as DocumentId, s as RelativeDocumentPath, t as DocumentGeneration, u as Slug } from "./scalars-B2lgGUnj.js";
2
- import { $ as StudioActor, B as SeoMediaUnavailable, C as PublishedDocumentLookup, D as RemoveDocumentInput, E as PublishedDocumentSurroundQuery, Et as RequestId, F as SaveSeoInput, G as SeoUrl, I as SeoCanonicalOrigin, J as AccessDenied, K as SeoVersionConflict, L as SeoCapabilities, N as GlobalSeoSettings, O as RestoreDocumentInput, P as PublishSeoInput, Q as Role, R as SeoConfiguration, S as PublishDocumentInput, T as PublishedDocumentQuery, U as SeoSnapshot, V as SeoMutationResult, W as SeoState, X as DOCUMENT_ACTIONS, Y as AuthenticationRequired, Z as MEDIA_ACTIONS, _ as DocumentSurround, _t as MediaReference, at as MediaCleanupReport, bt as MediaTooLarge, c as DocumentIssue, ct as MediaFolder, d as DocumentNavigationQuery, dt as MediaInvalid, et as StudioEntryDenied, f as DocumentQueryScalar, g as DocumentSummary, gt as MediaQuery, ht as MediaPage, i as CreateDocumentInput, it as MediaByteSize, j as UpdateDocumentInput, k as UndeleteDocumentInput, l as DocumentMutationResult, lt as MediaId, m as DocumentSource, mt as MediaObjectKey, n as CmsMarkdownDocument, nt as ImageMimeType, o as DeletedDocument, ot as MediaConflict, p as DocumentRevisionAction, pt as MediaNotFound, q as SitemapEntry, r as CompiledDocument, rt as MediaAsset, s as DocumentBrowseQuery, u as DocumentNavigationItem, ut as MediaIntegrityReport, v as DocumentValidationResult, vt as MediaReferenced, w as PublishedDocumentPage, x as MoveDocumentInput, xt as MediaUpload, y as DuplicateDocumentInput, z as SeoConfirmationRequired } from "./models-ChRxBChy.js";
3
- import { i as ContentSnapshot, n as ContentApplyResult, o as ContentTransferConflict, r as ContentPlan, s as ContentTransferInput, t as ContentApplyInput } from "./ContentTransfer-BhF9G56s.js";
1
+ import { i as DocumentVersion, l as Slug, n as DocumentHistoryCursor, o as PublicPath, r as DocumentId, s as RevisionId, t as DocumentGeneration, u as nonNegativeInt } from "./scalars-CVZqT3L0.js";
2
+ import { $ as MEDIA_ACTIONS, A as RestoreDocumentInput, B as SeoConfiguration, C as MoveDocumentInput, D as PublishedDocumentQuery, E as PublishedDocumentPage, F as GlobalSeoSettings, G as SeoSnapshot, H as SeoMediaUnavailable, I as PublishSeoInput, J as SeoVersionConflict, K as SeoState, L as SaveSeoInput, N as UpdateDocumentInput, O as PublishedDocumentSurroundQuery, Q as DOCUMENT_ACTIONS, R as SeoCanonicalOrigin, T as PublishedDocumentLookup, U as SeoMutationResult, V as SeoConfirmationRequired, X as AccessDenied, Y as SitemapEntry, Z as AuthenticationRequired, b as DocumentValidationResult, d as DocumentNavigationItem, et as Role, f as DocumentNavigationQuery, g as DocumentSource, h as DocumentRevisionAction, i as CreateDocumentInput, j as UndeleteDocumentInput, k as RemoveDocumentInput, l as DocumentIssue, m as DocumentQueryScalar, n as CmsMarkdownDocument, nt as StudioEntryDenied, o as DeletedDocument, p as DocumentPlacement, q as SeoUrl, r as CompiledDocument, s as DocumentBrowseQuery, st as RequestId, tt as StudioActor, u as DocumentMutationResult, v as DocumentSummary, w as PublishDocumentInput, x as DuplicateDocumentInput, y as DocumentSurround, z as SeoCapabilities } from "./models-VmGmG19-.js";
3
+ import { _ as MediaReferenced, a as MediaConflict, b as MediaUpload, c as MediaId, f as MediaNotFound, g as MediaReference, h as MediaQuery, i as MediaCleanupReport, l as MediaIntegrityReport, m as MediaPage, n as MediaAsset, p as MediaObjectKey, r as MediaByteSize, s as MediaFolder, t as ImageMimeType, u as MediaInvalid, y as MediaTooLarge } from "./Media-j1bTritO.js";
4
+ import { a as ContentSnapshot, c as ContentTransferConflict, d as DocumentNotFound, f as DocumentRouteConflict, h as PublishedDocumentNotFound, i as ContentPlan, l as ContentTransferInput, m as DocumentVersionConflict, n as ContentApplyResult, p as DocumentValidationFailed, t as ContentApplyInput, u as DocumentMediaUnavailable } from "./ContentTransfer-BxwswWn3.js";
4
5
  import { u as STUDIO_API_PATH } from "./HttpPaths-c9nLZ9H2.js";
5
- import { c as FormSubmission, o as FormResult } from "./Forms-BbkE7QfC.js";
6
- import { a as encodeCmsEditorComponentAttributes, d as createCmsComarkRenderData, m as toComarkDocument, p as mutableJsonObject, s as projectCmsEditorComponentAttributes } from "./Forms-DoB56hfe.js";
7
- import { i as withoutEditorMetadata } from "./metadata-BEZPWh-3.js";
8
- import { d as EditorRenderFailed, i as MediaConfiguration, r as DocumentConfiguration } from "./Configuration-BU-UyGoH.js";
9
- import { a as normalizeDocumentRelativePath, c as resolveDocumentAddress, i as localizeDocumentPath, n as referenceTarget, o as normalizePublicPath, r as DocumentCompilationFailed, s as parseDocumentAddress, t as createDocumentEngine } from "./DocumentEngine-ClhFXW9M.js";
6
+ import { c as FormSubmission, o as FormResult } from "./Forms-CfNPTiCr.js";
7
+ import { m as toComarkDocument } from "./Forms-CX-hwmJF.js";
8
+ import { i as withoutEditorMetadata } from "./metadata-UzZmjwxM.js";
9
+ import { f as EditorRenderFailed, i as MediaConfiguration, r as DocumentConfiguration } from "./Configuration-JQ1qyaU0.js";
10
+ import { n as DocumentCompilationFailed, t as createDocumentEngine } from "./DocumentEngine-j6Vi7nQo.js";
10
11
  import { Context, Crypto, Data, DateTime, Effect, Encoding, Layer, Option, Predicate, PubSub, Redacted, Ref, Result, Schedule, Schema, Semaphore, Stream, flow } from "effect";
11
12
  import { NodeStream } from "@effect/platform-node-shared";
12
13
  import { Multipart } from "effect/unstable/http";
@@ -15,25 +16,6 @@ import { renderMarkdown } from "comark/render";
15
16
  import { diffArrays } from "diff";
16
17
  import { CopyObjectCommand, CreateBucketCommand, DeleteObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
17
18
 
18
- //#region ../protocol/src/internal/documents/errors.ts
19
- var DocumentNotFound = class extends Schema.TaggedError()("Document.NotFound", {
20
- documentId: DocumentId,
21
- revisionId: Schema.optionalKey(RevisionId)
22
- }) {};
23
- var DocumentPathConflict = class extends Schema.TaggedError()("Document.PathConflict", { sourcePath: SourcePath }) {};
24
- var DocumentVersionConflict = class extends Schema.TaggedError()("Document.VersionConflict", {
25
- documentId: DocumentId,
26
- expectedVersion: DocumentVersion,
27
- actualVersion: DocumentVersion
28
- }) {};
29
- var DocumentMediaUnavailable = class extends Schema.TaggedError()("Document.MediaUnavailable", {
30
- documentId: DocumentId,
31
- mediaKeys: Schema.Array(MediaObjectKey).check(Schema.isMinLength(1))
32
- }) {};
33
- var DocumentValidationFailed = class extends Schema.TaggedError()("Document.ValidationFailed", { issues: Schema.Array(DocumentIssue) }) {};
34
- var PublishedDocumentNotFound = class extends Schema.TaggedError()("PublishedDocument.NotFound", { lookup: PublishedDocumentLookup }) {};
35
-
36
- //#endregion
37
19
  //#region ../protocol/src/Activity.ts
38
20
  /**
39
21
  * Immutable completed Activity facts. Pages are ordered newest-first; cursors are opaque,
@@ -139,8 +121,8 @@ const DocumentRevisionSummary = Schema.Struct({
139
121
  activity: ActivityEntry,
140
122
  createdAt: Schema.DateTimeUtcFromString,
141
123
  documentId: DocumentId,
124
+ placement: DocumentPlacement,
142
125
  revisionId: RevisionId,
143
- sourcePath: SourcePath,
144
126
  version: DocumentVersion
145
127
  }).annotate({ identifier: "DocumentRevisionSummary" });
146
128
  const DocumentRevision = Schema.Struct({
@@ -451,7 +433,7 @@ const ValidateDocumentPayload = Schema.Struct({
451
433
  const UpdateDocumentPayload = ValidateDocumentPayload.pipe(Schema.fieldsAssign({ reason: UpdateDocumentInput.fields.reason }));
452
434
  const MoveDocumentPayload = Schema.Struct({
453
435
  expectedVersion: MoveDocumentInput.fields.expectedVersion,
454
- location: MoveDocumentInput.fields.location,
436
+ placement: MoveDocumentInput.fields.placement,
455
437
  reason: MoveDocumentInput.fields.reason
456
438
  });
457
439
  const PublishDocumentPayload = Schema.Struct({
@@ -481,7 +463,7 @@ const NotFound = DocumentNotFound.pipe(HttpApiSchema.status(404));
481
463
  const VersionConflict = DocumentVersionConflict.pipe(HttpApiSchema.status(409));
482
464
  const Conflict = Schema.Union([
483
465
  DocumentMediaUnavailable,
484
- DocumentPathConflict,
466
+ DocumentRouteConflict,
485
467
  DocumentVersionConflict
486
468
  ]).pipe(HttpApiSchema.status(409));
487
469
  const ValidationFailed = DocumentValidationFailed.pipe(HttpApiSchema.status(422));
@@ -795,6 +777,14 @@ const StudioCollaborationApi = HttpApiGroup.make("collaboration").add(HttpApiEnd
795
777
  Forbidden,
796
778
  InternalError
797
779
  ]
780
+ }), HttpApiEndpoint.post("discard", "/:documentId/discard", {
781
+ params: { documentId: DocumentId },
782
+ success: CollaborationTicket,
783
+ error: [
784
+ NotFound,
785
+ Forbidden,
786
+ InternalError
787
+ ]
798
788
  })).middleware(StudioAuthentication).prefix("/collaboration");
799
789
  const FormsApi = HttpApiGroup.make("forms").add(HttpApiEndpoint.post("submit", "/submit", {
800
790
  payload: FormSubmission,
@@ -873,80 +863,18 @@ const renderEditorMarkdown = Effect.fn("EditorDocument.renderMarkdown")((documen
873
863
  });
874
864
  });
875
865
 
876
- //#endregion
877
- //#region ../document/src/internal/document-engine/relocate-links.ts
878
- /** Rewrites only semantic page links, preserving bindings and unrelated content. */
879
- function rewriteDocumentRoutes(document, destinations, components = []) {
880
- let changed = false;
881
- function link(value) {
882
- const target = referenceTarget("a", /* @__PURE__ */ new Map([["href", value]]), document.path, void 0);
883
- if (target?.kind !== "document" || !Predicate.isString(value)) return value;
884
- const destination = destinations.get(target.path);
885
- const sourceMoved = destinations.has(document.path);
886
- if (!destination && (!sourceMoved || value.startsWith("/"))) return value;
887
- const suffix = value.match(/[?#].*$/u)?.[0] ?? "";
888
- const next = `${destination ?? target.path}${suffix}`;
889
- if (next !== value) changed = true;
890
- return next;
891
- }
892
- function properties(fields, values) {
893
- const result = mutableJsonObject(values);
894
- for (const field of fields) {
895
- const value = values[field.name] === void 0 ? field.defaultValue : values[field.name];
896
- if (field.input === "link") {
897
- const next = link(value);
898
- if (next !== void 0 && next !== value) result[field.name] = next;
899
- } else if (field.fields) {
900
- 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;
901
- if (next !== void 0 && JSON.stringify(next) !== JSON.stringify(value)) result[field.name] = next;
902
- }
903
- }
904
- return result;
905
- }
906
- function visit(node, renderData) {
907
- if (Predicate.isString(node) || node[0] === null) return node;
908
- const [tag, attributes, ...children] = node;
909
- const projection = projectCmsEditorComponentAttributes(attributes, renderData);
910
- let props = properties(components.find((component) => component.name.toLowerCase() === tag.toLowerCase())?.authoring.props ?? [], projection.props);
911
- if (tag === "a") {
912
- const href = link(projection.props.href);
913
- if (href !== void 0) props = Object.assign({}, props, { href });
914
- }
915
- return [
916
- tag,
917
- JSON.stringify(props) === JSON.stringify(projection.props) ? attributes : encodeCmsEditorComponentAttributes(props, projection.bindings),
918
- ...children.map((child) => visit(child, projection.renderData))
919
- ];
920
- }
921
- const nodes = document.body.nodes.map((node) => visit(node, createCmsComarkRenderData(document.body)));
922
- return changed ? Object.assign({}, document.body, { nodes }) : void 0;
923
- }
924
-
925
866
  //#endregion
926
867
  //#region ../document/src/DocumentPlanning.ts
927
868
  function planDocumentCreation(configuration, input) {
928
869
  return Result.gen(function* () {
929
870
  const collection = configuration.types.find((candidate) => candidate.collection === input.collection);
930
- if (!collection) return yield* invalid$1(`Document collection "${input.collection}" does not exist.`, "collection");
871
+ if (!collection) return yield* invalid(`Document collection "${input.collection}" does not exist.`, "collection");
931
872
  const title = input.title.trim();
932
- if (!title) return yield* invalid$1("A document title is required.", "title");
873
+ if (!title) return yield* invalid("A document title is required.", "title");
933
874
  const locale = yield* resolveLocale(configuration, input.locale);
934
- const address = yield* parseDocumentAddress(collection.addressPattern).pipe(Result.mapError(addressInvalid));
935
- const relativePath = yield* resolveRelativePath(collection, input.location);
936
- const data = Object.fromEntries(address.tokens.flatMap((token) => {
937
- if (token.name === "slug" && relativePath) return [[token.name, relativePath.split("/").at(-1) ?? relativePath]];
938
- return [];
939
- }));
940
- const relativeKey = yield* normalizePublicPath(relativePath).pipe(Result.mapError(addressInvalid));
941
- const key = yield* resolveDocumentAddress(address, relativeKey, data).pipe(Result.mapError(addressInvalid));
942
- const sourcePath = documentSourcePath(collection, relativePath, locale);
943
- const path = localizeDocumentPath(key, locale, configuration.locales);
944
875
  return Object.assign({
945
876
  collection,
946
- data,
947
- key,
948
- path,
949
- sourcePath,
877
+ placement: input.placement,
950
878
  title
951
879
  }, locale === void 0 ? void 0 : { locale });
952
880
  });
@@ -966,69 +894,25 @@ const createInitialDocumentSource = Effect.fn("DocumentPlanning.createInitialSou
966
894
  markdown: yield* renderEditorMarkdown(body)
967
895
  };
968
896
  });
969
- function planDocumentMove(configuration, document, location) {
970
- return Result.gen(function* () {
971
- const collection = configuration.types.find((candidate) => candidate.collection === document.collection);
972
- if (!collection) return yield* invalid$1(`Document collection "${document.collection}" does not exist.`, "documentId");
973
- return documentSourcePath(collection, yield* resolveRelativePath(collection, location), document.locale);
974
- });
975
- }
976
- function documentRelativePath(collection, entry) {
977
- let sourcePath = entry.sourcePath;
978
- if (entry.document.locale && sourcePath.startsWith(`${entry.document.locale}/`)) sourcePath = sourcePath.slice(entry.document.locale.length + 1);
979
- if (collection.directory !== "." && sourcePath.startsWith(`${collection.directory}/`)) sourcePath = sourcePath.slice(collection.directory.length + 1);
980
- sourcePath = sourcePath.replace(/\.(?:md|mdc)$/iu, "");
981
- return sourcePath.toLocaleLowerCase() === "index" ? void 0 : sourcePath.replace(/\/index$/iu, "");
982
- }
983
- function documentSourcePath(collection, relativePath, locale) {
984
- const sourceName = relativePath ? `${relativePath}.md` : "index.md";
985
- return SourcePath.make([
986
- locale,
987
- collection.directory === "." ? void 0 : collection.directory,
988
- sourceName
989
- ].filter((value) => value !== void 0).join("/"));
990
- }
991
897
  function initialFrontmatter(plan) {
992
- let frontmatter = {
993
- ...plan.data,
994
- title: plan.title
995
- };
996
- 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);
997
- return frontmatter;
998
- }
999
- function nestedValue$1(value, path) {
1000
- return path.split(".").reduce((current, segment) => isJsonObject(current) ? current[segment] : void 0, value);
1001
- }
1002
- function setNestedValue(target, path, value) {
1003
- const [segment, ...rest] = path;
1004
- if (!segment) return target;
1005
- if (rest.length === 0) return Object.assign({}, target, { [segment]: value });
1006
- const current = target[segment];
1007
- return Object.assign({}, target, { [segment]: setNestedValue(isJsonObject(current) ? current : {}, rest, value) });
898
+ return Object.assign(structuredDefaults(plan.collection.authoringFields), { title: plan.title });
899
+ }
900
+ function structuredDefaults(fields) {
901
+ return Object.fromEntries(fields.flatMap((field) => {
902
+ if (field.defaultValue !== void 0) return [[field.name, structuredClone(field.defaultValue)]];
903
+ if (field.type !== "object") return [];
904
+ const nested = structuredDefaults(field.fields ?? []);
905
+ return Object.keys(nested).length === 0 ? [] : [[field.name, nested]];
906
+ }));
1008
907
  }
1009
- const isJsonObject = Schema.is(Schema.JsonObject);
1010
908
  function resolveLocale(configuration, requested) {
1011
909
  const locales = configuration.locales;
1012
- if (!locales) return requested === void 0 ? Result.succeed(void 0) : invalid$1(`Document locale "${requested}" is not configured.`, "locale");
910
+ if (!locales) return requested === void 0 ? Result.succeed(void 0) : invalid(`Document locale "${requested}" is not configured.`, "locale");
1013
911
  const locale = requested ?? locales.defaultLocale;
1014
- return locales.locales.includes(locale) ? Result.succeed(locale) : invalid$1(`Document locale "${locale}" does not exist.`, "locale");
1015
- }
1016
- function resolveRelativePath(collection, location) {
1017
- if (location.kind === "home") return collection.addressPattern === "/:path*" ? Result.succeed("") : invalid$1("Only a root catch-all collection can use the home location.", "location");
1018
- if (location.parent !== void 0 && !collection.hierarchy) return invalid$1(`Document collection "${collection.collection}" does not allow hierarchy.`, "location.parent");
1019
- return normalizeDocumentRelativePath([location.parent, location.slug].filter((value) => value !== void 0).join("/")).pipe(Result.mapError((error) => addressInvalid({
1020
- ...error,
1021
- field: "location"
1022
- })));
1023
- }
1024
- function addressInvalid(error) {
1025
- return new DocumentValidationFailed({ issues: [validationIssue(error.message, error.field)] });
912
+ return locales.locales.includes(locale) ? Result.succeed(locale) : invalid(`Document locale "${locale}" does not exist.`, "locale");
1026
913
  }
1027
- function invalid$1(message, field) {
1028
- return Result.fail(new DocumentValidationFailed({ issues: [validationIssue(message, field)] }));
1029
- }
1030
- function validationIssue(message, field) {
1031
- return field === void 0 ? {
914
+ function invalid(message, field) {
915
+ return Result.fail(new DocumentValidationFailed({ issues: [field === void 0 ? {
1032
916
  code: "document.invalid",
1033
917
  message,
1034
918
  severity: "error"
@@ -1037,20 +921,8 @@ function validationIssue(message, field) {
1037
921
  field,
1038
922
  message,
1039
923
  severity: "error"
1040
- };
924
+ }] }));
1041
925
  }
1042
- /** Updates route-owned frontmatter when relocating a page in a slug-addressed collection. */
1043
- const relocateDocumentMarkdown = Effect.fn("DocumentPlanning.relocateMarkdown")(function* (configuration, document, location, markdown) {
1044
- const collection = configuration.types.find((type) => type.collection === document.collection);
1045
- if (!collection || !collection.addressPattern.includes(":slug") || location.kind === "home") return markdown;
1046
- return yield* renderEditorMarkdown({
1047
- ...document.body,
1048
- frontmatter: {
1049
- ...document.body.frontmatter,
1050
- slug: location.slug
1051
- }
1052
- });
1053
- });
1054
926
 
1055
927
  //#endregion
1056
928
  //#region ../server/src/internal/documents/document-state.ts
@@ -1093,19 +965,19 @@ const compileDocumentState = Effect.fn("DocumentState.compile")(function* (engin
1093
965
  });
1094
966
  function authoredSources(snapshot) {
1095
967
  return snapshot.active.map((source) => ({
1096
- documentId: source.documentId,
1097
968
  draft: source.publishedVersion !== source.version,
969
+ identity: source.identity,
1098
970
  markdown: source.markdown,
1099
- sourcePath: source.sourcePath
971
+ placement: source.placement
1100
972
  }));
1101
973
  }
1102
974
  function publishedSources(snapshot) {
1103
975
  return snapshot.published.map((source) => ({
1104
- documentId: source.documentId,
1105
976
  draft: false,
977
+ identity: source.identity,
1106
978
  markdown: source.markdown,
1107
- publishedAt: source.editedAt,
1108
- sourcePath: source.sourcePath
979
+ placement: source.placement,
980
+ publishedAt: source.editedAt
1109
981
  }));
1110
982
  }
1111
983
  const compileValidCatalog = Effect.fn("DocumentState.compileValid")(function* (engine, sources, previous) {
@@ -1118,7 +990,7 @@ const compileValidCatalog = Effect.fn("DocumentState.compileValid")(function* (e
1118
990
  if (errors.length > 0) return yield* new DocumentValidationFailed({ issues: errors });
1119
991
  return result.success;
1120
992
  });
1121
- const validateDocumentSources = Effect.fn("DocumentState.validate")(function* (engine, sources, sourcePath, previous) {
993
+ const validateDocumentSources = Effect.fn("DocumentState.validate")(function* (engine, sources, documentId, previous) {
1122
994
  const result = yield* Effect.result(engine.compileAll({
1123
995
  previous,
1124
996
  sources
@@ -1128,7 +1000,7 @@ const validateDocumentSources = Effect.fn("DocumentState.validate")(function* (e
1128
1000
  valid: false
1129
1001
  });
1130
1002
  const catalog = result.success;
1131
- const document = catalog.documents.find((candidate) => candidate.source.path === sourcePath);
1003
+ const document = catalog.documents.find((candidate) => candidate.id === documentId);
1132
1004
  const issues = catalog.issues;
1133
1005
  const validation = {
1134
1006
  issues,
@@ -1144,8 +1016,7 @@ function authoringEntries(state, query) {
1144
1016
  if (query.locale !== void 0 && document.locale !== query.locale) return [];
1145
1017
  return [DocumentSummary.make({
1146
1018
  ...browseMetadata(state, source),
1147
- document,
1148
- sourcePath: source.sourcePath,
1019
+ document: summaryDocument(document),
1149
1020
  version: source.version
1150
1021
  })];
1151
1022
  });
@@ -1156,8 +1027,9 @@ function deletedEntries(state) {
1156
1027
  ...source.path ? { path: source.path } : void 0,
1157
1028
  ...source.trashBatchId ? { trashBatchId: source.trashBatchId } : void 0,
1158
1029
  documentId: source.documentId,
1030
+ identity: source.identity,
1031
+ placement: source.placement,
1159
1032
  deletedAt: source.deletedAt,
1160
- sourcePath: source.sourcePath,
1161
1033
  version: source.version
1162
1034
  }));
1163
1035
  }
@@ -1166,18 +1038,32 @@ function entryById(state, documentId) {
1166
1038
  const document = state.authoredById.get(documentId);
1167
1039
  return source && document ? DocumentSummary.make({
1168
1040
  ...browseMetadata(state, source),
1169
- document,
1170
- sourcePath: source.sourcePath,
1041
+ document: summaryDocument(document),
1171
1042
  version: source.version
1172
1043
  }) : void 0;
1173
1044
  }
1045
+ function summaryDocument(document) {
1046
+ return {
1047
+ collection: document.collection,
1048
+ draft: document.draft,
1049
+ id: document.id,
1050
+ identity: document.identity,
1051
+ issues: document.issues,
1052
+ key: document.key,
1053
+ ...document.locale ? { locale: document.locale } : void 0,
1054
+ path: document.path,
1055
+ placement: document.placement,
1056
+ searchText: document.analysis.searchText,
1057
+ title: document.title
1058
+ };
1059
+ }
1174
1060
  function compilationIssues(error) {
1175
1061
  if (isDocumentCompilationFailed(error)) return error.issues;
1176
1062
  return [{
1177
1063
  code: "markdown.parse",
1178
1064
  message: errorMessage(error.cause),
1179
1065
  severity: "error",
1180
- sourcePath: error.sourcePath
1066
+ documentId: error.documentId
1181
1067
  }];
1182
1068
  }
1183
1069
  function errorMessage(cause) {
@@ -1196,14 +1082,27 @@ function browseMetadata(state, source) {
1196
1082
 
1197
1083
  //#endregion
1198
1084
  //#region ../server/src/internal/documents/document-tree-mutation.ts
1199
- /** Selects by route boundaries, never by title or a raw prefix such as /about matching /about-us. */
1200
- function subtreeIds(state, documentId) {
1201
- const parent = state.authoredById.get(documentId);
1202
- if (!parent) return [documentId];
1203
- 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);
1085
+ function subtreeIds(state, documentId, catalog = "authored") {
1086
+ const documents = catalog === "authored" ? state.authored.documents : state.published.documents;
1087
+ const children = /* @__PURE__ */ new Map();
1088
+ for (const document of documents) {
1089
+ if (document.placement.kind === "home") continue;
1090
+ const existing = children.get(document.placement.parentDocumentId) ?? [];
1091
+ existing.push(document.id);
1092
+ children.set(document.placement.parentDocumentId, existing);
1093
+ }
1094
+ const result = [];
1095
+ const pending = [documentId];
1096
+ while (pending.length > 0) {
1097
+ const current = pending.shift();
1098
+ if (!current || result.includes(current)) continue;
1099
+ result.push(current);
1100
+ pending.push(...children.get(current) ?? []);
1101
+ }
1102
+ return result;
1204
1103
  }
1205
1104
  const prepareTreeMutation = Effect.fn("Documents.prepareTreeMutation")(function* (dependencies, state, command) {
1206
- const { configuration, engine } = dependencies;
1105
+ const { engine } = dependencies;
1207
1106
  const input = command.input;
1208
1107
  const root = command.action === "document.undelete" ? state.snapshot.deleted.find((source) => source.documentId === input.documentId) : state.activeById.get(input.documentId);
1209
1108
  if (!root) return yield* new DocumentNotFound({ documentId: input.documentId });
@@ -1212,152 +1111,121 @@ const prepareTreeMutation = Effect.fn("Documents.prepareTreeMutation")(function*
1212
1111
  actualVersion: root.version,
1213
1112
  expectedVersion: input.expectedVersion
1214
1113
  });
1215
- let sources;
1216
- if (command.action === "document.undelete") {
1217
- const deleted = state.snapshot.deleted.find((source) => source.documentId === root.documentId);
1218
- sources = state.snapshot.deleted.filter((source) => source.documentId === root.documentId || deleted && deleted.trashBatchId && source.trashBatchId === deleted.trashBatchId && deleted.path !== void 0 && source.path?.startsWith(`${deleted.path}/`));
1219
- } else {
1220
- const ids = new Set(command.action === "document.duplicate" && !command.input.includeChildren ? [root.documentId] : subtreeIds(state, root.documentId));
1221
- sources = state.snapshot.active.filter((source) => ids.has(source.documentId));
1222
- }
1223
- sources = [root, ...sources.filter((source) => source.documentId !== root.documentId)];
1114
+ const sources = affectedSources(state, root, command);
1115
+ const authored = authoredSources(state.snapshot);
1116
+ const published = publishedSources(state.snapshot);
1224
1117
  const plans = [];
1225
- const parent = state.authoredById.get(root.documentId);
1226
- for (const source of sources) {
1227
- if (command.action === "document.remove" || command.action === "document.undelete") {
1228
- const page = state.authoredById.get(source.documentId);
1118
+ let nextAuthored = [...authored];
1119
+ let nextPublished = [...published];
1120
+ switch (command.action) {
1121
+ case "document.move": {
1122
+ const next = compilationSource$2(root, command.input.placement);
1123
+ nextAuthored = replaceSource$1(nextAuthored, next);
1229
1124
  plans.push({
1230
- ...command.action === "document.remove" ? {
1231
- trashBatchId: root.version,
1232
- title: page?.title,
1233
- path: page?.path
1234
- } : void 0,
1235
1125
  action: command.action,
1236
- documentId: source.documentId,
1237
- expectedVersion: source.version,
1238
- markdown: source.markdown,
1239
- sourcePath: source.sourcePath
1126
+ documentId: root.documentId,
1127
+ expectedVersion: root.version,
1128
+ markdown: root.markdown,
1129
+ placement: command.input.placement,
1130
+ publishedVersion: root.publishedVersion
1240
1131
  });
1241
- continue;
1132
+ break;
1242
1133
  }
1243
- const page = state.authoredById.get(source.documentId);
1244
- const collection = configuration.types.find((type) => type.collection === page?.collection);
1245
- if (!page || !parent || !collection) return yield* new DocumentNotFound({ documentId: source.documentId });
1246
- if (command.action === "document.duplicate" && !collection.canCreate) return yield* invalid("This collection does not allow new pages.");
1247
- const destination = command.input.location;
1248
- if (parent.key === "/" && command.action === "document.move") return yield* invalid("The home page cannot be moved.");
1249
- const rootRelative = documentRelativePath(collection, {
1250
- document: parent,
1251
- sourcePath: root.sourcePath
1252
- }) ?? "";
1253
- const nextRelative = destination.kind === "home" ? "" : [destination.parent, destination.slug].filter(Boolean).join("/");
1254
- if (!(command.action === "document.duplicate" && command.input.locale !== void 0 && command.input.locale !== parent.locale) && rootRelative && (nextRelative === rootRelative || nextRelative.startsWith(`${rootRelative}/`))) return yield* invalid("Choose a destination outside this page and its subpages.");
1255
- const relative = documentRelativePath(collection, {
1256
- document: page,
1257
- sourcePath: source.sourcePath
1258
- }) ?? "";
1259
- const segments = `${nextRelative}${source.documentId === root.documentId ? "" : relative.slice(rootRelative.length)}`.split("/").filter(Boolean);
1260
- const slug = segments.pop();
1261
- const location = slug ? Object.assign({
1262
- kind: "entry",
1263
- slug: yield* decodeSlug(slug)
1264
- }, segments.length ? { parent: yield* decodeParent(segments.join("/")) } : void 0) : { kind: "home" };
1265
- const destinationPage = command.action === "document.duplicate" && command.input.locale ? Object.assign({}, page, { locale: command.input.locale }) : page;
1266
- const sourcePath = yield* Effect.fromResult(planDocumentMove(configuration, destinationPage, location));
1267
- const markdown = yield* relocateDocumentMarkdown(configuration, destinationPage, location, source.markdown).pipe(Effect.mapError(() => invalid("The page could not be copied to its new URL.")));
1268
- if (command.action === "document.duplicate") {
1269
- const documentId = yield* command.nextDocumentId;
1270
- plans.push({
1271
- action: "document.create",
1272
- documentId,
1273
- markdown,
1274
- sourcePath
1275
- });
1276
- } else plans.push({
1277
- action: "document.move",
1278
- documentId: source.documentId,
1279
- expectedVersion: source.version,
1280
- markdown,
1281
- sourcePath,
1282
- publishedVersion: source.publishedVersion
1283
- });
1284
- }
1285
- const changed = new Set(plans.map((plan) => plan.documentId));
1286
- const nextSources = authoredSources(state.snapshot).filter((source) => !changed.has(source.documentId));
1287
- if (command.action !== "document.remove") for (const plan of plans) {
1288
- if (nextSources.some((source) => source.sourcePath === plan.sourcePath)) return yield* new DocumentPathConflict({ sourcePath: plan.sourcePath });
1289
- nextSources.push({
1290
- documentId: plan.documentId,
1291
- draft: true,
1292
- markdown: plan.markdown,
1293
- sourcePath: plan.sourcePath
1294
- });
1295
- }
1296
- if (command.action === "document.move" || command.action === "document.duplicate") {
1297
- const preliminary = yield* engine.compileAll({
1298
- sources: nextSources,
1299
- previous: state.authored
1300
- });
1301
- const destinations = /* @__PURE__ */ new Map();
1302
- for (let index = 0; index < sources.length; index++) {
1303
- const original = sources[index];
1304
- const plan = plans[index];
1305
- const oldPage = original && state.authoredById.get(original.documentId);
1306
- const newPage = plan && preliminary.documents.find((document) => document.id === plan.documentId);
1307
- if (oldPage && newPage) destinations.set(oldPage.path, newPage.path);
1134
+ case "document.duplicate": {
1135
+ const ids = /* @__PURE__ */ new Map();
1136
+ for (const source of sources) ids.set(source.documentId, yield* command.nextDocumentId);
1137
+ for (const source of sources) {
1138
+ const documentId = ids.get(source.documentId);
1139
+ if (!documentId) return yield* Effect.die(/* @__PURE__ */ new Error("Duplicate document identity was not allocated."));
1140
+ const placement = duplicatePlacement(source, root, command.input.placement, ids);
1141
+ const identity = {
1142
+ ...source.identity,
1143
+ documentId,
1144
+ ...command.input.locale === void 0 ? void 0 : { locale: command.input.locale }
1145
+ };
1146
+ const next = {
1147
+ draft: true,
1148
+ identity,
1149
+ markdown: source.markdown,
1150
+ placement
1151
+ };
1152
+ nextAuthored.push(next);
1153
+ plans.push({
1154
+ action: "document.create",
1155
+ identity,
1156
+ markdown: source.markdown,
1157
+ placement
1158
+ });
1159
+ }
1160
+ break;
1308
1161
  }
1309
- const originals = command.action === "document.move" ? state.snapshot.active : sources;
1310
- for (const source of originals) {
1311
- const page = state.authoredById.get(source.documentId);
1312
- if (!page) continue;
1313
- const body = rewriteDocumentRoutes(page, destinations, dependencies.components);
1314
- if (!body) continue;
1315
- const index = command.action === "document.duplicate" ? sources.findIndex((item) => item.documentId === source.documentId) : plans.findIndex((plan) => plan.documentId === source.documentId);
1316
- const existing = index >= 0 ? plans[index] : void 0;
1317
- const projected = existing && preliminary.documents.find((document) => document.id === existing.documentId);
1318
- const markdown = yield* renderEditorMarkdown(Object.assign({}, body, { frontmatter: projected?.body.frontmatter ?? body.frontmatter })).pipe(Effect.mapError(() => invalid("Page links could not be updated.")));
1319
- if (existing) plans[index] = {
1320
- ...existing,
1321
- markdown
1322
- };
1323
- else plans.push({
1324
- action: "document.update",
1162
+ case "document.remove": {
1163
+ const ids = new Set(sources.map((source) => source.documentId));
1164
+ nextAuthored = nextAuthored.filter((source) => !ids.has(source.identity.documentId));
1165
+ nextPublished = nextPublished.filter((source) => !ids.has(source.identity.documentId));
1166
+ for (const source of sources) {
1167
+ const page = state.authoredById.get(source.documentId);
1168
+ plans.push({
1169
+ action: command.action,
1170
+ trashBatchId: root.version,
1171
+ title: page?.title,
1172
+ path: page?.path,
1173
+ documentId: source.documentId,
1174
+ expectedVersion: source.version,
1175
+ markdown: source.markdown,
1176
+ placement: source.placement
1177
+ });
1178
+ }
1179
+ break;
1180
+ }
1181
+ case "document.undelete": for (const source of sources) {
1182
+ const next = compilationSource$2(source, source.placement);
1183
+ nextAuthored.push(next);
1184
+ plans.push({
1185
+ action: command.action,
1325
1186
  documentId: source.documentId,
1326
1187
  expectedVersion: source.version,
1327
- sourcePath: source.sourcePath,
1328
- markdown,
1329
- publishedVersion: source.publishedVersion
1188
+ markdown: source.markdown,
1189
+ placement: source.placement
1330
1190
  });
1331
1191
  }
1332
- for (const plan of plans) {
1333
- const index = nextSources.findIndex((source) => source.documentId === plan.documentId);
1334
- const updated = {
1335
- documentId: plan.documentId,
1336
- sourcePath: plan.sourcePath,
1337
- markdown: plan.markdown,
1338
- draft: true
1339
- };
1340
- if (index >= 0) nextSources[index] = updated;
1341
- else nextSources.push(updated);
1342
- }
1343
1192
  }
1344
1193
  const first = plans[0];
1345
1194
  if (!first) return yield* Effect.die(/* @__PURE__ */ new Error("Empty subtree mutation."));
1346
1195
  return {
1347
- authored: yield* compileValidCatalog(engine, nextSources, state.authored),
1348
- published: command.action === "document.remove" ? yield* compileValidCatalog(engine, publishedSources(state.snapshot).filter((source) => !changed.has(source.documentId)), state.published) : state.published,
1196
+ authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1197
+ published: yield* compileValidCatalog(engine, nextPublished, state.published),
1349
1198
  commitPlan: first,
1350
1199
  additionalPlans: plans.slice(1)
1351
1200
  };
1352
1201
  });
1353
- const decodeSlug = (value) => Schema.decodeEffect(Slug)(value).pipe(Effect.mapError(() => invalid("Invalid URL ending.")));
1354
- const decodeParent = (value) => Schema.decodeEffect(RelativeDocumentPath)(value).pipe(Effect.mapError(() => invalid("Invalid parent URL.")));
1355
- function invalid(message) {
1356
- return new DocumentValidationFailed({ issues: [{
1357
- code: "document.invalid",
1358
- severity: "error",
1359
- message
1360
- }] });
1202
+ function affectedSources(state, root, command) {
1203
+ if (command.action === "document.undelete") {
1204
+ if (!("deletedAt" in root)) return [];
1205
+ return state.snapshot.deleted.filter((source) => source.documentId === root.documentId || root.trashBatchId !== void 0 && source.trashBatchId === root.trashBatchId);
1206
+ }
1207
+ const ids = new Set(command.action === "document.duplicate" && !command.input.includeChildren ? [root.documentId] : subtreeIds(state, root.documentId));
1208
+ return [root, ...state.snapshot.active.filter((source) => source.documentId !== root.documentId && ids.has(source.documentId))];
1209
+ }
1210
+ function compilationSource$2(source, placement) {
1211
+ return {
1212
+ draft: true,
1213
+ identity: source.identity,
1214
+ markdown: source.markdown,
1215
+ placement
1216
+ };
1217
+ }
1218
+ function duplicatePlacement(source, root, rootPlacement, ids) {
1219
+ if (source.documentId === root.documentId) return rootPlacement;
1220
+ if (source.placement.kind === "home") return source.placement;
1221
+ const parentDocumentId = ids.get(source.placement.parentDocumentId);
1222
+ return parentDocumentId === void 0 ? source.placement : {
1223
+ ...source.placement,
1224
+ parentDocumentId
1225
+ };
1226
+ }
1227
+ function replaceSource$1(sources, source) {
1228
+ return [...sources.filter((candidate) => candidate.identity.documentId !== source.identity.documentId), source];
1361
1229
  }
1362
1230
 
1363
1231
  //#endregion
@@ -1370,43 +1238,32 @@ const prepareDocumentMutation = Effect.fn("DocumentLifecycle.prepare")(function*
1370
1238
  const published = publishedSources(state.snapshot);
1371
1239
  switch (command.action) {
1372
1240
  case "document.create": {
1373
- yield* ensurePathAvailable(state, command.sourcePath);
1374
- const nextAuthored = [...authored, {
1375
- documentId: command.documentId,
1376
- draft: true,
1377
- markdown: command.markdown,
1378
- sourcePath: command.sourcePath
1379
- }];
1241
+ const source = compilationSource$1(command.identity, command.placement, command.markdown, true);
1380
1242
  return PreparedDocumentMutation.Commit({
1381
- authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1243
+ authored: yield* compileValidCatalog(engine, [...authored, source], state.authored),
1382
1244
  published: state.published,
1383
1245
  commitPlan: {
1384
1246
  action: command.action,
1385
- documentId: command.documentId,
1247
+ identity: command.identity,
1386
1248
  markdown: command.markdown,
1387
- sourcePath: command.sourcePath
1249
+ placement: command.placement
1388
1250
  }
1389
1251
  });
1390
1252
  }
1391
1253
  case "document.update": {
1392
1254
  const current = yield* requireActive(state, command.input.documentId);
1393
1255
  yield* requireVersion(current, command.input.expectedVersion);
1394
- const nextAuthored = replaceSource(authored, {
1395
- documentId: current.documentId,
1396
- draft: true,
1397
- markdown: command.input.markdown,
1398
- sourcePath: current.sourcePath
1399
- });
1256
+ const source = compilationSource$1(current.identity, current.placement, command.input.markdown, true);
1400
1257
  return PreparedDocumentMutation.Commit({
1401
- authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1258
+ authored: yield* compileValidCatalog(engine, replaceSource(authored, source), state.authored),
1402
1259
  published: state.published,
1403
1260
  commitPlan: {
1404
1261
  action: command.action,
1405
1262
  documentId: current.documentId,
1406
1263
  expectedVersion: command.input.expectedVersion,
1407
1264
  markdown: command.input.markdown,
1408
- publishedVersion: current.publishedVersion,
1409
- sourcePath: current.sourcePath
1265
+ placement: current.placement,
1266
+ publishedVersion: current.publishedVersion
1410
1267
  }
1411
1268
  });
1412
1269
  }
@@ -1414,69 +1271,47 @@ const prepareDocumentMutation = Effect.fn("DocumentLifecycle.prepare")(function*
1414
1271
  const current = yield* requireActive(state, command.input.documentId);
1415
1272
  yield* requireVersion(current, command.input.expectedVersion);
1416
1273
  const historical = yield* repository.revision(current.documentId, command.input.revisionId);
1417
- const nextAuthored = replaceSource(authored, {
1418
- documentId: current.documentId,
1419
- draft: true,
1420
- markdown: historical.markdown,
1421
- sourcePath: current.sourcePath
1422
- });
1274
+ const source = compilationSource$1(current.identity, historical.placement, historical.markdown, true);
1423
1275
  return PreparedDocumentMutation.Commit({
1424
- authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
1276
+ authored: yield* compileValidCatalog(engine, replaceSource(authored, source), state.authored),
1425
1277
  published: state.published,
1426
1278
  commitPlan: {
1427
1279
  action: command.action,
1428
1280
  documentId: current.documentId,
1429
1281
  expectedVersion: command.input.expectedVersion,
1430
1282
  markdown: historical.markdown,
1283
+ placement: historical.placement,
1431
1284
  publishedVersion: current.publishedVersion,
1432
- revisionId: command.input.revisionId,
1433
- sourcePath: current.sourcePath
1285
+ revisionId: command.input.revisionId
1434
1286
  }
1435
1287
  });
1436
1288
  }
1437
1289
  case "document.unpublish": {
1438
1290
  const current = yield* requireActive(state, command.input.documentId);
1439
1291
  yield* requireVersion(current, command.input.expectedVersion);
1292
+ const ids = new Set(subtreeIds(state, current.documentId, "published"));
1293
+ const plans = state.snapshot.active.filter((source) => ids.has(source.documentId) && source.publishedVersion !== void 0).map((source) => ({
1294
+ action: "document.unpublish",
1295
+ documentId: source.documentId,
1296
+ expectedVersion: source.version,
1297
+ markdown: source.markdown,
1298
+ placement: source.placement,
1299
+ revisionId: source.revisionId
1300
+ }));
1301
+ const first = plans[0];
1302
+ if (!first) return unchanged(state, current, "document.unpublish");
1440
1303
  return PreparedDocumentMutation.Commit({
1441
- authored: yield* compileValidCatalog(engine, replaceSource(authored, {
1442
- documentId: current.documentId,
1443
- draft: true,
1444
- markdown: current.markdown,
1445
- sourcePath: current.sourcePath
1446
- }), state.authored),
1447
- published: yield* compileValidCatalog(engine, withoutDocument(published, current.documentId), state.published),
1448
- commitPlan: {
1449
- action: command.action,
1450
- documentId: current.documentId,
1451
- expectedVersion: current.version,
1452
- markdown: current.markdown,
1453
- sourcePath: current.sourcePath,
1454
- revisionId: current.revisionId
1455
- }
1304
+ authored: yield* compileValidCatalog(engine, authored.map((source) => ids.has(source.identity.documentId) ? Object.assign({}, source, { draft: true }) : source), state.authored),
1305
+ published: yield* compileValidCatalog(engine, published.filter((source) => !ids.has(source.identity.documentId)), state.published),
1306
+ commitPlan: first,
1307
+ additionalPlans: plans.slice(1)
1456
1308
  });
1457
1309
  }
1458
1310
  case "document.publish": {
1459
1311
  const current = yield* requireActive(state, command.input.documentId);
1460
1312
  yield* requireVersion(current, command.input.expectedVersion);
1461
- if (current.publishedVersion === current.version) {
1462
- const entry = entryById(state, current.documentId);
1463
- const result = {
1464
- action: "document.publish",
1465
- affectedDocumentIds: [current.documentId],
1466
- documentId: current.documentId,
1467
- generation: state.snapshot.generation,
1468
- revisionId: current.revisionId,
1469
- sourcePath: current.sourcePath,
1470
- version: current.version
1471
- };
1472
- return PreparedDocumentMutation.Unchanged({ result: DocumentMutationResult.make(Object.assign(result, entry === void 0 ? void 0 : { entry })) });
1473
- }
1474
- const source = {
1475
- documentId: current.documentId,
1476
- draft: false,
1477
- markdown: current.markdown,
1478
- sourcePath: current.sourcePath
1479
- };
1313
+ if (current.publishedVersion === current.version) return unchanged(state, current, "document.publish");
1314
+ const source = compilationSource$1(current.identity, current.placement, current.markdown, false);
1480
1315
  return PreparedDocumentMutation.Commit({
1481
1316
  authored: yield* compileValidCatalog(engine, replaceSource(authored, source), state.authored),
1482
1317
  published: yield* compileValidCatalog(engine, replaceSource(published, source), state.published),
@@ -1485,8 +1320,8 @@ const prepareDocumentMutation = Effect.fn("DocumentLifecycle.prepare")(function*
1485
1320
  documentId: current.documentId,
1486
1321
  expectedVersion: command.input.expectedVersion,
1487
1322
  markdown: current.markdown,
1488
- revisionId: current.revisionId,
1489
- sourcePath: current.sourcePath
1323
+ placement: current.placement,
1324
+ revisionId: current.revisionId
1490
1325
  }
1491
1326
  });
1492
1327
  }
@@ -1495,14 +1330,30 @@ const prepareDocumentMutation = Effect.fn("DocumentLifecycle.prepare")(function*
1495
1330
  const validateDocumentUpdate = Effect.fn("DocumentLifecycle.validateUpdate")(function* (engine, state, input) {
1496
1331
  const current = yield* requireActive(state, input.documentId);
1497
1332
  yield* requireVersion(current, input.expectedVersion);
1498
- const source = {
1499
- documentId: current.documentId,
1500
- draft: true,
1501
- markdown: input.markdown,
1502
- sourcePath: current.sourcePath
1503
- };
1504
- return yield* validateDocumentSources(engine, replaceSource(authoredSources(state.snapshot), source), current.sourcePath, state.authored);
1333
+ const source = compilationSource$1(current.identity, current.placement, input.markdown, true);
1334
+ return yield* validateDocumentSources(engine, replaceSource(authoredSources(state.snapshot), source), current.documentId, state.authored);
1505
1335
  });
1336
+ function compilationSource$1(identity, placement, markdown, draft) {
1337
+ return {
1338
+ draft,
1339
+ identity,
1340
+ markdown,
1341
+ placement
1342
+ };
1343
+ }
1344
+ function unchanged(state, current, action) {
1345
+ const entry = entryById(state, current.documentId);
1346
+ const result = DocumentMutationResult.make({
1347
+ action,
1348
+ affectedDocumentIds: [current.documentId],
1349
+ documentId: current.documentId,
1350
+ generation: state.snapshot.generation,
1351
+ revisionId: current.revisionId,
1352
+ version: current.version,
1353
+ ...entry === void 0 ? void 0 : { entry }
1354
+ });
1355
+ return PreparedDocumentMutation.Unchanged({ result });
1356
+ }
1506
1357
  function requireActive(state, documentId) {
1507
1358
  const source = state.activeById.get(documentId);
1508
1359
  return source ? Effect.succeed(source) : Effect.fail(new DocumentNotFound({ documentId }));
@@ -1514,14 +1365,8 @@ function requireVersion(source, expectedVersion) {
1514
1365
  expectedVersion
1515
1366
  }));
1516
1367
  }
1517
- function ensurePathAvailable(state, sourcePath, exceptDocumentId) {
1518
- return state.snapshot.active.some((source) => source.sourcePath === sourcePath && source.documentId !== exceptDocumentId) ? Effect.fail(new DocumentPathConflict({ sourcePath })) : Effect.void;
1519
- }
1520
1368
  function replaceSource(sources, source) {
1521
- return [...sources.filter((candidate) => candidate.documentId !== source.documentId), source];
1522
- }
1523
- function withoutDocument(sources, documentId) {
1524
- return sources.filter((source) => source.documentId !== documentId);
1369
+ return [...sources.filter((candidate) => candidate.identity.documentId !== source.identity.documentId), source];
1525
1370
  }
1526
1371
 
1527
1372
  //#endregion
@@ -1540,108 +1385,109 @@ function toMediaReference(reference) {
1540
1385
  ...reference.target.mediaId ? { mediaId: reference.target.mediaId } : void 0,
1541
1386
  nodePath: reference.nodePath,
1542
1387
  objectKey: MediaObjectKey.make(reference.target.key),
1543
- path: reference.source.path,
1544
- sourcePath: reference.source.sourcePath
1388
+ route: reference.source.route
1545
1389
  });
1546
1390
  }
1547
1391
  function referenceIdentity(reference) {
1548
- return `${reference.documentId}:${reference.sourcePath}:${reference.nodePath.join(".")}:${reference.objectKey}`;
1392
+ return `${reference.documentId}:${reference.nodePath.join(".")}:${reference.objectKey}`;
1549
1393
  }
1550
1394
 
1551
1395
  //#endregion
1552
1396
  //#region ../server/src/internal/documents/content-transfer.ts
1553
- /** Plan all incoming sources against one document generation, including cross-document references. */
1554
- const planContentTransfer = Effect.fn("ContentTransfer.plan")(function* (engine, state, input, crypto) {
1555
- const currentByPath = new Map(state.snapshot.active.map((source) => [source.sourcePath, source]));
1397
+ /** Import/export adapter planning. File paths are consumed here and never enter document state. */
1398
+ const planContentTransfer = Effect.fn("ContentTransfer.plan")(function* (engine, configuration, state, input, crypto) {
1399
+ const incoming = yield* resolveSources(configuration, state, input.sources, crypto);
1400
+ const currentById = new Map(state.snapshot.active.map((source) => [source.documentId, source]));
1556
1401
  const seen = /* @__PURE__ */ new Set();
1557
1402
  const commitPlans = [];
1558
1403
  const entries = [];
1559
- const authored = new Map(authoredSources(state.snapshot).map((source) => [source.sourcePath, source]));
1560
- const published = new Map(publishedSources(state.snapshot).map((source) => [source.sourcePath, source]));
1404
+ const authored = new Map(authoredSources(state.snapshot).map((source) => [source.identity.documentId, source]));
1405
+ const published = new Map(publishedSources(state.snapshot).map((source) => [source.identity.documentId, source]));
1561
1406
  const now = yield* DateTime.now;
1562
1407
  let created = 0;
1563
1408
  let updated = 0;
1564
1409
  let publishedCount = 0;
1565
1410
  let unchanged = 0;
1566
- for (const source of input.sources) {
1567
- if (seen.has(source.sourcePath)) return yield* new ContentTransferConflict({ message: `Duplicate source path: ${source.sourcePath}` });
1568
- seen.add(source.sourcePath);
1569
- const current = currentByPath.get(source.sourcePath);
1411
+ for (const source of incoming) {
1412
+ if (seen.has(source.filePath)) return yield* new ContentTransferConflict({ message: `Duplicate content file path: ${source.filePath}` });
1413
+ seen.add(source.filePath);
1414
+ const current = currentById.get(source.identity.documentId);
1570
1415
  const shouldPublish = input.publish && source.published;
1571
- const action = !current ? "create" : current.markdown !== source.markdown ? input.overwrite ? "update" : "conflict" : shouldPublish && current.publishedVersion !== current.version ? "publish" : "unchanged";
1416
+ const shouldUnpublish = input.publish && !source.published && current?.publishedVersion !== void 0;
1417
+ const identityConflict = current !== void 0 && !sameIdentity(current.identity, source.identity);
1418
+ const changed = current !== void 0 && (current.markdown !== source.markdown || !samePlacement(current.placement, source.placement) || source.revisionId !== void 0 && current.revisionId !== source.revisionId);
1419
+ const action = identityConflict ? "conflict" : !current ? "create" : changed ? input.overwrite ? "update" : "conflict" : shouldPublish && current.publishedVersion !== current.version ? "publish" : shouldUnpublish ? "unpublish" : "unchanged";
1572
1420
  entries.push({
1573
- sourcePath: source.sourcePath,
1421
+ filePath: source.filePath,
1574
1422
  action,
1575
1423
  currentRevisionId: current?.revisionId ?? null
1576
1424
  });
1577
1425
  if (action === "create" || action === "update") {
1578
- const documentId = current?.documentId ?? DocumentId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
1579
1426
  if (current) {
1580
1427
  commitPlans.push({
1581
1428
  action: "document.update",
1582
- documentId,
1429
+ documentId: current.documentId,
1583
1430
  expectedVersion: current.version,
1584
1431
  markdown: source.markdown,
1432
+ placement: source.placement,
1585
1433
  publish: shouldPublish,
1586
1434
  publishedVersion: current.publishedVersion,
1587
- sourcePath: source.sourcePath
1435
+ ...source.revisionId === void 0 ? void 0 : { newRevisionId: source.revisionId },
1436
+ ...shouldUnpublish ? { unpublish: true } : void 0
1588
1437
  });
1589
1438
  updated++;
1590
1439
  } else {
1591
1440
  commitPlans.push({
1592
1441
  action: "document.create",
1593
- documentId,
1442
+ identity: source.identity,
1594
1443
  markdown: source.markdown,
1444
+ placement: source.placement,
1595
1445
  publish: shouldPublish,
1596
- sourcePath: source.sourcePath
1446
+ ...source.revisionId === void 0 ? void 0 : { newRevisionId: source.revisionId }
1597
1447
  });
1598
1448
  created++;
1599
1449
  }
1600
1450
  if (shouldPublish) publishedCount++;
1601
- authored.set(source.sourcePath, {
1602
- documentId,
1603
- draft: !shouldPublish,
1604
- markdown: source.markdown,
1605
- sourcePath: source.sourcePath
1606
- });
1607
- if (shouldPublish) published.set(source.sourcePath, {
1608
- documentId,
1451
+ const compiled = compilationSource(source, !shouldPublish);
1452
+ authored.set(source.identity.documentId, compiled);
1453
+ if (shouldPublish) published.set(source.identity.documentId, Object.assign({}, compiled, {
1609
1454
  draft: false,
1610
- markdown: source.markdown,
1611
- publishedAt: now,
1612
- sourcePath: source.sourcePath
1613
- });
1455
+ publishedAt: now
1456
+ }));
1457
+ else if (shouldUnpublish) published.delete(source.identity.documentId);
1614
1458
  } else if (action === "publish" && current) {
1615
1459
  commitPlans.push({
1616
1460
  action: "document.publish",
1617
1461
  documentId: current.documentId,
1618
1462
  expectedVersion: current.version,
1619
1463
  markdown: current.markdown,
1620
- revisionId: current.revisionId,
1621
- sourcePath: current.sourcePath
1464
+ placement: current.placement,
1465
+ revisionId: current.revisionId
1622
1466
  });
1623
1467
  publishedCount++;
1624
- authored.set(source.sourcePath, {
1625
- documentId: current.documentId,
1626
- draft: false,
1627
- markdown: current.markdown,
1628
- sourcePath: current.sourcePath
1629
- });
1630
- published.set(source.sourcePath, {
1468
+ const compiled = compilationSource(source, false);
1469
+ authored.set(source.identity.documentId, compiled);
1470
+ published.set(source.identity.documentId, Object.assign({}, compiled, { publishedAt: now }));
1471
+ } else if (action === "unpublish" && current) {
1472
+ commitPlans.push({
1473
+ action: "document.unpublish",
1631
1474
  documentId: current.documentId,
1632
- draft: false,
1475
+ expectedVersion: current.version,
1633
1476
  markdown: current.markdown,
1634
- publishedAt: now,
1635
- sourcePath: current.sourcePath
1477
+ placement: current.placement,
1478
+ revisionId: current.revisionId
1636
1479
  });
1480
+ published.delete(source.identity.documentId);
1637
1481
  } else if (action === "unchanged") unchanged++;
1638
1482
  }
1639
- const firstPath = input.sources[0]?.sourcePath;
1640
- const authoredValidation = firstPath ? yield* validateDocumentSources(engine, [...authored.values()], firstPath, state.authored) : DocumentValidationResult.make({
1483
+ const firstDocumentId = incoming[0]?.identity.documentId;
1484
+ const publishedValues = [...published.values()];
1485
+ const publishedDocumentId = publishedValues[0]?.identity.documentId;
1486
+ const authoredValidation = firstDocumentId ? yield* validateDocumentSources(engine, [...authored.values()], firstDocumentId, state.authored) : DocumentValidationResult.make({
1641
1487
  issues: [],
1642
1488
  valid: true
1643
1489
  });
1644
- const publishedValidation = input.publish && firstPath ? yield* validateDocumentSources(engine, [...published.values()], firstPath, state.published) : DocumentValidationResult.make({
1490
+ const publishedValidation = input.publish && publishedDocumentId ? yield* validateDocumentSources(engine, publishedValues, publishedDocumentId, state.published) : DocumentValidationResult.make({
1645
1491
  issues: [],
1646
1492
  valid: true
1647
1493
  });
@@ -1654,7 +1500,7 @@ const planContentTransfer = Effect.fn("ContentTransfer.plan")(function* (engine,
1654
1500
  valid: authoredValidation.valid && !entries.some((entry) => entry.action === "conflict") && !issues.some((issue) => issue.severity === "error")
1655
1501
  }),
1656
1502
  authoredSources: [...authored.values()],
1657
- publishedSources: [...published.values()],
1503
+ publishedSources: publishedValues,
1658
1504
  commitPlans,
1659
1505
  counts: {
1660
1506
  created,
@@ -1664,6 +1510,116 @@ const planContentTransfer = Effect.fn("ContentTransfer.plan")(function* (engine,
1664
1510
  }
1665
1511
  };
1666
1512
  });
1513
+ function resolveSources(configuration, state, sources, crypto) {
1514
+ return Effect.gen(function* () {
1515
+ const resolved = [];
1516
+ const indexed = [];
1517
+ const unindexed = [];
1518
+ for (const source of sources) {
1519
+ if (source.identity && source.placement) {
1520
+ indexed.push(Object.assign({}, source, {
1521
+ identity: source.identity,
1522
+ placement: source.placement
1523
+ }));
1524
+ continue;
1525
+ }
1526
+ if (source.identity || source.placement) return yield* new ContentTransferConflict({ message: `${source.filePath}: indexed content must include both identity and placement.` });
1527
+ unindexed.push(source);
1528
+ }
1529
+ resolved.push(...indexed);
1530
+ const byPlacement = placementIndex([...state.snapshot.active, ...indexed]);
1531
+ const pending = unindexed.map((source) => ({
1532
+ inferred: inferFilePlacement(configuration, source.filePath),
1533
+ source
1534
+ })).toSorted((left, right) => Number(right.inferred.home) - Number(left.inferred.home) || left.inferred.segments.length - right.inferred.segments.length || left.source.filePath.localeCompare(right.source.filePath));
1535
+ for (const { inferred, source } of pending) {
1536
+ const scope = `${inferred.collection}:${inferred.locale ?? ""}`;
1537
+ const key = `${scope}:${inferred.segments.join("/")}`;
1538
+ let identity = byPlacement.get(key);
1539
+ if (!identity) identity = {
1540
+ collection: inferred.collection,
1541
+ documentId: DocumentId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie)),
1542
+ ...inferred.locale === void 0 ? void 0 : { locale: inferred.locale }
1543
+ };
1544
+ const placement = inferred.home ? { kind: "home" } : {
1545
+ kind: "entry",
1546
+ parentDocumentId: byPlacement.get(`${scope}:${inferred.segments.slice(0, -1).join("/")}`)?.documentId ?? (yield* new ContentTransferConflict({ message: `${source.filePath}: parent document is missing from the import and target catalog.` })),
1547
+ slug: Slug.make(inferred.segments.at(-1) ?? "")
1548
+ };
1549
+ const value = Object.assign({}, source, {
1550
+ identity,
1551
+ placement
1552
+ });
1553
+ resolved.push(value);
1554
+ byPlacement.set(key, identity);
1555
+ }
1556
+ return resolved;
1557
+ });
1558
+ }
1559
+ function inferFilePlacement(configuration, filePath) {
1560
+ const segments = filePath.replace(/\.(?:md|mdc)$/iu, "").split("/");
1561
+ let locale = configuration.locales?.locales.find((candidate) => candidate === segments[0]);
1562
+ if (locale) segments.shift();
1563
+ else locale = configuration.locales?.defaultLocale;
1564
+ let collection = configuration.types[0]?.collection;
1565
+ const configured = configuration.types.find((candidate) => candidate.collection === segments[0]);
1566
+ if (configured) {
1567
+ collection = configured.collection;
1568
+ segments.shift();
1569
+ }
1570
+ if (!collection) throw new Error("Content import needs at least one configured collection.");
1571
+ const home = segments.length === 1 && segments[0]?.toLocaleLowerCase() === "index";
1572
+ const normalized = home ? [] : segments.at(-1)?.toLocaleLowerCase() === "index" ? segments.slice(0, -1) : segments;
1573
+ return {
1574
+ collection,
1575
+ home,
1576
+ locale,
1577
+ segments: normalized
1578
+ };
1579
+ }
1580
+ function placementIndex(sources) {
1581
+ const index = /* @__PURE__ */ new Map();
1582
+ const byId = new Map(sources.map((source) => [source.identity.documentId, source]));
1583
+ const resolved = /* @__PURE__ */ new Map();
1584
+ const segmentsFor = (documentId, ancestors) => {
1585
+ const cached = resolved.get(documentId);
1586
+ if (cached) return cached;
1587
+ if (ancestors.has(documentId)) return void 0;
1588
+ const source = byId.get(documentId);
1589
+ if (!source) return void 0;
1590
+ if (source.placement.kind === "home") {
1591
+ resolved.set(documentId, []);
1592
+ return [];
1593
+ }
1594
+ const parent = segmentsFor(source.placement.parentDocumentId, /* @__PURE__ */ new Set([...ancestors, documentId]));
1595
+ if (!parent) return void 0;
1596
+ const segments = [...parent, source.placement.slug];
1597
+ resolved.set(documentId, segments);
1598
+ return segments;
1599
+ };
1600
+ for (const source of sources) {
1601
+ const segments = segmentsFor(source.identity.documentId, /* @__PURE__ */ new Set());
1602
+ if (segments) index.set(placementIndexKey(source.identity, segments), source.identity);
1603
+ }
1604
+ return index;
1605
+ }
1606
+ function placementIndexKey(identity, segments) {
1607
+ return `${identity.collection}:${identity.locale ?? ""}:${segments.join("/")}`;
1608
+ }
1609
+ function compilationSource(source, draft) {
1610
+ return {
1611
+ draft,
1612
+ identity: source.identity,
1613
+ markdown: source.markdown,
1614
+ placement: source.placement
1615
+ };
1616
+ }
1617
+ function samePlacement(left, right) {
1618
+ return left.kind === right.kind && (left.kind === "home" || right.kind === "entry" && left.parentDocumentId === right.parentDocumentId && left.slug === right.slug);
1619
+ }
1620
+ function sameIdentity(left, right) {
1621
+ return left.documentId === right.documentId && left.collection === right.collection && left.locale === right.locale;
1622
+ }
1667
1623
 
1668
1624
  //#endregion
1669
1625
  //#region ../server/src/internal/documents/document-mutation.ts
@@ -1675,7 +1631,8 @@ const commitApprovedDocumentPlans = Effect.fn("Documents.commitApprovedPlans")(f
1675
1631
  plan,
1676
1632
  additionalPlans,
1677
1633
  expectedGeneration: input.current.snapshot.generation,
1678
- mediaReferences: input.plans.flatMap((candidate) => projectMediaReferences(input.authored, input.published, candidate.documentId))
1634
+ ...input.configurationRevision === void 0 ? void 0 : { expectedConfigurationRevision: input.configurationRevision },
1635
+ mediaReferences: input.plans.flatMap((candidate) => projectMediaReferences(input.authored, input.published, planDocumentId(candidate)))
1679
1636
  });
1680
1637
  });
1681
1638
  /** Authorize every affected document before the adapter commits the approved plan atomically. */
@@ -1708,18 +1665,6 @@ const commitDocumentMutation = Effect.fn("Documents.commitMutation")(function* (
1708
1665
  }
1709
1666
  }
1710
1667
  const prepared = yield* prepareDocumentMutation(dependencies, current, command);
1711
- if (command.action === "document.move" && Predicate.isTagged("Commit")(prepared)) {
1712
- for (const plan of prepared.additionalPlans ?? []) if (plan.action === "document.update") yield* accessControl.require({
1713
- actor: activity.actor,
1714
- requestId: activity.requestId
1715
- }, {
1716
- action: "document.update",
1717
- resource: {
1718
- kind: "document",
1719
- documentId: plan.documentId
1720
- }
1721
- });
1722
- }
1723
1668
  if (command.action === "document.undelete" && Predicate.isTagged("Commit")(prepared)) for (const plan of [prepared.commitPlan, ...prepared.additionalPlans ?? []]) yield* accessControl.require({
1724
1669
  actor: activity.actor,
1725
1670
  requestId: activity.requestId
@@ -1727,7 +1672,7 @@ const commitDocumentMutation = Effect.fn("Documents.commitMutation")(function* (
1727
1672
  action: "document.undelete",
1728
1673
  resource: {
1729
1674
  kind: "document",
1730
- documentId: plan.documentId
1675
+ documentId: planDocumentId(plan)
1731
1676
  }
1732
1677
  });
1733
1678
  if (Predicate.isTagged("Unchanged")(prepared)) return {
@@ -1740,7 +1685,8 @@ const commitDocumentMutation = Effect.fn("Documents.commitMutation")(function* (
1740
1685
  plans: [prepared.commitPlan, ...prepared.additionalPlans ?? []],
1741
1686
  authored: prepared.authored,
1742
1687
  published: prepared.published,
1743
- activity
1688
+ activity,
1689
+ ...dependencies.configurationRevision === void 0 ? void 0 : { configurationRevision: dependencies.configurationRevision }
1744
1690
  });
1745
1691
  const next = makeDocumentState(prepared.authored, prepared.published, committed.snapshot);
1746
1692
  const entry = entryById(next, committed.mutation.documentId);
@@ -1750,6 +1696,9 @@ const commitDocumentMutation = Effect.fn("Documents.commitMutation")(function* (
1750
1696
  result: DocumentMutationResult.make(Object.assign({}, committed.mutation, entry === void 0 ? void 0 : { entry }))
1751
1697
  };
1752
1698
  });
1699
+ function planDocumentId(plan) {
1700
+ return plan.action === "document.create" ? plan.identity.documentId : plan.documentId;
1701
+ }
1753
1702
 
1754
1703
  //#endregion
1755
1704
  //#region ../server/src/internal/documents/document-store.ts
@@ -1765,6 +1714,10 @@ var DocumentGenerationConflict = class extends Schema.TaggedError()("Documents.G
1765
1714
  expectedGeneration: DocumentGeneration,
1766
1715
  actualGeneration: DocumentGeneration
1767
1716
  }) {};
1717
+ var DocumentConfigurationConflict = class extends Schema.TaggedError()("Documents.ConfigurationConflict", {
1718
+ expectedRevision: Schema.NonEmptyString,
1719
+ actualRevision: Schema.NullOr(Schema.NonEmptyString)
1720
+ }) {};
1768
1721
 
1769
1722
  //#endregion
1770
1723
  //#region ../server/src/internal/cursor.ts
@@ -1940,41 +1893,17 @@ function createNavigation(documents, query, configuration) {
1940
1893
  const locale = query.locale ?? configuration.locales?.defaultLocale;
1941
1894
  const candidates = documents.filter((document) => (query.collection === void 0 || document.collection === query.collection) && (locale === void 0 || document.locale === locale) && document.data.navigation !== false);
1942
1895
  const roots = [];
1943
- const itemsByKey = /* @__PURE__ */ new Map();
1896
+ const itemsById = new Map(candidates.map((document) => [document.id, navigationItem(document)]));
1944
1897
  const orderByItem = /* @__PURE__ */ new Map();
1945
1898
  for (const document of candidates) {
1946
- if (document.key === "/") {
1947
- const item = navigationItem(document);
1948
- roots.push(item);
1949
- orderByItem.set(item, Number.NEGATIVE_INFINITY);
1950
- continue;
1951
- }
1952
- const segments = document.key.slice(1).split("/");
1953
- let parent;
1954
- for (let index = 0; index < segments.length; index += 1) {
1955
- const key = PublicPath.make(`/${segments.slice(0, index + 1).join("/")}`);
1956
- let item = itemsByKey.get(key);
1957
- if (!item) {
1958
- item = {
1959
- children: [],
1960
- key,
1961
- title: titleFromSegment(segments[index] ?? "")
1962
- };
1963
- itemsByKey.set(key, item);
1964
- if (parent) parent.children.push(item);
1965
- else roots.push(item);
1966
- }
1967
- if (index === segments.length - 1) {
1968
- const resolved = navigationItem(document);
1969
- item.title = resolved.title;
1970
- item.path = resolved.path;
1971
- if (resolved.locale !== void 0) item.locale = resolved.locale;
1972
- else delete item.locale;
1973
- item.documentId = resolved.documentId;
1974
- orderByItem.set(item, navigationOrder(document));
1975
- }
1976
- parent = item;
1977
- }
1899
+ const item = itemsById.get(document.id);
1900
+ if (!item) continue;
1901
+ orderByItem.set(item, document.placement.kind === "home" ? Number.NEGATIVE_INFINITY : navigationOrder(document));
1902
+ if (document.placement.kind === "entry") {
1903
+ const parent = itemsById.get(document.placement.parentDocumentId);
1904
+ if (parent) parent.children.push(item);
1905
+ else roots.push(item);
1906
+ } else roots.push(item);
1978
1907
  }
1979
1908
  sortNavigation(roots, orderByItem);
1980
1909
  return roots.map((item) => DocumentNavigationItem.make(item));
@@ -2009,7 +1938,6 @@ function documentField(document, field) {
2009
1938
  case "key": return document.key;
2010
1939
  case "locale": return document.locale;
2011
1940
  case "path": return document.path;
2012
- case "source.path": return document.source.path;
2013
1941
  case "title": return document.title;
2014
1942
  default: return nestedValue(document.data, field.startsWith("data.") ? field.slice(5) : field);
2015
1943
  }
@@ -2042,9 +1970,6 @@ function compareValues(left, right) {
2042
1970
  const rightText = Schema.is(Schema.String)(right) ? right : JSON.stringify(right) ?? "";
2043
1971
  return leftText.localeCompare(rightText);
2044
1972
  }
2045
- function titleFromSegment(segment) {
2046
- return segment.replace(/[-_]+/gu, " ").replace(/^./u, (character) => character.toUpperCase());
2047
- }
2048
1973
 
2049
1974
  //#endregion
2050
1975
  //#region ../server/src/internal/documents/documents.ts
@@ -2066,8 +1991,8 @@ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, opti
2066
1991
  const dependencies = {
2067
1992
  engine,
2068
1993
  configuration,
2069
- repository,
2070
- components: options.components
1994
+ ...options.configurationRevision === void 0 ? void 0 : { configurationRevision: options.configurationRevision },
1995
+ repository
2071
1996
  };
2072
1997
  const refreshUnlocked = Effect.fn("Documents.refreshUnlocked")(function* (force = false) {
2073
1998
  const current = yield* Ref.get(state);
@@ -2114,11 +2039,15 @@ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, opti
2114
2039
  const plan = yield* Effect.fromResult(planDocumentCreation(configuration, input));
2115
2040
  const source = yield* createInitialDocumentSource(plan);
2116
2041
  const documentId = DocumentId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
2042
+ const identity = Object.assign({
2043
+ collection: plan.collection.collection,
2044
+ documentId
2045
+ }, plan.locale === void 0 ? void 0 : { locale: plan.locale });
2117
2046
  return yield* runMutation({
2118
2047
  action: "document.create",
2119
- documentId,
2048
+ identity,
2120
2049
  markdown: source.markdown,
2121
- sourcePath: plan.sourcePath
2050
+ placement: plan.placement
2122
2051
  }, activity, input.reason);
2123
2052
  });
2124
2053
  const duplicate = Effect.fn("Documents.duplicate")((input, activity) => runMutation({
@@ -2197,24 +2126,25 @@ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, opti
2197
2126
  yield* refreshUnlocked();
2198
2127
  const current = yield* Ref.get(state);
2199
2128
  return ContentSnapshot.make({
2200
- version: 1,
2129
+ version: 2,
2201
2130
  sources: current.snapshot.active.map((source) => ({
2202
- documentId: source.documentId,
2203
- sourcePath: source.sourcePath,
2131
+ identity: source.identity,
2132
+ placement: source.placement,
2204
2133
  markdown: source.markdown,
2205
- published: source.publishedVersion === source.version
2206
- })).sort((left, right) => left.sourcePath.localeCompare(right.sourcePath))
2134
+ published: source.publishedVersion === source.version,
2135
+ revisionId: source.revisionId
2136
+ })).sort((left, right) => left.identity.documentId.localeCompare(right.identity.documentId))
2207
2137
  });
2208
2138
  }))));
2209
2139
  const contentPlan = (context, input) => requireContentTransfer(context).pipe(Effect.andThen(semaphore.withPermits(1)(Effect.gen(function* () {
2210
2140
  yield* refreshUnlocked();
2211
- return (yield* planContentTransfer(engine, yield* Ref.get(state), input, crypto)).plan;
2141
+ return (yield* planContentTransfer(engine, configuration, yield* Ref.get(state), input, crypto)).plan;
2212
2142
  }))));
2213
2143
  const contentApply = (context, input) => requireContentTransfer(context).pipe(Effect.andThen(semaphore.withPermits(1)(Effect.gen(function* () {
2214
2144
  yield* refreshUnlocked();
2215
2145
  const current = yield* Ref.get(state);
2216
2146
  if (current.snapshot.generation !== input.expectedGeneration) return yield* new ContentTransferConflict({ message: "Content changed since the plan. Run content plan again." });
2217
- const planned = yield* planContentTransfer(engine, current, input, crypto);
2147
+ const planned = yield* planContentTransfer(engine, configuration, current, input, crypto);
2218
2148
  if (planned.plan.entries.some((entry) => entry.action === "conflict")) return yield* new ContentTransferConflict({ message: "Destination content differs. Use --overwrite after reviewing the plan." });
2219
2149
  if (!planned.plan.valid) return yield* new DocumentValidationFailed({ issues: planned.plan.issues });
2220
2150
  if (planned.commitPlans.length === 0) return ContentApplyResult.make({
@@ -2231,7 +2161,8 @@ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, opti
2231
2161
  plans: [first, ...additionalPlans],
2232
2162
  authored,
2233
2163
  published: publishedCatalog,
2234
- activity: requestActivity$1(context, "CMS content import")
2164
+ activity: requestActivity$1(context, "CMS content import"),
2165
+ ...options.configurationRevision === void 0 ? void 0 : { configurationRevision: options.configurationRevision }
2235
2166
  }).pipe(Effect.mapError((error) => isDocumentGenerationConflict(error) ? new ContentTransferConflict({ message: "Content changed since the plan. Run content plan again." }) : error));
2236
2167
  yield* Ref.set(state, yield* compileDocumentState(engine, committed.snapshot, current));
2237
2168
  yield* PubSub.publish(mutationEvents, committed.mutation);
@@ -2290,6 +2221,12 @@ const makeDocuments = Effect.fn("Documents.make")(function* (configuration, opti
2290
2221
  return {
2291
2222
  documents,
2292
2223
  maintenance: DocumentMaintenance.of({
2224
+ compile: (source) => engine.compile({
2225
+ draft: source.publishedVersion !== source.version,
2226
+ identity: source.identity,
2227
+ markdown: source.markdown,
2228
+ placement: source.placement
2229
+ }),
2293
2230
  mediaReferences: (documentId) => semaphore.withPermits(1)(Effect.gen(function* () {
2294
2231
  yield* refreshUnlocked();
2295
2232
  const current = yield* Ref.get(state);
@@ -2680,26 +2617,27 @@ function statObject(bucket, prefix, objectKey, client) {
2680
2617
  }));
2681
2618
  }
2682
2619
  function listObjects(bucket, prefix, client) {
2683
- return Stream.paginate({ seenTokens: /* @__PURE__ */ new Set() }, (state) => providerRequest("MediaObjectStore.S3.list", client.listObjects({
2684
- bucket,
2685
- continuationToken: state.continuationToken,
2686
- prefix: `${prefix}${mediaObjectPrefix}`
2687
- })).pipe(Effect.flatMap((page) => Effect.gen(function* () {
2688
- return [yield* Effect.forEach(page.objects, (object) => decodeListedObject(object, prefix)), yield* nextListState(state, page)];
2689
- }))));
2620
+ return Stream.suspend(() => {
2621
+ const seenTokens = /* @__PURE__ */ new Set();
2622
+ return Stream.paginate(void 0, (continuationToken) => providerRequest("MediaObjectStore.S3.list", client.listObjects({
2623
+ bucket,
2624
+ continuationToken,
2625
+ prefix: `${prefix}${mediaObjectPrefix}`
2626
+ })).pipe(Effect.flatMap((page) => Effect.gen(function* () {
2627
+ return [yield* Effect.forEach(page.objects, (object) => decodeListedObject(object, prefix)), yield* validateNextContinuationToken(continuationToken, seenTokens, page)];
2628
+ }))));
2629
+ });
2690
2630
  }
2691
2631
  function decodeListedObject(object, prefix) {
2692
2632
  if (object.key === void 0) return Effect.fail(storageError("MediaObjectStore.S3.list", "The provider returned object metadata without a key."));
2693
2633
  return decodeStoredObject("MediaObjectStore.S3.list", object.key.slice(prefix.length), object.size);
2694
2634
  }
2695
- function nextListState(state, page) {
2635
+ function validateNextContinuationToken(continuationToken, seenTokens, page) {
2696
2636
  if (!page.isTruncated) return Effect.succeedNone;
2697
2637
  const token = page.nextContinuationToken;
2698
- if (!token || token === state.continuationToken || state.seenTokens.has(token)) return Effect.fail(storageError("MediaObjectStore.S3.list", "The provider returned an invalid continuation token."));
2699
- return Effect.succeedSome({
2700
- continuationToken: token,
2701
- seenTokens: /* @__PURE__ */ new Set([...state.seenTokens, token])
2702
- });
2638
+ if (!token || token === continuationToken || seenTokens.has(token)) return Effect.fail(storageError("MediaObjectStore.S3.list", "The provider returned an invalid continuation token."));
2639
+ seenTokens.add(token);
2640
+ return Effect.succeedSome(token);
2703
2641
  }
2704
2642
  function decodeStoredObject(operation, objectKey, byteSize, contentType) {
2705
2643
  const decodedObjectKey = Schema.decodeOption(MediaObjectKey)(objectKey);
@@ -2800,5 +2738,5 @@ function storageError(operation, message) {
2800
2738
  const maxS3SingleCopyBytes = 5e9;
2801
2739
 
2802
2740
  //#endregion
2803
- export { DocumentRevision as $, StudioBadRequest as A, User as B, DocumentStore as C, CurrentStudioRequest as D, defaultAccessControlLayer as E, studioInternalError as F, UserSelfRemovalDenied as G, UserAlreadyExists as H, InitializationAlreadyCompleted as I, CollaborationServerMessage as J, UserSelfRoleChangeDenied as K, InitializationProofInvalid as L, StudioRequestConflict as M, StudioSchemaError as N, StudioApi as O, StudioInternalError as P, DocumentHistoryInvalidCursor as Q, InitializationState as R, DocumentPersistenceError as S, AccessControl as T, UserNotFound as U, UserAdministrationDenied as V, UserPage as W, StateVector as X, CollaborationTicket as Y, DocumentHistoryComparisonTooLarge as Z, Documents as _, mediaObjectPrefix as a, ActivityEntry as at, cursorCodec as b, Seo as c, ActivityPage as ct, seoDeliveryLayer as d, DocumentMediaUnavailable as dt, ACTIVITY_ACTIONS as et, seoLayer as f, DocumentNotFound as ft, DocumentPreviews as g, PublishedDocumentNotFound as gt, DocumentMaintenance as h, DocumentVersionConflict as ht, MediaObjectStore as i, ActivityCursorInvalid as it, StudioBootstrap as j, StudioAuthentication as k, SeoDelivery as l, ActivityTarget as lt, SeoStore as m, DocumentValidationFailed as mt, s3MediaObjectStoreLayer as n, ActivityContext as nt, PreviewSeo as o, ActivityEntryDraft as ot, SeoPersistenceError as p, DocumentPathConflict as pt, CollaborationClientMessage as q, MediaPersistenceError as r, ActivityCursor as rt, PublishedSeo as s, ActivityId as st, maxS3SingleCopyBytes as t, ActivityAction as tt, makeDefaultSeoOptions as u, ActivityTargetKind as ut, PublishedDocuments as v, renderEditorMarkdown as w, DocumentGenerationConflict as x, documentsLayer as y, CreateUserInput as z };
2804
- //# sourceMappingURL=S3MediaObjects-CR3p1WTg.js.map
2741
+ export { DocumentHistoryInvalidCursor as $, StudioAuthentication as A, CreateUserInput as B, DocumentPersistenceError as C, defaultAccessControlLayer as D, AccessControl as E, StudioInternalError as F, UserPage as G, UserAdministrationDenied as H, studioInternalError as I, CollaborationClientMessage as J, UserSelfRemovalDenied as K, InitializationAlreadyCompleted as L, StudioBootstrap as M, StudioRequestConflict as N, CurrentStudioRequest as O, StudioSchemaError as P, DocumentHistoryComparisonTooLarge as Q, InitializationProofInvalid as R, DocumentGenerationConflict as S, renderEditorMarkdown as T, UserAlreadyExists as U, User as V, UserNotFound as W, CollaborationTicket as X, CollaborationServerMessage as Y, StateVector as Z, Documents as _, mediaObjectPrefix as a, ActivityCursorInvalid as at, cursorCodec as b, Seo as c, ActivityId as ct, seoDeliveryLayer as d, ActivityTargetKind as dt, DocumentRevision as et, seoLayer as f, DocumentPreviews as g, DocumentMaintenance as h, MediaObjectStore as i, ActivityCursor as it, StudioBadRequest as j, StudioApi as k, SeoDelivery as l, ActivityPage as lt, SeoStore as m, s3MediaObjectStoreLayer as n, ActivityAction as nt, PreviewSeo as o, ActivityEntry as ot, SeoPersistenceError as p, UserSelfRoleChangeDenied as q, MediaPersistenceError as r, ActivityContext as rt, PublishedSeo as s, ActivityEntryDraft as st, maxS3SingleCopyBytes as t, ACTIVITY_ACTIONS as tt, makeDefaultSeoOptions as u, ActivityTarget as ut, PublishedDocuments as v, DocumentStore as w, DocumentConfigurationConflict as x, documentsLayer as y, InitializationState as z };
2742
+ //# sourceMappingURL=S3MediaObjects-BRuPQbxl.js.map