@phtngyn/cms 0.1.0-beta.1 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -9
- package/package.json +30 -15
- package/src/Configuration-Br7tE9Qt.d.ts +68 -0
- package/src/{Configuration-DyKo0sLS.js → Configuration-CA_jvQKG.js} +26 -147
- package/src/Configuration-CA_jvQKG.js.map +1 -0
- package/src/Configuration.d.ts +2 -44
- package/src/Configuration.js +0 -1
- package/src/Configuration.js.map +1 -1
- package/src/{DocumentEngine-DYObwMDY.js → DocumentEngine-CgiBQU4g.js} +75 -27
- package/src/DocumentEngine-CgiBQU4g.js.map +1 -0
- package/src/DocumentModel-BB9igGC7.d.ts +34 -0
- package/src/DocumentModel.d.ts +2 -34
- package/src/DocumentModel.js +13 -3
- package/src/DocumentModel.js.map +1 -1
- package/src/Forms-BsJMev5_.d.ts +74 -0
- package/src/Forms-CfcI3tPU.js +79 -0
- package/src/Forms-CfcI3tPU.js.map +1 -0
- package/src/Forms-DxbkU3_5.js +367 -0
- package/src/Forms-DxbkU3_5.js.map +1 -0
- package/src/Forms.d.ts +35 -0
- package/src/Forms.js +13 -0
- package/src/Forms.js.map +1 -0
- package/src/{HttpPaths-BbS21Jy8.js → HttpPaths-c9nLZ9H2.js} +6 -2
- package/src/HttpPaths-c9nLZ9H2.js.map +1 -0
- package/src/I18n-DZK4CkJF.d.ts +21 -0
- package/src/I18n.d.ts +2 -0
- package/src/I18n.js +1 -0
- package/src/Module.js +57 -11
- package/src/Module.js.map +1 -1
- package/src/S3MediaObjects-JKMAWyVB.js +2664 -0
- package/src/S3MediaObjects-JKMAWyVB.js.map +1 -0
- package/src/Seed.d.ts +6 -0
- package/src/Seed.js +34 -0
- package/src/Seed.js.map +1 -0
- package/src/Seo-402YumpT.d.ts +198 -0
- package/src/{connection-CBTY-TzB.js → connection-B-iz0Y9Y.js} +5 -7
- package/src/connection-B-iz0Y9Y.js.map +1 -0
- package/src/{installation-DeuBiFRE.js → installation-DgF2M1gL.js} +1005 -2242
- package/src/installation-DgF2M1gL.js.map +1 -0
- package/src/internal/authoring-catalog.js +10 -4
- package/src/internal/authoring-catalog.js.map +1 -1
- package/src/internal/authoring-compiler.js +0 -1
- package/src/internal/authoring-compiler.js.map +1 -1
- package/src/internal/authoring-metadata.js +4 -4
- package/src/internal/authoring-metadata.js.map +1 -1
- package/src/internal/cms-config.js +0 -1
- package/src/internal/cms-config.js.map +1 -1
- package/src/internal/configuration.js +23 -10
- package/src/internal/configuration.js.map +1 -1
- package/src/internal/document-model.js +65 -20
- package/src/internal/document-model.js.map +1 -1
- package/src/internal/form-capabilities.js +12 -0
- package/src/internal/form-capabilities.js.map +1 -0
- package/src/internal/i18n.js +47 -0
- package/src/internal/i18n.js.map +1 -0
- package/src/internal/studio-app.js +2 -2
- package/src/internal/studio-app.js.map +1 -1
- package/src/{location-DtYv2aes.js → location-CFKTeK1K.js} +2 -3
- package/src/location-CFKTeK1K.js.map +1 -0
- package/src/{metadata-DE1Mc2td.js → metadata-BEZPWh-3.js} +3 -72
- package/src/metadata-BEZPWh-3.js.map +1 -0
- package/src/{models-C3witQBK.js → models-BWm1pTAf.js} +3 -2
- package/src/models-BWm1pTAf.js.map +1 -0
- package/src/{models-BkMgeaOL.js → models-DwYioyNV.js} +203 -32
- package/src/models-DwYioyNV.js.map +1 -0
- package/src/runtime/blocks.js +22 -9
- package/src/runtime/blocks.js.map +1 -1
- package/src/runtime/collaboration.js.map +1 -1
- package/src/runtime/delivery/DocumentRenderer.js +7 -4
- package/src/runtime/delivery/DocumentRenderer.js.map +1 -1
- package/src/runtime/delivery/blocks.js +3 -1
- package/src/runtime/delivery/blocks.js.map +1 -1
- package/src/runtime/delivery/context.js +1 -1
- package/src/runtime/delivery/document-page.js +12 -12
- package/src/runtime/delivery/document-page.js.map +1 -1
- package/src/runtime/delivery/document.js +4 -2
- package/src/runtime/delivery/document.js.map +1 -1
- package/src/runtime/delivery/error.js.map +1 -1
- package/src/runtime/delivery/plugin.js +22 -2
- package/src/runtime/delivery/plugin.js.map +1 -1
- package/src/runtime/delivery/types.d.ts +5 -0
- package/src/runtime/delivery/useCmsDocumentSeo.d.ts +56 -0
- package/src/runtime/delivery/useCmsDocumentSeo.js +99 -0
- package/src/runtime/delivery/useCmsDocumentSeo.js.map +1 -0
- package/src/runtime/delivery/useDocument.js +2 -2
- package/src/runtime/delivery/useDocument.js.map +1 -1
- package/src/runtime/delivery/useDocumentQuery.js +2 -2
- package/src/runtime/delivery/useDocumentQuery.js.map +1 -1
- package/src/runtime/forms/document.js +38 -0
- package/src/runtime/forms/document.js.map +1 -0
- package/src/runtime/forms/useCmsForm.js +149 -0
- package/src/runtime/forms/useCmsForm.js.map +1 -0
- package/src/runtime/i18n/translator.js +22 -0
- package/src/runtime/i18n/translator.js.map +1 -0
- package/src/runtime/i18n/useCmsI18n.d.ts +9 -0
- package/src/runtime/i18n/useCmsI18n.js +29 -0
- package/src/runtime/i18n/useCmsI18n.js.map +1 -0
- package/src/runtime/installation.js +1 -1
- package/src/runtime/plugin.js +1 -1
- package/src/runtime/plugin.js.map +1 -1
- package/src/runtime/preview/comark-document.js +0 -1
- package/src/runtime/preview/comark-document.js.map +1 -1
- package/src/runtime/preview/connection.js +1 -1
- package/src/runtime/preview/controller.js +10 -3
- package/src/runtime/preview/controller.js.map +1 -1
- package/src/runtime/preview/overlay.js.map +1 -1
- package/src/runtime/preview/plugin.client.js +59 -48
- package/src/runtime/preview/plugin.client.js.map +1 -1
- package/src/runtime/preview/protocol.js.map +1 -1
- package/src/runtime/preview/store.js +1 -1
- package/src/runtime/preview/targets.js +0 -1
- package/src/runtime/preview/targets.js.map +1 -1
- package/src/runtime/sitemap-entries.js +17 -0
- package/src/runtime/sitemap-entries.js.map +1 -0
- package/src/runtime/sitemap.js +24 -0
- package/src/runtime/sitemap.js.map +1 -0
- package/src/runtime/studio-app.js.map +1 -1
- package/src/runtime/studio-entry.js +2 -2
- package/src/runtime/studio-entry.js.map +1 -1
- package/src/runtime/studio-navigation.js +1 -1
- package/src/scalars-B2lgGUnj.js +26 -0
- package/src/scalars-B2lgGUnj.js.map +1 -0
- package/src/{store-CfFFx9Ua.js → store-DjJs3I7k.js} +12 -7
- package/src/store-DjJs3I7k.js.map +1 -0
- package/studio/{CreateDocumentDialog--L9lTfx6.js → CreateDocumentDialog-CTeIFlPb.js} +44 -44
- package/studio/{collaboration-DUuu678R.js → collaboration-DcK2F6v9.js} +7 -6
- package/studio/{documentPlanning-BXX04Woe.js → documentPlanning-Bfv-d7OU.js} +7310 -6479
- package/studio/{editor-PexnXkJY.js → editor-Bzlh3zXt.js} +7965 -7955
- package/studio/host.css +1 -1
- package/studio/host.js +10416 -7981
- package/src/Configuration-DyKo0sLS.js.map +0 -1
- package/src/DocumentEngine-DYObwMDY.js.map +0 -1
- package/src/HttpPaths-BbS21Jy8.js.map +0 -1
- package/src/StudioHttp-jR2-bc01.js +0 -718
- package/src/StudioHttp-jR2-bc01.js.map +0 -1
- package/src/connection-CBTY-TzB.js.map +0 -1
- package/src/installation-DeuBiFRE.js.map +0 -1
- package/src/location-DtYv2aes.js.map +0 -1
- package/src/metadata-DE1Mc2td.js.map +0 -1
- package/src/models-BkMgeaOL.js.map +0 -1
- package/src/models-C3witQBK.js.map +0 -1
- package/src/store-CfFFx9Ua.js.map +0 -1
|
@@ -1,31 +1,30 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
import {
|
|
3
|
-
import { a as
|
|
4
|
-
import { A as
|
|
5
|
-
import { a as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { a as EditorNodeId, c as RevisionId, d as SourcePath, i as DocumentVersion, r as DocumentId, t as DocumentGeneration } from "./scalars-B2lgGUnj.js";
|
|
2
|
+
import { i as FormResult, n as FORM_SUBMISSION_MAX_BYTES } from "./Forms-CfcI3tPU.js";
|
|
3
|
+
import { a as encodeCmsEditorComponentAttributes, d as createCmsComarkRenderData, i as validateFormValues, t as documentForms } from "./Forms-DxbkU3_5.js";
|
|
4
|
+
import { A as RemoveDocumentInput, C as MarkdownNode, Ct as MediaTooLarge, G as SeoRevision, H as SeoConfirmationRequired, M as UndeleteDocumentInput, N as UnpublishDocumentInput, P as UpdateDocumentInput, Q as AuthenticationRequired, S as DuplicateDocumentInput, St as MediaStatus, T as PublishDocumentInput, U as SeoMediaUnavailable, Y as SeoVersionConflict, Z as AccessDenied, _ as DocumentSource, _t as MediaObjectKey, a as DOCUMENT_REVISION_ACTIONS, at as ImageMimeType, bt as MediaReference, c as DocumentImportInput, ct as MediaCleanupReport, dt as MediaFolder, f as DocumentMutationResult, ft as MediaId, g as DocumentRevisionAction, gt as MediaNotFound, ht as MediaName, it as StudioRequestContext, j as RestoreDocumentInput, kt as RequestId, lt as MediaConflict, mt as MediaInvalid, n as CmsMarkdownDocument, nt as StudioActor, o as DeletedDocument, ot as MediaAsset, pt as MediaIntegrityReport, rt as StudioEntryDenied, st as MediaByteSize, tt as Role, ut as MediaCursor, vt as MediaPage, w as MoveDocumentInput, wt as MediaUpload, xt as MediaReferenced } from "./models-DwYioyNV.js";
|
|
5
|
+
import { $ as DocumentRevision, A as StudioBadRequest, B as User, C as DocumentStore, D as CurrentStudioRequest, E as defaultAccessControlLayer, F as studioInternalError, G as UserSelfRemovalDenied, H as UserAlreadyExists, I as InitializationAlreadyCompleted, J as CollaborationServerMessage, K as UserSelfRoleChangeDenied, L as InitializationProofInvalid, M as StudioRequestConflict, N as StudioSchemaError, O as StudioApi, P as StudioInternalError, Q as DocumentHistoryInvalidCursor, R as InitializationState, S as DocumentPersistenceError, T as AccessControl, U as UserNotFound, V as UserAdministrationDenied, W as UserPage, X as StateVector, Y as CollaborationTicket, Z as DocumentHistoryComparisonTooLarge, _ as Documents, a as mediaObjectPrefix, at as ActivityEntry, b as cursorCodec, c as Seo, ct as ActivityPage, d as seoDeliveryLayer, dt as DocumentMediaUnavailable, et as ACTIVITY_ACTIONS, f as seoLayer, ft as DocumentNotFound, g as DocumentPreviews, gt as PublishedDocumentNotFound, h as DocumentMaintenance, ht as DocumentVersionConflict, i as MediaObjectStore, it as ActivityCursorInvalid, k as StudioAuthentication, l as SeoDelivery, lt as ActivityTarget, m as SeoStore, mt as DocumentValidationFailed, n as s3MediaObjectStoreLayer, nt as ActivityContext, o as PreviewSeo, ot as ActivityEntryDraft, p as SeoPersistenceError, pt as DocumentPathConflict, q as CollaborationClientMessage, r as MediaPersistenceError, rt as ActivityCursor, s as PublishedSeo, st as ActivityId, tt as ActivityAction, u as makeDefaultSeoOptions, ut as ActivityTargetKind, v as PublishedDocuments, w as renderEditorMarkdown, x as DocumentGenerationConflict, y as documentsLayer, z as CreateUserInput } from "./S3MediaObjects-JKMAWyVB.js";
|
|
6
|
+
import { a as CMS_FORM_SUBMIT_PATH, l as CMS_READINESS_PATH, o as CMS_LIVENESS_PATH, p as STUDIO_COLLABORATION_PATH, t as CMS_AUTHENTICATION_PATH, u as STUDIO_API_PATH } from "./HttpPaths-c9nLZ9H2.js";
|
|
7
|
+
import { a as createEditorNodeId, o as editorNodeId, r as identifiedAttributes, t as assertCanonicalEditorContent } from "./metadata-BEZPWh-3.js";
|
|
8
|
+
import { C as finiteNumberAttribute, S as compactJsonAttributes, _ as comarkTagForEditorMark, b as encodeEditorImageAttributes, g as EDITOR_MARK_TYPES, h as comarkToEditor, u as EditorDocumentInvalid, v as isEditorMark, w as nonEmptyStringAttribute, x as normalizeEditorImageLayout, y as EDITOR_IMAGE_LAYOUT_ATTRIBUTE } from "./Configuration-CA_jvQKG.js";
|
|
9
|
+
import { Cause, Clock, Context, Crypto, Data, DateTime, Deferred, Effect, Encoding, Exit, Fiber, Layer, Option, Predicate, PubSub, Pull, Queue, Redacted, Ref, Result, Schedule, Schema, Scope, Semaphore, Stream, flow } from "effect";
|
|
8
10
|
import { Extension, Node, getSchema, mergeAttributes } from "@tiptap/core";
|
|
9
11
|
import { prosemirrorJSONToYDoc, ySyncPluginKey, yXmlFragmentToProseMirrorRootNode } from "@tiptap/y-tiptap";
|
|
10
|
-
import {
|
|
11
|
-
import { HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse, Multipart } from "effect/unstable/http";
|
|
12
|
+
import { FindMyWay, HttpEffect, HttpMiddleware, HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse, Multipart } from "effect/unstable/http";
|
|
12
13
|
import { HttpApiBuilder, HttpApiMiddleware } from "effect/unstable/httpapi";
|
|
13
|
-
import * as NodeCrypto
|
|
14
|
+
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto";
|
|
15
|
+
import { createAuthMiddleware, getSessionFromCtx, isAPIError } from "better-auth/api";
|
|
14
16
|
import { betterAuth } from "better-auth";
|
|
15
17
|
import { createAccessControl } from "better-auth/plugins/access";
|
|
16
18
|
import { admin } from "better-auth/plugins/admin";
|
|
17
19
|
import { defaultStatements } from "better-auth/plugins/admin/access";
|
|
18
|
-
import { APIError, createAuthMiddleware, getSessionFromCtx, isAPIError } from "better-auth/api";
|
|
19
20
|
import { Hocuspocus, IncomingMessage, MessageType, shouldSkipStoreHooks } from "@hocuspocus/server";
|
|
20
|
-
import { renderMarkdown } from "comark/render";
|
|
21
|
-
import { diffArrays } from "diff";
|
|
22
21
|
import StarterKit from "@tiptap/starter-kit";
|
|
23
22
|
import * as Y from "yjs";
|
|
24
23
|
import { Doc, applyUpdate, encodeStateAsUpdate, encodeStateVector } from "yjs";
|
|
25
|
-
import {
|
|
24
|
+
import { RateLimiter } from "effect/unstable/persistence";
|
|
26
25
|
import { PgClient } from "@effect/sql-pg";
|
|
27
26
|
import { isDeepStrictEqual } from "node:util";
|
|
28
|
-
import { and, asc, count, desc, eq, inArray, lt, lte, ne, or, sql } from "drizzle-orm";
|
|
27
|
+
import { and, asc, count, desc, eq, gt, inArray, lt, lte, ne, or, sql } from "drizzle-orm";
|
|
29
28
|
import * as PgDrizzle from "drizzle-orm/effect-postgres";
|
|
30
29
|
import { createInsertSchema, createSelectSchema } from "drizzle-orm/effect-schema";
|
|
31
30
|
import { alias, bigint, boolean, bytea, check, foreignKey, index, integer, jsonb, pgTable, primaryKey, text, timestamp, unique, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
|
@@ -58,50 +57,6 @@ function activityEntryFromDraft(id, draft, occurredAt) {
|
|
|
58
57
|
});
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
//#endregion
|
|
62
|
-
//#region ../server/src/internal/access-control.ts
|
|
63
|
-
const VIEWER_ACTIONS = /* @__PURE__ */ new Set([
|
|
64
|
-
"document.browse",
|
|
65
|
-
"document.read",
|
|
66
|
-
"document.trash",
|
|
67
|
-
"document.history",
|
|
68
|
-
"media.read",
|
|
69
|
-
"media.search",
|
|
70
|
-
"media.integrity"
|
|
71
|
-
]);
|
|
72
|
-
const EDITOR_ACTIONS = /* @__PURE__ */ new Set([...DOCUMENT_ACTIONS, ...MEDIA_ACTIONS.filter((action) => action !== "media.cleanup")]);
|
|
73
|
-
var AccessControl = class extends Context.Service()("@internal/server/AccessControl") {};
|
|
74
|
-
const defaultAccessControlLayer = Layer.succeed(AccessControl, AccessControl.of({ require: Effect.fn("AccessControl.require")(function* (context, decision) {
|
|
75
|
-
if (isAllowed(context, decision.action)) return;
|
|
76
|
-
return yield* new AccessDenied({
|
|
77
|
-
action: decision.action,
|
|
78
|
-
actorId: context.actor.id,
|
|
79
|
-
resource: decision.resource
|
|
80
|
-
});
|
|
81
|
-
}) }));
|
|
82
|
-
function isAllowed(context, action) {
|
|
83
|
-
return context.actor.roles.some((role) => {
|
|
84
|
-
if (role === "administrator") return true;
|
|
85
|
-
return role === "editor" && EDITOR_ACTIONS.has(action) || role === "viewer" && VIEWER_ACTIONS.has(action);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
//#endregion
|
|
90
|
-
//#region ../server/src/internal/cursor.ts
|
|
91
|
-
/** Owns the opaque base64url JSON representation shared by keyset cursors. */
|
|
92
|
-
function cursorCodec(schema, makeCursor, invalid) {
|
|
93
|
-
const decodeJson = Schema.decodeUnknownResult(schema);
|
|
94
|
-
const encodeJson = Schema.encodeSync(schema);
|
|
95
|
-
return {
|
|
96
|
-
decode: (cursor) => {
|
|
97
|
-
const decoded = Encoding.decodeBase64UrlString(cursor);
|
|
98
|
-
if (Result.isFailure(decoded)) return Result.fail(invalid());
|
|
99
|
-
return decodeJson(decoded.success).pipe(Result.mapError(invalid));
|
|
100
|
-
},
|
|
101
|
-
encode: (payload) => makeCursor(Encoding.encodeBase64Url(encodeJson(payload)))
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
60
|
//#endregion
|
|
106
61
|
//#region ../server/src/internal/activity/activity.ts
|
|
107
62
|
var ActivityUnavailable = class extends Schema.TaggedError()("Activity.Unavailable", {
|
|
@@ -109,11 +64,11 @@ var ActivityUnavailable = class extends Schema.TaggedError()("Activity.Unavailab
|
|
|
109
64
|
cause: Schema.Defect()
|
|
110
65
|
}) {};
|
|
111
66
|
var Activity = class extends Context.Service()("@internal/server/Activity") {};
|
|
112
|
-
const CursorPayload$
|
|
67
|
+
const CursorPayload$1 = Schema.Struct({
|
|
113
68
|
id: ActivityId,
|
|
114
69
|
occurredAt: Schema.DateTimeUtcFromString
|
|
115
70
|
});
|
|
116
|
-
const cursors$
|
|
71
|
+
const cursors$1 = cursorCodec(Schema.fromJsonString(CursorPayload$1), (value) => ActivityCursor.make(value), () => new ActivityCursorInvalid());
|
|
117
72
|
const activityLayer = Layer.effect(Activity, Effect.gen(function* () {
|
|
118
73
|
const accessControl = yield* AccessControl;
|
|
119
74
|
const storage = yield* ActivityStore;
|
|
@@ -122,7 +77,7 @@ const activityLayer = Layer.effect(Activity, Effect.gen(function* () {
|
|
|
122
77
|
action: "activity.read",
|
|
123
78
|
resource: { kind: "activity" }
|
|
124
79
|
});
|
|
125
|
-
const cursor = query.cursor ? yield* Effect.fromResult(cursors$
|
|
80
|
+
const cursor = query.cursor ? yield* Effect.fromResult(cursors$1.decode(query.cursor)) : void 0;
|
|
126
81
|
const page = yield* storage.list({
|
|
127
82
|
action: query.action,
|
|
128
83
|
actorId: query.actorId,
|
|
@@ -138,7 +93,7 @@ const activityLayer = Layer.effect(Activity, Effect.gen(function* () {
|
|
|
138
93
|
};
|
|
139
94
|
return page.next ? ActivityPage.make({
|
|
140
95
|
...output,
|
|
141
|
-
nextCursor: cursors$
|
|
96
|
+
nextCursor: cursors$1.encode(page.next)
|
|
142
97
|
}) : ActivityPage.make(output);
|
|
143
98
|
});
|
|
144
99
|
return Activity.of({ list });
|
|
@@ -254,7 +209,6 @@ const ROUND = [
|
|
|
254
209
|
3204031479,
|
|
255
210
|
3329325298
|
|
256
211
|
];
|
|
257
|
-
/** Portable synchronous SHA-256 for deterministic journal fingerprints and secret redaction. */
|
|
258
212
|
function sha256Hex(value) {
|
|
259
213
|
const input = new TextEncoder().encode(value);
|
|
260
214
|
const bitLength = BigInt(input.length) * 8n;
|
|
@@ -334,14 +288,14 @@ const PendingChange = ChangeDraft.pipe(Schema.fieldsAssign({
|
|
|
334
288
|
updatedAt: Schema.DateTimeUtcFromString
|
|
335
289
|
}));
|
|
336
290
|
function changeFingerprint(change) {
|
|
337
|
-
const encoded = Schema.
|
|
291
|
+
const encoded = Result.getOrThrow(Schema.encodeResult(ChangeDraft)(change));
|
|
338
292
|
return sha256Hex(JSON.stringify(canonicalJson(encoded)));
|
|
339
293
|
}
|
|
340
|
-
const isJsonObject$
|
|
341
|
-
const decodeJson$2 = Schema.
|
|
294
|
+
const isJsonObject$4 = Schema.is(Schema.JsonObject);
|
|
295
|
+
const decodeJson$2 = flow(Schema.decodeUnknownResult(Schema.Json), Result.getOrThrow);
|
|
342
296
|
function canonicalJson(value) {
|
|
343
297
|
if (Array.isArray(value)) return value.map(canonicalJson);
|
|
344
|
-
if (!isJsonObject$
|
|
298
|
+
if (!isJsonObject$4(value)) return value;
|
|
345
299
|
return Object.fromEntries(Object.keys(value).sort((left, right) => left.localeCompare(right)).map((key) => [key, canonicalJson(decodeJson$2(value[key]))]));
|
|
346
300
|
}
|
|
347
301
|
|
|
@@ -349,17 +303,17 @@ function canonicalJson(value) {
|
|
|
349
303
|
//#region ../server/src/internal/change-journal/change-journal.ts
|
|
350
304
|
const ChangeBegin = Data.taggedEnum();
|
|
351
305
|
function requireUnresolvedChange(result) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
changeId:
|
|
306
|
+
return ChangeBegin.$match(result, {
|
|
307
|
+
Unresolved: ({ change }) => Effect.succeed(change),
|
|
308
|
+
Recorded: ({ entry }) => Effect.fail(new ChangeAlreadyResolved({
|
|
309
|
+
changeId: entry.id,
|
|
356
310
|
outcome: "applied"
|
|
357
|
-
}))
|
|
358
|
-
|
|
359
|
-
changeId
|
|
311
|
+
})),
|
|
312
|
+
Cancelled: ({ changeId }) => Effect.fail(new ChangeAlreadyResolved({
|
|
313
|
+
changeId,
|
|
360
314
|
outcome: "cancelled"
|
|
361
|
-
}))
|
|
362
|
-
}
|
|
315
|
+
}))
|
|
316
|
+
});
|
|
363
317
|
}
|
|
364
318
|
var ChangeJournal = class extends Context.Service()("@internal/server/ChangeJournal") {};
|
|
365
319
|
const findPendingChange = Effect.fn("ChangeJournal.findPending")(function* (journal, subject) {
|
|
@@ -424,7 +378,7 @@ function initialAdministratorDraft(identity, intent) {
|
|
|
424
378
|
action: "user.created",
|
|
425
379
|
context: identity.context,
|
|
426
380
|
id: identity.id,
|
|
427
|
-
intent: Schema.
|
|
381
|
+
intent: Result.getOrThrow(Schema.encodeResult(InitialAdministratorIntent)(intent)),
|
|
428
382
|
scope: "initialization",
|
|
429
383
|
subject: userEmailSubject(intent.normalizedEmail)
|
|
430
384
|
});
|
|
@@ -566,7 +520,7 @@ function userChangeDraft(identity, intent) {
|
|
|
566
520
|
action: userChangeAction(intent),
|
|
567
521
|
context: identity.context,
|
|
568
522
|
id: identity.id,
|
|
569
|
-
intent: Schema.
|
|
523
|
+
intent: Result.getOrThrow(Schema.encodeResult(UserChangeIntent)(intent)),
|
|
570
524
|
scope: "users",
|
|
571
525
|
subject: userEmailSubject(intent.kind === "create" ? intent.normalizedEmail : intent.email)
|
|
572
526
|
};
|
|
@@ -732,12 +686,22 @@ function appliedResolution(change, intent, userId) {
|
|
|
732
686
|
//#region ../auth/src/internal/user.ts
|
|
733
687
|
const cmsUserCreationChangeField = "cmsCreationChangeId";
|
|
734
688
|
const cmsUserSessionsRevokedChangeField = "cmsSessionsRevokedChangeId";
|
|
689
|
+
const BetterAuthUser = Schema.Struct({
|
|
690
|
+
banned: Schema.optionalKey(Schema.NullOr(Schema.Boolean)),
|
|
691
|
+
createdAt: Schema.Date,
|
|
692
|
+
email: Schema.String,
|
|
693
|
+
emailVerified: Schema.Boolean,
|
|
694
|
+
id: Schema.String,
|
|
695
|
+
name: Schema.String,
|
|
696
|
+
role: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
697
|
+
updatedAt: Schema.Date
|
|
698
|
+
});
|
|
735
699
|
const BetterAuthUserRecoveryEvidence = Schema.Struct({
|
|
736
700
|
[cmsUserCreationChangeField]: Schema.optionalKey(Schema.UndefinedOr(Schema.NullOr(ActivityId))),
|
|
737
701
|
[cmsUserSessionsRevokedChangeField]: Schema.optionalKey(Schema.UndefinedOr(Schema.NullOr(ActivityId)))
|
|
738
702
|
});
|
|
739
|
-
function decodeBetterAuthUser(
|
|
740
|
-
return Schema.decodeUnknownEffect(User)({
|
|
703
|
+
function decodeBetterAuthUser(value) {
|
|
704
|
+
return Schema.decodeEffect(BetterAuthUser)(value).pipe(Effect.flatMap((user) => Schema.decodeUnknownEffect(User)({
|
|
741
705
|
banned: user.banned === true,
|
|
742
706
|
createdAt: user.createdAt.toISOString(),
|
|
743
707
|
email: user.email,
|
|
@@ -746,7 +710,7 @@ function decodeBetterAuthUser(user) {
|
|
|
746
710
|
name: user.name,
|
|
747
711
|
role: user.role,
|
|
748
712
|
updatedAt: user.updatedAt.toISOString()
|
|
749
|
-
});
|
|
713
|
+
})));
|
|
750
714
|
}
|
|
751
715
|
function decodeBetterAuthActor(user) {
|
|
752
716
|
return Schema.decodeEffect(StudioActor)(betterAuthActorInput(user));
|
|
@@ -783,12 +747,12 @@ function betterAuthActivityWriter(storage, crypto) {
|
|
|
783
747
|
return yield* storage.append(activityEntryFromDraft(id, draft, yield* DateTime.now));
|
|
784
748
|
});
|
|
785
749
|
}
|
|
786
|
-
function betterAuthActivityOptions(writeActivity, crypto) {
|
|
750
|
+
function betterAuthActivityOptions(writeActivity, crypto, runPromise) {
|
|
787
751
|
const capturedActors = /* @__PURE__ */ new WeakMap();
|
|
788
|
-
const write = (action, actor) =>
|
|
752
|
+
const write = (action, actor) => runPromise(crypto.randomUUIDv4.pipe(Effect.orDie, Effect.map((requestId) => activityDraft(action, actor, requestId)), Effect.flatMap(writeActivity), Effect.asVoid, Effect.catchCauseIf((cause) => Cause.interruptors(cause).size === 0, () => Effect.logWarning("Authentication Activity could not be recorded.", { action }).pipe(Effect.ignoreCause))));
|
|
789
753
|
const before = createAuthMiddleware((context) => {
|
|
790
754
|
if (context.path !== "/sign-out" && context.path !== "/change-password") return Promise.resolve();
|
|
791
|
-
return
|
|
755
|
+
return runPromise(Effect.tryPromise(() => getSessionFromCtx(context, { disableRefresh: true })).pipe(Effect.orElseSucceed(() => null), Effect.tap((session) => Effect.sync(() => {
|
|
792
756
|
const actor = session?.user ? decodeActor(session.user) : void 0;
|
|
793
757
|
if (actor) capturedActors.set(context.context, actor);
|
|
794
758
|
})), Effect.asVoid));
|
|
@@ -832,7 +796,149 @@ function activityDraft(action, actor, requestId) {
|
|
|
832
796
|
}
|
|
833
797
|
function decodeActor(user) {
|
|
834
798
|
const option = decodeBetterAuthActorOption(user);
|
|
835
|
-
return
|
|
799
|
+
return Option.getOrUndefined(option);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
//#endregion
|
|
803
|
+
//#region ../auth/src/internal/better-auth.ts
|
|
804
|
+
const cmsBetterAuthAccess = createAccessControl(defaultStatements);
|
|
805
|
+
const cmsBetterAuthRoles = {
|
|
806
|
+
administrator: cmsBetterAuthAccess.newRole({
|
|
807
|
+
session: ["list", "revoke"],
|
|
808
|
+
user: [
|
|
809
|
+
"create",
|
|
810
|
+
"list",
|
|
811
|
+
"get",
|
|
812
|
+
"set-role",
|
|
813
|
+
"delete",
|
|
814
|
+
"update"
|
|
815
|
+
]
|
|
816
|
+
}),
|
|
817
|
+
editor: cmsBetterAuthAccess.newRole({
|
|
818
|
+
session: [],
|
|
819
|
+
user: []
|
|
820
|
+
}),
|
|
821
|
+
viewer: cmsBetterAuthAccess.newRole({
|
|
822
|
+
session: [],
|
|
823
|
+
user: []
|
|
824
|
+
})
|
|
825
|
+
};
|
|
826
|
+
const timestampFields = {
|
|
827
|
+
createdAt: "created_at",
|
|
828
|
+
updatedAt: "updated_at"
|
|
829
|
+
};
|
|
830
|
+
function createCmsBetterAuthOptions(options, activity) {
|
|
831
|
+
return {
|
|
832
|
+
advanced: { database: {
|
|
833
|
+
generateId: "uuid",
|
|
834
|
+
joins: true
|
|
835
|
+
} },
|
|
836
|
+
appName: "CMS Studio",
|
|
837
|
+
basePath: options.basePath ?? "/api/cms/auth",
|
|
838
|
+
baseURL: options.baseURL,
|
|
839
|
+
database: options.database,
|
|
840
|
+
emailAndPassword: {
|
|
841
|
+
disableSignUp: true,
|
|
842
|
+
enabled: true,
|
|
843
|
+
...activity?.emailAndPassword
|
|
844
|
+
},
|
|
845
|
+
hooks: activity?.hooks,
|
|
846
|
+
user: {
|
|
847
|
+
additionalFields: {
|
|
848
|
+
[cmsUserCreationChangeField]: hiddenChangeField("cms_creation_change_id"),
|
|
849
|
+
[cmsUserSessionsRevokedChangeField]: hiddenChangeField("cms_sessions_revoked_change_id")
|
|
850
|
+
},
|
|
851
|
+
modelName: "auth_users",
|
|
852
|
+
fields: {
|
|
853
|
+
...timestampFields,
|
|
854
|
+
emailVerified: "email_verified"
|
|
855
|
+
}
|
|
856
|
+
},
|
|
857
|
+
session: {
|
|
858
|
+
cookieCache: {
|
|
859
|
+
enabled: false,
|
|
860
|
+
strategy: "jwt"
|
|
861
|
+
},
|
|
862
|
+
modelName: "auth_sessions",
|
|
863
|
+
fields: {
|
|
864
|
+
...timestampFields,
|
|
865
|
+
expiresAt: "expires_at",
|
|
866
|
+
ipAddress: "ip_address",
|
|
867
|
+
userAgent: "user_agent",
|
|
868
|
+
userId: "user_id"
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
account: {
|
|
872
|
+
modelName: "auth_accounts",
|
|
873
|
+
fields: {
|
|
874
|
+
accessToken: "access_token",
|
|
875
|
+
accessTokenExpiresAt: "access_token_expires_at",
|
|
876
|
+
accountId: "account_id",
|
|
877
|
+
...timestampFields,
|
|
878
|
+
idToken: "id_token",
|
|
879
|
+
providerId: "provider_id",
|
|
880
|
+
refreshToken: "refresh_token",
|
|
881
|
+
refreshTokenExpiresAt: "refresh_token_expires_at",
|
|
882
|
+
userId: "user_id"
|
|
883
|
+
}
|
|
884
|
+
},
|
|
885
|
+
verification: {
|
|
886
|
+
modelName: "auth_verifications",
|
|
887
|
+
fields: {
|
|
888
|
+
...timestampFields,
|
|
889
|
+
expiresAt: "expires_at"
|
|
890
|
+
}
|
|
891
|
+
},
|
|
892
|
+
rateLimit: {
|
|
893
|
+
enabled: true,
|
|
894
|
+
storage: "database",
|
|
895
|
+
modelName: "auth_rate_limits",
|
|
896
|
+
fields: { lastRequest: "last_request" }
|
|
897
|
+
},
|
|
898
|
+
plugins: [admin({
|
|
899
|
+
ac: cmsBetterAuthAccess,
|
|
900
|
+
adminRoles: ["administrator"],
|
|
901
|
+
defaultRole: "editor",
|
|
902
|
+
roles: cmsBetterAuthRoles,
|
|
903
|
+
schema: {
|
|
904
|
+
session: { fields: { impersonatedBy: "impersonated_by" } },
|
|
905
|
+
user: { fields: {
|
|
906
|
+
banExpires: "ban_expires",
|
|
907
|
+
banReason: "ban_reason",
|
|
908
|
+
banned: "banned",
|
|
909
|
+
role: "role"
|
|
910
|
+
} }
|
|
911
|
+
}
|
|
912
|
+
})],
|
|
913
|
+
secret: options.secret
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
function createCmsBetterAuth(options, activity) {
|
|
917
|
+
return betterAuth(createCmsBetterAuthOptions(options, activity));
|
|
918
|
+
}
|
|
919
|
+
function hiddenChangeField(fieldName) {
|
|
920
|
+
return {
|
|
921
|
+
fieldName,
|
|
922
|
+
input: false,
|
|
923
|
+
required: false,
|
|
924
|
+
returned: false,
|
|
925
|
+
type: "string"
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
//#endregion
|
|
930
|
+
//#region ../auth/src/internal/handler.ts
|
|
931
|
+
function rejectExternalAdminRoutes(handler, basePath) {
|
|
932
|
+
const mountedAt = canonicalBasePath(basePath);
|
|
933
|
+
return (request) => {
|
|
934
|
+
const path = new URL(request.url).pathname;
|
|
935
|
+
const relative = mountedAt === "/" ? path : path === mountedAt ? "/" : path.startsWith(`${mountedAt}/`) ? path.slice(mountedAt.length) : path;
|
|
936
|
+
if (relative === "/admin" || relative.startsWith("/admin/")) return Promise.resolve(new Response(null, { status: 404 }));
|
|
937
|
+
return handler(request);
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function canonicalBasePath(basePath) {
|
|
941
|
+
return (basePath.startsWith("/") ? basePath : `/${basePath}`).replace(/\/+$/u, "") || "/";
|
|
836
942
|
}
|
|
837
943
|
|
|
838
944
|
//#endregion
|
|
@@ -915,9 +1021,24 @@ function matches(code, ...candidates) {
|
|
|
915
1021
|
return code !== void 0 && candidates.some((candidate) => candidate.code === code);
|
|
916
1022
|
}
|
|
917
1023
|
function apiErrorCode(cause) {
|
|
918
|
-
if (!(cause instanceof APIError)
|
|
919
|
-
const
|
|
920
|
-
|
|
1024
|
+
if (!(cause instanceof Error) || cause.name !== "APIError") return void 0;
|
|
1025
|
+
const apiError = Schema.decodeUnknownOption(Schema.Struct({
|
|
1026
|
+
body: Schema.Struct({ code: Schema.String }),
|
|
1027
|
+
statusCode: Schema.Int
|
|
1028
|
+
}))(cause);
|
|
1029
|
+
return Option.map(apiError, ({ body }) => body.code).pipe(Option.getOrUndefined);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
//#endregion
|
|
1033
|
+
//#region ../auth/src/internal/provider.ts
|
|
1034
|
+
function providerCall(operation, request, onError) {
|
|
1035
|
+
return Effect.tryPromise({
|
|
1036
|
+
catch: (cause) => onError(operation, cause),
|
|
1037
|
+
try: request
|
|
1038
|
+
}).pipe(Effect.withSpan(operation));
|
|
1039
|
+
}
|
|
1040
|
+
function providerAdapter(auth, operation, onError) {
|
|
1041
|
+
return providerCall(operation, () => auth.$context, onError).pipe(Effect.map((context) => context.internalAdapter));
|
|
921
1042
|
}
|
|
922
1043
|
|
|
923
1044
|
//#endregion
|
|
@@ -926,13 +1047,10 @@ function betterAuthIdentity(auth) {
|
|
|
926
1047
|
const authorityHeaders = /* @__PURE__ */ new WeakMap();
|
|
927
1048
|
const authenticate = Effect.fn("BetterAuth.Authentication.authenticate")(function* (headers) {
|
|
928
1049
|
const providerHeaders = new globalThis.Headers(headers);
|
|
929
|
-
const session = yield*
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
query: { disableCookieCache: true }
|
|
934
|
-
})
|
|
935
|
-
});
|
|
1050
|
+
const session = yield* providerCall("BetterAuth.getSession", () => auth.api.getSession({
|
|
1051
|
+
headers: providerHeaders,
|
|
1052
|
+
query: { disableCookieCache: true }
|
|
1053
|
+
}), () => studioInternalError());
|
|
936
1054
|
if (!session?.user) return null;
|
|
937
1055
|
const actor = yield* decodeBetterAuthActor(session.user).pipe(Effect.mapError(() => studioInternalError()));
|
|
938
1056
|
const authority = issueIdentityAuthority();
|
|
@@ -1026,16 +1144,13 @@ function betterAuthIdentity(auth) {
|
|
|
1026
1144
|
const apply = (changeId) => applyOperation(auth, "BetterAuth.revokeUserSessions", command, () => auth.api.revokeUserSessions({
|
|
1027
1145
|
body: { userId: command.userId },
|
|
1028
1146
|
headers
|
|
1029
|
-
})).pipe(Effect.flatMap(() =>
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
headers
|
|
1037
|
-
})
|
|
1038
|
-
})), Effect.as(user));
|
|
1147
|
+
})).pipe(Effect.flatMap(() => providerCall("BetterAuth.adminUpdateUser.sessionRevocationReceipt", () => auth.api.adminUpdateUser({
|
|
1148
|
+
body: {
|
|
1149
|
+
data: { [cmsUserSessionsRevokedChangeField]: changeId },
|
|
1150
|
+
userId: command.userId
|
|
1151
|
+
},
|
|
1152
|
+
headers
|
|
1153
|
+
}), (operation, cause) => IdentityApplyFailure.Unknown({ error: unavailable$4(operation, cause) }))), Effect.as(user));
|
|
1039
1154
|
return IdentityPreparation.Prepared({
|
|
1040
1155
|
apply,
|
|
1041
1156
|
intent,
|
|
@@ -1053,22 +1168,13 @@ function betterAuthIdentity(auth) {
|
|
|
1053
1168
|
};
|
|
1054
1169
|
}
|
|
1055
1170
|
function prepareOperation(auth, operation, request) {
|
|
1056
|
-
return
|
|
1057
|
-
catch: (cause) => prepareError(auth.$ERROR_CODES, operation, cause),
|
|
1058
|
-
try: request
|
|
1059
|
-
});
|
|
1171
|
+
return providerCall(operation, request, (name, cause) => prepareError(auth.$ERROR_CODES, name, cause));
|
|
1060
1172
|
}
|
|
1061
1173
|
function prepareUserOperation(auth, operation, userId, request) {
|
|
1062
|
-
return
|
|
1063
|
-
catch: (cause) => prepareError(auth.$ERROR_CODES, operation, cause, userId),
|
|
1064
|
-
try: request
|
|
1065
|
-
});
|
|
1174
|
+
return providerCall(operation, request, (name, cause) => prepareError(auth.$ERROR_CODES, name, cause, userId));
|
|
1066
1175
|
}
|
|
1067
1176
|
function applyOperation(auth, operation, command, request) {
|
|
1068
|
-
return
|
|
1069
|
-
catch: (cause) => applyError(auth.$ERROR_CODES, operation, command, cause),
|
|
1070
|
-
try: request
|
|
1071
|
-
});
|
|
1177
|
+
return providerCall(operation, request, (name, cause) => applyError(auth.$ERROR_CODES, name, command, cause));
|
|
1072
1178
|
}
|
|
1073
1179
|
function requireAuthority(authorityHeaders, authority) {
|
|
1074
1180
|
const headers = authorityHeaders.get(authority);
|
|
@@ -1106,13 +1212,10 @@ function betterAuthInitialIdentity(auth) {
|
|
|
1106
1212
|
});
|
|
1107
1213
|
const intent = initialAdministratorIntent(input);
|
|
1108
1214
|
return {
|
|
1109
|
-
apply:
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
...createInput
|
|
1114
|
-
} })
|
|
1115
|
-
}).pipe(Effect.flatMap((result) => decodeBetterAuthUser(result.user).pipe(Effect.mapError((cause) => InitialIdentityFailure.Unknown({ error: initializationUnavailable$1("BetterAuth.initialization.createUser", cause) }))))),
|
|
1215
|
+
apply: providerCall("BetterAuth.initialization.createUser", () => auth.api.createUser({ body: {
|
|
1216
|
+
data: { emailVerified: true },
|
|
1217
|
+
...createInput
|
|
1218
|
+
} }), (operation, cause) => initialIdentityError(auth.$ERROR_CODES, operation, createInput, cause)).pipe(Effect.flatMap((result) => decodeBetterAuthUser(result.user).pipe(Effect.mapError((cause) => InitialIdentityFailure.Unknown({ error: initializationUnavailable$1("BetterAuth.initialization.createUser", cause) }))))),
|
|
1116
1219
|
intent
|
|
1117
1220
|
};
|
|
1118
1221
|
} });
|
|
@@ -1121,22 +1224,13 @@ function betterAuthInitialIdentity(auth) {
|
|
|
1121
1224
|
//#endregion
|
|
1122
1225
|
//#region ../auth/src/internal/recovery.ts
|
|
1123
1226
|
function betterAuthRecovery(auth) {
|
|
1124
|
-
const userAdapter =
|
|
1125
|
-
|
|
1126
|
-
try: () => auth.$context
|
|
1127
|
-
}).pipe(Effect.map((context) => context.internalAdapter));
|
|
1128
|
-
const initialAdapter = Effect.tryPromise({
|
|
1129
|
-
catch: (cause) => initializationUnavailable$1("BetterAuth.initialization.recovery.context", cause),
|
|
1130
|
-
try: () => auth.$context
|
|
1131
|
-
}).pipe(Effect.map((context) => context.internalAdapter));
|
|
1227
|
+
const userAdapter = providerAdapter(auth, "BetterAuth.recovery.context", unavailable$4);
|
|
1228
|
+
const initialAdapter = providerAdapter(auth, "BetterAuth.initialization.recovery.context", initializationUnavailable$1);
|
|
1132
1229
|
const users = IdentityRecovery.of({ observe: Effect.fn("BetterAuth.Recovery.observeUserChange")(function* (intent) {
|
|
1133
1230
|
const adapter = yield* userAdapter;
|
|
1134
1231
|
switch (intent.kind) {
|
|
1135
1232
|
case "create": {
|
|
1136
|
-
const result = yield*
|
|
1137
|
-
catch: (cause) => unavailable$4("BetterAuth.recovery.findUserByEmail", cause),
|
|
1138
|
-
try: () => adapter.findUserByEmail(intent.normalizedEmail, { includeAccounts: true })
|
|
1139
|
-
});
|
|
1233
|
+
const result = yield* providerCall("BetterAuth.recovery.findUserByEmail", () => adapter.findUserByEmail(intent.normalizedEmail, { includeAccounts: true }), unavailable$4);
|
|
1140
1234
|
if (!result) return {
|
|
1141
1235
|
creationChangeId: null,
|
|
1142
1236
|
credentialReady: false,
|
|
@@ -1178,10 +1272,7 @@ function betterAuthRecovery(auth) {
|
|
|
1178
1272
|
return {
|
|
1179
1273
|
initial: InitialIdentityRecovery.of({ observe: Effect.fn("BetterAuth.Recovery.observeInitialAdministrator")(function* (intent) {
|
|
1180
1274
|
const adapter = yield* initialAdapter;
|
|
1181
|
-
const result = yield*
|
|
1182
|
-
catch: (cause) => initializationUnavailable$1("BetterAuth.recovery.findInitialAdministrator", cause),
|
|
1183
|
-
try: () => adapter.findUserByEmail(intent.normalizedEmail, { includeAccounts: true })
|
|
1184
|
-
});
|
|
1275
|
+
const result = yield* providerCall("BetterAuth.recovery.findInitialAdministrator", () => adapter.findUserByEmail(intent.normalizedEmail, { includeAccounts: true }), initializationUnavailable$1);
|
|
1185
1276
|
if (!result) return {
|
|
1186
1277
|
credentialReady: false,
|
|
1187
1278
|
user: null
|
|
@@ -1195,1589 +1286,165 @@ function betterAuthRecovery(auth) {
|
|
|
1195
1286
|
};
|
|
1196
1287
|
}
|
|
1197
1288
|
function hasCredential(accounts) {
|
|
1198
|
-
return accounts.some((account) => account.providerId === "credential" && (account.password?.length ?? 0) > 0);
|
|
1199
|
-
}
|
|
1200
|
-
function decodeUser(user, operation) {
|
|
1201
|
-
return decodeBetterAuthUser(user).pipe(Effect.mapError((cause) => unavailable$4(operation, cause)));
|
|
1202
|
-
}
|
|
1203
|
-
function findUser(adapter, userId) {
|
|
1204
|
-
return findRawUser(adapter, userId).pipe(Effect.flatMap((user) => user ? decodeUser(user, "BetterAuth.recovery.findUserById") : Effect.succeed(null)));
|
|
1205
|
-
}
|
|
1206
|
-
function findRawUser(adapter, userId) {
|
|
1207
|
-
return Effect.tryPromise({
|
|
1208
|
-
catch: (cause) => unavailable$4("BetterAuth.recovery.findUserById", cause),
|
|
1209
|
-
try: () => adapter.findUserById(userId)
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
function findSessionFingerprints(adapter, userId) {
|
|
1213
|
-
return Effect.tryPromise({
|
|
1214
|
-
catch: (cause) => unavailable$4("BetterAuth.recovery.listSessions", cause),
|
|
1215
|
-
try: () => adapter.listSessions(userId)
|
|
1216
|
-
}).pipe(Effect.map((sessions) => sessions.map((session) => sessionFingerprint(session.id))));
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
//#endregion
|
|
1220
|
-
//#region ../auth/src/internal/better-auth.ts
|
|
1221
|
-
const cmsBetterAuthAccess = createAccessControl(defaultStatements);
|
|
1222
|
-
const cmsBetterAuthRoles = {
|
|
1223
|
-
administrator: cmsBetterAuthAccess.newRole({
|
|
1224
|
-
session: ["list", "revoke"],
|
|
1225
|
-
user: [
|
|
1226
|
-
"create",
|
|
1227
|
-
"list",
|
|
1228
|
-
"get",
|
|
1229
|
-
"set-role",
|
|
1230
|
-
"delete",
|
|
1231
|
-
"update"
|
|
1232
|
-
]
|
|
1233
|
-
}),
|
|
1234
|
-
editor: cmsBetterAuthAccess.newRole({
|
|
1235
|
-
session: [],
|
|
1236
|
-
user: []
|
|
1237
|
-
}),
|
|
1238
|
-
viewer: cmsBetterAuthAccess.newRole({
|
|
1239
|
-
session: [],
|
|
1240
|
-
user: []
|
|
1241
|
-
})
|
|
1242
|
-
};
|
|
1243
|
-
const timestampFields = {
|
|
1244
|
-
createdAt: "created_at",
|
|
1245
|
-
updatedAt: "updated_at"
|
|
1246
|
-
};
|
|
1247
|
-
function createCmsBetterAuthOptions(options, activity) {
|
|
1248
|
-
return {
|
|
1249
|
-
advanced: { database: {
|
|
1250
|
-
generateId: "uuid",
|
|
1251
|
-
joins: true
|
|
1252
|
-
} },
|
|
1253
|
-
appName: "CMS Studio",
|
|
1254
|
-
basePath: options.basePath ?? "/api/cms/auth",
|
|
1255
|
-
baseURL: options.baseURL,
|
|
1256
|
-
database: options.database,
|
|
1257
|
-
emailAndPassword: {
|
|
1258
|
-
disableSignUp: true,
|
|
1259
|
-
enabled: true,
|
|
1260
|
-
...activity?.emailAndPassword
|
|
1261
|
-
},
|
|
1262
|
-
hooks: activity?.hooks,
|
|
1263
|
-
user: {
|
|
1264
|
-
additionalFields: {
|
|
1265
|
-
[cmsUserCreationChangeField]: hiddenChangeField("cms_creation_change_id"),
|
|
1266
|
-
[cmsUserSessionsRevokedChangeField]: hiddenChangeField("cms_sessions_revoked_change_id")
|
|
1267
|
-
},
|
|
1268
|
-
modelName: "auth_users",
|
|
1269
|
-
fields: {
|
|
1270
|
-
...timestampFields,
|
|
1271
|
-
emailVerified: "email_verified"
|
|
1272
|
-
}
|
|
1273
|
-
},
|
|
1274
|
-
session: {
|
|
1275
|
-
cookieCache: {
|
|
1276
|
-
enabled: false,
|
|
1277
|
-
strategy: "jwt"
|
|
1278
|
-
},
|
|
1279
|
-
modelName: "auth_sessions",
|
|
1280
|
-
fields: {
|
|
1281
|
-
...timestampFields,
|
|
1282
|
-
expiresAt: "expires_at",
|
|
1283
|
-
ipAddress: "ip_address",
|
|
1284
|
-
userAgent: "user_agent",
|
|
1285
|
-
userId: "user_id"
|
|
1286
|
-
}
|
|
1287
|
-
},
|
|
1288
|
-
account: {
|
|
1289
|
-
modelName: "auth_accounts",
|
|
1290
|
-
fields: {
|
|
1291
|
-
accessToken: "access_token",
|
|
1292
|
-
accessTokenExpiresAt: "access_token_expires_at",
|
|
1293
|
-
accountId: "account_id",
|
|
1294
|
-
...timestampFields,
|
|
1295
|
-
idToken: "id_token",
|
|
1296
|
-
providerId: "provider_id",
|
|
1297
|
-
refreshToken: "refresh_token",
|
|
1298
|
-
refreshTokenExpiresAt: "refresh_token_expires_at",
|
|
1299
|
-
userId: "user_id"
|
|
1300
|
-
}
|
|
1301
|
-
},
|
|
1302
|
-
verification: {
|
|
1303
|
-
modelName: "auth_verifications",
|
|
1304
|
-
fields: {
|
|
1305
|
-
...timestampFields,
|
|
1306
|
-
expiresAt: "expires_at"
|
|
1307
|
-
}
|
|
1308
|
-
},
|
|
1309
|
-
rateLimit: {
|
|
1310
|
-
enabled: true,
|
|
1311
|
-
storage: "database",
|
|
1312
|
-
modelName: "auth_rate_limits",
|
|
1313
|
-
fields: { lastRequest: "last_request" }
|
|
1314
|
-
},
|
|
1315
|
-
plugins: [admin({
|
|
1316
|
-
ac: cmsBetterAuthAccess,
|
|
1317
|
-
adminRoles: ["administrator"],
|
|
1318
|
-
defaultRole: "editor",
|
|
1319
|
-
roles: cmsBetterAuthRoles,
|
|
1320
|
-
schema: {
|
|
1321
|
-
session: { fields: { impersonatedBy: "impersonated_by" } },
|
|
1322
|
-
user: { fields: {
|
|
1323
|
-
banExpires: "ban_expires",
|
|
1324
|
-
banReason: "ban_reason",
|
|
1325
|
-
banned: "banned",
|
|
1326
|
-
role: "role"
|
|
1327
|
-
} }
|
|
1328
|
-
}
|
|
1329
|
-
})],
|
|
1330
|
-
secret: options.secret
|
|
1331
|
-
};
|
|
1332
|
-
}
|
|
1333
|
-
function createCmsBetterAuth(options, activity) {
|
|
1334
|
-
return betterAuth(createCmsBetterAuthOptions(options, activity));
|
|
1335
|
-
}
|
|
1336
|
-
function betterAuthLayer(options) {
|
|
1337
|
-
return Layer.effectContext(Effect.gen(function* () {
|
|
1338
|
-
const crypto = yield* Crypto.Crypto;
|
|
1339
|
-
const storage = yield* ActivityStore;
|
|
1340
|
-
const auth = createCmsBetterAuth(options, betterAuthActivityOptions(betterAuthActivityWriter(storage, crypto), crypto));
|
|
1341
|
-
const identity = betterAuthIdentity(auth);
|
|
1342
|
-
const recovery = betterAuthRecovery(auth);
|
|
1343
|
-
const initialization = betterAuthInitialIdentity(auth);
|
|
1344
|
-
const basePath = options.basePath ?? "/api/cms/auth";
|
|
1345
|
-
const handler = AuthenticationHandler.of({ handle: rejectExternalAdminRoutes(auth.handler, basePath) });
|
|
1346
|
-
return Context.empty().pipe(Context.add(Authentication, identity.authentication), Context.add(IdentityAdministration, identity.administration), Context.add(IdentityRecovery, recovery.users), Context.add(InitialIdentity, initialization), Context.add(InitialIdentityRecovery, recovery.initial), Context.add(AuthenticationHandler, handler));
|
|
1347
|
-
}));
|
|
1348
|
-
}
|
|
1349
|
-
function rejectExternalAdminRoutes(handler, basePath) {
|
|
1350
|
-
const mountedAt = canonicalBasePath(basePath);
|
|
1351
|
-
return (request) => {
|
|
1352
|
-
const path = new URL(request.url).pathname;
|
|
1353
|
-
const relative = mountedAt === "/" ? path : path === mountedAt ? "/" : path.startsWith(`${mountedAt}/`) ? path.slice(mountedAt.length) : path;
|
|
1354
|
-
if (relative === "/admin" || relative.startsWith("/admin/")) return Promise.resolve(new Response(null, { status: 404 }));
|
|
1355
|
-
return handler(request);
|
|
1356
|
-
};
|
|
1357
|
-
}
|
|
1358
|
-
function canonicalBasePath(basePath) {
|
|
1359
|
-
return (basePath.startsWith("/") ? basePath : `/${basePath}`).replace(/\/+$/u, "") || "/";
|
|
1360
|
-
}
|
|
1361
|
-
function hiddenChangeField(fieldName) {
|
|
1362
|
-
return {
|
|
1363
|
-
fieldName,
|
|
1364
|
-
input: false,
|
|
1365
|
-
required: false,
|
|
1366
|
-
returned: false,
|
|
1367
|
-
type: "string"
|
|
1368
|
-
};
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
//#endregion
|
|
1372
|
-
//#region ../server/src/adapter/Readiness.ts
|
|
1373
|
-
var ReadinessFailed = class extends Schema.TaggedError()("Cms.ReadinessFailed", { cause: Schema.Defect() }) {};
|
|
1374
|
-
var Readiness = class extends Context.Service()("@internal/server/Readiness") {};
|
|
1375
|
-
|
|
1376
|
-
//#endregion
|
|
1377
|
-
//#region ../server/src/internal/working-documents/working-document-model.ts
|
|
1378
|
-
const WorkingDocumentGenerationId = Schema.String.check(Schema.isUUID()).pipe(Schema.brand("WorkingDocumentGenerationId"));
|
|
1379
|
-
const WorkingDocumentUpdateSequence = Schema.Int.check(Schema.isGreaterThan(0)).pipe(Schema.brand("WorkingDocumentUpdateSequence"));
|
|
1380
|
-
const WorkingDocumentHighWatermark = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)).pipe(Schema.brand("WorkingDocumentHighWatermark"));
|
|
1381
|
-
var WorkingDocumentPersistenceError = class extends Schema.TaggedError()("WorkingDocuments.PersistenceError", {
|
|
1382
|
-
operation: Schema.NonEmptyString,
|
|
1383
|
-
cause: Schema.Defect()
|
|
1384
|
-
}) {};
|
|
1385
|
-
var WorkingDocumentSuperseded = class extends Schema.TaggedError()("WorkingDocuments.Superseded", {
|
|
1386
|
-
documentId: DocumentId,
|
|
1387
|
-
expectedGeneration: WorkingDocumentGenerationId,
|
|
1388
|
-
actualGeneration: Schema.optionalKey(WorkingDocumentGenerationId)
|
|
1389
|
-
}) {};
|
|
1390
|
-
var WorkingDocumentConflict = class extends Schema.TaggedError()("WorkingDocuments.Conflict", {
|
|
1391
|
-
documentId: DocumentId,
|
|
1392
|
-
operation: Schema.NonEmptyString,
|
|
1393
|
-
message: Schema.NonEmptyString
|
|
1394
|
-
}) {};
|
|
1395
|
-
|
|
1396
|
-
//#endregion
|
|
1397
|
-
//#region ../server/src/internal/working-documents/working-document.ts
|
|
1398
|
-
/** Durable identity for one exact recoverable Working Document generation. */
|
|
1399
|
-
const WorkingDocumentRef = Schema.Struct({
|
|
1400
|
-
documentId: DocumentId,
|
|
1401
|
-
generationId: WorkingDocumentGenerationId
|
|
1402
|
-
});
|
|
1403
|
-
var WorkingDocumentInvalid = class extends Schema.TaggedError()("WorkingDocuments.Invalid", {
|
|
1404
|
-
documentId: DocumentId,
|
|
1405
|
-
message: Schema.NonEmptyString,
|
|
1406
|
-
cause: Schema.Defect()
|
|
1407
|
-
}) {};
|
|
1408
|
-
var WorkingDocumentUnavailable = class extends Schema.TaggedError()("WorkingDocuments.Unavailable", {
|
|
1409
|
-
operation: Schema.NonEmptyString,
|
|
1410
|
-
cause: Schema.Defect()
|
|
1411
|
-
}) {};
|
|
1412
|
-
/** An authored or working baseline committed, but the publish workflow still needs recovery. */
|
|
1413
|
-
var WorkingDocumentCheckpointed = class extends Schema.TaggedError()("WorkingDocuments.Checkpointed", {
|
|
1414
|
-
checkpointVersion: DocumentVersion,
|
|
1415
|
-
documentId: DocumentId,
|
|
1416
|
-
result: Schema.optionalKey(DocumentMutationResult),
|
|
1417
|
-
stateVector: Schema.Uint8Array,
|
|
1418
|
-
cause: Schema.Defect()
|
|
1419
|
-
}) {};
|
|
1420
|
-
/** Signals that one exact Working Document generation changed or became invalid. */
|
|
1421
|
-
const WorkingDocumentChange = Schema.Struct({
|
|
1422
|
-
kind: Schema.Literals(["updated", "invalidated"]),
|
|
1423
|
-
ref: WorkingDocumentRef
|
|
1424
|
-
});
|
|
1425
|
-
var WorkingDocuments = class extends Context.Service()("@internal/server/WorkingDocuments") {};
|
|
1426
|
-
|
|
1427
|
-
//#endregion
|
|
1428
|
-
//#region ../document/src/internal/editor/render.ts
|
|
1429
|
-
const renderEditorMarkdown = Effect.fn("EditorDocument.renderMarkdown")((document) => {
|
|
1430
|
-
return Effect.tryPromise({
|
|
1431
|
-
try: () => renderMarkdown(toComarkDocument(withoutEditorMetadata(document))),
|
|
1432
|
-
catch: (cause) => new EditorRenderFailed({ cause })
|
|
1433
|
-
});
|
|
1434
|
-
});
|
|
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
|
-
|
|
1485
|
-
//#endregion
|
|
1486
|
-
//#region ../document/src/DocumentPlanning.ts
|
|
1487
|
-
/** Derives one canonical Source path and public path without performing persistence. */
|
|
1488
|
-
function planDocumentCreation(configuration, input) {
|
|
1489
|
-
return Result.gen(function* () {
|
|
1490
|
-
const collection = configuration.types.find((candidate) => candidate.collection === input.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");
|
|
1493
|
-
const title = input.title.trim();
|
|
1494
|
-
if (!title) return yield* invalid$2("A document title is required.", "title");
|
|
1495
|
-
const locale = yield* resolveLocale(configuration, input.locale);
|
|
1496
|
-
const address = yield* parseDocumentAddress(collection.addressPattern).pipe(Result.mapError(addressInvalid));
|
|
1497
|
-
const relativePath = yield* resolveRelativePath(collection, input.location);
|
|
1498
|
-
const data = Object.fromEntries(address.tokens.flatMap((token) => {
|
|
1499
|
-
if (token.name === "slug" && relativePath) return [[token.name, relativePath.split("/").at(-1) ?? relativePath]];
|
|
1500
|
-
return [];
|
|
1501
|
-
}));
|
|
1502
|
-
const relativeKey = yield* normalizePublicPath(relativePath).pipe(Result.mapError(addressInvalid));
|
|
1503
|
-
const key = yield* resolveDocumentAddress(address, relativeKey, data).pipe(Result.mapError(addressInvalid));
|
|
1504
|
-
const sourcePath = documentSourcePath(collection, relativePath, locale);
|
|
1505
|
-
const path = localizeDocumentPath(key, locale, configuration.locales);
|
|
1506
|
-
return Object.assign({
|
|
1507
|
-
collection,
|
|
1508
|
-
data,
|
|
1509
|
-
key,
|
|
1510
|
-
path,
|
|
1511
|
-
sourcePath,
|
|
1512
|
-
title
|
|
1513
|
-
}, locale === void 0 ? void 0 : { locale });
|
|
1514
|
-
});
|
|
1515
|
-
}
|
|
1516
|
-
/** Creates the canonical initial editor source, including configured field defaults. */
|
|
1517
|
-
const createInitialDocumentSource = Effect.fn("DocumentPlanning.createInitialSource")(function* (plan) {
|
|
1518
|
-
const body = CmsMarkdownDocument.make({
|
|
1519
|
-
frontmatter: initialFrontmatter(plan),
|
|
1520
|
-
meta: {},
|
|
1521
|
-
nodes: [[
|
|
1522
|
-
"h1",
|
|
1523
|
-
{},
|
|
1524
|
-
plan.title
|
|
1525
|
-
]]
|
|
1526
|
-
});
|
|
1527
|
-
return {
|
|
1528
|
-
body,
|
|
1529
|
-
markdown: yield* renderEditorMarkdown(body)
|
|
1530
|
-
};
|
|
1531
|
-
});
|
|
1532
|
-
/** Derives the canonical destination Source path while preserving the Document's collection and locale. */
|
|
1533
|
-
function planDocumentMove(configuration, document, location) {
|
|
1534
|
-
return Result.gen(function* () {
|
|
1535
|
-
const collection = configuration.types.find((candidate) => candidate.collection === document.collection);
|
|
1536
|
-
if (!collection) return yield* invalid$2(`Document collection "${document.collection}" does not exist.`, "documentId");
|
|
1537
|
-
return documentSourcePath(collection, yield* resolveRelativePath(collection, location), document.locale);
|
|
1538
|
-
});
|
|
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
|
-
}
|
|
1548
|
-
function documentSourcePath(collection, relativePath, locale) {
|
|
1549
|
-
const sourceName = relativePath ? `${relativePath}.md` : "index.md";
|
|
1550
|
-
return SourcePath.make([
|
|
1551
|
-
locale,
|
|
1552
|
-
collection.directory === "." ? void 0 : collection.directory,
|
|
1553
|
-
sourceName
|
|
1554
|
-
].filter((value) => value !== void 0).join("/"));
|
|
1555
|
-
}
|
|
1556
|
-
function initialFrontmatter(plan) {
|
|
1557
|
-
let frontmatter = {
|
|
1558
|
-
...plan.data,
|
|
1559
|
-
title: plan.title
|
|
1560
|
-
};
|
|
1561
|
-
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);
|
|
1562
|
-
return frontmatter;
|
|
1563
|
-
}
|
|
1564
|
-
function nestedValue$1(value, path) {
|
|
1565
|
-
return path.split(".").reduce((current, segment) => isJsonObject$4(current) ? current[segment] : void 0, value);
|
|
1566
|
-
}
|
|
1567
|
-
function setNestedValue(target, path, value) {
|
|
1568
|
-
const [segment, ...rest] = path;
|
|
1569
|
-
if (!segment) return target;
|
|
1570
|
-
if (rest.length === 0) return Object.assign({}, target, { [segment]: value });
|
|
1571
|
-
const current = target[segment];
|
|
1572
|
-
return Object.assign({}, target, { [segment]: setNestedValue(isJsonObject$4(current) ? current : {}, rest, value) });
|
|
1573
|
-
}
|
|
1574
|
-
const isJsonObject$4 = Schema.is(Schema.JsonObject);
|
|
1575
|
-
function resolveLocale(configuration, requested) {
|
|
1576
|
-
const locales = configuration.locales;
|
|
1577
|
-
if (!locales) return requested === void 0 ? Result.succeed(void 0) : invalid$2(`Document locale "${requested}" is not configured.`, "locale");
|
|
1578
|
-
const locale = requested ?? locales.defaultLocale;
|
|
1579
|
-
return locales.locales.includes(locale) ? Result.succeed(locale) : invalid$2(`Document locale "${locale}" does not exist.`, "locale");
|
|
1580
|
-
}
|
|
1581
|
-
function resolveRelativePath(collection, location) {
|
|
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");
|
|
1584
|
-
return normalizeDocumentRelativePath([location.parent, location.slug].filter((value) => value !== void 0).join("/")).pipe(Result.mapError((error) => addressInvalid({
|
|
1585
|
-
...error,
|
|
1586
|
-
field: "location"
|
|
1587
|
-
})));
|
|
1588
|
-
}
|
|
1589
|
-
function addressInvalid(error) {
|
|
1590
|
-
return new DocumentValidationFailed({ issues: [validationIssue(error.message, error.field)] });
|
|
1591
|
-
}
|
|
1592
|
-
function invalid$2(message, field) {
|
|
1593
|
-
return Result.fail(new DocumentValidationFailed({ issues: [validationIssue(message, field)] }));
|
|
1594
|
-
}
|
|
1595
|
-
function validationIssue(message, field) {
|
|
1596
|
-
return field === void 0 ? {
|
|
1597
|
-
code: "document.invalid",
|
|
1598
|
-
message,
|
|
1599
|
-
severity: "error"
|
|
1600
|
-
} : {
|
|
1601
|
-
code: "document.invalid",
|
|
1602
|
-
field,
|
|
1603
|
-
message,
|
|
1604
|
-
severity: "error"
|
|
1605
|
-
};
|
|
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
|
-
});
|
|
1619
|
-
|
|
1620
|
-
//#endregion
|
|
1621
|
-
//#region ../server/src/internal/documents/document-state.ts
|
|
1622
|
-
const isDocumentCompilationFailed = Schema.is(DocumentCompilationFailed);
|
|
1623
|
-
function makeDocumentState(authored, published, snapshot) {
|
|
1624
|
-
const activeById = /* @__PURE__ */ new Map();
|
|
1625
|
-
for (const source of snapshot.active) {
|
|
1626
|
-
if (activeById.has(source.documentId)) throw new Error(`Document "${source.documentId}" appears more than once in the repository snapshot.`);
|
|
1627
|
-
activeById.set(source.documentId, source);
|
|
1628
|
-
}
|
|
1629
|
-
const authoredById = /* @__PURE__ */ new Map();
|
|
1630
|
-
for (const document of authored.documents) {
|
|
1631
|
-
if (authoredById.has(document.id)) throw new Error(`Compiled document "${document.id}" appears more than once in the authored catalog.`);
|
|
1632
|
-
if (!activeById.has(document.id)) throw new Error(`Compiled document "${document.id}" is missing from the repository snapshot.`);
|
|
1633
|
-
authoredById.set(document.id, document);
|
|
1634
|
-
}
|
|
1635
|
-
for (const documentId of activeById.keys()) if (!authoredById.has(documentId)) throw new Error(`Compiled document "${documentId}" is missing from the repository snapshot.`);
|
|
1636
|
-
return {
|
|
1637
|
-
activeById,
|
|
1638
|
-
authored,
|
|
1639
|
-
authoredById,
|
|
1640
|
-
published,
|
|
1641
|
-
snapshot
|
|
1642
|
-
};
|
|
1643
|
-
}
|
|
1644
|
-
const compileDocumentState = Effect.fn("DocumentState.compile")(function* (engine, snapshot, previous) {
|
|
1645
|
-
return makeDocumentState(yield* engine.compileAll({
|
|
1646
|
-
previous: previous?.authored,
|
|
1647
|
-
sources: authoredSources(snapshot)
|
|
1648
|
-
}), yield* engine.compileAll({
|
|
1649
|
-
previous: previous?.published,
|
|
1650
|
-
sources: publishedSources(snapshot)
|
|
1651
|
-
}), snapshot);
|
|
1652
|
-
});
|
|
1653
|
-
function authoredSources(snapshot) {
|
|
1654
|
-
return snapshot.active.map((source) => ({
|
|
1655
|
-
documentId: source.documentId,
|
|
1656
|
-
draft: source.publishedVersion !== source.version,
|
|
1657
|
-
markdown: source.markdown,
|
|
1658
|
-
sourcePath: source.sourcePath
|
|
1659
|
-
}));
|
|
1660
|
-
}
|
|
1661
|
-
function publishedSources(snapshot) {
|
|
1662
|
-
return snapshot.published.map((source) => ({
|
|
1663
|
-
documentId: source.documentId,
|
|
1664
|
-
draft: false,
|
|
1665
|
-
markdown: source.markdown,
|
|
1666
|
-
sourcePath: source.sourcePath
|
|
1667
|
-
}));
|
|
1668
|
-
}
|
|
1669
|
-
const compileValidCatalog = Effect.fn("DocumentState.compileValid")(function* (engine, sources, previous) {
|
|
1670
|
-
const result = yield* Effect.result(engine.compileAll({
|
|
1671
|
-
previous,
|
|
1672
|
-
sources
|
|
1673
|
-
}));
|
|
1674
|
-
if (Result.isFailure(result)) return yield* new DocumentValidationFailed({ issues: compilationIssues(result.failure) });
|
|
1675
|
-
const errors = result.success.issues.filter((issue) => issue.severity === "error");
|
|
1676
|
-
if (errors.length > 0) return yield* new DocumentValidationFailed({ issues: errors });
|
|
1677
|
-
return result.success;
|
|
1678
|
-
});
|
|
1679
|
-
const validateDocumentSources = Effect.fn("DocumentState.validate")(function* (engine, sources, sourcePath, previous) {
|
|
1680
|
-
const result = yield* Effect.result(engine.compileAll({
|
|
1681
|
-
previous,
|
|
1682
|
-
sources
|
|
1683
|
-
}));
|
|
1684
|
-
if (Result.isFailure(result)) return DocumentValidationResult.make({
|
|
1685
|
-
issues: compilationIssues(result.failure),
|
|
1686
|
-
valid: false
|
|
1687
|
-
});
|
|
1688
|
-
const catalog = result.success;
|
|
1689
|
-
const document = catalog.documents.find((candidate) => candidate.source.path === sourcePath);
|
|
1690
|
-
const issues = catalog.issues;
|
|
1691
|
-
const validation = {
|
|
1692
|
-
issues,
|
|
1693
|
-
valid: document !== void 0 && !issues.some((issue) => issue.severity === "error")
|
|
1694
|
-
};
|
|
1695
|
-
return DocumentValidationResult.make(Object.assign(validation, document === void 0 ? void 0 : { document }));
|
|
1696
|
-
});
|
|
1697
|
-
function authoringEntries(state, query) {
|
|
1698
|
-
return state.snapshot.active.flatMap((source) => {
|
|
1699
|
-
const document = state.authoredById.get(source.documentId);
|
|
1700
|
-
if (!document) throw new Error(`Compiled document "${source.documentId}" is missing from the repository snapshot.`);
|
|
1701
|
-
if (query.collection !== void 0 && document.collection !== query.collection) return [];
|
|
1702
|
-
if (query.locale !== void 0 && document.locale !== query.locale) return [];
|
|
1703
|
-
return [DocumentSummary.make({
|
|
1704
|
-
...browseMetadata(state, source),
|
|
1705
|
-
document,
|
|
1706
|
-
sourcePath: source.sourcePath,
|
|
1707
|
-
version: source.version
|
|
1708
|
-
})];
|
|
1709
|
-
});
|
|
1710
|
-
}
|
|
1711
|
-
function deletedEntries(state) {
|
|
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,
|
|
1716
|
-
documentId: source.documentId,
|
|
1717
|
-
deletedAt: source.deletedAt,
|
|
1718
|
-
sourcePath: source.sourcePath,
|
|
1719
|
-
version: source.version
|
|
1720
|
-
}));
|
|
1721
|
-
}
|
|
1722
|
-
function entryById(state, documentId) {
|
|
1723
|
-
const source = state.activeById.get(documentId);
|
|
1724
|
-
const document = state.authoredById.get(documentId);
|
|
1725
|
-
return source && document ? DocumentSummary.make({
|
|
1726
|
-
...browseMetadata(state, source),
|
|
1727
|
-
document,
|
|
1728
|
-
sourcePath: source.sourcePath,
|
|
1729
|
-
version: source.version
|
|
1730
|
-
}) : void 0;
|
|
1731
|
-
}
|
|
1732
|
-
function compilationIssues(error) {
|
|
1733
|
-
if (isDocumentCompilationFailed(error)) return error.issues;
|
|
1734
|
-
return [{
|
|
1735
|
-
code: "markdown.parse",
|
|
1736
|
-
message: errorMessage(error.cause),
|
|
1737
|
-
severity: "error",
|
|
1738
|
-
sourcePath: error.sourcePath
|
|
1739
|
-
}];
|
|
1740
|
-
}
|
|
1741
|
-
function errorMessage(cause) {
|
|
1742
|
-
return cause instanceof Error && cause.message ? cause.message : "Markdown could not be parsed.";
|
|
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
|
-
}
|
|
1920
|
-
|
|
1921
|
-
//#endregion
|
|
1922
|
-
//#region ../server/src/internal/documents/document-lifecycle.ts
|
|
1923
|
-
const PreparedDocumentMutation = Data.taggedEnum();
|
|
1924
|
-
const prepareDocumentMutation = Effect.fn("DocumentLifecycle.prepare")(function* (dependencies, state, command) {
|
|
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));
|
|
1927
|
-
const authored = authoredSources(state.snapshot);
|
|
1928
|
-
const published = publishedSources(state.snapshot);
|
|
1929
|
-
switch (command.action) {
|
|
1930
|
-
case "document.create": {
|
|
1931
|
-
yield* ensurePathAvailable(state, command.sourcePath);
|
|
1932
|
-
const nextAuthored = [...authored, {
|
|
1933
|
-
documentId: command.documentId,
|
|
1934
|
-
draft: true,
|
|
1935
|
-
markdown: command.markdown,
|
|
1936
|
-
sourcePath: command.sourcePath
|
|
1937
|
-
}];
|
|
1938
|
-
return PreparedDocumentMutation.Commit({
|
|
1939
|
-
authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
|
|
1940
|
-
published: state.published,
|
|
1941
|
-
commitPlan: {
|
|
1942
|
-
action: command.action,
|
|
1943
|
-
documentId: command.documentId,
|
|
1944
|
-
markdown: command.markdown,
|
|
1945
|
-
sourcePath: command.sourcePath
|
|
1946
|
-
}
|
|
1947
|
-
});
|
|
1948
|
-
}
|
|
1949
|
-
case "document.update": {
|
|
1950
|
-
const current = yield* requireActive(state, command.input.documentId);
|
|
1951
|
-
yield* requireVersion(current, command.input.expectedVersion);
|
|
1952
|
-
const nextAuthored = replaceSource(authored, {
|
|
1953
|
-
documentId: current.documentId,
|
|
1954
|
-
draft: true,
|
|
1955
|
-
markdown: command.input.markdown,
|
|
1956
|
-
sourcePath: current.sourcePath
|
|
1957
|
-
});
|
|
1958
|
-
return PreparedDocumentMutation.Commit({
|
|
1959
|
-
authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
|
|
1960
|
-
published: state.published,
|
|
1961
|
-
commitPlan: {
|
|
1962
|
-
action: command.action,
|
|
1963
|
-
documentId: current.documentId,
|
|
1964
|
-
expectedVersion: command.input.expectedVersion,
|
|
1965
|
-
markdown: command.input.markdown,
|
|
1966
|
-
publishedVersion: current.publishedVersion,
|
|
1967
|
-
sourcePath: current.sourcePath
|
|
1968
|
-
}
|
|
1969
|
-
});
|
|
1970
|
-
}
|
|
1971
|
-
case "document.restore": {
|
|
1972
|
-
const current = yield* requireActive(state, command.input.documentId);
|
|
1973
|
-
yield* requireVersion(current, command.input.expectedVersion);
|
|
1974
|
-
const historical = yield* repository.revision(current.documentId, command.input.revisionId);
|
|
1975
|
-
const nextAuthored = replaceSource(authored, {
|
|
1976
|
-
documentId: current.documentId,
|
|
1977
|
-
draft: true,
|
|
1978
|
-
markdown: historical.markdown,
|
|
1979
|
-
sourcePath: current.sourcePath
|
|
1980
|
-
});
|
|
1981
|
-
return PreparedDocumentMutation.Commit({
|
|
1982
|
-
authored: yield* compileValidCatalog(engine, nextAuthored, state.authored),
|
|
1983
|
-
published: state.published,
|
|
1984
|
-
commitPlan: {
|
|
1985
|
-
action: command.action,
|
|
1986
|
-
documentId: current.documentId,
|
|
1987
|
-
expectedVersion: command.input.expectedVersion,
|
|
1988
|
-
markdown: historical.markdown,
|
|
1989
|
-
publishedVersion: current.publishedVersion,
|
|
1990
|
-
revisionId: command.input.revisionId,
|
|
1991
|
-
sourcePath: current.sourcePath
|
|
1992
|
-
}
|
|
1993
|
-
});
|
|
1994
|
-
}
|
|
1995
|
-
case "document.unpublish": {
|
|
1996
|
-
const current = yield* requireActive(state, command.input.documentId);
|
|
1997
|
-
yield* requireVersion(current, command.input.expectedVersion);
|
|
1998
|
-
return PreparedDocumentMutation.Commit({
|
|
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),
|
|
2006
|
-
commitPlan: {
|
|
2007
|
-
action: command.action,
|
|
2008
|
-
documentId: current.documentId,
|
|
2009
|
-
expectedVersion: current.version,
|
|
2010
|
-
markdown: current.markdown,
|
|
2011
|
-
sourcePath: current.sourcePath,
|
|
2012
|
-
revisionId: current.revisionId
|
|
2013
|
-
}
|
|
2014
|
-
});
|
|
2015
|
-
}
|
|
2016
|
-
case "document.publish": {
|
|
2017
|
-
const current = yield* requireActive(state, command.input.documentId);
|
|
2018
|
-
yield* requireVersion(current, command.input.expectedVersion);
|
|
2019
|
-
if (current.publishedVersion === current.version) {
|
|
2020
|
-
const entry = entryById(state, current.documentId);
|
|
2021
|
-
const result = {
|
|
2022
|
-
action: "document.publish",
|
|
2023
|
-
affectedDocumentIds: [current.documentId],
|
|
2024
|
-
documentId: current.documentId,
|
|
2025
|
-
generation: state.snapshot.generation,
|
|
2026
|
-
revisionId: current.revisionId,
|
|
2027
|
-
sourcePath: current.sourcePath,
|
|
2028
|
-
version: current.version
|
|
2029
|
-
};
|
|
2030
|
-
return PreparedDocumentMutation.Unchanged({ result: DocumentMutationResult.make(Object.assign(result, entry === void 0 ? void 0 : { entry })) });
|
|
2031
|
-
}
|
|
2032
|
-
const source = {
|
|
2033
|
-
documentId: current.documentId,
|
|
2034
|
-
draft: false,
|
|
2035
|
-
markdown: current.markdown,
|
|
2036
|
-
sourcePath: current.sourcePath
|
|
2037
|
-
};
|
|
2038
|
-
return PreparedDocumentMutation.Commit({
|
|
2039
|
-
authored: yield* compileValidCatalog(engine, replaceSource(authored, source), state.authored),
|
|
2040
|
-
published: yield* compileValidCatalog(engine, replaceSource(published, source), state.published),
|
|
2041
|
-
commitPlan: {
|
|
2042
|
-
action: command.action,
|
|
2043
|
-
documentId: current.documentId,
|
|
2044
|
-
expectedVersion: command.input.expectedVersion,
|
|
2045
|
-
markdown: current.markdown,
|
|
2046
|
-
revisionId: current.revisionId,
|
|
2047
|
-
sourcePath: current.sourcePath
|
|
2048
|
-
}
|
|
2049
|
-
});
|
|
2050
|
-
}
|
|
2051
|
-
}
|
|
2052
|
-
});
|
|
2053
|
-
const validateDocumentUpdate = Effect.fn("DocumentLifecycle.validateUpdate")(function* (engine, state, input) {
|
|
2054
|
-
const current = yield* requireActive(state, input.documentId);
|
|
2055
|
-
yield* requireVersion(current, input.expectedVersion);
|
|
2056
|
-
const source = {
|
|
2057
|
-
documentId: current.documentId,
|
|
2058
|
-
draft: true,
|
|
2059
|
-
markdown: input.markdown,
|
|
2060
|
-
sourcePath: current.sourcePath
|
|
2061
|
-
};
|
|
2062
|
-
return yield* validateDocumentSources(engine, replaceSource(authoredSources(state.snapshot), source), current.sourcePath, state.authored);
|
|
2063
|
-
});
|
|
2064
|
-
function requireActive(state, documentId) {
|
|
2065
|
-
const source = state.activeById.get(documentId);
|
|
2066
|
-
return source ? Effect.succeed(source) : Effect.fail(new DocumentNotFound({ documentId }));
|
|
1289
|
+
return accounts.some((account) => account.providerId === "credential" && (account.password?.length ?? 0) > 0);
|
|
2067
1290
|
}
|
|
2068
|
-
function
|
|
2069
|
-
return
|
|
2070
|
-
actualVersion: source.version,
|
|
2071
|
-
documentId: source.documentId,
|
|
2072
|
-
expectedVersion
|
|
2073
|
-
}));
|
|
1291
|
+
function decodeUser(user, operation) {
|
|
1292
|
+
return decodeBetterAuthUser(user).pipe(Effect.mapError((cause) => unavailable$4(operation, cause)));
|
|
2074
1293
|
}
|
|
2075
|
-
function
|
|
2076
|
-
return
|
|
1294
|
+
function findUser(adapter, userId) {
|
|
1295
|
+
return findRawUser(adapter, userId).pipe(Effect.flatMap((user) => user ? decodeUser(user, "BetterAuth.recovery.findUserById") : Effect.succeed(null)));
|
|
2077
1296
|
}
|
|
2078
|
-
function
|
|
2079
|
-
return
|
|
1297
|
+
function findRawUser(adapter, userId) {
|
|
1298
|
+
return providerCall("BetterAuth.recovery.findUserById", () => adapter.findUserById(userId), unavailable$4);
|
|
2080
1299
|
}
|
|
2081
|
-
function
|
|
2082
|
-
return
|
|
1300
|
+
function findSessionFingerprints(adapter, userId) {
|
|
1301
|
+
return providerCall("BetterAuth.recovery.listSessions", () => adapter.listSessions(userId), unavailable$4).pipe(Effect.map((sessions) => sessions.map((session) => sessionFingerprint(session.id))));
|
|
2083
1302
|
}
|
|
2084
1303
|
|
|
2085
1304
|
//#endregion
|
|
2086
|
-
//#region ../
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
path: reference.source.path,
|
|
2102
|
-
sourcePath: reference.source.sourcePath
|
|
2103
|
-
});
|
|
2104
|
-
}
|
|
2105
|
-
function referenceIdentity(reference) {
|
|
2106
|
-
return `${reference.documentId}:${reference.sourcePath}:${reference.nodePath.join(".")}:${reference.objectKey}`;
|
|
1305
|
+
//#region ../auth/src/BetterAuth.ts
|
|
1306
|
+
function betterAuthLayer(options) {
|
|
1307
|
+
if (options.basePath !== void 0 && options.basePath !== "/api/cms/auth") throw new Error(`Authentication basePath must be ${CMS_AUTHENTICATION_PATH}.`);
|
|
1308
|
+
return Layer.effectContext(Effect.gen(function* () {
|
|
1309
|
+
const crypto = yield* Crypto.Crypto;
|
|
1310
|
+
const storage = yield* ActivityStore;
|
|
1311
|
+
const runPromise = Effect.runPromiseWith(yield* Effect.context());
|
|
1312
|
+
const auth = createCmsBetterAuth(options, betterAuthActivityOptions(betterAuthActivityWriter(storage, crypto), crypto, runPromise));
|
|
1313
|
+
const identity = betterAuthIdentity(auth);
|
|
1314
|
+
const recovery = betterAuthRecovery(auth);
|
|
1315
|
+
const initialization = betterAuthInitialIdentity(auth);
|
|
1316
|
+
const basePath = options.basePath ?? "/api/cms/auth";
|
|
1317
|
+
const handler = AuthenticationHandler.of({ handle: rejectExternalAdminRoutes(auth.handler, basePath) });
|
|
1318
|
+
return Context.empty().pipe(Context.add(Authentication, identity.authentication), Context.add(IdentityAdministration, identity.administration), Context.add(IdentityRecovery, recovery.users), Context.add(InitialIdentity, initialization), Context.add(InitialIdentityRecovery, recovery.initial), Context.add(AuthenticationHandler, handler));
|
|
1319
|
+
}));
|
|
2107
1320
|
}
|
|
2108
1321
|
|
|
2109
1322
|
//#endregion
|
|
2110
|
-
//#region ../
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
report.created += 1;
|
|
2131
|
-
if (plan.publish) {
|
|
2132
|
-
yield* publish(dependencies, created, input, activity);
|
|
2133
|
-
report.published += 1;
|
|
2134
|
-
}
|
|
2135
|
-
break;
|
|
2136
|
-
}
|
|
2137
|
-
case "preserve":
|
|
2138
|
-
case "unchanged":
|
|
2139
|
-
report[plan.kind === "preserve" ? "preserved" : "unchanged"] += 1;
|
|
2140
|
-
break;
|
|
2141
|
-
case "publish":
|
|
2142
|
-
yield* publish(dependencies, plan.current, input, activity);
|
|
2143
|
-
report.published += 1;
|
|
2144
|
-
break;
|
|
2145
|
-
case "update": {
|
|
2146
|
-
const updated = yield* dependencies.runMutation({
|
|
2147
|
-
action: "document.update",
|
|
2148
|
-
input: {
|
|
2149
|
-
documentId: plan.current.documentId,
|
|
2150
|
-
expectedVersion: plan.current.version,
|
|
2151
|
-
markdown: source.markdown,
|
|
2152
|
-
reason: input.reason
|
|
1323
|
+
//#region ../platform-node/src/internal/http/form-ingress.ts
|
|
1324
|
+
function handleFormRequest(request, handle) {
|
|
1325
|
+
return Effect.runPromise(Effect.gen(function* () {
|
|
1326
|
+
const ingress = yield* Effect.gen(function* () {
|
|
1327
|
+
const origin = request.headers.get("origin");
|
|
1328
|
+
if (origin && origin !== new URL(request.url).origin) return Response.json({ error: "Origin rejected." }, { status: 403 });
|
|
1329
|
+
if (request.method !== "POST") return new Response(null, { status: 405 });
|
|
1330
|
+
if (request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") return new Response(null, { status: 415 });
|
|
1331
|
+
const reader = request.body?.getReader();
|
|
1332
|
+
if (!reader) return new Response(null, { status: 400 });
|
|
1333
|
+
const chunks = [];
|
|
1334
|
+
let size = 0;
|
|
1335
|
+
try {
|
|
1336
|
+
while (true) {
|
|
1337
|
+
const chunk = yield* Effect.tryPromise(() => reader.read());
|
|
1338
|
+
if (chunk.done) break;
|
|
1339
|
+
size += chunk.value.byteLength;
|
|
1340
|
+
if (size > 131072) {
|
|
1341
|
+
yield* Effect.tryPromise(() => reader.cancel()).pipe(Effect.ignore);
|
|
1342
|
+
return new Response(null, { status: 413 });
|
|
2153
1343
|
}
|
|
2154
|
-
|
|
2155
|
-
report.updated += 1;
|
|
2156
|
-
if (plan.publish) {
|
|
2157
|
-
yield* publish(dependencies, updated, input, activity);
|
|
2158
|
-
report.published += 1;
|
|
1344
|
+
chunks.push(chunk.value);
|
|
2159
1345
|
}
|
|
2160
|
-
|
|
1346
|
+
} finally {
|
|
1347
|
+
reader.releaseLock();
|
|
2161
1348
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
};
|
|
2181
|
-
return { kind: "unchanged" };
|
|
2182
|
-
}
|
|
2183
|
-
function publish(dependencies, source, input, activity) {
|
|
2184
|
-
return dependencies.runMutation({
|
|
2185
|
-
action: "document.publish",
|
|
2186
|
-
input: {
|
|
2187
|
-
documentId: source.documentId,
|
|
2188
|
-
expectedVersion: source.version,
|
|
2189
|
-
reason: input.reason
|
|
2190
|
-
}
|
|
2191
|
-
}, activity, input.reason);
|
|
1349
|
+
const body = new Uint8Array(size);
|
|
1350
|
+
let offset = 0;
|
|
1351
|
+
for (const chunk of chunks) {
|
|
1352
|
+
body.set(chunk, offset);
|
|
1353
|
+
offset += chunk.byteLength;
|
|
1354
|
+
}
|
|
1355
|
+
return body;
|
|
1356
|
+
}).pipe(Effect.orElseSucceed(() => new Response(null, { status: 400 })));
|
|
1357
|
+
if (ingress instanceof Response) return ingress;
|
|
1358
|
+
return yield* Effect.promise(() => handle(new Request(request.url, {
|
|
1359
|
+
method: "POST",
|
|
1360
|
+
headers: request.headers,
|
|
1361
|
+
body: ingress,
|
|
1362
|
+
signal: request.signal
|
|
1363
|
+
})));
|
|
1364
|
+
}).pipe(Effect.map((response) => {
|
|
1365
|
+
response.headers.set("cache-control", "no-store");
|
|
1366
|
+
return response;
|
|
1367
|
+
})));
|
|
2192
1368
|
}
|
|
2193
1369
|
|
|
2194
1370
|
//#endregion
|
|
2195
|
-
//#region ../server/src/
|
|
2196
|
-
var
|
|
1371
|
+
//#region ../server/src/Readiness.ts
|
|
1372
|
+
var ReadinessFailed = class extends Schema.TaggedError()("Cms.ReadinessFailed", { cause: Schema.Defect() }) {};
|
|
1373
|
+
var Readiness = class extends Context.Service()("@internal/server/Readiness") {};
|
|
1374
|
+
/** A stale unresolved external change makes the installation unavailable until reconciliation resolves it. */
|
|
1375
|
+
const readinessLayer = Layer.effect(Readiness, Effect.gen(function* () {
|
|
1376
|
+
const journal = yield* ChangeJournal;
|
|
1377
|
+
const check = Effect.gen(function* () {
|
|
1378
|
+
const now = yield* DateTime.now;
|
|
1379
|
+
const pending = yield* journal.pending({
|
|
1380
|
+
before: DateTime.subtractDuration(now, "30 seconds"),
|
|
1381
|
+
limit: 1
|
|
1382
|
+
}).pipe(Effect.mapError((cause) => new ReadinessFailed({ cause })));
|
|
1383
|
+
const oldest = pending.changes[0];
|
|
1384
|
+
if (pending.total === 0) return;
|
|
1385
|
+
if (oldest === void 0) return yield* new ReadinessFailed({ cause: /* @__PURE__ */ new Error("The pending change journal returned no change.") });
|
|
1386
|
+
yield* Effect.logWarning("CMS readiness found a stale pending change.", {
|
|
1387
|
+
action: oldest.action,
|
|
1388
|
+
ageMillis: DateTime.toEpochMillis(now) - DateTime.toEpochMillis(oldest.updatedAt),
|
|
1389
|
+
changeId: oldest.id,
|
|
1390
|
+
phase: "pending"
|
|
1391
|
+
});
|
|
1392
|
+
return yield* new ReadinessFailed({ cause: /* @__PURE__ */ new Error("A stale change remains pending.") });
|
|
1393
|
+
}).pipe(Effect.withSpan("Installation.readiness"));
|
|
1394
|
+
return Readiness.of({ check });
|
|
1395
|
+
}));
|
|
2197
1396
|
|
|
2198
1397
|
//#endregion
|
|
2199
|
-
//#region ../server/src/internal/documents/document-
|
|
2200
|
-
|
|
1398
|
+
//#region ../server/src/internal/working-documents/working-document-model.ts
|
|
1399
|
+
const WorkingDocumentGenerationId = Schema.String.check(Schema.isUUID()).pipe(Schema.brand("WorkingDocumentGenerationId"));
|
|
1400
|
+
const WorkingDocumentUpdateSequence = Schema.Int.check(Schema.isGreaterThan(0)).pipe(Schema.brand("WorkingDocumentUpdateSequence"));
|
|
1401
|
+
const WorkingDocumentHighWatermark = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)).pipe(Schema.brand("WorkingDocumentHighWatermark"));
|
|
1402
|
+
var WorkingDocumentPersistenceError = class extends Schema.TaggedError()("WorkingDocuments.PersistenceError", {
|
|
2201
1403
|
operation: Schema.NonEmptyString,
|
|
2202
1404
|
cause: Schema.Defect()
|
|
2203
1405
|
}) {};
|
|
2204
|
-
var
|
|
2205
|
-
|
|
2206
|
-
|
|
1406
|
+
var WorkingDocumentSuperseded = class extends Schema.TaggedError()("WorkingDocuments.Superseded", {
|
|
1407
|
+
documentId: DocumentId,
|
|
1408
|
+
expectedGeneration: WorkingDocumentGenerationId,
|
|
1409
|
+
actualGeneration: Schema.optionalKey(WorkingDocumentGenerationId)
|
|
1410
|
+
}) {};
|
|
1411
|
+
var WorkingDocumentConflict = class extends Schema.TaggedError()("WorkingDocuments.Conflict", {
|
|
1412
|
+
documentId: DocumentId,
|
|
1413
|
+
operation: Schema.NonEmptyString,
|
|
1414
|
+
message: Schema.NonEmptyString
|
|
2207
1415
|
}) {};
|
|
2208
1416
|
|
|
2209
1417
|
//#endregion
|
|
2210
|
-
//#region ../server/src/internal/documents/document
|
|
2211
|
-
const
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
});
|
|
2215
|
-
const CursorJson$1 = Schema.fromJsonString(CursorPayload$1);
|
|
2216
|
-
const cursors$1 = cursorCodec(CursorJson$1, (value) => DocumentHistoryCursor.make(value), () => new DocumentHistoryInvalidCursor());
|
|
2217
|
-
function documentHistoryCapability(accessControl, repository) {
|
|
2218
|
-
const requireHistory = (context, documentId) => accessControl.require(context, {
|
|
2219
|
-
action: "document.history",
|
|
2220
|
-
resource: {
|
|
2221
|
-
documentId,
|
|
2222
|
-
kind: "document"
|
|
2223
|
-
}
|
|
2224
|
-
});
|
|
2225
|
-
const read = Effect.fn("Documents.history.read")(function* (context, documentId, revisionId) {
|
|
2226
|
-
yield* requireHistory(context, documentId);
|
|
2227
|
-
return yield* repository.revision(documentId, revisionId);
|
|
2228
|
-
});
|
|
2229
|
-
const list = Effect.fn("Documents.history.list")(function* (context, query) {
|
|
2230
|
-
yield* requireHistory(context, query.documentId);
|
|
2231
|
-
const cursor = query.cursor ? yield* Effect.fromResult(cursors$1.decode(query.cursor)) : void 0;
|
|
2232
|
-
const page = yield* repository.revisions({
|
|
2233
|
-
cursor,
|
|
2234
|
-
documentId: query.documentId,
|
|
2235
|
-
limit: query.limit ?? 25
|
|
2236
|
-
});
|
|
2237
|
-
const output = {
|
|
2238
|
-
revisions: page.revisions.map(({ markdown: _markdown, ...revision }) => revision),
|
|
2239
|
-
total: page.total
|
|
2240
|
-
};
|
|
2241
|
-
return page.next ? DocumentRevisionPage.make({
|
|
2242
|
-
...output,
|
|
2243
|
-
nextCursor: cursors$1.encode(page.next)
|
|
2244
|
-
}) : DocumentRevisionPage.make(output);
|
|
2245
|
-
});
|
|
2246
|
-
return {
|
|
2247
|
-
compare: Effect.fn("Documents.history.compare")(function* (context, input) {
|
|
2248
|
-
yield* requireHistory(context, input.documentId);
|
|
2249
|
-
const from = yield* repository.revision(input.documentId, input.fromRevisionId);
|
|
2250
|
-
const to = yield* repository.revision(input.documentId, input.toRevisionId);
|
|
2251
|
-
const changes = yield* compareLines(from.markdown, to.markdown);
|
|
2252
|
-
return DocumentRevisionComparison.make({
|
|
2253
|
-
changes,
|
|
2254
|
-
from,
|
|
2255
|
-
to
|
|
2256
|
-
});
|
|
2257
|
-
}),
|
|
2258
|
-
list,
|
|
2259
|
-
read
|
|
2260
|
-
};
|
|
2261
|
-
}
|
|
2262
|
-
const MAXIMUM_COMPARISON_LINES = 1e4;
|
|
2263
|
-
const MAXIMUM_EDIT_LENGTH = 2e3;
|
|
2264
|
-
const comparisonTooLarge = () => new DocumentHistoryComparisonTooLarge({
|
|
2265
|
-
maximumEditLength: MAXIMUM_EDIT_LENGTH,
|
|
2266
|
-
maximumLines: MAXIMUM_COMPARISON_LINES
|
|
2267
|
-
});
|
|
2268
|
-
const compareLines = Effect.fn("Documents.history.compareLines")(function* (beforeMarkdown, afterMarkdown) {
|
|
2269
|
-
const before = beforeMarkdown.split("\n");
|
|
2270
|
-
const after = afterMarkdown.split("\n");
|
|
2271
|
-
if (before.length > MAXIMUM_COMPARISON_LINES || after.length > MAXIMUM_COMPARISON_LINES) return yield* comparisonTooLarge();
|
|
2272
|
-
const diff = diffArrays(before, after, {
|
|
2273
|
-
maxEditLength: MAXIMUM_EDIT_LENGTH,
|
|
2274
|
-
oneChangePerToken: true,
|
|
2275
|
-
timeout: 100
|
|
2276
|
-
});
|
|
2277
|
-
if (!diff) return yield* comparisonTooLarge();
|
|
2278
|
-
const changes = [];
|
|
2279
|
-
let beforeLine = 1;
|
|
2280
|
-
let afterLine = 1;
|
|
2281
|
-
for (const change of diff) for (const line of change.value) if (change.removed) {
|
|
2282
|
-
changes.push({
|
|
2283
|
-
beforeLine,
|
|
2284
|
-
kind: "removed",
|
|
2285
|
-
line
|
|
2286
|
-
});
|
|
2287
|
-
beforeLine += 1;
|
|
2288
|
-
} else if (change.added) {
|
|
2289
|
-
changes.push({
|
|
2290
|
-
afterLine,
|
|
2291
|
-
kind: "added",
|
|
2292
|
-
line
|
|
2293
|
-
});
|
|
2294
|
-
afterLine += 1;
|
|
2295
|
-
} else {
|
|
2296
|
-
changes.push({
|
|
2297
|
-
afterLine,
|
|
2298
|
-
beforeLine,
|
|
2299
|
-
kind: "equal",
|
|
2300
|
-
line
|
|
2301
|
-
});
|
|
2302
|
-
beforeLine += 1;
|
|
2303
|
-
afterLine += 1;
|
|
2304
|
-
}
|
|
2305
|
-
return changes;
|
|
1418
|
+
//#region ../server/src/internal/working-documents/working-document.ts
|
|
1419
|
+
const WorkingDocumentRef = Schema.Struct({
|
|
1420
|
+
documentId: DocumentId,
|
|
1421
|
+
generationId: WorkingDocumentGenerationId
|
|
2306
1422
|
});
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
if (Option.isNone(document)) return yield* new PublishedDocumentNotFound({ lookup });
|
|
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 });
|
|
2329
|
-
const page = queryDocuments(current.documents, surroundQuery);
|
|
2330
|
-
const index = page.items.findIndex((candidate) => candidate.id === document.value.id);
|
|
2331
|
-
return DocumentSurround.make({
|
|
2332
|
-
previous: index > 0 ? page.items[index - 1] ?? null : null,
|
|
2333
|
-
next: index >= 0 ? page.items[index + 1] ?? null : null
|
|
2334
|
-
});
|
|
2335
|
-
});
|
|
2336
|
-
return {
|
|
2337
|
-
diagnostics: catalog.pipe(Effect.map((current) => current.issues)),
|
|
2338
|
-
navigation,
|
|
2339
|
-
query,
|
|
2340
|
-
read,
|
|
2341
|
-
surround
|
|
2342
|
-
};
|
|
2343
|
-
}
|
|
2344
|
-
function findDocument(catalog, lookup) {
|
|
2345
|
-
switch (lookup.kind) {
|
|
2346
|
-
case "id": return Option.fromNullishOr(catalog.documents.find((document) => document.id === lookup.documentId));
|
|
2347
|
-
case "path": return catalog.findByPath(lookup.path);
|
|
2348
|
-
case "key": return catalog.findByKey(lookup.key, lookup.locale).pipe(Option.filter((document) => lookup.collection === void 0 || document.collection === lookup.collection));
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
function queryDocuments(documents, query, page) {
|
|
2352
|
-
const filtered = documents.filter((document) => {
|
|
2353
|
-
if (query.collection !== void 0 && document.collection !== query.collection) return false;
|
|
2354
|
-
if (query.locale !== void 0 && document.locale !== query.locale) return false;
|
|
2355
|
-
return Object.entries(query.where ?? {}).every(([field, expected]) => matchesValue(documentField(document, field), expected));
|
|
2356
|
-
});
|
|
2357
|
-
const sorted = query.sort && query.sort.length > 0 ? sortDocuments(filtered, query.sort) : filtered;
|
|
2358
|
-
const items = page ? sorted.slice(page.offset, page.offset + page.limit) : sorted;
|
|
2359
|
-
return PublishedDocumentPage.make({
|
|
2360
|
-
items,
|
|
2361
|
-
total: filtered.length
|
|
2362
|
-
});
|
|
2363
|
-
}
|
|
2364
|
-
function createNavigation(documents, query, configuration) {
|
|
2365
|
-
const locale = query.locale ?? configuration.locales?.defaultLocale;
|
|
2366
|
-
const candidates = documents.filter((document) => (query.collection === void 0 || document.collection === query.collection) && (locale === void 0 || document.locale === locale) && document.data.navigation !== false);
|
|
2367
|
-
const roots = [];
|
|
2368
|
-
const itemsByKey = /* @__PURE__ */ new Map();
|
|
2369
|
-
const orderByItem = /* @__PURE__ */ new Map();
|
|
2370
|
-
for (const document of candidates) {
|
|
2371
|
-
if (document.key === "/") {
|
|
2372
|
-
const item = navigationItem(document);
|
|
2373
|
-
roots.push(item);
|
|
2374
|
-
orderByItem.set(item, Number.NEGATIVE_INFINITY);
|
|
2375
|
-
continue;
|
|
2376
|
-
}
|
|
2377
|
-
const segments = document.key.slice(1).split("/");
|
|
2378
|
-
let parent;
|
|
2379
|
-
for (let index = 0; index < segments.length; index += 1) {
|
|
2380
|
-
const key = PublicPath.make(`/${segments.slice(0, index + 1).join("/")}`);
|
|
2381
|
-
let item = itemsByKey.get(key);
|
|
2382
|
-
if (!item) {
|
|
2383
|
-
item = {
|
|
2384
|
-
children: [],
|
|
2385
|
-
key,
|
|
2386
|
-
title: titleFromSegment(segments[index] ?? "")
|
|
2387
|
-
};
|
|
2388
|
-
itemsByKey.set(key, item);
|
|
2389
|
-
if (parent) parent.children.push(item);
|
|
2390
|
-
else roots.push(item);
|
|
2391
|
-
}
|
|
2392
|
-
if (index === segments.length - 1) {
|
|
2393
|
-
const resolved = navigationItem(document);
|
|
2394
|
-
item.title = resolved.title;
|
|
2395
|
-
item.path = resolved.path;
|
|
2396
|
-
if (resolved.locale !== void 0) item.locale = resolved.locale;
|
|
2397
|
-
else delete item.locale;
|
|
2398
|
-
item.documentId = resolved.documentId;
|
|
2399
|
-
orderByItem.set(item, navigationOrder(document));
|
|
2400
|
-
}
|
|
2401
|
-
parent = item;
|
|
2402
|
-
}
|
|
2403
|
-
}
|
|
2404
|
-
sortNavigation(roots, orderByItem);
|
|
2405
|
-
return roots.map((item) => DocumentNavigationItem.make(item));
|
|
2406
|
-
}
|
|
2407
|
-
function navigationItem(document) {
|
|
2408
|
-
const navigation = document.data.navigation;
|
|
2409
|
-
const configuredTitle = Schema.is(Schema.JsonObject)(navigation) ? navigation.title : void 0;
|
|
2410
|
-
return Object.assign({
|
|
2411
|
-
children: [],
|
|
2412
|
-
documentId: document.id,
|
|
2413
|
-
key: document.key,
|
|
2414
|
-
path: document.path,
|
|
2415
|
-
title: Schema.is(Schema.String)(configuredTitle) && configuredTitle.trim() ? configuredTitle.trim() : document.title
|
|
2416
|
-
}, document.locale === void 0 ? void 0 : { locale: document.locale });
|
|
2417
|
-
}
|
|
2418
|
-
function navigationOrder(document) {
|
|
2419
|
-
const navigation = document.data.navigation;
|
|
2420
|
-
const order = (Schema.is(Schema.JsonObject)(navigation) ? navigation.order : void 0) ?? document.data.order;
|
|
2421
|
-
return Schema.is(Schema.Finite)(order) ? order : Number.POSITIVE_INFINITY;
|
|
2422
|
-
}
|
|
2423
|
-
function sortNavigation(items, orderByItem) {
|
|
2424
|
-
items.sort((left, right) => {
|
|
2425
|
-
return (orderByItem.get(left) ?? Number.POSITIVE_INFINITY) - (orderByItem.get(right) ?? Number.POSITIVE_INFINITY) || left.title.localeCompare(right.title);
|
|
2426
|
-
});
|
|
2427
|
-
items.forEach((item) => sortNavigation(item.children, orderByItem));
|
|
2428
|
-
}
|
|
2429
|
-
function documentField(document, field) {
|
|
2430
|
-
switch (field) {
|
|
2431
|
-
case "collection": return document.collection;
|
|
2432
|
-
case "description": return document.description;
|
|
2433
|
-
case "id": return document.id;
|
|
2434
|
-
case "key": return document.key;
|
|
2435
|
-
case "locale": return document.locale;
|
|
2436
|
-
case "path": return document.path;
|
|
2437
|
-
case "source.path": return document.source.path;
|
|
2438
|
-
case "title": return document.title;
|
|
2439
|
-
default: return nestedValue(document.data, field.startsWith("data.") ? field.slice(5) : field);
|
|
2440
|
-
}
|
|
2441
|
-
}
|
|
2442
|
-
function nestedValue(value, path) {
|
|
2443
|
-
return path.split(".").reduce((current, segment) => {
|
|
2444
|
-
if (!Schema.is(Schema.JsonObject)(current)) return void 0;
|
|
2445
|
-
return current[segment];
|
|
2446
|
-
}, value);
|
|
2447
|
-
}
|
|
2448
|
-
function matchesValue(actual, expected) {
|
|
2449
|
-
const expectedValues = Array.isArray(expected) ? expected : [expected];
|
|
2450
|
-
return (Array.isArray(actual) ? actual : [actual]).some((value) => Schema.is(DocumentQueryScalar)(value) && expectedValues.includes(value));
|
|
2451
|
-
}
|
|
2452
|
-
function sortDocuments(documents, sorts) {
|
|
2453
|
-
return documents.toSorted((left, right) => {
|
|
2454
|
-
for (const sort of sorts) {
|
|
2455
|
-
const comparison = compareValues(documentField(left, sort.field), documentField(right, sort.field));
|
|
2456
|
-
if (comparison !== 0) return sort.direction === "desc" ? -comparison : comparison;
|
|
2457
|
-
}
|
|
2458
|
-
return 0;
|
|
2459
|
-
});
|
|
2460
|
-
}
|
|
2461
|
-
function compareValues(left, right) {
|
|
2462
|
-
if (left === right) return 0;
|
|
2463
|
-
if (left === void 0 || left === null) return 1;
|
|
2464
|
-
if (right === void 0 || right === null) return -1;
|
|
2465
|
-
if (Schema.is(Schema.Finite)(left) && Schema.is(Schema.Finite)(right)) return left - right;
|
|
2466
|
-
const leftText = Schema.is(Schema.String)(left) ? left : JSON.stringify(left) ?? "";
|
|
2467
|
-
const rightText = Schema.is(Schema.String)(right) ? right : JSON.stringify(right) ?? "";
|
|
2468
|
-
return leftText.localeCompare(rightText);
|
|
2469
|
-
}
|
|
2470
|
-
function titleFromSegment(segment) {
|
|
2471
|
-
return segment.replace(/[-_]+/gu, " ").replace(/^./u, (character) => character.toUpperCase());
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
//#endregion
|
|
2475
|
-
//#region ../server/src/internal/documents/documents.ts
|
|
2476
|
-
const isDocumentGenerationConflict$2 = Schema.is(DocumentGenerationConflict);
|
|
2477
|
-
const isDocumentMediaUnavailable$1 = Schema.is(DocumentMediaUnavailable);
|
|
2478
|
-
const CONTENT_MUTATION_BUFFER_CAPACITY = 16;
|
|
2479
|
-
var Documents = class extends Context.Service()("@internal/server/Documents") {};
|
|
2480
|
-
var DocumentPreviews = class extends Context.Service()("@internal/server/DocumentPreviews") {};
|
|
2481
|
-
var PublishedDocuments = class extends Context.Service()("@internal/server/PublishedDocuments") {};
|
|
2482
|
-
var DocumentMaintenance = class extends Context.Service()("@internal/server/internal/DocumentMaintenance") {};
|
|
2483
|
-
const makeDocuments = Effect.fn("Documents.make")(function* (configuration, options) {
|
|
2484
|
-
const repository = yield* DocumentStore;
|
|
2485
|
-
const accessControl = yield* AccessControl;
|
|
2486
|
-
const crypto = yield* Crypto.Crypto;
|
|
2487
|
-
const engine = yield* createDocumentEngine(configuration, options);
|
|
2488
|
-
const state = yield* Ref.make(yield* loadSynchronizedState(repository, engine));
|
|
2489
|
-
const semaphore = yield* Semaphore.make(1);
|
|
2490
|
-
const mutationEvents = yield* Effect.acquireRelease(PubSub.sliding(CONTENT_MUTATION_BUFFER_CAPACITY), PubSub.shutdown);
|
|
2491
|
-
const dependencies = {
|
|
2492
|
-
engine,
|
|
2493
|
-
configuration,
|
|
2494
|
-
repository,
|
|
2495
|
-
components: options.components
|
|
2496
|
-
};
|
|
2497
|
-
const refreshUnlocked = Effect.fn("Documents.refreshUnlocked")(function* (force = false) {
|
|
2498
|
-
const current = yield* Ref.get(state);
|
|
2499
|
-
const snapshot = force ? yield* loadRequired(repository) : yield* repository.load(current.snapshot.generation);
|
|
2500
|
-
if (!snapshot || !generationIsAfter(snapshot.generation, current.snapshot.generation)) return current.snapshot.generation;
|
|
2501
|
-
const refreshed = yield* compileDocumentState(engine, snapshot, current);
|
|
2502
|
-
yield* Ref.set(state, refreshed);
|
|
2503
|
-
return refreshed.snapshot.generation;
|
|
2504
|
-
});
|
|
2505
|
-
const executeUnlocked = Effect.fn("Documents.execute")(function* (command, activity) {
|
|
2506
|
-
return yield* Effect.gen(function* () {
|
|
2507
|
-
yield* refreshUnlocked();
|
|
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
|
-
}
|
|
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
|
-
});
|
|
2559
|
-
if (prepared._tag === "Unchanged") return prepared.result;
|
|
2560
|
-
const committed = yield* repository.commit({
|
|
2561
|
-
activity,
|
|
2562
|
-
plan: prepared.commitPlan,
|
|
2563
|
-
additionalPlans: prepared.additionalPlans,
|
|
2564
|
-
expectedGeneration: current.snapshot.generation,
|
|
2565
|
-
mediaReferences: [prepared.commitPlan, ...prepared.additionalPlans ?? []].flatMap((plan) => projectMediaReferences(prepared.authored, prepared.published, plan.documentId))
|
|
2566
|
-
});
|
|
2567
|
-
const next = makeDocumentState(prepared.authored, prepared.published, committed.snapshot);
|
|
2568
|
-
yield* Ref.set(state, next);
|
|
2569
|
-
const entry = entryById(next, committed.mutation.documentId);
|
|
2570
|
-
const result = DocumentMutationResult.make(Object.assign({}, committed.mutation, entry === void 0 ? void 0 : { entry }));
|
|
2571
|
-
yield* PubSub.publish(mutationEvents, result);
|
|
2572
|
-
return result;
|
|
2573
|
-
}).pipe(Effect.tapError((error) => isDocumentGenerationConflict$2(error) ? refreshUnlocked(true) : Effect.void), Effect.retry({
|
|
2574
|
-
schedule: Schedule.recurs(3),
|
|
2575
|
-
while: isDocumentGenerationConflict$2
|
|
2576
|
-
}));
|
|
2577
|
-
});
|
|
2578
|
-
const runMutation = Effect.fn("Documents.runMutation")(function* (command, activity, reason) {
|
|
2579
|
-
const resolvedActivity = enrichActivity(activity, reason);
|
|
2580
|
-
return yield* semaphore.withPermits(1)(executeUnlocked(command, resolvedActivity));
|
|
2581
|
-
});
|
|
2582
|
-
const authored = semaphore.withPermits(1)(Effect.gen(function* () {
|
|
2583
|
-
yield* refreshUnlocked();
|
|
2584
|
-
return (yield* Ref.get(state)).authored;
|
|
2585
|
-
})).pipe(Effect.withSpan("Documents.authored"));
|
|
2586
|
-
const browse = Effect.fn("Documents.browse")((query) => semaphore.withPermits(1)(Effect.gen(function* () {
|
|
2587
|
-
yield* refreshUnlocked();
|
|
2588
|
-
return authoringEntries(yield* Ref.get(state), query);
|
|
2589
|
-
})));
|
|
2590
|
-
const create = Effect.fn("Documents.create")(function* (input, activity) {
|
|
2591
|
-
const plan = yield* Effect.fromResult(planDocumentCreation(configuration, input));
|
|
2592
|
-
const source = yield* createInitialDocumentSource(plan);
|
|
2593
|
-
const documentId = DocumentId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
|
|
2594
|
-
return yield* runMutation({
|
|
2595
|
-
action: "document.create",
|
|
2596
|
-
documentId,
|
|
2597
|
-
markdown: source.markdown,
|
|
2598
|
-
sourcePath: plan.sourcePath
|
|
2599
|
-
}, activity, input.reason);
|
|
2600
|
-
});
|
|
2601
|
-
const importSources = (input, activity) => importDocumentSources({
|
|
2602
|
-
current: (sourcePath) => semaphore.withPermits(1)(Effect.gen(function* () {
|
|
2603
|
-
yield* refreshUnlocked();
|
|
2604
|
-
return (yield* Ref.get(state)).snapshot.active.find((candidate) => candidate.sourcePath === sourcePath);
|
|
2605
|
-
})),
|
|
2606
|
-
nextDocumentId: crypto.randomUUIDv4.pipe(Effect.orDie, Effect.map((value) => DocumentId.make(value))),
|
|
2607
|
-
runMutation
|
|
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));
|
|
2618
|
-
const move = Effect.fn("Documents.move")((input, activity) => runMutation({
|
|
2619
|
-
action: "document.move",
|
|
2620
|
-
input
|
|
2621
|
-
}, activity, input.reason));
|
|
2622
|
-
const subscribeMutations = Effect.gen(function* () {
|
|
2623
|
-
const subscription = yield* PubSub.subscribe(mutationEvents);
|
|
2624
|
-
const baseline = yield* semaphore.withPermits(1)(Ref.get(state).pipe(Effect.map((current) => current.snapshot.generation)));
|
|
2625
|
-
return {
|
|
2626
|
-
baseline,
|
|
2627
|
-
mutations: Stream.fromSubscription(subscription).pipe(Stream.filter((mutation) => generationIsAfter(mutation.generation, baseline)))
|
|
2628
|
-
};
|
|
2629
|
-
}).pipe(Effect.withSpan("Documents.subscribeMutations"));
|
|
2630
|
-
const publish = Effect.fn("Documents.publish")((input, activity) => runMutation({
|
|
2631
|
-
action: "document.publish",
|
|
2632
|
-
input
|
|
2633
|
-
}, activity, input.reason));
|
|
2634
|
-
const published = semaphore.withPermits(1)(Effect.gen(function* () {
|
|
2635
|
-
yield* refreshUnlocked();
|
|
2636
|
-
return (yield* Ref.get(state)).published;
|
|
2637
|
-
})).pipe(Effect.withSpan("Documents.published"));
|
|
2638
|
-
const read = Effect.fn("Documents.read")((documentId) => semaphore.withPermits(1)(Effect.gen(function* () {
|
|
2639
|
-
yield* refreshUnlocked();
|
|
2640
|
-
return (yield* Ref.get(state)).activeById.get(documentId) || (yield* new DocumentNotFound({ documentId }));
|
|
2641
|
-
})));
|
|
2642
|
-
const refresh = semaphore.withPermits(1)(refreshUnlocked()).pipe(Effect.withSpan("Documents.refresh"));
|
|
2643
|
-
const remove = Effect.fn("Documents.remove")((input, activity) => runMutation({
|
|
2644
|
-
action: "document.remove",
|
|
2645
|
-
input
|
|
2646
|
-
}, activity, input.reason));
|
|
2647
|
-
const restore = Effect.fn("Documents.restore")((input, activity) => runMutation({
|
|
2648
|
-
action: "document.restore",
|
|
2649
|
-
input
|
|
2650
|
-
}, activity, input.reason));
|
|
2651
|
-
const trash = semaphore.withPermits(1)(Effect.gen(function* () {
|
|
2652
|
-
yield* refreshUnlocked();
|
|
2653
|
-
return deletedEntries(yield* Ref.get(state));
|
|
2654
|
-
})).pipe(Effect.withSpan("Documents.trash"));
|
|
2655
|
-
const undelete = Effect.fn("Documents.undelete")((input, activity) => runMutation({
|
|
2656
|
-
action: "document.undelete",
|
|
2657
|
-
input
|
|
2658
|
-
}, activity, input.reason));
|
|
2659
|
-
const update = Effect.fn("Documents.update")((input, activity) => runMutation({
|
|
2660
|
-
action: "document.update",
|
|
2661
|
-
input
|
|
2662
|
-
}, activity, input.reason));
|
|
2663
|
-
const validate = Effect.fn("Documents.validate")((input) => semaphore.withPermits(1)(Effect.gen(function* () {
|
|
2664
|
-
yield* refreshUnlocked();
|
|
2665
|
-
return yield* validateDocumentUpdate(engine, yield* Ref.get(state), input);
|
|
2666
|
-
})));
|
|
2667
|
-
const history = documentHistoryCapability(accessControl, repository);
|
|
2668
|
-
const delivery = documentDelivery(configuration, published);
|
|
2669
|
-
const document = (documentId) => ({
|
|
2670
|
-
documentId,
|
|
2671
|
-
kind: "document"
|
|
2672
|
-
});
|
|
2673
|
-
const requireThen = (context, action, resource, effect) => accessControl.require(context, {
|
|
2674
|
-
action,
|
|
2675
|
-
resource
|
|
2676
|
-
}).pipe(Effect.andThen(effect));
|
|
2677
|
-
const documents = Documents.of({
|
|
2678
|
-
browse: (context, query) => requireThen(context, "document.browse", query.collection ? {
|
|
2679
|
-
collection: query.collection,
|
|
2680
|
-
kind: "collection",
|
|
2681
|
-
locale: query.locale
|
|
2682
|
-
} : { kind: "catalog" }, () => browse(query)),
|
|
2683
|
-
create: (context, input) => requireThen(context, "document.create", {
|
|
2684
|
-
collection: input.collection,
|
|
2685
|
-
kind: "collection",
|
|
2686
|
-
locale: input.locale
|
|
2687
|
-
}, () => create(input, requestActivity(context, input.reason))),
|
|
2688
|
-
historyCompare: history.compare,
|
|
2689
|
-
historyList: history.list,
|
|
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))),
|
|
2693
|
-
move: (context, input) => requireThen(context, "document.move", document(input.documentId), () => move(input, requestActivity(context, input.reason))),
|
|
2694
|
-
publish: (context, input) => requireThen(context, "document.publish", document(input.documentId), () => publish(input, requestActivity(context, input.reason))),
|
|
2695
|
-
read: (context, documentId) => requireThen(context, "document.read", document(documentId), () => read(documentId)),
|
|
2696
|
-
remove: (context, input) => requireThen(context, "document.remove", document(input.documentId), () => remove(input, requestActivity(context, input.reason))),
|
|
2697
|
-
restore: (context, input) => requireThen(context, "document.restore", document(input.documentId), () => restore(input, requestActivity(context, input.reason))),
|
|
2698
|
-
trash: (context) => requireThen(context, "document.trash", { kind: "catalog" }, () => trash),
|
|
2699
|
-
undelete: (context, input) => requireThen(context, "document.undelete", document(input.documentId), () => undelete(input, requestActivity(context, input.reason))),
|
|
2700
|
-
update: (context, input) => requireThen(context, "document.update", document(input.documentId), () => update(input, requestActivity(context, input.reason))),
|
|
2701
|
-
validate: (context, input) => requireThen(context, "document.validate", document(input.documentId), () => validate(input))
|
|
2702
|
-
});
|
|
2703
|
-
const draftDelivery = documentDelivery(configuration, authored);
|
|
2704
|
-
const previews = DocumentPreviews.of({
|
|
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))
|
|
2709
|
-
});
|
|
2710
|
-
const publishedService = PublishedDocuments.of({
|
|
2711
|
-
navigation: delivery.navigation,
|
|
2712
|
-
query: delivery.query,
|
|
2713
|
-
read: delivery.read,
|
|
2714
|
-
surround: delivery.surround,
|
|
2715
|
-
diagnostics: (context) => accessControl.require(context, {
|
|
2716
|
-
action: "document.browse",
|
|
2717
|
-
resource: { kind: "catalog" }
|
|
2718
|
-
}).pipe(Effect.andThen(delivery.diagnostics))
|
|
2719
|
-
});
|
|
2720
|
-
return {
|
|
2721
|
-
documents,
|
|
2722
|
-
maintenance: DocumentMaintenance.of({
|
|
2723
|
-
importSources,
|
|
2724
|
-
mediaReferences: (documentId) => semaphore.withPermits(1)(Effect.gen(function* () {
|
|
2725
|
-
yield* refreshUnlocked();
|
|
2726
|
-
const current = yield* Ref.get(state);
|
|
2727
|
-
return projectMediaReferences(current.authored, current.published, documentId);
|
|
2728
|
-
})),
|
|
2729
|
-
publish,
|
|
2730
|
-
read,
|
|
2731
|
-
refresh,
|
|
2732
|
-
subscribeMutations,
|
|
2733
|
-
validate
|
|
2734
|
-
}),
|
|
2735
|
-
previews,
|
|
2736
|
-
publishedDocuments: publishedService
|
|
2737
|
-
};
|
|
1423
|
+
var WorkingDocumentInvalid = class extends Schema.TaggedError()("WorkingDocuments.Invalid", {
|
|
1424
|
+
documentId: DocumentId,
|
|
1425
|
+
message: Schema.NonEmptyString,
|
|
1426
|
+
cause: Schema.Defect()
|
|
1427
|
+
}) {};
|
|
1428
|
+
var WorkingDocumentUnavailable = class extends Schema.TaggedError()("WorkingDocuments.Unavailable", {
|
|
1429
|
+
operation: Schema.NonEmptyString,
|
|
1430
|
+
cause: Schema.Defect()
|
|
1431
|
+
}) {};
|
|
1432
|
+
/** An authored or working baseline committed, but the publish workflow still needs recovery. */
|
|
1433
|
+
var WorkingDocumentCheckpointed = class extends Schema.TaggedError()("WorkingDocuments.Checkpointed", {
|
|
1434
|
+
checkpointVersion: DocumentVersion,
|
|
1435
|
+
documentId: DocumentId,
|
|
1436
|
+
result: Schema.optionalKey(DocumentMutationResult),
|
|
1437
|
+
stateVector: Schema.Uint8Array,
|
|
1438
|
+
cause: Schema.Defect()
|
|
1439
|
+
}) {};
|
|
1440
|
+
const WorkingDocumentChange = Schema.Struct({
|
|
1441
|
+
kind: Schema.Literals(["updated", "invalidated"]),
|
|
1442
|
+
ref: WorkingDocumentRef
|
|
2738
1443
|
});
|
|
2739
|
-
|
|
2740
|
-
return Layer.effectContext(makeDocuments(configuration, options).pipe(Effect.map(({ documents, maintenance, previews, publishedDocuments }) => Context.empty().pipe(Context.add(Documents, documents), Context.add(DocumentMaintenance, maintenance), Context.add(DocumentPreviews, previews), Context.add(PublishedDocuments, publishedDocuments)))));
|
|
2741
|
-
}
|
|
2742
|
-
function loadRequired(repository) {
|
|
2743
|
-
return repository.load().pipe(Effect.filterOrFail((snapshot) => snapshot !== void 0, () => new DocumentPersistenceError({
|
|
2744
|
-
cause: /* @__PURE__ */ new Error("DocumentStore returned no initial snapshot."),
|
|
2745
|
-
operation: "DocumentStore.load"
|
|
2746
|
-
})));
|
|
2747
|
-
}
|
|
2748
|
-
function loadSynchronizedState(repository, engine) {
|
|
2749
|
-
return Effect.gen(function* () {
|
|
2750
|
-
const snapshot = yield* loadRequired(repository);
|
|
2751
|
-
const state = yield* compileDocumentState(engine, snapshot);
|
|
2752
|
-
yield* repository.synchronizeMediaReferences({
|
|
2753
|
-
expectedGeneration: snapshot.generation,
|
|
2754
|
-
references: projectMediaReferences(state.authored, state.published)
|
|
2755
|
-
});
|
|
2756
|
-
return state;
|
|
2757
|
-
}).pipe(Effect.retry({
|
|
2758
|
-
schedule: Schedule.recurs(3),
|
|
2759
|
-
while: isDocumentGenerationConflict$2
|
|
2760
|
-
}), Effect.mapError((error) => isDocumentGenerationConflict$2(error) || isDocumentMediaUnavailable$1(error) ? new DocumentPersistenceError({
|
|
2761
|
-
cause: error,
|
|
2762
|
-
operation: "Documents.synchronizeMediaReferences"
|
|
2763
|
-
}) : error));
|
|
2764
|
-
}
|
|
2765
|
-
function generationIsAfter(candidate, current) {
|
|
2766
|
-
return BigInt(candidate) > BigInt(current);
|
|
2767
|
-
}
|
|
2768
|
-
function enrichActivity(activity, reason) {
|
|
2769
|
-
return ActivityContext.make(Object.assign({}, activity, reason === void 0 ? void 0 : { reason }));
|
|
2770
|
-
}
|
|
2771
|
-
function requestActivity(context, reason) {
|
|
2772
|
-
return ActivityContext.make(Object.assign({
|
|
2773
|
-
actor: context.actor,
|
|
2774
|
-
requestId: context.requestId
|
|
2775
|
-
}, reason === void 0 ? void 0 : { reason }));
|
|
2776
|
-
}
|
|
1444
|
+
var WorkingDocuments = class extends Context.Service()("@internal/server/WorkingDocuments") {};
|
|
2777
1445
|
|
|
2778
1446
|
//#endregion
|
|
2779
1447
|
//#region ../document/src/internal/editor/identity-extension.ts
|
|
2780
|
-
/** Ensures Yjs only observes authorable nodes with final, unique identities. */
|
|
2781
1448
|
const EditorNodeIdentity = Extension.create({
|
|
2782
1449
|
name: "cmsNodeIdentity",
|
|
2783
1450
|
priority: 1e4,
|
|
@@ -3326,7 +1993,7 @@ function unwrapFirstParagraph(nodes) {
|
|
|
3326
1993
|
}
|
|
3327
1994
|
function restoreOpaqueNode(value, nodeId) {
|
|
3328
1995
|
const decoded = Schema.decodeUnknownOption(MarkdownNode)(value);
|
|
3329
|
-
if (decoded
|
|
1996
|
+
if (Option.isNone(decoded) || Predicate.isString(decoded.value)) return void 0;
|
|
3330
1997
|
const node = structuredClone(decoded.value);
|
|
3331
1998
|
if (nodeId === void 0) return node;
|
|
3332
1999
|
if (node[0] === null) return [
|
|
@@ -3436,10 +2103,10 @@ const PROPERTIES_NAME = "properties";
|
|
|
3436
2103
|
const PROPS_PREFIX = "props:";
|
|
3437
2104
|
const CMS_PROPS_PREFIX = "cmsProps:";
|
|
3438
2105
|
const editorSchema$1 = createEditorSchema();
|
|
3439
|
-
const decodeEditorNodeId = Schema.
|
|
3440
|
-
const decodeJson$1 = Schema.
|
|
3441
|
-
const decodeJsonObject$1 = Schema.
|
|
3442
|
-
const decodePath = Schema.
|
|
2106
|
+
const decodeEditorNodeId = flow(Schema.decodeUnknownResult(EditorNodeId), Result.getOrThrow);
|
|
2107
|
+
const decodeJson$1 = flow(Schema.decodeUnknownResult(Schema.Json), Result.getOrThrow);
|
|
2108
|
+
const decodeJsonObject$1 = flow(Schema.decodeUnknownResult(Schema.JsonObject), Result.getOrThrow);
|
|
2109
|
+
const decodePath = flow(Schema.decodeUnknownResult(Schema.Array(Schema.String)), Result.getOrThrow);
|
|
3443
2110
|
function seedCollaborationFields(document, content) {
|
|
3444
2111
|
const state = extractGranularState(content);
|
|
3445
2112
|
replaceMap(document.getMap(FRONTMATTER_NAME), flatten(state.frontmatter));
|
|
@@ -3589,9 +2256,9 @@ const METADATA_NAME = "cms";
|
|
|
3589
2256
|
const META_KEY = "meta";
|
|
3590
2257
|
const SOURCE_PATH_KEY = "sourcePath";
|
|
3591
2258
|
const editorSchema = createEditorSchema();
|
|
3592
|
-
const decodeJson = Schema.
|
|
3593
|
-
const decodeJsonObject = Schema.
|
|
3594
|
-
const decodeSourcePath = Schema.
|
|
2259
|
+
const decodeJson = flow(Schema.decodeUnknownResult(Schema.Json), Result.getOrThrow);
|
|
2260
|
+
const decodeJsonObject = flow(Schema.decodeUnknownResult(Schema.JsonObject), Result.getOrThrow);
|
|
2261
|
+
const decodeSourcePath = flow(Schema.decodeUnknownResult(SourcePath), Result.getOrThrow);
|
|
3595
2262
|
function seedCollaborationDocument(sourcePath, seed, meta) {
|
|
3596
2263
|
const document = prosemirrorJSONToYDoc(editorSchema, seed, BODY_NAME);
|
|
3597
2264
|
try {
|
|
@@ -3688,7 +2355,6 @@ function sameBytes$1(left, right) {
|
|
|
3688
2355
|
|
|
3689
2356
|
//#endregion
|
|
3690
2357
|
//#region ../document/src/CollaborationDocument.ts
|
|
3691
|
-
/** Creates the initial collaborative state from the canonical editor projection. */
|
|
3692
2358
|
const createCollaborationDocument = Effect.fn("CollaborationDocument.create")(function* (input) {
|
|
3693
2359
|
const seed = yield* openEditorDocument(input.body);
|
|
3694
2360
|
return yield* Effect.try({
|
|
@@ -3696,7 +2362,6 @@ const createCollaborationDocument = Effect.fn("CollaborationDocument.create")(fu
|
|
|
3696
2362
|
catch: (cause) => invalidCollaboration("Could not create the collaborative editor document.", cause)
|
|
3697
2363
|
});
|
|
3698
2364
|
});
|
|
3699
|
-
/** Renders collaborative state through the canonical editor-to-Markdown encoder. */
|
|
3700
2365
|
const renderCollaborationDocument = Effect.fn("CollaborationDocument.render")(function* (document) {
|
|
3701
2366
|
const inspected = yield* inspectCollaborationDocument(document);
|
|
3702
2367
|
return {
|
|
@@ -3715,31 +2380,26 @@ const captureCollaborationDocument = Effect.fn("CollaborationDocument.capture")(
|
|
|
3715
2380
|
catch: (cause) => invalidCollaboration("Could not capture the collaborative editor document.", cause)
|
|
3716
2381
|
});
|
|
3717
2382
|
});
|
|
3718
|
-
/** Restores a validated, caller-owned document from a full collaboration snapshot. */
|
|
3719
2383
|
const restoreCollaborationDocument = Effect.fn("CollaborationDocument.restore")(function* (snapshot) {
|
|
3720
2384
|
return yield* validateRestoredDocument(yield* Effect.try({
|
|
3721
2385
|
try: () => restoreCollaborationSnapshot(snapshot),
|
|
3722
2386
|
catch: (cause) => invalidCollaboration("Could not restore the collaborative editor document.", cause)
|
|
3723
2387
|
}));
|
|
3724
2388
|
});
|
|
3725
|
-
/** Restores a validated document when only its authoritative full-state update is persisted. */
|
|
3726
2389
|
const restoreCollaborationState = Effect.fn("CollaborationDocument.restoreState")(function* (state) {
|
|
3727
2390
|
return yield* validateRestoredDocument(yield* Effect.try({
|
|
3728
2391
|
try: () => restoreCollaborationUpdate(state),
|
|
3729
2392
|
catch: (cause) => invalidCollaboration("Could not restore the collaborative editor document.", cause)
|
|
3730
2393
|
}));
|
|
3731
2394
|
});
|
|
3732
|
-
/** Applies one encoded collaboration update while translating corrupt data to a typed failure. */
|
|
3733
2395
|
const applyCollaborationUpdate = Effect.fn("CollaborationDocument.applyUpdate")((document, update) => Effect.try({
|
|
3734
2396
|
try: () => applyCollaborationStateUpdate(document, update),
|
|
3735
2397
|
catch: (cause) => invalidCollaboration("Could not apply the collaborative document update.", cause)
|
|
3736
2398
|
}));
|
|
3737
|
-
/** Captures the current state vector and an optional differential update. */
|
|
3738
2399
|
const synchronizeCollaborationState = Effect.fn("CollaborationDocument.synchronizeState")((document, stateVector) => Effect.try({
|
|
3739
2400
|
try: () => synchronizeCollaborationStateValue(document, stateVector),
|
|
3740
2401
|
catch: (cause) => invalidCollaboration("Could not synchronize the collaborative document state.", cause)
|
|
3741
2402
|
}));
|
|
3742
|
-
/** Releases a collaboration document owned by a scoped workflow. */
|
|
3743
2403
|
function destroyCollaborationDocument(document) {
|
|
3744
2404
|
return Effect.sync(() => document.destroy());
|
|
3745
2405
|
}
|
|
@@ -3840,6 +2500,25 @@ function sameJson(left, right) {
|
|
|
3840
2500
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
3841
2501
|
}
|
|
3842
2502
|
|
|
2503
|
+
//#endregion
|
|
2504
|
+
//#region ../server/src/internal/working-documents/working-document-source.ts
|
|
2505
|
+
const validatedWorkingDocument = Effect.fn("WorkingDocuments.validateSource")(function* (source, validate) {
|
|
2506
|
+
const validation = yield* validate(UpdateDocumentInput.make({
|
|
2507
|
+
documentId: source.documentId,
|
|
2508
|
+
expectedVersion: source.version,
|
|
2509
|
+
markdown: source.markdown
|
|
2510
|
+
}));
|
|
2511
|
+
if (!validation.valid || !validation.document) return yield* new WorkingDocumentInvalid({
|
|
2512
|
+
cause: validation.issues,
|
|
2513
|
+
documentId: source.documentId,
|
|
2514
|
+
message: "Authored document cannot initialize a Working Document."
|
|
2515
|
+
});
|
|
2516
|
+
return validation.document;
|
|
2517
|
+
});
|
|
2518
|
+
const newWorkingSnapshot = Effect.fn("WorkingDocuments.newSnapshot")(function* (source, document, generationId) {
|
|
2519
|
+
return yield* createWorkingSnapshot(source, document, yield* generationId(source.documentId));
|
|
2520
|
+
});
|
|
2521
|
+
|
|
3843
2522
|
//#endregion
|
|
3844
2523
|
//#region ../server/src/internal/working-documents/working-document-reconciliation.ts
|
|
3845
2524
|
const isDocumentNotFound$3 = Schema.is(DocumentNotFound);
|
|
@@ -3854,18 +2533,6 @@ const startWorkingDocumentReconciliation = Effect.fn("WorkingDocuments.startReco
|
|
|
3854
2533
|
})
|
|
3855
2534
|
}).pipe(Effect.asVoid);
|
|
3856
2535
|
const currentSource = (documentId) => options.documents.read(documentId).pipe(Effect.asSome, Effect.catchIf(isDocumentNotFound$3, () => Effect.succeedNone));
|
|
3857
|
-
const validatedDocument = Effect.fn("WorkingDocuments.reconcile.validateSource")(function* (source) {
|
|
3858
|
-
const validation = yield* options.documents.validate(UpdateDocumentInput.make({
|
|
3859
|
-
documentId: source.documentId,
|
|
3860
|
-
expectedVersion: source.version,
|
|
3861
|
-
markdown: source.markdown
|
|
3862
|
-
}));
|
|
3863
|
-
if (!validation.valid || !validation.document) return yield* invalidSource(source.documentId, validation);
|
|
3864
|
-
return validation.document;
|
|
3865
|
-
});
|
|
3866
|
-
const newSnapshot = Effect.fn("WorkingDocuments.reconcile.newSnapshot")(function* (source, document) {
|
|
3867
|
-
return yield* createWorkingSnapshot(source, document, yield* options.generationId(source.documentId));
|
|
3868
|
-
});
|
|
3869
2536
|
const rotateDirty = Effect.fn("WorkingDocuments.reconcile.rotateDirty")(function* (snapshot) {
|
|
3870
2537
|
const generationId = yield* options.generationId(snapshot.documentId);
|
|
3871
2538
|
const seed = yield* useWorkingSnapshot(snapshot, (document) => captureCollaborationDocument(document).pipe(Effect.map((captured) => rotatedSeed(snapshot, generationId, captured))));
|
|
@@ -3901,10 +2568,10 @@ const startWorkingDocumentReconciliation = Effect.fn("WorkingDocuments.startReco
|
|
|
3901
2568
|
yield* publishChange(loaded.value);
|
|
3902
2569
|
return;
|
|
3903
2570
|
}
|
|
3904
|
-
const document = yield*
|
|
2571
|
+
const document = yield* validatedWorkingDocument(source.value, options.documents.validate);
|
|
3905
2572
|
yield* options.store.replace({
|
|
3906
2573
|
expectedGeneration: loaded.value.generationId,
|
|
3907
|
-
snapshot: yield*
|
|
2574
|
+
snapshot: yield* newWorkingSnapshot(source.value, document, options.generationId)
|
|
3908
2575
|
});
|
|
3909
2576
|
yield* publishChange(loaded.value);
|
|
3910
2577
|
}));
|
|
@@ -3990,13 +2657,6 @@ function rotatedSeed(snapshot, generationId, captured) {
|
|
|
3990
2657
|
state: Uint8Array.from(captured.state)
|
|
3991
2658
|
};
|
|
3992
2659
|
}
|
|
3993
|
-
function invalidSource(documentId, validation) {
|
|
3994
|
-
return Effect.fail(new WorkingDocumentInvalid({
|
|
3995
|
-
cause: validation.issues,
|
|
3996
|
-
documentId,
|
|
3997
|
-
message: "Authored document cannot initialize a Working Document."
|
|
3998
|
-
}));
|
|
3999
|
-
}
|
|
4000
2660
|
function generationAtOrBefore(candidate, current) {
|
|
4001
2661
|
return BigInt(candidate) <= BigInt(current);
|
|
4002
2662
|
}
|
|
@@ -4004,6 +2664,28 @@ function generationsAreAdjacent(current, candidate) {
|
|
|
4004
2664
|
return BigInt(candidate) === BigInt(current) + 1n;
|
|
4005
2665
|
}
|
|
4006
2666
|
|
|
2667
|
+
//#endregion
|
|
2668
|
+
//#region ../server/src/internal/working-documents/document-locks.ts
|
|
2669
|
+
/** Retain a lock only while an operation is running or waiting for that document. */
|
|
2670
|
+
function documentSynchronization() {
|
|
2671
|
+
const locks = /* @__PURE__ */ new Map();
|
|
2672
|
+
return (documentId, effect) => Effect.acquireUseRelease(Effect.sync(() => {
|
|
2673
|
+
let lock = locks.get(documentId);
|
|
2674
|
+
if (!lock) {
|
|
2675
|
+
lock = {
|
|
2676
|
+
semaphore: Semaphore.makeUnsafe(1),
|
|
2677
|
+
users: 0
|
|
2678
|
+
};
|
|
2679
|
+
locks.set(documentId, lock);
|
|
2680
|
+
}
|
|
2681
|
+
lock.users += 1;
|
|
2682
|
+
return lock;
|
|
2683
|
+
}), (lock) => lock.semaphore.withPermit(effect), (lock) => Effect.sync(() => {
|
|
2684
|
+
lock.users -= 1;
|
|
2685
|
+
if (lock.users === 0 && locks.get(documentId) === lock) locks.delete(documentId);
|
|
2686
|
+
}));
|
|
2687
|
+
}
|
|
2688
|
+
|
|
4007
2689
|
//#endregion
|
|
4008
2690
|
//#region ../server/src/internal/working-documents/working-documents.ts
|
|
4009
2691
|
function workingDocumentsLayer(options = {}) {
|
|
@@ -4017,31 +2699,11 @@ const makeWorkingDocuments = Effect.fn("WorkingDocuments.make")(function* (optio
|
|
|
4017
2699
|
const store = yield* WorkingDocumentStore;
|
|
4018
2700
|
const changeSource = yield* WorkingDocumentChanges;
|
|
4019
2701
|
const changes = yield* Effect.acquireRelease(PubSub.bounded(64), PubSub.shutdown);
|
|
4020
|
-
const
|
|
2702
|
+
const synchronized = documentSynchronization();
|
|
4021
2703
|
const compactAfterUpdates = validCompactionLimit(options.compactAfterUpdates);
|
|
4022
|
-
const synchronized = (documentId, effect) => Effect.suspend(() => {
|
|
4023
|
-
let semaphore = locks.get(documentId);
|
|
4024
|
-
if (!semaphore) {
|
|
4025
|
-
semaphore = Semaphore.makeUnsafe(1);
|
|
4026
|
-
locks.set(documentId, semaphore);
|
|
4027
|
-
}
|
|
4028
|
-
return semaphore.withPermit(effect);
|
|
4029
|
-
});
|
|
4030
2704
|
const generationId = (documentId) => crypto.randomUUIDv4.pipe(Effect.map((uuid) => WorkingDocumentGenerationId.make(uuid)), Effect.mapError((cause) => invalid(documentId, "Could not create a working document generation.", cause)));
|
|
4031
2705
|
const attemptId = (documentId) => crypto.randomUUIDv4.pipe(Effect.map((uuid) => WorkingDocumentPublishAttemptId.make(uuid)), Effect.mapError((cause) => invalid(documentId, "Could not create a publish attempt.", cause)));
|
|
4032
2706
|
const markdownHash = (documentId, markdown) => crypto.digest("SHA-256", new TextEncoder().encode(markdown)).pipe(Effect.map(hex), Effect.mapError((cause) => invalid(documentId, "Could not fingerprint Working Document Markdown.", cause)));
|
|
4033
|
-
const validatedDocument = Effect.fn("WorkingDocuments.validateSource")(function* (source) {
|
|
4034
|
-
const validation = yield* maintenance.validate(UpdateDocumentInput.make({
|
|
4035
|
-
documentId: source.documentId,
|
|
4036
|
-
expectedVersion: source.version,
|
|
4037
|
-
markdown: source.markdown
|
|
4038
|
-
}));
|
|
4039
|
-
if (!validation.valid || !validation.document) return yield* invalid(source.documentId, "Authored document cannot initialize a Working Document.", validation.issues);
|
|
4040
|
-
return validation.document;
|
|
4041
|
-
});
|
|
4042
|
-
const newSnapshot = Effect.fn("WorkingDocuments.newSnapshot")(function* (source, document) {
|
|
4043
|
-
return yield* createWorkingSnapshot(source, document, yield* generationId(source.documentId));
|
|
4044
|
-
});
|
|
4045
2707
|
const compactSnapshot = Effect.fn("WorkingDocuments.compactSnapshot")(function* (snapshot) {
|
|
4046
2708
|
const lastUpdate = snapshot.updates.at(-1);
|
|
4047
2709
|
if (!lastUpdate) return false;
|
|
@@ -4121,21 +2783,21 @@ const makeWorkingDocuments = Effect.fn("WorkingDocuments.make")(function* (optio
|
|
|
4121
2783
|
const ensureOpenSnapshot = Effect.fn("WorkingDocuments.ensureOpenSnapshot")(function* (source) {
|
|
4122
2784
|
const loaded = yield* store.load(source.documentId);
|
|
4123
2785
|
if (Option.isNone(loaded)) {
|
|
4124
|
-
const document = yield*
|
|
4125
|
-
const initialized = yield* store.initialize(yield*
|
|
2786
|
+
const document = yield* validatedWorkingDocument(source, maintenance.validate);
|
|
2787
|
+
const initialized = yield* store.initialize(yield* newWorkingSnapshot(source, document, generationId));
|
|
4126
2788
|
if (initialized.savedVersion === source.version) return initialized;
|
|
4127
2789
|
if (yield* workingSnapshotIsDirty(initialized)) return initialized;
|
|
4128
2790
|
return yield* store.replace({
|
|
4129
2791
|
expectedGeneration: initialized.generationId,
|
|
4130
|
-
snapshot: yield*
|
|
2792
|
+
snapshot: yield* newWorkingSnapshot(source, document, generationId)
|
|
4131
2793
|
});
|
|
4132
2794
|
}
|
|
4133
2795
|
if (loaded.value.savedVersion === source.version) return loaded.value;
|
|
4134
2796
|
if (yield* workingSnapshotIsDirty(loaded.value)) return loaded.value;
|
|
4135
|
-
const document = yield*
|
|
2797
|
+
const document = yield* validatedWorkingDocument(source, maintenance.validate);
|
|
4136
2798
|
return yield* store.replace({
|
|
4137
2799
|
expectedGeneration: loaded.value.generationId,
|
|
4138
|
-
snapshot: yield*
|
|
2800
|
+
snapshot: yield* newWorkingSnapshot(source, document, generationId)
|
|
4139
2801
|
});
|
|
4140
2802
|
});
|
|
4141
2803
|
const open = (context, documentId) => synchronized(documentId, Effect.gen(function* () {
|
|
@@ -4359,7 +3021,7 @@ function closeHocuspocus(hocuspocus, operations) {
|
|
|
4359
3021
|
const store = hocuspocus.debouncer.executeNow(`onStoreDocument-${document.name}`);
|
|
4360
3022
|
return store === void 0 ? [] : [store];
|
|
4361
3023
|
});
|
|
4362
|
-
yield* Effect.promise(() => Promise.allSettled(stores));
|
|
3024
|
+
const storeResults = yield* Effect.promise(() => Promise.allSettled(stores));
|
|
4363
3025
|
yield* Effect.promise(operations.drain);
|
|
4364
3026
|
const cleanupResults = yield* Effect.promise(() => Promise.allSettled(documents.map((document) => document.saveMutex.runExclusive(() => Promise.resolve()).then(() => hocuspocus.unloadDocument(document)))));
|
|
4365
3027
|
const destroyResult = yield* Effect.result(Effect.tryPromise({
|
|
@@ -4367,6 +3029,8 @@ function closeHocuspocus(hocuspocus, operations) {
|
|
|
4367
3029
|
catch: (cause) => gatewayError("close", cause)
|
|
4368
3030
|
}).pipe(Effect.asVoid));
|
|
4369
3031
|
const cleanupFailure = cleanupResults.find((result) => result.status === "rejected");
|
|
3032
|
+
const storeFailure = storeResults.find((result) => result.status === "rejected");
|
|
3033
|
+
if (storeFailure?.status === "rejected") return yield* gatewayError("close", storeFailure.reason);
|
|
4370
3034
|
if (cleanupFailure?.status === "rejected") return yield* gatewayError("close", cleanupFailure.reason);
|
|
4371
3035
|
if (Result.isFailure(destroyResult)) return yield* destroyResult.failure;
|
|
4372
3036
|
});
|
|
@@ -4380,7 +3044,6 @@ function gatewayError(operation, cause) {
|
|
|
4380
3044
|
|
|
4381
3045
|
//#endregion
|
|
4382
3046
|
//#region ../platform-node/src/internal/realtime/keyed-operations.ts
|
|
4383
|
-
/** Serializes each room independently and retains poisoned writes for shutdown reporting. */
|
|
4384
3047
|
function createKeyedOperations(options) {
|
|
4385
3048
|
const pending = /* @__PURE__ */ new Map();
|
|
4386
3049
|
const failures = /* @__PURE__ */ new Map();
|
|
@@ -4474,7 +3137,6 @@ function stableStringHash(value) {
|
|
|
4474
3137
|
//#region ../platform-node/src/internal/realtime/wake-dispatcher.ts
|
|
4475
3138
|
const DEFAULT_CAPACITY = 1024;
|
|
4476
3139
|
const DEFAULT_CONCURRENCY = 32;
|
|
4477
|
-
/** Bounded wake scheduling: duplicate rooms coalesce and unrelated rooms run concurrently. */
|
|
4478
3140
|
const runStudioRealtimeWakeDispatcher = Effect.fn("StudioRealtimeWakeDispatcher.run")(function* (wakes, handle, options = {}) {
|
|
4479
3141
|
const queue = yield* Queue.dropping(options.capacity ?? DEFAULT_CAPACITY);
|
|
4480
3142
|
const queued = /* @__PURE__ */ new Set();
|
|
@@ -4687,7 +3349,7 @@ const makeStudioRealtimeRoomHost = Effect.fn("StudioRealtimeRoomHost.make")(func
|
|
|
4687
3349
|
},
|
|
4688
3350
|
onStateless: (payload) => runTracked(Effect.gen(function* () {
|
|
4689
3351
|
const decoded = yield* Effect.option(decodeClientMessage(payload.payload));
|
|
4690
|
-
if (decoded
|
|
3352
|
+
if (Option.isNone(decoded) || decoded.value.type !== "checkpoint") return;
|
|
4691
3353
|
const name = yield* decodeRoomName(payload.documentName).pipe(Effect.mapError((cause) => roomHostError("checkpoint", cause)));
|
|
4692
3354
|
const stateVector = Y.encodeStateVector(payload.document);
|
|
4693
3355
|
yield* Effect.tryPromise({
|
|
@@ -4820,7 +3482,9 @@ function decodeBytes(value, tokenInvalid) {
|
|
|
4820
3482
|
try: () => {
|
|
4821
3483
|
const normalized = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
4822
3484
|
const binary = atob(normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "="));
|
|
4823
|
-
|
|
3485
|
+
const bytes = Uint8Array.from(binary, (character) => character.codePointAt(0) ?? 0);
|
|
3486
|
+
if (encodeBytes(bytes) !== value) throw new Error("Noncanonical base64url encoding");
|
|
3487
|
+
return bytes;
|
|
4824
3488
|
},
|
|
4825
3489
|
catch: tokenInvalid
|
|
4826
3490
|
});
|
|
@@ -4962,7 +3626,7 @@ const makeStudioRealtimeGateway = Effect.gen(function* () {
|
|
|
4962
3626
|
onStateless: (payload) => {
|
|
4963
3627
|
return runTracked(Effect.gen(function* () {
|
|
4964
3628
|
const decoded = yield* Effect.option(decodeClientMessage(payload.payload));
|
|
4965
|
-
if (decoded
|
|
3629
|
+
if (Option.isNone(decoded)) return;
|
|
4966
3630
|
const request = decoded.value;
|
|
4967
3631
|
const context = authenticatedContext(payload.connection.context, payload.documentName);
|
|
4968
3632
|
if (request.type === "checkpoint") return;
|
|
@@ -5172,11 +3836,10 @@ function httpProtocol(protocol) {
|
|
|
5172
3836
|
* StudioRealtimeRoomHost synchronizes durable Yjs state across replicas. Presence
|
|
5173
3837
|
* and stateless broadcasts remain process-local until an ephemeral bus is added.
|
|
5174
3838
|
*/
|
|
5175
|
-
const studioRealtimeGatewayLayer = Layer.effect(StudioRealtimeGateway, Effect.acquireRelease(makeStudioRealtimeGateway, (gateway) => gateway.close.pipe(Effect.
|
|
3839
|
+
const studioRealtimeGatewayLayer = Layer.effect(StudioRealtimeGateway, Effect.acquireRelease(makeStudioRealtimeGateway, (gateway) => gateway.close.pipe(Effect.tapError((error) => Effect.logError("Studio realtime gateway shutdown failed.", error)), Effect.orDie)));
|
|
5176
3840
|
|
|
5177
3841
|
//#endregion
|
|
5178
3842
|
//#region ../platform-node/src/internal/realtime/websocket.ts
|
|
5179
|
-
/** Hides Hocuspocus connection bookkeeping from framework WebSocket adapters. */
|
|
5180
3843
|
function studioRealtimeWebSocketBridge(gateway) {
|
|
5181
3844
|
const connections = /* @__PURE__ */ new WeakMap();
|
|
5182
3845
|
const release = (socket) => {
|
|
@@ -5201,21 +3864,25 @@ function studioRealtimeWebSocketBridge(gateway) {
|
|
|
5201
3864
|
//#endregion
|
|
5202
3865
|
//#region ../platform-node/src/internal/installation/host.ts
|
|
5203
3866
|
var Installation = class extends Context.Service()("@internal/platform-node/Installation") {};
|
|
5204
|
-
function installationHttpLayer(bootstrap,
|
|
3867
|
+
function installationHttpLayer(bootstrap, cleanup) {
|
|
5205
3868
|
return Layer.effect(Installation, Effect.gen(function* () {
|
|
5206
|
-
const outerContext = yield* Effect.context();
|
|
5207
3869
|
const cleanupContext = yield* Effect.context();
|
|
5208
|
-
const
|
|
5209
|
-
const
|
|
5210
|
-
|
|
3870
|
+
const routerConfig = yield* HttpRouter.RouterConfig;
|
|
3871
|
+
const router = yield* HttpRouter.HttpRouter;
|
|
3872
|
+
const handle = HttpEffect.toWebHandlerWith(yield* Effect.context())(router.asHttpEffect(), HttpMiddleware.logger);
|
|
3873
|
+
return yield* makeInstallation(bootstrap, handle, (context) => cleanup(context).pipe(Effect.provide(cleanupContext)), routerConfig);
|
|
5211
3874
|
}));
|
|
5212
3875
|
}
|
|
5213
|
-
const makeInstallation = Effect.fn("Installation.make")(function* (bootstrap, handle, cleanup) {
|
|
3876
|
+
const makeInstallation = Effect.fn("Installation.make")(function* (bootstrap, handle, cleanup, routerConfig) {
|
|
5214
3877
|
const authentication = yield* AuthenticationHandler;
|
|
5215
3878
|
const crypto = yield* Crypto.Crypto;
|
|
5216
3879
|
const realtime = yield* StudioRealtimeGateway;
|
|
5217
3880
|
const roomHost = yield* StudioRealtimeRoomHost;
|
|
5218
|
-
const
|
|
3881
|
+
const readiness = yield* Readiness;
|
|
3882
|
+
const protectedRoutes = FindMyWay.make(routerConfig);
|
|
3883
|
+
protectedRoutes.all(CMS_FORM_SUBMIT_PATH, "form");
|
|
3884
|
+
protectedRoutes.all(`${STUDIO_API_PATH}/preview/*`, "preview");
|
|
3885
|
+
const health = readiness.check.pipe(Effect.andThen(roomHost.check), Effect.match({
|
|
5219
3886
|
onFailure: () => ({
|
|
5220
3887
|
live: true,
|
|
5221
3888
|
ready: false
|
|
@@ -5241,13 +3908,15 @@ const makeInstallation = Effect.fn("Installation.make")(function* (bootstrap, ha
|
|
|
5241
3908
|
cleanupMedia,
|
|
5242
3909
|
fetch: (request) => {
|
|
5243
3910
|
const pathname = new URL(request.url).pathname;
|
|
3911
|
+
const protection = protectedRoutes.find(request.method, pathname)?.handler;
|
|
3912
|
+
if (protection === "form") return handleFormRequest(request, handle);
|
|
5244
3913
|
if (pathname === "/api/cms/auth" || pathname.startsWith(`${"/api/cms/auth"}/`)) return authentication.handle(request);
|
|
5245
3914
|
if (pathname === CMS_LIVENESS_PATH) return Promise.resolve(healthResponse({
|
|
5246
3915
|
live: true,
|
|
5247
3916
|
ready: true
|
|
5248
3917
|
}, false));
|
|
5249
3918
|
if (pathname === CMS_READINESS_PATH) return runPromise(health).then((status) => healthResponse(status, true));
|
|
5250
|
-
if (
|
|
3919
|
+
if (protection === "preview") return handle(request).then((response) => {
|
|
5251
3920
|
response.headers.set("cache-control", "private, no-store");
|
|
5252
3921
|
return response;
|
|
5253
3922
|
});
|
|
@@ -5295,21 +3964,6 @@ const startInstallation = Effect.fn("CmsInstallation.start")(function* (options)
|
|
|
5295
3964
|
};
|
|
5296
3965
|
});
|
|
5297
3966
|
|
|
5298
|
-
//#endregion
|
|
5299
|
-
//#region ../server/src/internal/media/media-object-store.ts
|
|
5300
|
-
var MediaObjectStore = class extends Context.Service()("@internal/server/MediaObjectStore") {};
|
|
5301
|
-
|
|
5302
|
-
//#endregion
|
|
5303
|
-
//#region ../server/src/internal/media/media-failures.ts
|
|
5304
|
-
var MediaPersistenceError = class extends Schema.TaggedError()("Media.PersistenceError", {
|
|
5305
|
-
operation: Schema.NonEmptyString,
|
|
5306
|
-
cause: Schema.Defect()
|
|
5307
|
-
}) {};
|
|
5308
|
-
var MediaStorageError = class extends Schema.TaggedError()("Media.StorageError", {
|
|
5309
|
-
operation: Schema.NonEmptyString,
|
|
5310
|
-
cause: Schema.Defect()
|
|
5311
|
-
}) {};
|
|
5312
|
-
|
|
5313
3967
|
//#endregion
|
|
5314
3968
|
//#region ../server/src/internal/media/media-change.ts
|
|
5315
3969
|
const MediaUploadIntent = Schema.Struct({
|
|
@@ -5472,7 +4126,7 @@ function makeMediaChange(input) {
|
|
|
5472
4126
|
action: input.action,
|
|
5473
4127
|
context: input.change.context,
|
|
5474
4128
|
id: input.change.id,
|
|
5475
|
-
intent: Schema.
|
|
4129
|
+
intent: Result.getOrThrow(Schema.encodeResult(MediaChangeIntent)(input.intent)),
|
|
5476
4130
|
scope: "media",
|
|
5477
4131
|
subject: input.subject,
|
|
5478
4132
|
target: input.target
|
|
@@ -5481,55 +4135,43 @@ function makeMediaChange(input) {
|
|
|
5481
4135
|
|
|
5482
4136
|
//#endregion
|
|
5483
4137
|
//#region ../server/src/internal/media/media-reconciliation.ts
|
|
5484
|
-
|
|
4138
|
+
const recoveryBatchSize = 100;
|
|
4139
|
+
function recoverPendingMedia(store, objectStore, operations, recoveryAge, cursor = {}) {
|
|
5485
4140
|
return Effect.gen(function* () {
|
|
5486
4141
|
const before = DateTime.subtractDuration(yield* DateTime.now, recoveryAge);
|
|
5487
|
-
const
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
4142
|
+
const pending = yield* store.pending({
|
|
4143
|
+
after: cursor.media,
|
|
4144
|
+
before,
|
|
4145
|
+
limit: recoveryBatchSize
|
|
4146
|
+
});
|
|
4147
|
+
for (const record of pending) {
|
|
4148
|
+
if (record.status === "deleting") {
|
|
4149
|
+
yield* objectStore.remove(record.objectKey);
|
|
4150
|
+
yield* store.finishDelete(record.id);
|
|
4151
|
+
continue;
|
|
4152
|
+
}
|
|
4153
|
+
const stored = yield* objectStore.stat(record.objectKey);
|
|
4154
|
+
if (Option.isSome(stored) && stored.value.byteSize === record.byteSize && (stored.value.mimeType === void 0 || stored.value.mimeType === record.mimeType)) yield* store.activate(record.id);
|
|
4155
|
+
else {
|
|
4156
|
+
if (Option.isSome(stored)) {
|
|
5495
4157
|
yield* objectStore.remove(record.objectKey);
|
|
5496
|
-
yield*
|
|
5497
|
-
continue;
|
|
5498
|
-
}
|
|
5499
|
-
const stored = yield* objectStore.stat(record.objectKey);
|
|
5500
|
-
if (Option.isSome(stored) && stored.value.byteSize === record.byteSize && (stored.value.mimeType === void 0 || stored.value.mimeType === record.mimeType)) yield* store.activate(record.id);
|
|
5501
|
-
else {
|
|
5502
|
-
if (Option.isSome(stored)) {
|
|
5503
|
-
yield* objectStore.remove(record.objectKey);
|
|
5504
|
-
const remaining = yield* objectStore.stat(record.objectKey);
|
|
5505
|
-
if (Option.isSome(remaining)) continue;
|
|
5506
|
-
}
|
|
5507
|
-
yield* store.abandonUpload(record.id);
|
|
4158
|
+
const remaining = yield* objectStore.stat(record.objectKey);
|
|
4159
|
+
if (Option.isSome(remaining)) continue;
|
|
5508
4160
|
}
|
|
4161
|
+
yield* store.abandonUpload(record.id);
|
|
5509
4162
|
}
|
|
5510
|
-
return pending.length < 100;
|
|
5511
|
-
});
|
|
5512
|
-
let mediaBatch = 0;
|
|
5513
|
-
let mediaComplete = false;
|
|
5514
|
-
while (!mediaComplete && mediaBatch < maxBatches) {
|
|
5515
|
-
mediaComplete = yield* recoverBatch;
|
|
5516
|
-
mediaBatch += 1;
|
|
5517
|
-
}
|
|
5518
|
-
const recoverReplacementBatch = Effect.gen(function* () {
|
|
5519
|
-
const replacements = yield* store.pendingReplacements({
|
|
5520
|
-
before,
|
|
5521
|
-
limit: 100
|
|
5522
|
-
});
|
|
5523
|
-
yield* Effect.forEach(replacements, (replacement) => recoverReplacement(store, objectStore, replacement).pipe(Effect.catchTag("Media.Conflict", () => Effect.void)), { discard: true });
|
|
5524
|
-
return replacements.length < 100;
|
|
5525
|
-
});
|
|
5526
|
-
let replacementBatch = 0;
|
|
5527
|
-
let replacementsComplete = false;
|
|
5528
|
-
while (!replacementsComplete && replacementBatch < maxBatches) {
|
|
5529
|
-
replacementsComplete = yield* recoverReplacementBatch;
|
|
5530
|
-
replacementBatch += 1;
|
|
5531
4163
|
}
|
|
4164
|
+
const replacements = yield* store.pendingReplacements({
|
|
4165
|
+
after: cursor.replacements,
|
|
4166
|
+
before,
|
|
4167
|
+
limit: recoveryBatchSize
|
|
4168
|
+
});
|
|
4169
|
+
yield* Effect.forEach(replacements, (replacement) => recoverReplacement(store, objectStore, replacement).pipe(Effect.catchTag("Media.Conflict", () => Effect.void)), { discard: true });
|
|
5532
4170
|
yield* recoverPendingMediaCleanup(operations, objectStore, before);
|
|
4171
|
+
return {
|
|
4172
|
+
media: pending.length < recoveryBatchSize ? void 0 : pending.at(-1)?.id,
|
|
4173
|
+
replacements: replacements.length < recoveryBatchSize ? void 0 : replacements.at(-1)?.mediaId
|
|
4174
|
+
};
|
|
5533
4175
|
});
|
|
5534
4176
|
}
|
|
5535
4177
|
function recoverPendingMediaCleanup(operations, objectStore, before) {
|
|
@@ -5717,10 +4359,14 @@ const makeMediaLibrary = Effect.fn("MediaLibrary.make")(function* (options) {
|
|
|
5717
4359
|
const store = yield* MediaStore;
|
|
5718
4360
|
const baseUrl = options.publicBaseUrl.replace(/\/+$/gu, "");
|
|
5719
4361
|
const pendingRecoveryAge = options.pendingRecoveryAge ?? "5 minutes";
|
|
5720
|
-
yield* recoverPendingMedia(store, objectStore, operations, pendingRecoveryAge);
|
|
4362
|
+
const recoveryCursor = yield* Ref.make(yield* recoverPendingMedia(store, objectStore, operations, pendingRecoveryAge));
|
|
5721
4363
|
const mutations = yield* Semaphore.make(1);
|
|
5722
4364
|
const recoveryInterval = options.pendingRecoveryInterval ?? MEDIA_OPERATION_RECOVERY_INTERVAL;
|
|
5723
|
-
if (recoveryInterval !== false) yield* mutations.withPermits(1)(
|
|
4365
|
+
if (recoveryInterval !== false) yield* mutations.withPermits(1)(Effect.gen(function* () {
|
|
4366
|
+
const cursor = yield* Ref.get(recoveryCursor);
|
|
4367
|
+
const next = yield* recoverPendingMedia(store, objectStore, operations, pendingRecoveryAge, cursor);
|
|
4368
|
+
yield* Ref.set(recoveryCursor, next);
|
|
4369
|
+
})).pipe(Effect.catch((error) => Effect.logWarning("Pending media change recovery sweep failed.", { errorTag: error._tag }))).pipe(Effect.repeat(Schedule.spaced(recoveryInterval)), Effect.delay(recoveryInterval), Effect.forkScoped({ startImmediately: true }));
|
|
5724
4370
|
const toAsset = (record) => MediaAsset.make({
|
|
5725
4371
|
byteSize: record.byteSize,
|
|
5726
4372
|
createdAt: record.createdAt,
|
|
@@ -5789,7 +4435,7 @@ const makeMediaLibrary = Effect.fn("MediaLibrary.make")(function* (options) {
|
|
|
5789
4435
|
return yield* Effect.scoped(prepareUpload(input).pipe(Effect.flatMap((prepared) => mutations.withPermits(1)(Effect.gen(function* () {
|
|
5790
4436
|
const id = MediaId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
|
|
5791
4437
|
const operationId = ActivityId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
|
|
5792
|
-
const objectKey = MediaObjectKey.make(
|
|
4438
|
+
const objectKey = MediaObjectKey.make(`${mediaObjectPrefix}${id}.${imageExtension(prepared.mimeType)}`);
|
|
5793
4439
|
const now = yield* DateTime.now;
|
|
5794
4440
|
const reserved = yield* store.reserve({
|
|
5795
4441
|
byteSize: input.metadata.declaredByteSize,
|
|
@@ -5837,7 +4483,7 @@ const makeMediaLibrary = Effect.fn("MediaLibrary.make")(function* (options) {
|
|
|
5837
4483
|
name: input.metadata.name,
|
|
5838
4484
|
objectKey: current.value.objectKey,
|
|
5839
4485
|
staged: false,
|
|
5840
|
-
stagingObjectKey: MediaObjectKey.make(
|
|
4486
|
+
stagingObjectKey: MediaObjectKey.make(`${mediaObjectPrefix}replacements/${operationId}.${imageExtension(prepared.mimeType)}`),
|
|
5841
4487
|
updatedAt: timestamp
|
|
5842
4488
|
};
|
|
5843
4489
|
const pendingReplacement = yield* store.beginReplace(replacement, {
|
|
@@ -5967,6 +4613,37 @@ function activityFailure(operation) {
|
|
|
5967
4613
|
});
|
|
5968
4614
|
}
|
|
5969
4615
|
|
|
4616
|
+
//#endregion
|
|
4617
|
+
//#region ../server/src/ProductModules.ts
|
|
4618
|
+
function productModulesLayer(options) {
|
|
4619
|
+
const accessControl = defaultAccessControlLayer;
|
|
4620
|
+
const seo = seoLayer(options.bootstrap.seo ?? makeDefaultSeoOptions(options.baseUrl)).pipe(Layer.provide(accessControl));
|
|
4621
|
+
const authoredDocuments = documentsLayer(options.bootstrap.documents, {
|
|
4622
|
+
...options.documentEngine,
|
|
4623
|
+
components: options.documentEngine.components ?? [],
|
|
4624
|
+
mediaBaseUrl: options.documentEngine.mediaBaseUrl ?? "https://media.invalid"
|
|
4625
|
+
}).pipe(Layer.provide(accessControl));
|
|
4626
|
+
const documentAccess = Layer.merge(authoredDocuments, accessControl);
|
|
4627
|
+
const activity = activityLayer.pipe(Layer.provide(accessControl));
|
|
4628
|
+
const workingDocuments = workingDocumentsLayer(options.workingDocuments).pipe(Layer.provide(documentAccess));
|
|
4629
|
+
const seoDelivery = seoDeliveryLayer.pipe(Layer.provide(Layer.merge(documentAccess, seo)));
|
|
4630
|
+
return Layer.mergeAll(activity, documentAccess, workingDocuments, seo, seoDelivery, readinessLayer);
|
|
4631
|
+
}
|
|
4632
|
+
function productModulesWithMediaLayer(options) {
|
|
4633
|
+
const products = productModulesLayer({
|
|
4634
|
+
...options,
|
|
4635
|
+
documentEngine: {
|
|
4636
|
+
...options.documentEngine,
|
|
4637
|
+
mediaBaseUrl: options.documentEngine.mediaBaseUrl ?? options.mediaPublicBaseUrl
|
|
4638
|
+
}
|
|
4639
|
+
});
|
|
4640
|
+
const media = mediaLibraryLayer({
|
|
4641
|
+
maxUploadBytes: options.bootstrap.media.maxUploadBytes,
|
|
4642
|
+
publicBaseUrl: options.mediaPublicBaseUrl
|
|
4643
|
+
}).pipe(Layer.provide(products));
|
|
4644
|
+
return Layer.merge(products, media);
|
|
4645
|
+
}
|
|
4646
|
+
|
|
5970
4647
|
//#endregion
|
|
5971
4648
|
//#region ../server/src/internal/initialization/initialization.ts
|
|
5972
4649
|
const InitializationUnavailableError = InitializationUnavailable;
|
|
@@ -6067,20 +4744,21 @@ const reconcileStored = Effect.fn("Initialization.reconcileStored")(function* (s
|
|
|
6067
4744
|
id: state.claim.changeId
|
|
6068
4745
|
}, state.claim.intent);
|
|
6069
4746
|
const begin = yield* journal.begin(draft).pipe(Effect.mapError((cause) => unavailable$3("Initialization.recover", cause)));
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
return yield* store.read;
|
|
6074
|
-
case "Cancelled":
|
|
6075
|
-
yield* store.reset(state.claim.changeId);
|
|
6076
|
-
return yield* store.read;
|
|
6077
|
-
case "Unresolved": {
|
|
6078
|
-
const resolution = yield* reconcileInitialAdministrator(journal, begin.change, recovery.observe).pipe(Effect.mapError((cause) => unavailable$3("Initialization.recover", cause)));
|
|
6079
|
-
if (resolution.kind === "applied") yield* store.complete(state.claim.changeId);
|
|
6080
|
-
if (resolution.kind === "cancelled") yield* store.reset(state.claim.changeId);
|
|
6081
|
-
return yield* store.read;
|
|
6082
|
-
}
|
|
4747
|
+
if (Predicate.isTagged("Recorded")(begin)) {
|
|
4748
|
+
yield* store.complete(state.claim.changeId);
|
|
4749
|
+
return yield* store.read;
|
|
6083
4750
|
}
|
|
4751
|
+
if (Predicate.isTagged("Cancelled")(begin)) {
|
|
4752
|
+
yield* store.reset(state.claim.changeId);
|
|
4753
|
+
return yield* store.read;
|
|
4754
|
+
}
|
|
4755
|
+
if (Predicate.isTagged("Unresolved")(begin)) {
|
|
4756
|
+
const resolution = yield* reconcileInitialAdministrator(journal, begin.change, recovery.observe).pipe(Effect.mapError((cause) => unavailable$3("Initialization.recover", cause)));
|
|
4757
|
+
if (resolution.kind === "applied") yield* store.complete(state.claim.changeId);
|
|
4758
|
+
if (resolution.kind === "cancelled") yield* store.reset(state.claim.changeId);
|
|
4759
|
+
return yield* store.read;
|
|
4760
|
+
}
|
|
4761
|
+
return yield* store.read;
|
|
6084
4762
|
});
|
|
6085
4763
|
function completeBestEffort(store, changeId) {
|
|
6086
4764
|
return store.complete(changeId).pipe(Effect.catch((error) => Effect.logWarning("Initialization completion remains recoverable.", {
|
|
@@ -6281,6 +4959,69 @@ function targetError(operation) {
|
|
|
6281
4959
|
return (error) => Schema.is(UserAlreadyExists)(error) ? unavailable$2(operation)(error) : error;
|
|
6282
4960
|
}
|
|
6283
4961
|
|
|
4962
|
+
//#endregion
|
|
4963
|
+
//#region ../server/src/Forms.ts
|
|
4964
|
+
/** Only the currently published definition can authorize a submission. Never trust client rules. */
|
|
4965
|
+
const submitForm = Effect.fn("Forms.submit")(function* (input, components, handlers) {
|
|
4966
|
+
const document = yield* (yield* PublishedDocuments).read({
|
|
4967
|
+
kind: "id",
|
|
4968
|
+
documentId: input.documentId
|
|
4969
|
+
}).pipe(Effect.orElseSucceed(() => void 0));
|
|
4970
|
+
if (!document) return FormResult.make({
|
|
4971
|
+
status: "unavailable",
|
|
4972
|
+
errors: {}
|
|
4973
|
+
});
|
|
4974
|
+
const form = documentForms(document, components).find((candidate) => JSON.stringify(candidate.nodePath) === JSON.stringify(input.nodePath));
|
|
4975
|
+
if (!form || form.revision !== input.revision) return FormResult.make({
|
|
4976
|
+
status: "stale",
|
|
4977
|
+
errors: {}
|
|
4978
|
+
});
|
|
4979
|
+
const handler = Object.hasOwn(handlers, form.name) ? handlers[form.name] : void 0;
|
|
4980
|
+
if (!handler) return FormResult.make({
|
|
4981
|
+
status: "unavailable",
|
|
4982
|
+
errors: {}
|
|
4983
|
+
});
|
|
4984
|
+
const fields = form.steps.flatMap((step) => step.fields);
|
|
4985
|
+
const errors = validateFormValues(fields, input.values);
|
|
4986
|
+
if (Object.keys(input.values).some((id) => !fields.some((field) => field.id === id))) errors._form = "The submission contains unknown answers.";
|
|
4987
|
+
if (Object.keys(errors).length) return FormResult.make({
|
|
4988
|
+
status: "invalid",
|
|
4989
|
+
errors
|
|
4990
|
+
});
|
|
4991
|
+
const values = Object.fromEntries(fields.map((field) => [field.id, normalizedAnswer(Object.hasOwn(input.values, field.id) ? input.values[field.id] : void 0)]));
|
|
4992
|
+
return yield* Effect.tryPromise(() => Promise.resolve(handler({
|
|
4993
|
+
form,
|
|
4994
|
+
values
|
|
4995
|
+
}))).pipe(Effect.as(FormResult.make({
|
|
4996
|
+
status: "accepted",
|
|
4997
|
+
errors: {}
|
|
4998
|
+
})), Effect.orElseSucceed(() => FormResult.make({
|
|
4999
|
+
status: "failed",
|
|
5000
|
+
errors: {}
|
|
5001
|
+
})));
|
|
5002
|
+
});
|
|
5003
|
+
function normalizedAnswer(value) {
|
|
5004
|
+
return value === void 0 || Predicate.isString(value) && !value.trim() ? null : value;
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
//#endregion
|
|
5008
|
+
//#region ../platform-node/src/internal/http/forms.ts
|
|
5009
|
+
function formsApiLayer(components, handlers) {
|
|
5010
|
+
return HttpApiBuilder.group(StudioApi, "forms", (group) => Effect.gen(function* () {
|
|
5011
|
+
const documents = yield* PublishedDocuments;
|
|
5012
|
+
const limiter = yield* RateLimiter.RateLimiter;
|
|
5013
|
+
return group.handle("submit", ({ payload }) => limiter.consume({
|
|
5014
|
+
key: "cms-forms",
|
|
5015
|
+
limit: 60,
|
|
5016
|
+
window: "1 minute",
|
|
5017
|
+
onExceeded: "fail"
|
|
5018
|
+
}).pipe(Effect.andThen(submitForm(payload, components, handlers).pipe(Effect.provideService(PublishedDocuments, documents))), Effect.orElseSucceed(() => FormResult.make({
|
|
5019
|
+
status: "unavailable",
|
|
5020
|
+
errors: {}
|
|
5021
|
+
}))));
|
|
5022
|
+
})).pipe(Layer.provide(RateLimiter.layer.pipe(Layer.provide(RateLimiter.layerStoreMemory))));
|
|
5023
|
+
}
|
|
5024
|
+
|
|
6284
5025
|
//#endregion
|
|
6285
5026
|
//#region ../platform-node/src/internal/http/errors.ts
|
|
6286
5027
|
const studioSchemaErrorLayer = HttpApiMiddleware.layerSchemaErrorTransform(StudioSchemaError, (error) => Effect.fail(mapStudioSchemaError(error.kind)));
|
|
@@ -6462,7 +5203,7 @@ function decodeMediaUpload(payload, query, maxUploadBytes) {
|
|
|
6462
5203
|
}
|
|
6463
5204
|
function mapMultipartError(error) {
|
|
6464
5205
|
if (isMediaInvalid(error)) return error;
|
|
6465
|
-
return
|
|
5206
|
+
return Predicate.isTagged("InternalError")(error.reason) ? studioInternalError() : invalidMultipart();
|
|
6466
5207
|
}
|
|
6467
5208
|
function invalidMultipart() {
|
|
6468
5209
|
return new MediaInvalid({ message: "Upload exactly one file whose body matches its declared size." });
|
|
@@ -6474,7 +5215,16 @@ const publicError = Schema.is(Schema.Union([AccessDenied, PublishedDocumentNotFo
|
|
|
6474
5215
|
const denied = Schema.is(AccessDenied);
|
|
6475
5216
|
const previewApiLayer = HttpApiBuilder.group(StudioApi, "previewDelivery", (handlers) => Effect.gen(function* () {
|
|
6476
5217
|
const documents = yield* DocumentPreviews;
|
|
6477
|
-
|
|
5218
|
+
const accessControl = yield* AccessControl;
|
|
5219
|
+
const seo = yield* PreviewSeo;
|
|
5220
|
+
const seoDelivery = yield* SeoDelivery;
|
|
5221
|
+
return handlers.handle("read", ({ payload }) => CurrentStudioRequest.pipe(Effect.flatMap((context) => documents.read(context, payload).pipe(Effect.flatMap((document) => seoDelivery.previewDocument(document)))), preservePublicErrors(publicError))).handle("query", ({ payload }) => CurrentStudioRequest.pipe(Effect.flatMap((context) => documents.query(context, payload).pipe(Effect.flatMap((page) => Effect.forEach(page.items, (document) => seoDelivery.previewDocument(document)).pipe(Effect.map((items) => Object.assign({}, page, { items })))))), preservePublicErrors(denied))).handle("navigation", ({ payload }) => CurrentStudioRequest.pipe(Effect.flatMap((context) => documents.navigation(context, payload)), preservePublicErrors(denied))).handle("surround", ({ payload }) => CurrentStudioRequest.pipe(Effect.flatMap((context) => documents.surround(context, payload.lookup, payload.query).pipe(Effect.flatMap((surround) => Effect.all({
|
|
5222
|
+
previous: surround.previous ? seoDelivery.previewDocument(surround.previous) : Effect.succeed(null),
|
|
5223
|
+
next: surround.next ? seoDelivery.previewDocument(surround.next) : Effect.succeed(null)
|
|
5224
|
+
})))), preservePublicErrors(publicError))).handle("seo", () => CurrentStudioRequest.pipe(Effect.flatMap((context) => accessControl.require(context, {
|
|
5225
|
+
action: "document.browse",
|
|
5226
|
+
resource: { kind: "catalog" }
|
|
5227
|
+
}).pipe(Effect.andThen(seo.read))), preservePublicErrors(denied)));
|
|
6478
5228
|
}));
|
|
6479
5229
|
|
|
6480
5230
|
//#endregion
|
|
@@ -6482,7 +5232,15 @@ const previewApiLayer = HttpApiBuilder.group(StudioApi, "previewDelivery", (hand
|
|
|
6482
5232
|
const isPublishedDocumentNotFound = Schema.is(PublishedDocumentNotFound);
|
|
6483
5233
|
const deliveryApiLayer = Layer.merge(HttpApiBuilder.group(StudioApi, "delivery", (handlers) => Effect.gen(function* () {
|
|
6484
5234
|
const documents = yield* PublishedDocuments;
|
|
6485
|
-
|
|
5235
|
+
const seo = yield* PublishedSeo;
|
|
5236
|
+
const seoDelivery = yield* SeoDelivery;
|
|
5237
|
+
return handlers.handle("read", ({ payload }) => documents.read(payload).pipe(Effect.flatMap(seoDelivery.publishedDocument), preservePublicErrors(isPublishedDocumentNotFound))).handle("query", ({ payload }) => documents.query(payload).pipe(Effect.flatMap((page) => Effect.forEach(page.items, seoDelivery.publishedDocument).pipe(Effect.map((items) => ({
|
|
5238
|
+
items,
|
|
5239
|
+
total: page.total
|
|
5240
|
+
})))), mapUnexpectedError)).handle("navigation", ({ payload }) => documents.navigation(payload).pipe(mapUnexpectedError)).handle("surround", ({ payload }) => documents.surround(payload.lookup, payload.query).pipe(Effect.flatMap((surround) => Effect.all({
|
|
5241
|
+
previous: surround.previous ? seoDelivery.publishedDocument(surround.previous) : Effect.succeed(null),
|
|
5242
|
+
next: surround.next ? seoDelivery.publishedDocument(surround.next) : Effect.succeed(null)
|
|
5243
|
+
})), preservePublicErrors(isPublishedDocumentNotFound))).handle("seo", () => seo.read.pipe(mapUnexpectedError)).handle("sitemap", () => seoDelivery.sitemap.pipe(mapUnexpectedError));
|
|
6486
5244
|
})), HttpApiBuilder.group(StudioApi, "diagnostics", (handlers) => Effect.gen(function* () {
|
|
6487
5245
|
const documents = yield* PublishedDocuments;
|
|
6488
5246
|
return handlers.handle("documents", () => CurrentStudioRequest.pipe(Effect.flatMap((context) => documents.diagnostics(context)), preservePublicErrors(Schema.is(AccessDenied))));
|
|
@@ -6498,6 +5256,12 @@ const isHistoryPublicError = Schema.is(Schema.Union([
|
|
|
6498
5256
|
DocumentNotFound
|
|
6499
5257
|
]));
|
|
6500
5258
|
const isInitializationPublicError = Schema.is(Schema.Union([InitializationAlreadyCompleted, InitializationProofInvalid]));
|
|
5259
|
+
const isSeoPublicError = Schema.is(Schema.Union([
|
|
5260
|
+
AccessDenied,
|
|
5261
|
+
SeoConfirmationRequired,
|
|
5262
|
+
SeoMediaUnavailable,
|
|
5263
|
+
SeoVersionConflict
|
|
5264
|
+
]));
|
|
6501
5265
|
const administrationApiLayer = Layer.mergeAll(HttpApiBuilder.group(StudioApi, "initialization", (handlers) => Effect.gen(function* () {
|
|
6502
5266
|
const initialization = yield* Initialization;
|
|
6503
5267
|
return handlers.handle("status", () => initialization.status.pipe(mapUnexpectedError)).handle("claim", ({ payload }) => initialization.claim(payload).pipe(preservePublicErrors(isInitializationPublicError)));
|
|
@@ -6507,6 +5271,9 @@ const administrationApiLayer = Layer.mergeAll(HttpApiBuilder.group(StudioApi, "i
|
|
|
6507
5271
|
})), HttpApiBuilder.group(StudioApi, "activity", (handlers) => Effect.gen(function* () {
|
|
6508
5272
|
const activity = yield* Activity;
|
|
6509
5273
|
return handlers.handle("list", ({ query }) => withStudioRequest((context) => activity.list(context, query)).pipe(preservePublicErrors(isActivityPublicError)));
|
|
5274
|
+
})), HttpApiBuilder.group(StudioApi, "seo", (handlers) => Effect.gen(function* () {
|
|
5275
|
+
const seo = yield* Seo;
|
|
5276
|
+
return handlers.handle("read", () => withStudioRequest((context) => seo.read(context)).pipe(preservePublicErrors(isSeoPublicError))).handle("save", ({ payload }) => withStudioRequest((context) => seo.save(context, payload)).pipe(preservePublicErrors(isSeoPublicError))).handle("publish", ({ payload }) => withStudioRequest((context) => seo.publish(context, payload)).pipe(preservePublicErrors(isSeoPublicError)));
|
|
6510
5277
|
})));
|
|
6511
5278
|
|
|
6512
5279
|
//#endregion
|
|
@@ -6532,43 +5299,14 @@ const userAdministrationHttpLayer = HttpApiBuilder.group(StudioApi, "users", (ha
|
|
|
6532
5299
|
|
|
6533
5300
|
//#endregion
|
|
6534
5301
|
//#region ../platform-node/src/internal/http/api.ts
|
|
6535
|
-
function studioApiLayer(bootstrap, authoringRevision) {
|
|
5302
|
+
function studioApiLayer(bootstrap, authoringRevision, components = [], formHandlers = {}) {
|
|
6536
5303
|
const configuration = HttpApiBuilder.group(StudioApi, "configuration", (handlers) => handlers.handle("bootstrap", () => Effect.succeed(bootstrap)));
|
|
6537
|
-
return authenticatedRouterHandlers(Layer.mergeAll(configuration, documentsApiLayer, mediaApiLayer(bootstrap.media.enabled, bootstrap.media.maxUploadBytes), collaborationApiLayer(authoringRevision), deliveryApiLayer, previewApiLayer, administrationApiLayer, userAdministrationHttpLayer).pipe(Layer.provideMerge(studioSchemaErrorLayer)));
|
|
5304
|
+
return authenticatedRouterHandlers(Layer.mergeAll(configuration, formsApiLayer(components, formHandlers), documentsApiLayer, mediaApiLayer(bootstrap.media.enabled, bootstrap.media.maxUploadBytes), collaborationApiLayer(authoringRevision), deliveryApiLayer, previewApiLayer, administrationApiLayer, userAdministrationHttpLayer).pipe(Layer.provideMerge(studioSchemaErrorLayer)));
|
|
6538
5305
|
}
|
|
6539
5306
|
function authenticatedRouterHandlers(handlers) {
|
|
6540
5307
|
return handlers;
|
|
6541
5308
|
}
|
|
6542
5309
|
|
|
6543
|
-
//#endregion
|
|
6544
|
-
//#region ../platform-node/src/internal/installation/server-modules.ts
|
|
6545
|
-
function productModulesLayer(options) {
|
|
6546
|
-
const accessControl = defaultAccessControlLayer;
|
|
6547
|
-
const authoredDocuments = documentsLayer(options.bootstrap.documents, {
|
|
6548
|
-
...options.documentEngine,
|
|
6549
|
-
components: options.documentEngine.components ?? [],
|
|
6550
|
-
mediaBaseUrl: options.documentEngine.mediaBaseUrl ?? "https://media.invalid"
|
|
6551
|
-
}).pipe(Layer.provide(accessControl));
|
|
6552
|
-
const documentAccess = Layer.merge(authoredDocuments, accessControl);
|
|
6553
|
-
const activity = activityLayer.pipe(Layer.provide(accessControl));
|
|
6554
|
-
const workingDocuments = workingDocumentsLayer(options.workingDocuments).pipe(Layer.provide(documentAccess));
|
|
6555
|
-
return Layer.mergeAll(activity, documentAccess, workingDocuments);
|
|
6556
|
-
}
|
|
6557
|
-
function productModulesWithMediaLayer(options) {
|
|
6558
|
-
const products = productModulesLayer({
|
|
6559
|
-
...options,
|
|
6560
|
-
documentEngine: {
|
|
6561
|
-
...options.documentEngine,
|
|
6562
|
-
mediaBaseUrl: options.documentEngine.mediaBaseUrl ?? options.mediaPublicBaseUrl
|
|
6563
|
-
}
|
|
6564
|
-
});
|
|
6565
|
-
const media = mediaLibraryLayer({
|
|
6566
|
-
maxUploadBytes: options.bootstrap.media.maxUploadBytes,
|
|
6567
|
-
publicBaseUrl: options.mediaPublicBaseUrl
|
|
6568
|
-
}).pipe(Layer.provide(products));
|
|
6569
|
-
return Layer.merge(products, media);
|
|
6570
|
-
}
|
|
6571
|
-
|
|
6572
5310
|
//#endregion
|
|
6573
5311
|
//#region ../platform-node/src/internal/installation/error.ts
|
|
6574
5312
|
var CmsInstallationError = class extends Schema.TaggedError()("Cms.InstallationError", {
|
|
@@ -6585,6 +5323,7 @@ function makeInstallationLayer(validated) {
|
|
|
6585
5323
|
const options = validated.options;
|
|
6586
5324
|
const core = options.composition.core;
|
|
6587
5325
|
const productOptions = {
|
|
5326
|
+
baseUrl: options.baseUrl,
|
|
6588
5327
|
bootstrap: options.bootstrap,
|
|
6589
5328
|
documentEngine: {
|
|
6590
5329
|
documentModel: options.documentModel,
|
|
@@ -6607,9 +5346,9 @@ function makeInstallationLayer(validated) {
|
|
|
6607
5346
|
const roomHost = studioRealtimeRoomHostLayer().pipe(Layer.provide(productModules));
|
|
6608
5347
|
const gateway = studioRealtimeGatewayLayer.pipe(Layer.provide(Layer.mergeAll(productModules, roomHost, tickets)));
|
|
6609
5348
|
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));
|
|
6611
|
-
const httpApi = HttpApiBuilder.layer(StudioApi).pipe(Layer.provide(handlers), Layer.provide(HttpServer.layerServices));
|
|
6612
|
-
return validated.media.enabled ? installationHttpLayer(options.bootstrap,
|
|
5349
|
+
const handlers = studioApiLayer(options.bootstrap, options.authoring.revision, options.authoring.components, options.formHandlers).pipe(Layer.provide(httpDependencies));
|
|
5350
|
+
const httpApi = HttpApiBuilder.layer(StudioApi).pipe(Layer.provide(handlers), Layer.provide(HttpServer.layerServices), Layer.provideMerge(HttpRouter.layer));
|
|
5351
|
+
return validated.media.enabled ? installationHttpLayer(options.bootstrap, (context) => Effect.flatMap(MediaLibrary, (library) => library.cleanup(context))).pipe(Layer.provide(Layer.merge(httpDependencies, httpApi))) : installationHttpLayer(options.bootstrap, () => Effect.succeed(MediaCleanupReport.make({ removedObjectKeys: [] }))).pipe(Layer.provide(Layer.merge(httpDependencies, httpApi)));
|
|
6613
5352
|
}
|
|
6614
5353
|
function validateInstallationOptions(options) {
|
|
6615
5354
|
if (options.bootstrap.media.enabled) {
|
|
@@ -6647,7 +5386,6 @@ function mapStartupError(layer, operation) {
|
|
|
6647
5386
|
|
|
6648
5387
|
//#endregion
|
|
6649
5388
|
//#region ../platform-node/src/Installation.ts
|
|
6650
|
-
/** Starts auth, persistence, Studio realtime, delivery, health, and maintenance as one deep module. */
|
|
6651
5389
|
const installCms$1 = Effect.fn("CmsInstallation.install")(function* (options) {
|
|
6652
5390
|
return yield* startInstallation({
|
|
6653
5391
|
cleanup: options.bootstrap.media.enabled ? options.cleanup : void 0,
|
|
@@ -6656,6 +5394,8 @@ const installCms$1 = Effect.fn("CmsInstallation.install")(function* (options) {
|
|
|
6656
5394
|
});
|
|
6657
5395
|
function createCmsInstallationLayer(options) {
|
|
6658
5396
|
return installationLayer({
|
|
5397
|
+
baseUrl: options.baseUrl,
|
|
5398
|
+
formHandlers: options.formHandlers,
|
|
6659
5399
|
authoring: options.authoring,
|
|
6660
5400
|
bootstrap: options.bootstrap,
|
|
6661
5401
|
composition: options.composition,
|
|
@@ -6666,262 +5406,6 @@ function createCmsInstallationLayer(options) {
|
|
|
6666
5406
|
});
|
|
6667
5407
|
}
|
|
6668
5408
|
|
|
6669
|
-
//#endregion
|
|
6670
|
-
//#region ../s3/src/internal/client.ts
|
|
6671
|
-
var S3RequestError = class extends Schema.TaggedError()("S3.RequestError", {
|
|
6672
|
-
cause: Schema.Defect(),
|
|
6673
|
-
operation: Schema.NonEmptyString
|
|
6674
|
-
}) {};
|
|
6675
|
-
function openAwsS3MediaObjectClient(options) {
|
|
6676
|
-
const credentials = options.credentials ? {
|
|
6677
|
-
accessKeyId: options.credentials.accessKeyId,
|
|
6678
|
-
secretAccessKey: Redacted.value(options.credentials.secretAccessKey),
|
|
6679
|
-
sessionToken: options.credentials.sessionToken ? Redacted.value(options.credentials.sessionToken) : void 0
|
|
6680
|
-
} : void 0;
|
|
6681
|
-
return makeAwsS3MediaObjectClient(new S3Client({
|
|
6682
|
-
credentials,
|
|
6683
|
-
endpoint: options.endpoint,
|
|
6684
|
-
forcePathStyle: options.forcePathStyle,
|
|
6685
|
-
region: options.region
|
|
6686
|
-
}));
|
|
6687
|
-
}
|
|
6688
|
-
/** Wraps the official client; exported only through the package-private path for transport-level tests. */
|
|
6689
|
-
function makeAwsS3MediaObjectClient(client) {
|
|
6690
|
-
const request = (operation, send) => Effect.tryPromise({
|
|
6691
|
-
catch: (cause) => new S3RequestError({
|
|
6692
|
-
cause,
|
|
6693
|
-
operation
|
|
6694
|
-
}),
|
|
6695
|
-
try: send
|
|
6696
|
-
});
|
|
6697
|
-
return {
|
|
6698
|
-
close: Effect.sync(() => client.destroy()),
|
|
6699
|
-
copyObject: (input) => request("MediaObjectStore.S3.copyObject", (signal) => client.send(new CopyObjectCommand({
|
|
6700
|
-
Bucket: input.bucket,
|
|
6701
|
-
CacheControl: input.cacheControl,
|
|
6702
|
-
ContentType: input.contentType,
|
|
6703
|
-
CopySource: input.copySource,
|
|
6704
|
-
Key: input.key,
|
|
6705
|
-
MetadataDirective: "REPLACE"
|
|
6706
|
-
}), { abortSignal: signal })).pipe(Effect.asVoid),
|
|
6707
|
-
createBucket: (input) => request("MediaObjectStore.S3.createBucket", (signal) => client.send(new CreateBucketCommand({
|
|
6708
|
-
Bucket: input.bucket,
|
|
6709
|
-
CreateBucketConfiguration: input.locationConstraint ? { LocationConstraint: input.locationConstraint } : void 0
|
|
6710
|
-
}), { abortSignal: signal })).pipe(Effect.asVoid),
|
|
6711
|
-
deleteObject: (input) => request("MediaObjectStore.S3.deleteObject", (signal) => client.send(new DeleteObjectCommand({
|
|
6712
|
-
Bucket: input.bucket,
|
|
6713
|
-
Key: input.key
|
|
6714
|
-
}), { abortSignal: signal })).pipe(Effect.asVoid),
|
|
6715
|
-
headBucket: (input) => request("MediaObjectStore.S3.headBucket", (signal) => client.send(new HeadBucketCommand({ Bucket: input.bucket }), { abortSignal: signal })).pipe(Effect.asVoid),
|
|
6716
|
-
headObject: (input) => request("MediaObjectStore.S3.headObject", (signal) => client.send(new HeadObjectCommand({
|
|
6717
|
-
Bucket: input.bucket,
|
|
6718
|
-
Key: input.key
|
|
6719
|
-
}), { abortSignal: signal })).pipe(Effect.map((output) => ({
|
|
6720
|
-
contentLength: output.ContentLength,
|
|
6721
|
-
contentType: output.ContentType
|
|
6722
|
-
}))),
|
|
6723
|
-
listObjects: (input) => request("MediaObjectStore.S3.listObjects", (signal) => client.send(new ListObjectsV2Command({
|
|
6724
|
-
Bucket: input.bucket,
|
|
6725
|
-
ContinuationToken: input.continuationToken,
|
|
6726
|
-
Prefix: input.prefix
|
|
6727
|
-
}), { abortSignal: signal })).pipe(Effect.map((output) => ({
|
|
6728
|
-
isTruncated: output.IsTruncated === true,
|
|
6729
|
-
nextContinuationToken: output.NextContinuationToken,
|
|
6730
|
-
objects: (output.Contents ?? []).map((object) => ({
|
|
6731
|
-
key: object.Key,
|
|
6732
|
-
size: object.Size
|
|
6733
|
-
}))
|
|
6734
|
-
}))),
|
|
6735
|
-
putObject: (input) => request("MediaObjectStore.S3.putObject", (signal) => client.send(new PutObjectCommand({
|
|
6736
|
-
Body: NodeStream.toReadableNever(Stream.fromAsyncIterable(input.body, (cause) => cause)),
|
|
6737
|
-
Bucket: input.bucket,
|
|
6738
|
-
CacheControl: input.cacheControl,
|
|
6739
|
-
ContentLength: input.contentLength,
|
|
6740
|
-
ContentType: input.contentType,
|
|
6741
|
-
Key: input.key
|
|
6742
|
-
}), { abortSignal: signal })).pipe(Effect.asVoid)
|
|
6743
|
-
};
|
|
6744
|
-
}
|
|
6745
|
-
|
|
6746
|
-
//#endregion
|
|
6747
|
-
//#region ../s3/src/internal/media-object-store.ts
|
|
6748
|
-
const mediaPrefix = "media/";
|
|
6749
|
-
const replaceableCacheControl = "public, max-age=0, must-revalidate";
|
|
6750
|
-
const isWriteBodyError = Schema.is(Schema.Union([MediaInvalid, MediaStorageError]));
|
|
6751
|
-
/** Owns and closes the official AWS client for the lifetime of the layer. */
|
|
6752
|
-
function s3MediaObjectStoreLayer(options) {
|
|
6753
|
-
return Layer.effect(MediaObjectStore, resolveOptions(options).pipe(Effect.flatMap((resolved) => Effect.acquireRelease(Effect.sync(() => openAwsS3MediaObjectClient(resolved)), (client) => client.close).pipe(Effect.tap((client) => provisionBucket(resolved, client)), Effect.map((client) => makeStore(resolved, client))))));
|
|
6754
|
-
}
|
|
6755
|
-
function makeStore(options, client) {
|
|
6756
|
-
return {
|
|
6757
|
-
list: listObjects(options.bucket, client),
|
|
6758
|
-
promote: (input) => promoteObject(options.bucket, input, client),
|
|
6759
|
-
put: (input) => putObject(options.bucket, input, client),
|
|
6760
|
-
remove: (objectKey) => providerRequest("MediaObjectStore.S3.remove", client.deleteObject({
|
|
6761
|
-
bucket: options.bucket,
|
|
6762
|
-
key: objectKey
|
|
6763
|
-
})),
|
|
6764
|
-
stat: (objectKey) => statObject(options.bucket, objectKey, client)
|
|
6765
|
-
};
|
|
6766
|
-
}
|
|
6767
|
-
function promoteObject(bucket, input, client) {
|
|
6768
|
-
return statObject(bucket, input.sourceObjectKey, client).pipe(Effect.flatMap((source) => Option.isSome(source) && source.value.byteSize === input.byteSize && source.value.mimeType === input.mimeType ? Effect.void : Effect.fail(storageError("MediaObjectStore.S3.promote.source", "The staged object metadata does not match its intent."))), Effect.flatMap(() => providerRequest("MediaObjectStore.S3.promote.copy", client.copyObject({
|
|
6769
|
-
bucket,
|
|
6770
|
-
cacheControl: replaceableCacheControl,
|
|
6771
|
-
contentType: input.mimeType,
|
|
6772
|
-
copySource: copySource(bucket, input.sourceObjectKey),
|
|
6773
|
-
key: input.destinationObjectKey
|
|
6774
|
-
}))), Effect.flatMap(() => statObject(bucket, input.destinationObjectKey, client)), Effect.flatMap((destination) => Option.isSome(destination) && destination.value.byteSize === input.byteSize && destination.value.mimeType === input.mimeType ? Effect.succeed(destination.value) : Effect.fail(storageError("MediaObjectStore.S3.promote.verify", "The promoted object metadata does not match its intent."))));
|
|
6775
|
-
}
|
|
6776
|
-
function copySource(bucket, objectKey) {
|
|
6777
|
-
return `/${encodeURIComponent(bucket)}/${objectKey.split("/").map(encodeURIComponent).join("/")}`;
|
|
6778
|
-
}
|
|
6779
|
-
function putObject(bucket, input, client) {
|
|
6780
|
-
return Effect.gen(function* () {
|
|
6781
|
-
const body = yield* validatedWriteBody(input);
|
|
6782
|
-
const iterable = yield* Stream.toAsyncIterableEffect(body);
|
|
6783
|
-
yield* client.putObject({
|
|
6784
|
-
body: iterable,
|
|
6785
|
-
bucket,
|
|
6786
|
-
cacheControl: replaceableCacheControl,
|
|
6787
|
-
contentLength: input.byteSize,
|
|
6788
|
-
contentType: input.mimeType,
|
|
6789
|
-
key: input.objectKey
|
|
6790
|
-
}).pipe(Effect.catch((failure) => isWriteBodyError(failure.cause) ? Effect.fail(failure.cause) : Effect.fail(providerError("MediaObjectStore.S3.put", failure))));
|
|
6791
|
-
return {
|
|
6792
|
-
byteSize: input.byteSize,
|
|
6793
|
-
mimeType: input.mimeType,
|
|
6794
|
-
objectKey: input.objectKey
|
|
6795
|
-
};
|
|
6796
|
-
});
|
|
6797
|
-
}
|
|
6798
|
-
const validatedWriteBody = Effect.fn("MediaObjectStore.S3.validateWriteBody")(function* (input) {
|
|
6799
|
-
const byteLength = yield* Ref.make(0);
|
|
6800
|
-
return input.body.pipe(Stream.mapEffect((chunk) => Effect.gen(function* () {
|
|
6801
|
-
if ((yield* Ref.updateAndGet(byteLength, (current) => current + chunk.byteLength)) > input.byteSize) return yield* storageError("MediaObjectStore.S3.put.body", "The object body exceeds its declared byte size.");
|
|
6802
|
-
return chunk;
|
|
6803
|
-
})), Stream.concat(Stream.fromEffect(Ref.get(byteLength).pipe(Effect.flatMap((actual) => actual === input.byteSize ? Effect.void : Effect.fail(storageError("MediaObjectStore.S3.put.body", "The object body does not match its declared byte size."))))).pipe(Stream.drain)));
|
|
6804
|
-
});
|
|
6805
|
-
function statObject(bucket, objectKey, client) {
|
|
6806
|
-
return client.headObject({
|
|
6807
|
-
bucket,
|
|
6808
|
-
key: objectKey
|
|
6809
|
-
}).pipe(Effect.matchEffect({
|
|
6810
|
-
onFailure: (failure) => isMissingObject(failure.cause) ? Effect.succeed(Option.none()) : Effect.fail(providerError("MediaObjectStore.S3.stat", failure)),
|
|
6811
|
-
onSuccess: (output) => decodeStoredObject("MediaObjectStore.S3.stat", objectKey, output.contentLength, output.contentType).pipe(Effect.asSome)
|
|
6812
|
-
}));
|
|
6813
|
-
}
|
|
6814
|
-
function listObjects(bucket, client) {
|
|
6815
|
-
return Stream.paginate({ seenTokens: /* @__PURE__ */ new Set() }, (state) => providerRequest("MediaObjectStore.S3.list", client.listObjects({
|
|
6816
|
-
bucket,
|
|
6817
|
-
continuationToken: state.continuationToken,
|
|
6818
|
-
prefix: mediaPrefix
|
|
6819
|
-
})).pipe(Effect.flatMap((page) => Effect.gen(function* () {
|
|
6820
|
-
return [yield* Effect.forEach(page.objects, decodeListedObject), yield* nextListState(state, page)];
|
|
6821
|
-
}))));
|
|
6822
|
-
}
|
|
6823
|
-
function decodeListedObject(object) {
|
|
6824
|
-
if (object.key === void 0) return Effect.fail(storageError("MediaObjectStore.S3.list", "The provider returned object metadata without a key."));
|
|
6825
|
-
return decodeStoredObject("MediaObjectStore.S3.list", object.key, object.size);
|
|
6826
|
-
}
|
|
6827
|
-
function nextListState(state, page) {
|
|
6828
|
-
if (!page.isTruncated) return Effect.succeedNone;
|
|
6829
|
-
const token = page.nextContinuationToken;
|
|
6830
|
-
if (!token || token === state.continuationToken || state.seenTokens.has(token)) return Effect.fail(storageError("MediaObjectStore.S3.list", "The provider returned an invalid continuation token."));
|
|
6831
|
-
return Effect.succeedSome({
|
|
6832
|
-
continuationToken: token,
|
|
6833
|
-
seenTokens: /* @__PURE__ */ new Set([...state.seenTokens, token])
|
|
6834
|
-
});
|
|
6835
|
-
}
|
|
6836
|
-
function decodeStoredObject(operation, objectKey, byteSize, contentType) {
|
|
6837
|
-
const decodedObjectKey = Schema.decodeOption(MediaObjectKey)(objectKey);
|
|
6838
|
-
const decodedByteSize = Schema.decodeUnknownOption(MediaByteSize)(byteSize);
|
|
6839
|
-
if (Option.isNone(decodedObjectKey) || Option.isNone(decodedByteSize)) return Effect.fail(storageError(operation, "The provider returned invalid object metadata."));
|
|
6840
|
-
const decodedMimeType = Schema.decodeUnknownOption(ImageMimeType)(contentType);
|
|
6841
|
-
const storedObject = {
|
|
6842
|
-
byteSize: decodedByteSize.value,
|
|
6843
|
-
objectKey: decodedObjectKey.value
|
|
6844
|
-
};
|
|
6845
|
-
return Effect.succeed(Option.isSome(decodedMimeType) ? {
|
|
6846
|
-
...storedObject,
|
|
6847
|
-
mimeType: decodedMimeType.value
|
|
6848
|
-
} : storedObject);
|
|
6849
|
-
}
|
|
6850
|
-
function providerRequest(operation, request) {
|
|
6851
|
-
return request.pipe(Effect.mapError((failure) => providerError(operation, failure)));
|
|
6852
|
-
}
|
|
6853
|
-
function provisionBucket(options, client) {
|
|
6854
|
-
if (!options.provisionBucket) return Effect.void;
|
|
6855
|
-
return client.headBucket({ bucket: options.bucket }).pipe(Effect.catch((failure) => isMissingBucket(failure.cause) ? createMissingBucket(options, client) : Effect.fail(providerError("MediaObjectStore.S3.bucket.head", failure))));
|
|
6856
|
-
}
|
|
6857
|
-
function createMissingBucket(options, client) {
|
|
6858
|
-
return client.createBucket({
|
|
6859
|
-
bucket: options.bucket,
|
|
6860
|
-
locationConstraint: options.endpoint || options.region === "us-east-1" ? void 0 : options.region
|
|
6861
|
-
}).pipe(Effect.catch((failure) => isBucketAlreadyOwned(failure.cause) ? providerRequest("MediaObjectStore.S3.bucket.verify", client.headBucket({ bucket: options.bucket })) : Effect.fail(providerError("MediaObjectStore.S3.bucket.create", failure))));
|
|
6862
|
-
}
|
|
6863
|
-
const ProviderFailureDetails = Schema.Struct({
|
|
6864
|
-
$metadata: Schema.optionalKey(Schema.Struct({
|
|
6865
|
-
attempts: Schema.optionalKey(Schema.Finite),
|
|
6866
|
-
httpStatusCode: Schema.optionalKey(Schema.Finite)
|
|
6867
|
-
})),
|
|
6868
|
-
name: Schema.optionalKey(Schema.String)
|
|
6869
|
-
});
|
|
6870
|
-
const decodeProviderFailure = Schema.decodeUnknownOption(ProviderFailureDetails);
|
|
6871
|
-
function providerError(operation, failure) {
|
|
6872
|
-
const details = decodeProviderFailure(failure.cause);
|
|
6873
|
-
const diagnostics = [];
|
|
6874
|
-
if (Option.isSome(details)) {
|
|
6875
|
-
if (details.value.name) diagnostics.push(`name=${details.value.name}`);
|
|
6876
|
-
if (details.value.$metadata?.httpStatusCode !== void 0) diagnostics.push(`status=${details.value.$metadata.httpStatusCode}`);
|
|
6877
|
-
if (details.value.$metadata?.attempts !== void 0) diagnostics.push(`attempts=${details.value.$metadata.attempts}`);
|
|
6878
|
-
}
|
|
6879
|
-
return storageError(operation, `The S3 provider request failed${diagnostics.length > 0 ? ` (${diagnostics.join(", ")}).` : "."}`);
|
|
6880
|
-
}
|
|
6881
|
-
function isMissingObject(cause) {
|
|
6882
|
-
const details = decodeProviderFailure(cause);
|
|
6883
|
-
if (Option.isNone(details)) return false;
|
|
6884
|
-
return details.value.name === "NoSuchKey" || details.value.name === "NotFound";
|
|
6885
|
-
}
|
|
6886
|
-
function isMissingBucket(cause) {
|
|
6887
|
-
const details = decodeProviderFailure(cause);
|
|
6888
|
-
if (Option.isNone(details)) return false;
|
|
6889
|
-
return details.value.name === "NoSuchBucket" || details.value.$metadata?.httpStatusCode === 404;
|
|
6890
|
-
}
|
|
6891
|
-
function isBucketAlreadyOwned(cause) {
|
|
6892
|
-
const details = decodeProviderFailure(cause);
|
|
6893
|
-
return Option.isSome(details) && details.value.name === "BucketAlreadyOwnedByYou";
|
|
6894
|
-
}
|
|
6895
|
-
function resolveOptions(options) {
|
|
6896
|
-
const bucket = options.bucket.trim();
|
|
6897
|
-
const region = options.region.trim();
|
|
6898
|
-
const endpoint = options.endpoint?.trim();
|
|
6899
|
-
if (!bucket || !region || options.endpoint !== void 0 && !endpoint) return Effect.fail(storageError("MediaObjectStore.S3.configure", "The bucket, region, and configured endpoint must be non-empty."));
|
|
6900
|
-
if (options.credentials) {
|
|
6901
|
-
const secretAccessKey = Redacted.value(options.credentials.secretAccessKey);
|
|
6902
|
-
const sessionToken = options.credentials.sessionToken && Redacted.value(options.credentials.sessionToken);
|
|
6903
|
-
if (!options.credentials.accessKeyId.trim() || !secretAccessKey || sessionToken === "") return Effect.fail(storageError("MediaObjectStore.S3.configure", "The configured credentials must be non-empty."));
|
|
6904
|
-
}
|
|
6905
|
-
return Effect.succeed({
|
|
6906
|
-
...options,
|
|
6907
|
-
bucket,
|
|
6908
|
-
credentials: options.credentials ? {
|
|
6909
|
-
...options.credentials,
|
|
6910
|
-
accessKeyId: options.credentials.accessKeyId.trim()
|
|
6911
|
-
} : void 0,
|
|
6912
|
-
endpoint,
|
|
6913
|
-
forcePathStyle: options.forcePathStyle ?? false,
|
|
6914
|
-
provisionBucket: options.provisionBucket ?? false,
|
|
6915
|
-
region
|
|
6916
|
-
});
|
|
6917
|
-
}
|
|
6918
|
-
function storageError(operation, message) {
|
|
6919
|
-
return new MediaStorageError({
|
|
6920
|
-
cause: new Error(message),
|
|
6921
|
-
operation
|
|
6922
|
-
});
|
|
6923
|
-
}
|
|
6924
|
-
|
|
6925
5409
|
//#endregion
|
|
6926
5410
|
//#region ../sql-pg/src/internal/database/table-helpers.ts
|
|
6927
5411
|
const transactionTimestamp = sql`transaction_timestamp()`;
|
|
@@ -6935,6 +5419,7 @@ const ACTIVITY_TARGET_KINDS = [
|
|
|
6935
5419
|
"document",
|
|
6936
5420
|
"media",
|
|
6937
5421
|
"media-library",
|
|
5422
|
+
"seo",
|
|
6938
5423
|
"user"
|
|
6939
5424
|
];
|
|
6940
5425
|
const IDENTIFIED_ACTIVITY_TARGET_KINDS = [
|
|
@@ -6961,8 +5446,8 @@ const activityEntries = pgTable("activity_entries", {
|
|
|
6961
5446
|
}, (table) => [
|
|
6962
5447
|
check("activity_entries_action_check", isOneOf(table.action, ACTIVITY_ACTIONS)),
|
|
6963
5448
|
check("activity_entries_target_kind_check", isOneOf(table.targetKind, ACTIVITY_TARGET_KINDS)),
|
|
6964
|
-
check("activity_entries_target_check", sql`(${table.targetKind}
|
|
6965
|
-
OR (${table.targetKind}
|
|
5449
|
+
check("activity_entries_target_check", sql`(${table.targetKind} in ('media-library', 'seo') AND ${table.targetId} IS NULL)
|
|
5450
|
+
OR (${table.targetKind} not in ('media-library', 'seo') AND ${table.targetId} IS NOT NULL)`),
|
|
6966
5451
|
check("activity_entries_actor_roles_check", sql`jsonb_typeof(${table.actorRoles}) = 'array'`),
|
|
6967
5452
|
check("activity_entries_details_check", sql`jsonb_typeof(${table.details}) = 'object'`),
|
|
6968
5453
|
index("activity_entries_history").on(table.occurredAt.desc(), table.id.desc()),
|
|
@@ -7053,27 +5538,30 @@ function activityEntryValues(entry) {
|
|
|
7053
5538
|
};
|
|
7054
5539
|
}
|
|
7055
5540
|
function activityEntriesEqual(left, right) {
|
|
7056
|
-
return isDeepStrictEqual(Schema.
|
|
5541
|
+
return isDeepStrictEqual(Result.getOrThrow(Schema.encodeResult(ActivityEntry)(left)), Result.getOrThrow(Schema.encodeResult(ActivityEntry)(right)));
|
|
7057
5542
|
}
|
|
7058
5543
|
function decodeActivityTarget(kind, id) {
|
|
7059
|
-
|
|
5544
|
+
if (kind === "document") return Schema.decodeUnknownEffect(ActivityTarget)({
|
|
7060
5545
|
documentId: id,
|
|
7061
5546
|
kind
|
|
7062
|
-
}
|
|
5547
|
+
});
|
|
5548
|
+
if (kind === "media") return Schema.decodeUnknownEffect(ActivityTarget)({
|
|
7063
5549
|
kind,
|
|
7064
5550
|
mediaId: id
|
|
7065
|
-
}
|
|
5551
|
+
});
|
|
5552
|
+
if (kind === "user") return Schema.decodeUnknownEffect(ActivityTarget)({
|
|
7066
5553
|
kind,
|
|
7067
5554
|
userId: id
|
|
7068
|
-
}
|
|
7069
|
-
return Schema.
|
|
5555
|
+
});
|
|
5556
|
+
return Schema.decodeEffect(ActivityTarget)({ kind });
|
|
7070
5557
|
}
|
|
7071
5558
|
function activityTargetId(entry) {
|
|
7072
5559
|
switch (entry.target.kind) {
|
|
7073
5560
|
case "document": return entry.target.documentId;
|
|
7074
5561
|
case "media": return entry.target.mediaId;
|
|
7075
5562
|
case "user": return entry.target.userId;
|
|
7076
|
-
case "media-library":
|
|
5563
|
+
case "media-library":
|
|
5564
|
+
case "seo": return;
|
|
7077
5565
|
}
|
|
7078
5566
|
}
|
|
7079
5567
|
function activityPage(entries, limit, total) {
|
|
@@ -7372,7 +5860,8 @@ function targetColumns(target) {
|
|
|
7372
5860
|
targetId: target.userId,
|
|
7373
5861
|
targetKind: target.kind
|
|
7374
5862
|
};
|
|
7375
|
-
case "media-library":
|
|
5863
|
+
case "media-library":
|
|
5864
|
+
case "seo": return { targetKind: target.kind };
|
|
7376
5865
|
}
|
|
7377
5866
|
}
|
|
7378
5867
|
function decodeOptionalTarget(kind, id) {
|
|
@@ -7380,7 +5869,7 @@ function decodeOptionalTarget(kind, id) {
|
|
|
7380
5869
|
return decodeActivityTarget(kind, id).pipe(Effect.mapError(() => new ChangeMismatch({})));
|
|
7381
5870
|
}
|
|
7382
5871
|
function draftEqual(change, draft) {
|
|
7383
|
-
return isDeepStrictEqual(Schema.
|
|
5872
|
+
return isDeepStrictEqual(Result.getOrThrow(Schema.encodeResult(ChangeDraft)(draftFromChange(change))), Result.getOrThrow(Schema.encodeResult(ChangeDraft)(draft)));
|
|
7384
5873
|
}
|
|
7385
5874
|
function draftFromChange(change) {
|
|
7386
5875
|
const value = {
|
|
@@ -7397,7 +5886,7 @@ function draftFromChange(change) {
|
|
|
7397
5886
|
});
|
|
7398
5887
|
}
|
|
7399
5888
|
function changeEqual(left, right) {
|
|
7400
|
-
return isDeepStrictEqual(Schema.
|
|
5889
|
+
return isDeepStrictEqual(Result.getOrThrow(Schema.encodeResult(PendingChange)(left)), Result.getOrThrow(Schema.encodeResult(PendingChange)(right)));
|
|
7401
5890
|
}
|
|
7402
5891
|
function boundedLimit(limit) {
|
|
7403
5892
|
return Math.min(100, Math.max(0, Math.trunc(limit)));
|
|
@@ -7505,6 +5994,26 @@ const embeddedMigrations = [
|
|
|
7505
5994
|
folderMillis: 1788866265e3,
|
|
7506
5995
|
hash: "24c56a37796caf8d76c0a97128a382b31e71808aa5b0ca7e5bac96795f9e16c7",
|
|
7507
5996
|
name: "20260908111745_classy_johnny_storm"
|
|
5997
|
+
},
|
|
5998
|
+
{
|
|
5999
|
+
sql: [
|
|
6000
|
+
"CREATE TABLE \"seo_media_references\" (\n \"id\" uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n \"scope\" text NOT NULL,\n \"media_id\" uuid NOT NULL,\n CONSTRAINT \"seo_media_references_scope_media_unique\" UNIQUE(\"scope\",\"media_id\"),\n CONSTRAINT \"seo_media_references_scope_check\" CHECK (\"scope\" in ('working', 'published'))\n);\n",
|
|
6001
|
+
"\nCREATE TABLE \"seo_revisions\" (\n \"id\" uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n \"settings\" jsonb NOT NULL,\n \"actor_id\" text NOT NULL,\n \"actor_email\" text NOT NULL,\n \"actor_name\" text NOT NULL,\n \"actor_roles\" jsonb NOT NULL,\n \"request_id\" uuid NOT NULL,\n \"reason\" text,\n \"created_at\" timestamp with time zone DEFAULT transaction_timestamp() NOT NULL,\n CONSTRAINT \"seo_revisions_actor_roles_check\" CHECK (jsonb_typeof(\"actor_roles\") = 'array'),\n CONSTRAINT \"seo_revisions_settings_check\" CHECK (jsonb_typeof(\"settings\") = 'object')\n);\n",
|
|
6002
|
+
"\nCREATE TABLE \"seo_state\" (\n \"singleton\" boolean PRIMARY KEY DEFAULT true,\n \"working_revision_id\" uuid NOT NULL,\n \"published_revision_id\" uuid NOT NULL,\n CONSTRAINT \"seo_state_singleton_check\" CHECK (\"singleton\")\n);\n",
|
|
6003
|
+
"\nCREATE INDEX \"seo_media_references_media\" ON \"seo_media_references\" (\"media_id\");",
|
|
6004
|
+
"\nCREATE INDEX \"seo_revisions_history\" ON \"seo_revisions\" (\"created_at\" DESC NULLS LAST,\"id\" DESC NULLS LAST);",
|
|
6005
|
+
"\nALTER TABLE \"seo_media_references\" ADD CONSTRAINT \"seo_media_references_media_id_media_assets_id_fkey\" FOREIGN KEY (\"media_id\") REFERENCES \"media_assets\"(\"id\") ON DELETE RESTRICT;",
|
|
6006
|
+
"\nALTER TABLE \"seo_state\" ADD CONSTRAINT \"seo_state_working_revision_fkey\" FOREIGN KEY (\"working_revision_id\") REFERENCES \"seo_revisions\"(\"id\") ON DELETE RESTRICT;",
|
|
6007
|
+
"\nALTER TABLE \"seo_state\" ADD CONSTRAINT \"seo_state_published_revision_fkey\" FOREIGN KEY (\"published_revision_id\") REFERENCES \"seo_revisions\"(\"id\") ON DELETE RESTRICT;",
|
|
6008
|
+
"\nALTER TABLE \"activity_entries\" DROP CONSTRAINT \"activity_entries_action_check\", ADD CONSTRAINT \"activity_entries_action_check\" CHECK (\"action\" in ('document.created', 'document.updated', 'document.moved', 'document.trashed', 'document.revision.restored', 'document.untrashed', 'document.published', 'document.unpublished', 'media.uploaded', 'media.replaced', 'media.removed', 'media.cleaned', 'seo.saved', 'seo.published', 'user.created', 'user.removed', 'user.role.changed', 'user.sessions.revoked', 'authentication.signed-in', 'authentication.signed-out', 'authentication.password.changed', 'authentication.password.reset'));",
|
|
6009
|
+
"\nALTER TABLE \"activity_entries\" DROP CONSTRAINT \"activity_entries_target_kind_check\", ADD CONSTRAINT \"activity_entries_target_kind_check\" CHECK (\"target_kind\" in ('document', 'media', 'media-library', 'seo', 'user'));",
|
|
6010
|
+
"\nALTER TABLE \"activity_entries\" DROP CONSTRAINT \"activity_entries_target_check\", ADD CONSTRAINT \"activity_entries_target_check\" CHECK ((\"target_kind\" in ('media-library', 'seo') AND \"target_id\" IS NULL)\n OR (\"target_kind\" not in ('media-library', 'seo') AND \"target_id\" IS NOT NULL));",
|
|
6011
|
+
"\nALTER TABLE \"pending_changes\" DROP CONSTRAINT \"pending_changes_action_check\", ADD CONSTRAINT \"pending_changes_action_check\" CHECK (\"action\" in ('document.created', 'document.updated', 'document.moved', 'document.trashed', 'document.revision.restored', 'document.untrashed', 'document.published', 'document.unpublished', 'media.uploaded', 'media.replaced', 'media.removed', 'media.cleaned', 'seo.saved', 'seo.published', 'user.created', 'user.removed', 'user.role.changed', 'user.sessions.revoked', 'authentication.signed-in', 'authentication.signed-out', 'authentication.password.changed', 'authentication.password.reset'));"
|
|
6012
|
+
],
|
|
6013
|
+
bps: true,
|
|
6014
|
+
folderMillis: 1789233351e3,
|
|
6015
|
+
hash: "8e6a6abf5385ab3ecf8eb083c1923ac9c74d72aa71bd1d444b7402d3e7e77000",
|
|
6016
|
+
name: "20260912171551_charming_valkyrie"
|
|
7508
6017
|
}
|
|
7509
6018
|
];
|
|
7510
6019
|
|
|
@@ -7603,12 +6112,16 @@ const initializationState = pgTable("initialization_state", {
|
|
|
7603
6112
|
|
|
7604
6113
|
//#endregion
|
|
7605
6114
|
//#region ../sql-pg/src/internal/database/migrations.ts
|
|
7606
|
-
|
|
6115
|
+
const MIGRATION_LOCK_KEY = "cms:database-migrations:v1";
|
|
7607
6116
|
const migrateCmsDatabase = Effect.fn("CmsDatabase.migrate")(function* () {
|
|
6117
|
+
const postgres = yield* PgClient.PgClient;
|
|
7608
6118
|
const database = yield* PgDrizzle.makeWithDefaults();
|
|
7609
|
-
yield*
|
|
7610
|
-
|
|
7611
|
-
|
|
6119
|
+
yield* postgres.withTransaction(Effect.gen(function* () {
|
|
6120
|
+
yield* postgres.unsafe("select pg_advisory_xact_lock(hashtextextended($1, 0))", [MIGRATION_LOCK_KEY]);
|
|
6121
|
+
yield* migrate(embeddedMigrations, database._.session, "__drizzle_migrations");
|
|
6122
|
+
yield* database.insert(cmsState).values({}).onConflictDoNothing({ target: cmsState.singleton });
|
|
6123
|
+
yield* database.insert(initializationState).values({}).onConflictDoNothing({ target: initializationState.singleton });
|
|
6124
|
+
}));
|
|
7612
6125
|
});
|
|
7613
6126
|
const cmsDatabaseMigrationLayer = Layer.effectDiscard(migrateCmsDatabase());
|
|
7614
6127
|
|
|
@@ -7712,7 +6225,7 @@ function mapActivePathConflict(sourcePath) {
|
|
|
7712
6225
|
const causeError = Cause$1.findErrorOption(error.cause);
|
|
7713
6226
|
if (Option$1.isNone(causeError) || !SqlError.isSqlError(causeError.value)) return error;
|
|
7714
6227
|
const reason = causeError.value.reason;
|
|
7715
|
-
return
|
|
6228
|
+
return Predicate.isTagged("UniqueViolation")(reason) && reason.constraint === "documents_active_path" ? new DocumentPathConflict({ sourcePath }) : error;
|
|
7716
6229
|
}));
|
|
7717
6230
|
}
|
|
7718
6231
|
function updateAuthoredHead(transaction, documentId, revisionId) {
|
|
@@ -8026,7 +6539,7 @@ const isRepositoryDomainError = Schema.is(RepositoryDomainError);
|
|
|
8026
6539
|
const isDocumentNotFound = Schema.is(DocumentNotFound);
|
|
8027
6540
|
const isDocumentMediaUnavailable = Schema.is(DocumentMediaUnavailable);
|
|
8028
6541
|
const isDocumentPersistenceError = Schema.is(DocumentPersistenceError);
|
|
8029
|
-
const systemActivity = ActivityContext.make({
|
|
6542
|
+
const systemActivity$1 = ActivityContext.make({
|
|
8030
6543
|
actor: {
|
|
8031
6544
|
email: "system@cms.invalid",
|
|
8032
6545
|
id: "system",
|
|
@@ -8049,15 +6562,15 @@ const documentStoreLayer = Layer.effect(DocumentStore, Effect.gen(function* () {
|
|
|
8049
6562
|
const state = yield* Schema.decodeUnknownEffect(CmsStateSelect)(stateRow);
|
|
8050
6563
|
const plans = [input.plan, ...input.additionalPlans ?? []];
|
|
8051
6564
|
const references = plans.flatMap((plan) => input.mediaReferences.filter((reference) => reference.documentId === plan.documentId));
|
|
8052
|
-
if (references.length !== input.mediaReferences.length) return yield* persistenceFailure$
|
|
6565
|
+
if (references.length !== input.mediaReferences.length) return yield* persistenceFailure$3("DocumentStore.commit", /* @__PURE__ */ new Error("Unexpected media reference owner."));
|
|
8053
6566
|
yield* lockMediaReferenceKeys(transaction, references.map((reference) => reference.objectKey));
|
|
8054
|
-
yield*
|
|
8055
|
-
const activity = input.activity ?? systemActivity;
|
|
6567
|
+
yield* rejectUnavailableMedia(transaction, references);
|
|
6568
|
+
const activity = input.activity ?? systemActivity$1;
|
|
8056
6569
|
for (const plan of plans) {
|
|
8057
6570
|
yield* applyDocumentCommitPlan(transaction, plan, activity);
|
|
8058
6571
|
yield* replaceDocumentMediaReferences(transaction, plan.documentId, references.filter((reference) => reference.documentId === plan.documentId));
|
|
8059
6572
|
}
|
|
8060
|
-
const snapshot = yield* readSnapshot(transaction, state.generation);
|
|
6573
|
+
const snapshot = yield* readSnapshot$1(transaction, state.generation);
|
|
8061
6574
|
for (const plan of plans) {
|
|
8062
6575
|
const affected = yield* sourceForMutation(snapshot, plan.documentId, plan.action === "document.remove");
|
|
8063
6576
|
yield* recordDocumentActivity(transaction, affected, plan, activity);
|
|
@@ -8079,15 +6592,15 @@ const documentStoreLayer = Layer.effect(DocumentStore, Effect.gen(function* () {
|
|
|
8079
6592
|
const load = Effect.fn("DocumentStore.Drizzle.load")((after) => database.transaction((transaction) => Effect.gen(function* () {
|
|
8080
6593
|
const generation = yield* readGeneration(transaction);
|
|
8081
6594
|
if (after === generation.toString()) return void 0;
|
|
8082
|
-
return yield* readSnapshot(transaction, generation);
|
|
6595
|
+
return yield* readSnapshot$1(transaction, generation);
|
|
8083
6596
|
}), {
|
|
8084
6597
|
accessMode: "read only",
|
|
8085
6598
|
isolationLevel: "repeatable read"
|
|
8086
|
-
}).pipe(mapPersistenceFailure$
|
|
6599
|
+
}).pipe(mapPersistenceFailure$3("DocumentStore.Drizzle.load")));
|
|
8087
6600
|
const synchronizeMediaReferences = Effect.fn("DocumentStore.Drizzle.synchronizeMediaReferences")((input) => database.transaction((transaction) => Effect.gen(function* () {
|
|
8088
6601
|
const stateRows = yield* transaction.select().from(cmsState).where(eq(cmsState.singleton, true)).limit(2).for("update");
|
|
8089
6602
|
const stateRow = stateRows[0];
|
|
8090
|
-
if (stateRows.length !== 1 || !stateRow) return yield* persistenceFailure$
|
|
6603
|
+
if (stateRows.length !== 1 || !stateRow) return yield* persistenceFailure$3("DocumentStore.Drizzle.synchronizeMediaReferences", /* @__PURE__ */ new Error(`Expected exactly one document generation row; received ${stateRows.length}.`));
|
|
8091
6604
|
const state = yield* Schema.decodeUnknownEffect(CmsStateSelect)(stateRow);
|
|
8092
6605
|
if (state.generation.toString() !== input.expectedGeneration) return yield* new DocumentGenerationConflict({
|
|
8093
6606
|
actualGeneration: DocumentGeneration.make(String(state.generation)),
|
|
@@ -8095,7 +6608,7 @@ const documentStoreLayer = Layer.effect(DocumentStore, Effect.gen(function* () {
|
|
|
8095
6608
|
});
|
|
8096
6609
|
const references = uniqueMediaReferences(input.references);
|
|
8097
6610
|
yield* lockMediaReferenceKeys(transaction, references.map((reference) => reference.objectKey));
|
|
8098
|
-
yield*
|
|
6611
|
+
yield* rejectUnavailableMedia(transaction, references);
|
|
8099
6612
|
yield* transaction.delete(documentMediaReferences);
|
|
8100
6613
|
if (references.length > 0) yield* transaction.insert(documentMediaReferences).values(references.map(referenceValues));
|
|
8101
6614
|
})).pipe(mapSynchronizationFailure("DocumentStore.Drizzle.synchronizeMediaReferences")));
|
|
@@ -8111,12 +6624,12 @@ const documentStoreLayer = Layer.effect(DocumentStore, Effect.gen(function* () {
|
|
|
8111
6624
|
const cursor = query.cursor ? or(lt(documentRevisions.createdAt, DateTime.formatIso(query.cursor.createdAt)), and(eq(documentRevisions.createdAt, DateTime.formatIso(query.cursor.createdAt)), lt(documentRevisions.id, query.cursor.revisionId))) : void 0;
|
|
8112
6625
|
const rows = yield* transaction.select().from(documentRevisions).where(and(eq(documentRevisions.documentId, query.documentId), cursor)).orderBy(desc(documentRevisions.createdAt), desc(documentRevisions.id)).limit(query.limit + 1);
|
|
8113
6626
|
const total = (yield* transaction.select({ value: count() }).from(documentRevisions).where(eq(documentRevisions.documentId, query.documentId)))[0]?.value;
|
|
8114
|
-
if (total === void 0) return yield* persistenceFailure$
|
|
6627
|
+
if (total === void 0) return yield* persistenceFailure$3("DocumentStore.Drizzle.revisions", /* @__PURE__ */ new Error("Documents persistence did not return a history total."));
|
|
8115
6628
|
return revisionPage(yield* Effect.forEach(rows, decodeDocumentRevision), query.limit, total);
|
|
8116
6629
|
}), {
|
|
8117
6630
|
accessMode: "read only",
|
|
8118
6631
|
isolationLevel: "repeatable read"
|
|
8119
|
-
}).pipe(mapPersistenceFailure$
|
|
6632
|
+
}).pipe(mapPersistenceFailure$3("DocumentStore.Drizzle.revisions")));
|
|
8120
6633
|
return DocumentStore.of({
|
|
8121
6634
|
commit,
|
|
8122
6635
|
load,
|
|
@@ -8158,7 +6671,7 @@ const recordDocumentActivity = Effect.fn("DocumentStore.Drizzle.recordActivity")
|
|
|
8158
6671
|
if (plan.action !== "document.publish" && plan.action !== "document.unpublish") values.id = source.revisionId;
|
|
8159
6672
|
yield* database.insert(activityEntries).values(values);
|
|
8160
6673
|
});
|
|
8161
|
-
const readSnapshot = Effect.fn("DocumentStore.Drizzle.readSnapshot")(function* (database, generation) {
|
|
6674
|
+
const readSnapshot$1 = Effect.fn("DocumentStore.Drizzle.readSnapshot")(function* (database, generation) {
|
|
8162
6675
|
const rows = yield* database.select({
|
|
8163
6676
|
authored: authoredRevision,
|
|
8164
6677
|
created: createdRevision,
|
|
@@ -8171,12 +6684,12 @@ const readSnapshot = Effect.fn("DocumentStore.Drizzle.readSnapshot")(function* (
|
|
|
8171
6684
|
const readGeneration = Effect.fn("DocumentStore.Drizzle.readGeneration")(function* (database) {
|
|
8172
6685
|
const rows = yield* database.select().from(cmsState);
|
|
8173
6686
|
const row = rows[0];
|
|
8174
|
-
if (rows.length !== 1 || !row) return yield* persistenceFailure$
|
|
6687
|
+
if (rows.length !== 1 || !row) return yield* persistenceFailure$3("DocumentStore.Drizzle.readGeneration", /* @__PURE__ */ new Error(`Expected exactly one document generation row; received ${rows.length}.`));
|
|
8175
6688
|
return (yield* Schema.decodeUnknownEffect(CmsStateSelect)(row)).generation;
|
|
8176
6689
|
});
|
|
8177
6690
|
const sourceForMutation = Effect.fn("DocumentStore.Drizzle.sourceForMutation")(function* (snapshot, documentId, deleted) {
|
|
8178
6691
|
const source = (deleted ? snapshot.deleted : snapshot.active).find((candidate) => candidate.documentId === documentId);
|
|
8179
|
-
if (!source) return yield* persistenceFailure$
|
|
6692
|
+
if (!source) return yield* persistenceFailure$3("DocumentStore.Drizzle.sourceForMutation", /* @__PURE__ */ new Error(`Committed document "${documentId}" is missing from its resulting snapshot.`));
|
|
8180
6693
|
return source;
|
|
8181
6694
|
});
|
|
8182
6695
|
function mapCommitFailure(operation) {
|
|
@@ -8188,14 +6701,27 @@ function mapSynchronizationFailure(operation) {
|
|
|
8188
6701
|
return documentPersistenceError(operation, cause);
|
|
8189
6702
|
}));
|
|
8190
6703
|
}
|
|
8191
|
-
const
|
|
6704
|
+
const rejectUnavailableMedia = Effect.fn("DocumentStore.Drizzle.rejectUnavailableMedia")(function* (transaction, references) {
|
|
8192
6705
|
const keys = [...new Set(references.map((reference) => reference.objectKey))];
|
|
6706
|
+
const mediaIds = [...new Set(references.flatMap((reference) => reference.mediaId ? [reference.mediaId] : []))];
|
|
8193
6707
|
if (keys.length === 0) return;
|
|
8194
|
-
|
|
8195
|
-
|
|
6708
|
+
if (mediaIds.length > 0) {
|
|
6709
|
+
const identityRows = yield* transaction.select({ objectKey: mediaAssets.objectKey }).from(mediaAssets).where(inArray(mediaAssets.id, mediaIds));
|
|
6710
|
+
yield* lockMediaReferenceKeys(transaction, yield* Schema.decodeEffect(Schema.Array(MediaObjectKey))(identityRows.map((row) => row.objectKey)));
|
|
6711
|
+
}
|
|
6712
|
+
const rows = yield* transaction.select({
|
|
6713
|
+
id: mediaAssets.id,
|
|
6714
|
+
objectKey: mediaAssets.objectKey,
|
|
6715
|
+
status: mediaAssets.status
|
|
6716
|
+
}).from(mediaAssets).where(mediaIds.length > 0 ? or(inArray(mediaAssets.objectKey, keys), inArray(mediaAssets.id, mediaIds)) : inArray(mediaAssets.objectKey, keys));
|
|
6717
|
+
const deletingKeys = new Set(yield* Schema.decodeEffect(Schema.Array(MediaObjectKey))(rows.filter((row) => row.status === "deleting").map((row) => row.objectKey)));
|
|
6718
|
+
const unavailable = references.filter((reference) => {
|
|
6719
|
+
if (!reference.mediaId) return deletingKeys.has(reference.objectKey);
|
|
6720
|
+
const asset = rows.find((row) => row.id === reference.mediaId);
|
|
6721
|
+
return !asset || asset.status !== "ready" || asset.objectKey !== reference.objectKey;
|
|
6722
|
+
});
|
|
8196
6723
|
if (unavailable.length === 0) return;
|
|
8197
|
-
const
|
|
8198
|
-
const affected = references.filter((reference) => unavailableSet.has(reference.objectKey)).sort((left, right) => left.documentId === right.documentId ? left.objectKey.localeCompare(right.objectKey) : left.documentId.localeCompare(right.documentId));
|
|
6724
|
+
const affected = unavailable.sort((left, right) => left.documentId === right.documentId ? left.objectKey.localeCompare(right.objectKey) : left.documentId.localeCompare(right.documentId));
|
|
8199
6725
|
const first = affected[0];
|
|
8200
6726
|
if (!first) return;
|
|
8201
6727
|
return yield* new DocumentMediaUnavailable({
|
|
@@ -8219,7 +6745,7 @@ function referenceValues(reference) {
|
|
|
8219
6745
|
sourcePath: reference.sourcePath
|
|
8220
6746
|
};
|
|
8221
6747
|
}
|
|
8222
|
-
function mapPersistenceFailure$
|
|
6748
|
+
function mapPersistenceFailure$3(operation) {
|
|
8223
6749
|
return (effect) => effect.pipe(Effect.mapError((cause) => isDocumentPersistenceError(cause) ? cause : documentPersistenceError(operation, cause)));
|
|
8224
6750
|
}
|
|
8225
6751
|
function mapRevisionFailure(operation) {
|
|
@@ -8228,7 +6754,7 @@ function mapRevisionFailure(operation) {
|
|
|
8228
6754
|
return documentPersistenceError(operation, cause);
|
|
8229
6755
|
}));
|
|
8230
6756
|
}
|
|
8231
|
-
function persistenceFailure$
|
|
6757
|
+
function persistenceFailure$3(operation, cause) {
|
|
8232
6758
|
return Effect.fail(documentPersistenceError(operation, cause));
|
|
8233
6759
|
}
|
|
8234
6760
|
|
|
@@ -8291,7 +6817,7 @@ const initializationStoreLayer = Layer.effect(InitializationStore, Effect.gen(fu
|
|
|
8291
6817
|
const locks = yield* PostgresAdvisoryLock;
|
|
8292
6818
|
const claim = Effect.fn("InitializationStore.Postgres.claim")((value) => database.update(initializationState).set({
|
|
8293
6819
|
changeId: value.changeId,
|
|
8294
|
-
claim: Schema.
|
|
6820
|
+
claim: Result.getOrThrow(Schema.encodeResult(InitializationClaim)(value)),
|
|
8295
6821
|
state: "claiming"
|
|
8296
6822
|
}).where(and(eq(initializationState.singleton, true), eq(initializationState.state, "uninitialized"))).returning({ singleton: initializationState.singleton }).pipe(Effect.map((rows) => rows.length === 1), mapUnavailable("InitializationStore.Postgres.claim")));
|
|
8297
6823
|
const complete = Effect.fn("InitializationStore.Postgres.complete")((changeId) => transition(database, changeId, "ready"));
|
|
@@ -8343,6 +6869,53 @@ function mapUnavailable(operation) {
|
|
|
8343
6869
|
})));
|
|
8344
6870
|
}
|
|
8345
6871
|
|
|
6872
|
+
//#endregion
|
|
6873
|
+
//#region ../sql-pg/src/internal/seo/tables.ts
|
|
6874
|
+
const seoRevisions = pgTable("seo_revisions", {
|
|
6875
|
+
id: uuid().defaultRandom().primaryKey(),
|
|
6876
|
+
settings: jsonb().$type().notNull(),
|
|
6877
|
+
actorId: text("actor_id").notNull(),
|
|
6878
|
+
actorEmail: text("actor_email").notNull(),
|
|
6879
|
+
actorName: text("actor_name").notNull(),
|
|
6880
|
+
actorRoles: jsonb("actor_roles").$type().notNull(),
|
|
6881
|
+
requestId: uuid("request_id").notNull(),
|
|
6882
|
+
reason: text(),
|
|
6883
|
+
createdAt: timestamp("created_at", {
|
|
6884
|
+
mode: "string",
|
|
6885
|
+
withTimezone: true
|
|
6886
|
+
}).default(transactionTimestamp).notNull()
|
|
6887
|
+
}, (table) => [
|
|
6888
|
+
check("seo_revisions_actor_roles_check", sql`jsonb_typeof(${table.actorRoles}) = 'array'`),
|
|
6889
|
+
check("seo_revisions_settings_check", sql`jsonb_typeof(${table.settings}) = 'object'`),
|
|
6890
|
+
index("seo_revisions_history").on(table.createdAt.desc(), table.id.desc())
|
|
6891
|
+
]);
|
|
6892
|
+
const seoState = pgTable("seo_state", {
|
|
6893
|
+
singleton: boolean().default(true).primaryKey(),
|
|
6894
|
+
workingRevisionId: uuid("working_revision_id").notNull(),
|
|
6895
|
+
publishedRevisionId: uuid("published_revision_id").notNull()
|
|
6896
|
+
}, (table) => [
|
|
6897
|
+
check("seo_state_singleton_check", sql`${table.singleton}`),
|
|
6898
|
+
foreignKey({
|
|
6899
|
+
columns: [table.workingRevisionId],
|
|
6900
|
+
foreignColumns: [seoRevisions.id],
|
|
6901
|
+
name: "seo_state_working_revision_fkey"
|
|
6902
|
+
}).onDelete("restrict"),
|
|
6903
|
+
foreignKey({
|
|
6904
|
+
columns: [table.publishedRevisionId],
|
|
6905
|
+
foreignColumns: [seoRevisions.id],
|
|
6906
|
+
name: "seo_state_published_revision_fkey"
|
|
6907
|
+
}).onDelete("restrict")
|
|
6908
|
+
]);
|
|
6909
|
+
const seoMediaReferences = pgTable("seo_media_references", {
|
|
6910
|
+
id: uuid().defaultRandom().primaryKey(),
|
|
6911
|
+
scope: text({ enum: ["working", "published"] }).notNull(),
|
|
6912
|
+
mediaId: uuid("media_id").notNull().references(() => mediaAssets.id, { onDelete: "restrict" })
|
|
6913
|
+
}, (table) => [
|
|
6914
|
+
check("seo_media_references_scope_check", sql`${table.scope} in ('working', 'published')`),
|
|
6915
|
+
unique("seo_media_references_scope_media_unique").on(table.scope, table.mediaId),
|
|
6916
|
+
index("seo_media_references_media").on(table.mediaId)
|
|
6917
|
+
]);
|
|
6918
|
+
|
|
8346
6919
|
//#endregion
|
|
8347
6920
|
//#region ../sql-pg/src/internal/media/row-schema.ts
|
|
8348
6921
|
const mediaRefinements = {
|
|
@@ -8410,14 +6983,14 @@ const isMediaReferenced = Schema.is(MediaReferenced);
|
|
|
8410
6983
|
const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
|
|
8411
6984
|
const database = yield* PgDrizzle.makeWithDefaults();
|
|
8412
6985
|
const reserve = Effect.fn("MediaStore.Drizzle.reserve")((record, operation) => database.transaction((transaction) => Effect.gen(function* () {
|
|
8413
|
-
if (record.status !== "uploading") return yield* persistenceFailure$
|
|
6986
|
+
if (record.status !== "uploading") return yield* persistenceFailure$2("MediaStore.Drizzle.reserve", /* @__PURE__ */ new Error(`A media reservation must start as uploading, received "${record.status}".`));
|
|
8414
6987
|
yield* beginChange(transaction, mediaUploadChange(record, operation)).pipe(Effect.flatMap(requireUnresolvedChange));
|
|
8415
6988
|
const rows = yield* transaction.insert(mediaAssets).values(recordValues(record)).onConflictDoNothing().returning();
|
|
8416
6989
|
if (rows[0]) return yield* decodeMediaRecord(rows[0]);
|
|
8417
6990
|
const existing = yield* rowById(transaction, record.id);
|
|
8418
6991
|
if (existing && existing.status !== "deleting" && isSameReservation(existing, record)) return existing;
|
|
8419
|
-
return yield* persistenceFailure$
|
|
8420
|
-
})).pipe(mapPersistenceFailure$
|
|
6992
|
+
return yield* persistenceFailure$2("MediaStore.Drizzle.reserve", /* @__PURE__ */ new Error(`Media reservation "${record.id}" conflicts with durable media state.`));
|
|
6993
|
+
})).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.reserve")));
|
|
8421
6994
|
const activate = Effect.fn("MediaStore.Drizzle.activate")((id) => database.transaction((transaction) => Effect.gen(function* () {
|
|
8422
6995
|
const existing = yield* lockedRowById(transaction, id);
|
|
8423
6996
|
if (existing?.status === "ready") return existing;
|
|
@@ -8435,11 +7008,11 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
|
|
|
8435
7008
|
kind: "applied"
|
|
8436
7009
|
});
|
|
8437
7010
|
return activated;
|
|
8438
|
-
})).pipe(mapPersistenceFailure$
|
|
7011
|
+
})).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.activate")));
|
|
8439
7012
|
const find = Effect.fn("MediaStore.Drizzle.find")((id) => Effect.gen(function* () {
|
|
8440
7013
|
const rows = yield* database.select().from(mediaAssets).where(and(eq(mediaAssets.id, id), eq(mediaAssets.status, "ready"))).limit(1);
|
|
8441
7014
|
return rows[0] ? Option.some(yield* decodeMediaRecord(rows[0])) : Option.none();
|
|
8442
|
-
}).pipe(mapPersistenceFailure$
|
|
7015
|
+
}).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.find")));
|
|
8443
7016
|
const beginDelete = Effect.fn("MediaStore.Drizzle.beginDelete")((id, operation) => database.transaction((transaction) => Effect.gen(function* () {
|
|
8444
7017
|
const existing = yield* lockedRowById(transaction, id);
|
|
8445
7018
|
if (!existing) return yield* new MediaNotFound({ mediaId: id });
|
|
@@ -8452,7 +7025,13 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
|
|
|
8452
7025
|
yield* lockMediaReferenceKeys(transaction, [existing.objectKey]);
|
|
8453
7026
|
const referenceRows = yield* transaction.select().from(documentMediaReferences).where(eq(documentMediaReferences.objectKey, existing.objectKey)).orderBy(asc(documentMediaReferences.documentId), asc(documentMediaReferences.sourcePath), asc(documentMediaReferences.nodePath));
|
|
8454
7027
|
const references = yield* decodeMediaReferences(referenceRows);
|
|
8455
|
-
|
|
7028
|
+
const seoReferenceRows = yield* transaction.select({ scope: seoMediaReferences.scope }).from(seoMediaReferences).where(eq(seoMediaReferences.mediaId, id)).orderBy(asc(seoMediaReferences.scope));
|
|
7029
|
+
const seoScopes = yield* Schema.decodeEffect(Schema.Array(Schema.Literals(["working", "published"])))(seoReferenceRows.map((row) => row.scope)).pipe(Effect.mapError((cause) => mediaPersistenceError("MediaStore.Drizzle.beginDelete", cause)));
|
|
7030
|
+
if (references.length > 0 || seoScopes.length > 0) return yield* new MediaReferenced(seoScopes.length > 0 ? {
|
|
7031
|
+
mediaId: id,
|
|
7032
|
+
references,
|
|
7033
|
+
seoScopes
|
|
7034
|
+
} : {
|
|
8456
7035
|
mediaId: id,
|
|
8457
7036
|
references
|
|
8458
7037
|
});
|
|
@@ -8473,7 +7052,7 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
|
|
|
8473
7052
|
const existing = yield* replacementByMediaId(transaction, replacement.mediaId);
|
|
8474
7053
|
if (existing && isSameReplacement(existing, replacement)) return existing;
|
|
8475
7054
|
return yield* new MediaConflict({ mediaId: replacement.mediaId });
|
|
8476
|
-
})).pipe(mapStoreFailure$
|
|
7055
|
+
})).pipe(mapStoreFailure$2("MediaStore.Drizzle.beginReplace")));
|
|
8477
7056
|
const markReplaceStaged = Effect.fn("MediaStore.Drizzle.markReplaceStaged")((replacement) => database.transaction((transaction) => Effect.gen(function* () {
|
|
8478
7057
|
yield* lockedRowById(transaction, replacement.mediaId);
|
|
8479
7058
|
const existing = yield* replacementByMediaId(transaction, replacement.mediaId);
|
|
@@ -8481,14 +7060,14 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
|
|
|
8481
7060
|
if (existing.staged) return existing;
|
|
8482
7061
|
const rows = yield* transaction.update(mediaReplacements).set({ staged: true }).where(and(eq(mediaReplacements.mediaId, replacement.mediaId), eq(mediaReplacements.stagingObjectKey, replacement.stagingObjectKey))).returning();
|
|
8483
7062
|
if (rows[0]) return yield* decodeMediaReplacement(rows[0]);
|
|
8484
|
-
return yield* persistenceFailure$
|
|
8485
|
-
})).pipe(mapStoreFailure$
|
|
7063
|
+
return yield* persistenceFailure$2("MediaStore.Drizzle.markReplaceStaged", /* @__PURE__ */ new Error(`Media replacement "${replacement.mediaId}" lost its durable intent.`));
|
|
7064
|
+
})).pipe(mapStoreFailure$2("MediaStore.Drizzle.markReplaceStaged")));
|
|
8486
7065
|
const finishReplace = Effect.fn("MediaStore.Drizzle.finishReplace")((replacement) => database.transaction((transaction) => Effect.gen(function* () {
|
|
8487
7066
|
const asset = yield* lockedRowById(transaction, replacement.mediaId);
|
|
8488
7067
|
const pendingReplacement = yield* replacementByMediaId(transaction, replacement.mediaId);
|
|
8489
7068
|
if (!pendingReplacement) {
|
|
8490
7069
|
if (asset && isCompletedReplacement(asset, replacement)) return asset;
|
|
8491
|
-
return yield* persistenceFailure$
|
|
7070
|
+
return yield* persistenceFailure$2("MediaStore.Drizzle.finishReplace", /* @__PURE__ */ new Error(`Media replacement "${replacement.mediaId}" cannot be replayed from durable state.`));
|
|
8492
7071
|
}
|
|
8493
7072
|
if (!isSameReplacement(pendingReplacement, replacement)) return yield* new MediaConflict({ mediaId: replacement.mediaId });
|
|
8494
7073
|
if (!asset || asset.status !== "ready" || asset.objectKey !== replacement.objectKey || asset.mimeType !== replacement.mimeType) return yield* new MediaConflict({ mediaId: replacement.mediaId });
|
|
@@ -8501,7 +7080,7 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
|
|
|
8501
7080
|
updatedAt: transactionTimestamp
|
|
8502
7081
|
}).where(and(eq(mediaAssets.id, replacement.mediaId), eq(mediaAssets.status, "ready"))).returning())[0];
|
|
8503
7082
|
if (!replaced) return yield* transitionFailure("MediaStore.Drizzle.finishReplace", replacement.mediaId, asset.status);
|
|
8504
|
-
if (!(yield* transaction.delete(mediaReplacements).where(and(eq(mediaReplacements.mediaId, replacement.mediaId), eq(mediaReplacements.stagingObjectKey, replacement.stagingObjectKey))).returning({ mediaId: mediaReplacements.mediaId }))[0]) return yield* persistenceFailure$
|
|
7083
|
+
if (!(yield* transaction.delete(mediaReplacements).where(and(eq(mediaReplacements.mediaId, replacement.mediaId), eq(mediaReplacements.stagingObjectKey, replacement.stagingObjectKey))).returning({ mediaId: mediaReplacements.mediaId }))[0]) return yield* persistenceFailure$2("MediaStore.Drizzle.finishReplace", /* @__PURE__ */ new Error(`Media replacement "${replacement.mediaId}" lost its durable intent.`));
|
|
8505
7084
|
const decoded = yield* decodeMediaRecord(replaced);
|
|
8506
7085
|
const intent = yield* decodeMediaChangeIntent(change);
|
|
8507
7086
|
yield* resolveChange(transaction, change, {
|
|
@@ -8509,7 +7088,7 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
|
|
|
8509
7088
|
kind: "applied"
|
|
8510
7089
|
});
|
|
8511
7090
|
return decoded;
|
|
8512
|
-
})).pipe(mapStoreFailure$
|
|
7091
|
+
})).pipe(mapStoreFailure$2("MediaStore.Drizzle.finishReplace")));
|
|
8513
7092
|
const cancelReplace = Effect.fn("MediaStore.Drizzle.cancelReplace")((replacement) => database.transaction((transaction) => Effect.gen(function* () {
|
|
8514
7093
|
yield* lockedRowById(transaction, replacement.mediaId);
|
|
8515
7094
|
const existing = yield* replacementByMediaId(transaction, replacement.mediaId);
|
|
@@ -8518,7 +7097,7 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
|
|
|
8518
7097
|
const change = yield* requirePendingChange(transaction, mediaSubject(replacement.mediaId), "media.replaced");
|
|
8519
7098
|
if (!(yield* transaction.delete(mediaReplacements).where(and(eq(mediaReplacements.mediaId, replacement.mediaId), eq(mediaReplacements.staged, replacement.staged), eq(mediaReplacements.stagingObjectKey, replacement.stagingObjectKey))).returning({ mediaId: mediaReplacements.mediaId }))[0]) return yield* new MediaConflict({ mediaId: replacement.mediaId });
|
|
8520
7099
|
yield* resolveChange(transaction, change, { kind: "cancelled" });
|
|
8521
|
-
})).pipe(mapStoreFailure$
|
|
7100
|
+
})).pipe(mapStoreFailure$2("MediaStore.Drizzle.cancelReplace")));
|
|
8522
7101
|
const finishDelete = Effect.fn("MediaStore.Drizzle.finishDelete")((id) => database.transaction((transaction) => Effect.gen(function* () {
|
|
8523
7102
|
const existing = yield* lockedRowById(transaction, id);
|
|
8524
7103
|
if (!existing) return;
|
|
@@ -8530,7 +7109,7 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
|
|
|
8530
7109
|
details: mediaChangeDetails(intent),
|
|
8531
7110
|
kind: "applied"
|
|
8532
7111
|
});
|
|
8533
|
-
})).pipe(mapPersistenceFailure$
|
|
7112
|
+
})).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.finishDelete")));
|
|
8534
7113
|
const abandonUpload = Effect.fn("MediaStore.Drizzle.abandonUpload")((id) => database.transaction((transaction) => Effect.gen(function* () {
|
|
8535
7114
|
const existing = yield* lockedRowById(transaction, id);
|
|
8536
7115
|
if (!existing) return;
|
|
@@ -8538,15 +7117,15 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
|
|
|
8538
7117
|
const change = yield* requirePendingChange(transaction, mediaSubject(id), "media.uploaded");
|
|
8539
7118
|
if (!(yield* transaction.delete(mediaAssets).where(and(eq(mediaAssets.id, id), eq(mediaAssets.status, "uploading"))).returning({ id: mediaAssets.id }))[0]) return yield* transitionFailure("MediaStore.Drizzle.abandonUpload", id, existing.status);
|
|
8540
7119
|
yield* resolveChange(transaction, change, { kind: "cancelled" });
|
|
8541
|
-
})).pipe(mapPersistenceFailure$
|
|
7120
|
+
})).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.abandonUpload")));
|
|
8542
7121
|
const pending = Effect.fn("MediaStore.Drizzle.pending")((query) => Effect.gen(function* () {
|
|
8543
|
-
const rows = yield* database.select().from(mediaAssets).where(and(ne(mediaAssets.status, "ready"), lte(mediaAssets.updatedAt, DateTime.formatIso(query.before))
|
|
7122
|
+
const rows = yield* database.select().from(mediaAssets).where(and(ne(mediaAssets.status, "ready"), lte(mediaAssets.updatedAt, DateTime.formatIso(query.before)), query.after === void 0 ? void 0 : gt(mediaAssets.id, query.after))).orderBy(asc(mediaAssets.id)).limit(query.limit);
|
|
8544
7123
|
return yield* Effect.forEach(rows, decodeMediaRecord);
|
|
8545
|
-
}).pipe(mapPersistenceFailure$
|
|
7124
|
+
}).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.pending")));
|
|
8546
7125
|
const pendingReplacements = Effect.fn("MediaStore.Drizzle.pendingReplacements")((query) => Effect.gen(function* () {
|
|
8547
|
-
const rows = yield* database.select().from(mediaReplacements).where(lte(mediaReplacements.updatedAt, DateTime.formatIso(query.before))
|
|
7126
|
+
const rows = yield* database.select().from(mediaReplacements).where(and(lte(mediaReplacements.updatedAt, DateTime.formatIso(query.before)), query.after === void 0 ? void 0 : gt(mediaReplacements.mediaId, query.after))).orderBy(asc(mediaReplacements.mediaId)).limit(query.limit);
|
|
8548
7127
|
return yield* Effect.forEach(rows, decodeMediaReplacement);
|
|
8549
|
-
}).pipe(mapPersistenceFailure$
|
|
7128
|
+
}).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.pendingReplacements")));
|
|
8550
7129
|
const inspectReplacement = Effect.fn("MediaStore.Drizzle.inspectReplacement")((replacement) => database.transaction((transaction) => Effect.gen(function* () {
|
|
8551
7130
|
const asset = yield* lockedRowById(transaction, replacement.mediaId);
|
|
8552
7131
|
const pending = yield* replacementByMediaId(transaction, replacement.mediaId);
|
|
@@ -8555,17 +7134,17 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
|
|
|
8555
7134
|
return yield* new MediaConflict({ mediaId: replacement.mediaId });
|
|
8556
7135
|
}
|
|
8557
7136
|
return asset && isCompletedReplacement(asset, replacement) ? "completed" : "absent";
|
|
8558
|
-
})).pipe(mapStoreFailure$
|
|
7137
|
+
})).pipe(mapStoreFailure$2("MediaStore.Drizzle.inspectReplacement")));
|
|
8559
7138
|
const inventory = Effect.gen(function* () {
|
|
8560
7139
|
const rows = yield* database.select().from(mediaAssets).where(eq(mediaAssets.status, "ready")).orderBy(asc(mediaAssets.objectKey));
|
|
8561
7140
|
return yield* Effect.forEach(rows, decodeMediaRecord);
|
|
8562
|
-
}).pipe(mapPersistenceFailure$
|
|
7141
|
+
}).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.inventory"), Effect.withSpan("MediaStore.Drizzle.inventory"));
|
|
8563
7142
|
const knownObjectKeys = Effect.gen(function* () {
|
|
8564
7143
|
const assetRows = yield* database.select({ objectKey: mediaAssets.objectKey }).from(mediaAssets).orderBy(asc(mediaAssets.objectKey));
|
|
8565
7144
|
const replacementRows = yield* database.select({ objectKey: mediaReplacements.stagingObjectKey }).from(mediaReplacements).orderBy(asc(mediaReplacements.stagingObjectKey));
|
|
8566
7145
|
const objectKeys = yield* Schema.decodeEffect(Schema.Array(MediaObjectKey))([...assetRows, ...replacementRows].map((row) => row.objectKey));
|
|
8567
7146
|
return new Set(objectKeys);
|
|
8568
|
-
}).pipe(mapPersistenceFailure$
|
|
7147
|
+
}).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.knownObjectKeys"), Effect.withSpan("MediaStore.Drizzle.knownObjectKeys"));
|
|
8569
7148
|
const search = Effect.fn("MediaStore.Drizzle.search")((query) => database.transaction((transaction) => Effect.gen(function* () {
|
|
8570
7149
|
const normalizedSearch = query.search?.trim();
|
|
8571
7150
|
const pattern = normalizedSearch ? `%${escapeLike(normalizedSearch)}%` : void 0;
|
|
@@ -8574,14 +7153,14 @@ const mediaStoreLayer = Layer.effect(MediaStore, Effect.gen(function* () {
|
|
|
8574
7153
|
const rows = yield* transaction.select().from(mediaAssets).where(and(filters, cursorFilter)).orderBy(desc(mediaAssets.createdAt), desc(mediaAssets.id)).limit(query.limit + 1);
|
|
8575
7154
|
const folderRows = yield* transaction.selectDistinct({ folder: mediaAssets.folder }).from(mediaAssets).where(and(eq(mediaAssets.status, "ready"), ne(mediaAssets.folder, ""))).orderBy(asc(mediaAssets.folder));
|
|
8576
7155
|
const total = (yield* transaction.select({ value: count() }).from(mediaAssets).where(filters))[0]?.value;
|
|
8577
|
-
if (total === void 0) return yield* persistenceFailure$
|
|
7156
|
+
if (total === void 0) return yield* persistenceFailure$2("MediaStore.Drizzle.search", /* @__PURE__ */ new Error("Media persistence did not return a search total."));
|
|
8578
7157
|
const decoded = yield* Effect.forEach(rows, decodeMediaRecord);
|
|
8579
7158
|
const folders = yield* Schema.decodeEffect(Schema.Array(MediaFolder))(folderRows.map((row) => row.folder)).pipe(Effect.mapError((cause) => mediaPersistenceError("MediaStore.Drizzle.search", cause)));
|
|
8580
7159
|
return mediaPage(decoded, query.limit, folders, total);
|
|
8581
7160
|
}), {
|
|
8582
7161
|
accessMode: "read only",
|
|
8583
7162
|
isolationLevel: "repeatable read"
|
|
8584
|
-
}).pipe(mapPersistenceFailure$
|
|
7163
|
+
}).pipe(mapPersistenceFailure$2("MediaStore.Drizzle.search")));
|
|
8585
7164
|
return MediaStore.of({
|
|
8586
7165
|
abandonUpload,
|
|
8587
7166
|
activate,
|
|
@@ -8666,45 +7245,230 @@ const replacementByMediaId = Effect.fn("MediaStore.Drizzle.replacementByMediaId"
|
|
|
8666
7245
|
return rows[0] ? yield* decodeMediaReplacement(rows[0]) : void 0;
|
|
8667
7246
|
});
|
|
8668
7247
|
function transitionFailure(operation, id, actualStatus) {
|
|
8669
|
-
return persistenceFailure$
|
|
7248
|
+
return persistenceFailure$2(operation, /* @__PURE__ */ new Error(actualStatus === void 0 ? `Media record "${id}" does not exist.` : `Media record "${id}" cannot transition from "${actualStatus}".`));
|
|
8670
7249
|
}
|
|
8671
|
-
function mapPersistenceFailure$
|
|
7250
|
+
function mapPersistenceFailure$2(operation) {
|
|
8672
7251
|
return (effect) => effect.pipe(Effect.mapError((cause) => isMediaPersistenceError(cause) ? cause : mediaPersistenceError(operation, cause)));
|
|
8673
7252
|
}
|
|
8674
|
-
function mapStoreFailure$
|
|
7253
|
+
function mapStoreFailure$2(operation) {
|
|
8675
7254
|
return (effect) => effect.pipe(Effect.mapError((cause) => isMediaConflict(cause) || isMediaPersistenceError(cause) ? cause : mediaPersistenceError(operation, cause)));
|
|
8676
7255
|
}
|
|
8677
7256
|
function mapBeginDeleteFailure(operation) {
|
|
8678
7257
|
return (effect) => effect.pipe(Effect.mapError((cause) => isMediaConflict(cause) || isMediaNotFound(cause) || isMediaPersistenceError(cause) || isMediaReferenced(cause) ? cause : mediaPersistenceError(operation, cause)));
|
|
8679
7258
|
}
|
|
8680
|
-
function persistenceFailure$
|
|
7259
|
+
function persistenceFailure$2(operation, cause) {
|
|
8681
7260
|
return Effect.fail(mediaPersistenceError(operation, cause));
|
|
8682
7261
|
}
|
|
8683
7262
|
|
|
8684
7263
|
//#endregion
|
|
8685
|
-
//#region ../sql-pg/src/internal/
|
|
8686
|
-
const
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
7264
|
+
//#region ../sql-pg/src/internal/seo/seo-store.ts
|
|
7265
|
+
const SeoStoreDomainError = Schema.Union([
|
|
7266
|
+
SeoMediaUnavailable,
|
|
7267
|
+
SeoPersistenceError,
|
|
7268
|
+
SeoVersionConflict
|
|
7269
|
+
]);
|
|
7270
|
+
const isSeoStoreDomainError = Schema.is(SeoStoreDomainError);
|
|
7271
|
+
const isSeoPersistenceError = Schema.is(SeoPersistenceError);
|
|
7272
|
+
const systemActivity = {
|
|
7273
|
+
actor: {
|
|
7274
|
+
email: "system@cms.invalid",
|
|
7275
|
+
id: "system",
|
|
7276
|
+
name: "CMS System",
|
|
7277
|
+
roles: ["system"]
|
|
7278
|
+
},
|
|
7279
|
+
requestId: RequestId.make("00000000-0000-4000-8000-000000000000")
|
|
7280
|
+
};
|
|
7281
|
+
const seoStoreLayer = Layer.effect(SeoStore, Effect.gen(function* () {
|
|
7282
|
+
const database = yield* PgDrizzle.makeWithDefaults();
|
|
7283
|
+
const initialize = Effect.fn("SeoStore.Drizzle.initialize")((defaults) => database.transaction((transaction) => Effect.gen(function* () {
|
|
7284
|
+
yield* transaction.execute(sql`SELECT pg_advisory_xact_lock(hashtext('cms:seo:singleton:v1'))`);
|
|
7285
|
+
const current = yield* readSnapshot(transaction);
|
|
7286
|
+
if (current) return current;
|
|
7287
|
+
yield* ensureMediaAvailable(transaction, defaults).pipe(Effect.mapError((cause) => new SeoPersistenceError({
|
|
7288
|
+
cause,
|
|
7289
|
+
operation: "SeoStore.Drizzle.initializeDefaults"
|
|
7290
|
+
})));
|
|
7291
|
+
const revision = yield* insertRevision(transaction, defaults, systemActivity);
|
|
7292
|
+
yield* transaction.insert(seoState).values({
|
|
7293
|
+
publishedRevisionId: revision.revisionId,
|
|
7294
|
+
workingRevisionId: revision.revisionId
|
|
8703
7295
|
});
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
7296
|
+
yield* replaceMediaReferences(transaction, "working", defaults);
|
|
7297
|
+
yield* replaceMediaReferences(transaction, "published", defaults);
|
|
7298
|
+
return {
|
|
7299
|
+
published: revision,
|
|
7300
|
+
working: revision
|
|
7301
|
+
};
|
|
7302
|
+
})).pipe(mapStoreFailure$1("SeoStore.Drizzle.initialize")));
|
|
7303
|
+
const load = database.transaction((transaction) => readSnapshot(transaction), {
|
|
7304
|
+
accessMode: "read only",
|
|
7305
|
+
isolationLevel: "repeatable read"
|
|
7306
|
+
}).pipe(mapPersistenceFailure$1("SeoStore.Drizzle.load"));
|
|
7307
|
+
const save = Effect.fn("SeoStore.Drizzle.save")((input) => database.transaction((transaction) => Effect.gen(function* () {
|
|
7308
|
+
const current = yield* lockedSnapshot(transaction);
|
|
7309
|
+
yield* requireVersion(current, input.expectedVersion);
|
|
7310
|
+
yield* ensureMediaAvailable(transaction, input.settings);
|
|
7311
|
+
const revision = yield* insertRevision(transaction, input.settings, input.activity);
|
|
7312
|
+
yield* transaction.update(seoState).set({ workingRevisionId: revision.revisionId }).where(eq(seoState.singleton, true));
|
|
7313
|
+
yield* replaceMediaReferences(transaction, "working", input.settings);
|
|
7314
|
+
yield* recordActivity(transaction, "seo.saved", input.activity, {
|
|
7315
|
+
previousVersion: current.working.version,
|
|
7316
|
+
version: revision.version
|
|
7317
|
+
}, ActivityId.make(revision.revisionId));
|
|
7318
|
+
return {
|
|
7319
|
+
...current,
|
|
7320
|
+
working: revision
|
|
7321
|
+
};
|
|
7322
|
+
})).pipe(mapStoreFailure$1("SeoStore.Drizzle.save")));
|
|
7323
|
+
const publish = Effect.fn("SeoStore.Drizzle.publish")((input) => database.transaction((transaction) => Effect.gen(function* () {
|
|
7324
|
+
const current = yield* lockedSnapshot(transaction);
|
|
7325
|
+
yield* requireVersion(current, input.expectedVersion);
|
|
7326
|
+
yield* ensureMediaAvailable(transaction, current.working.settings);
|
|
7327
|
+
yield* transaction.update(seoState).set({ publishedRevisionId: current.working.revisionId }).where(eq(seoState.singleton, true));
|
|
7328
|
+
yield* replaceMediaReferences(transaction, "published", current.working.settings);
|
|
7329
|
+
yield* recordActivity(transaction, "seo.published", input.activity, { version: current.working.version });
|
|
7330
|
+
return {
|
|
7331
|
+
...current,
|
|
7332
|
+
published: current.working
|
|
7333
|
+
};
|
|
7334
|
+
})).pipe(mapStoreFailure$1("SeoStore.Drizzle.publish")));
|
|
7335
|
+
return SeoStore.of({
|
|
7336
|
+
initialize,
|
|
7337
|
+
load,
|
|
7338
|
+
publish,
|
|
7339
|
+
save
|
|
7340
|
+
});
|
|
8707
7341
|
}));
|
|
7342
|
+
const readSnapshot = Effect.fn("SeoStore.Drizzle.readSnapshot")(function* (database) {
|
|
7343
|
+
const state = (yield* database.select().from(seoState).limit(1))[0];
|
|
7344
|
+
if (!state) return void 0;
|
|
7345
|
+
const revisionRows = yield* database.select().from(seoRevisions).where(inArray(seoRevisions.id, [state.workingRevisionId, state.publishedRevisionId]));
|
|
7346
|
+
const revisions = yield* Effect.forEach(revisionRows, decodeRevision);
|
|
7347
|
+
const working = revisions.find((revision) => revision.revisionId === state.workingRevisionId);
|
|
7348
|
+
const published = revisions.find((revision) => revision.revisionId === state.publishedRevisionId);
|
|
7349
|
+
if (!working || !published) return yield* persistenceFailure$1("SeoStore.Drizzle.readSnapshot", /* @__PURE__ */ new Error("SEO state points to a missing revision."));
|
|
7350
|
+
return {
|
|
7351
|
+
published,
|
|
7352
|
+
working
|
|
7353
|
+
};
|
|
7354
|
+
});
|
|
7355
|
+
const lockedSnapshot = Effect.fn("SeoStore.Drizzle.lockSnapshot")(function* (transaction) {
|
|
7356
|
+
if (!(yield* transaction.select().from(seoState).where(eq(seoState.singleton, true)).for("update").limit(1))[0]) return yield* persistenceFailure$1("SeoStore.Drizzle.lockSnapshot", /* @__PURE__ */ new Error("SEO state is not initialized."));
|
|
7357
|
+
return (yield* readSnapshot(transaction)) ?? (yield* persistenceFailure$1("SeoStore.Drizzle.lockSnapshot", /* @__PURE__ */ new Error("SEO state disappeared.")));
|
|
7358
|
+
});
|
|
7359
|
+
const insertRevision = Effect.fn("SeoStore.Drizzle.insertRevision")(function* (transaction, settings, activity) {
|
|
7360
|
+
const row = (yield* transaction.insert(seoRevisions).values({
|
|
7361
|
+
actorEmail: activity.actor.email,
|
|
7362
|
+
actorId: activity.actor.id,
|
|
7363
|
+
actorName: activity.actor.name,
|
|
7364
|
+
actorRoles: activity.actor.roles,
|
|
7365
|
+
reason: activity.reason,
|
|
7366
|
+
requestId: activity.requestId,
|
|
7367
|
+
settings
|
|
7368
|
+
}).returning())[0];
|
|
7369
|
+
return row ? yield* decodeRevision(row) : yield* persistenceFailure$1("SeoStore.Drizzle.insertRevision", /* @__PURE__ */ new Error("No revision row returned."));
|
|
7370
|
+
});
|
|
7371
|
+
const decodeRevision = Effect.fn("SeoStore.Drizzle.decodeRevision")(function* (row) {
|
|
7372
|
+
return yield* Schema.decodeEffect(SeoRevision)({
|
|
7373
|
+
editedAt: row.createdAt,
|
|
7374
|
+
editedBy: {
|
|
7375
|
+
email: row.actorEmail,
|
|
7376
|
+
id: row.actorId,
|
|
7377
|
+
name: row.actorName,
|
|
7378
|
+
roles: row.actorRoles
|
|
7379
|
+
},
|
|
7380
|
+
revisionId: row.id,
|
|
7381
|
+
settings: row.settings,
|
|
7382
|
+
version: row.id
|
|
7383
|
+
}).pipe(Effect.mapError((cause) => new SeoPersistenceError({
|
|
7384
|
+
cause,
|
|
7385
|
+
operation: "SeoStore.Drizzle.decodeRevision"
|
|
7386
|
+
})));
|
|
7387
|
+
});
|
|
7388
|
+
function requireVersion(snapshot, expectedVersion) {
|
|
7389
|
+
return snapshot.working.version === expectedVersion ? Effect.void : Effect.fail(new SeoVersionConflict({
|
|
7390
|
+
actualVersion: snapshot.working.version,
|
|
7391
|
+
expectedVersion
|
|
7392
|
+
}));
|
|
7393
|
+
}
|
|
7394
|
+
const ensureMediaAvailable = Effect.fn("SeoStore.Drizzle.ensureMediaAvailable")(function* (transaction, settings) {
|
|
7395
|
+
const mediaIds = settingsMediaIds(settings);
|
|
7396
|
+
if (mediaIds.length === 0) return;
|
|
7397
|
+
const initialRows = yield* transaction.select({
|
|
7398
|
+
id: mediaAssets.id,
|
|
7399
|
+
objectKey: mediaAssets.objectKey
|
|
7400
|
+
}).from(mediaAssets).where(inArray(mediaAssets.id, mediaIds));
|
|
7401
|
+
const objectKeys = yield* Schema.decodeEffect(Schema.Array(MediaObjectKey))(initialRows.map((row) => row.objectKey));
|
|
7402
|
+
yield* lockMediaReferenceKeys(transaction, objectKeys);
|
|
7403
|
+
const readyRows = yield* transaction.select({
|
|
7404
|
+
id: mediaAssets.id,
|
|
7405
|
+
objectKey: mediaAssets.objectKey
|
|
7406
|
+
}).from(mediaAssets).where(and(inArray(mediaAssets.id, mediaIds), eq(mediaAssets.status, "ready")));
|
|
7407
|
+
const unavailable = settingsMediaImages(settings).find((image) => {
|
|
7408
|
+
const asset = readyRows.find((row) => row.id === image.mediaId);
|
|
7409
|
+
return !asset || mediaObjectKey(image.url) !== asset.objectKey;
|
|
7410
|
+
});
|
|
7411
|
+
if (unavailable) return yield* new SeoMediaUnavailable({ mediaId: unavailable.mediaId });
|
|
7412
|
+
});
|
|
7413
|
+
const replaceMediaReferences = Effect.fn("SeoStore.Drizzle.replaceMediaReferences")(function* (transaction, scope, settings) {
|
|
7414
|
+
yield* transaction.delete(seoMediaReferences).where(eq(seoMediaReferences.scope, scope));
|
|
7415
|
+
const mediaIds = settingsMediaIds(settings);
|
|
7416
|
+
if (mediaIds.length > 0) yield* transaction.insert(seoMediaReferences).values(mediaIds.map((mediaId) => ({
|
|
7417
|
+
mediaId,
|
|
7418
|
+
scope
|
|
7419
|
+
})));
|
|
7420
|
+
});
|
|
7421
|
+
function settingsMediaIds(settings) {
|
|
7422
|
+
return [...new Set(settingsMediaImages(settings).map((image) => image.mediaId))];
|
|
7423
|
+
}
|
|
7424
|
+
function settingsMediaImages(settings) {
|
|
7425
|
+
const images = [settings.socialImage];
|
|
7426
|
+
const identity = settings.identity;
|
|
7427
|
+
if (identity?.type === "Person") images.push(identity.image);
|
|
7428
|
+
if (identity?.type === "Organization") images.push(identity.logo);
|
|
7429
|
+
if (identity?.type === "LocalBusiness") images.push(identity.logo, identity.image);
|
|
7430
|
+
return images.filter((image) => image !== void 0);
|
|
7431
|
+
}
|
|
7432
|
+
function mediaObjectKey(url) {
|
|
7433
|
+
try {
|
|
7434
|
+
const encodedKey = new URL(url).pathname.match(/(?:^|\/)(media\/[^/]+)$/u)?.[1];
|
|
7435
|
+
return encodedKey ? encodedKey.split("/").map(decodeURIComponent).join("/") : void 0;
|
|
7436
|
+
} catch {
|
|
7437
|
+
return;
|
|
7438
|
+
}
|
|
7439
|
+
}
|
|
7440
|
+
const recordActivity = Effect.fn("SeoStore.Drizzle.recordActivity")(function* (transaction, action, context, details, id) {
|
|
7441
|
+
yield* transaction.insert(activityEntries).values({
|
|
7442
|
+
action,
|
|
7443
|
+
actorEmail: context.actor.email,
|
|
7444
|
+
actorId: context.actor.id,
|
|
7445
|
+
actorName: context.actor.name,
|
|
7446
|
+
actorRoles: context.actor.roles,
|
|
7447
|
+
details,
|
|
7448
|
+
id,
|
|
7449
|
+
reason: context.reason,
|
|
7450
|
+
requestId: context.requestId,
|
|
7451
|
+
targetKind: "seo"
|
|
7452
|
+
});
|
|
7453
|
+
});
|
|
7454
|
+
function mapStoreFailure$1(operation) {
|
|
7455
|
+
return (effect) => effect.pipe(Effect.mapError((cause) => isSeoStoreDomainError(cause) ? cause : new SeoPersistenceError({
|
|
7456
|
+
cause,
|
|
7457
|
+
operation
|
|
7458
|
+
})));
|
|
7459
|
+
}
|
|
7460
|
+
function mapPersistenceFailure$1(operation) {
|
|
7461
|
+
return (effect) => effect.pipe(Effect.mapError((cause) => isSeoPersistenceError(cause) ? cause : new SeoPersistenceError({
|
|
7462
|
+
cause,
|
|
7463
|
+
operation
|
|
7464
|
+
})));
|
|
7465
|
+
}
|
|
7466
|
+
function persistenceFailure$1(operation, cause) {
|
|
7467
|
+
return Effect.fail(new SeoPersistenceError({
|
|
7468
|
+
cause,
|
|
7469
|
+
operation
|
|
7470
|
+
}));
|
|
7471
|
+
}
|
|
8708
7472
|
|
|
8709
7473
|
//#endregion
|
|
8710
7474
|
//#region ../sql-pg/src/internal/user-administration/user-command-fence.ts
|
|
@@ -8722,7 +7486,7 @@ const postgresUserCommandFenceLayer = Layer.effect(UserCommandFence, PostgresAdv
|
|
|
8722
7486
|
const WORKING_DOCUMENT_CHANGES_CHANNEL = "cms_working_documents";
|
|
8723
7487
|
const WorkingDocumentChangePayload = Schema.fromJsonString(WorkingDocumentChange);
|
|
8724
7488
|
function workingDocumentChangePayload(change) {
|
|
8725
|
-
return Schema.
|
|
7489
|
+
return Result.getOrThrow(Schema.encodeResult(WorkingDocumentChangePayload)(change));
|
|
8726
7490
|
}
|
|
8727
7491
|
const decodeWorkingDocumentChangePayload = Schema.decodeUnknownEffect(WorkingDocumentChangePayload);
|
|
8728
7492
|
|
|
@@ -8757,12 +7521,13 @@ const makePostgresChanges = Effect.fn("WorkingDocumentChanges.makePostgres")(fun
|
|
|
8757
7521
|
const postgres = Context.get(context, PgClient.PgClient);
|
|
8758
7522
|
const heartbeats = /* @__PURE__ */ new Map();
|
|
8759
7523
|
let heartbeat = 0;
|
|
8760
|
-
const
|
|
8761
|
-
|
|
7524
|
+
const notifications = yield* postgres.listen(WORKING_DOCUMENT_CHANGES_CHANNEL).pipe(Effect.mapError((cause) => changesError("listen", cause)));
|
|
7525
|
+
const listener = yield* Stream.fromQueue(notifications).pipe(Stream.runForEach((payload) => {
|
|
7526
|
+
const waiting = heartbeats.get(payload.payload);
|
|
8762
7527
|
if (waiting !== void 0) return Deferred.succeed(waiting, void 0).pipe(Effect.asVoid);
|
|
8763
|
-
if (payload.startsWith("__cms_probe__:")) return Effect.void;
|
|
8764
|
-
return decodeWorkingDocumentChangePayload(payload).pipe(Effect.flatMap((change) => PubSub.publish(changes, change)), Effect.asVoid, Effect.catchCause((cause) => Effect.logWarning("Ignored an invalid Working Document change.", Cause.pretty(cause))));
|
|
8765
|
-
}), Effect.
|
|
7528
|
+
if (payload.payload.startsWith("__cms_probe__:")) return Effect.void;
|
|
7529
|
+
return decodeWorkingDocumentChangePayload(payload.payload).pipe(Effect.flatMap((change) => PubSub.publish(changes, change)), Effect.asVoid, Effect.catchCause((cause) => Effect.logWarning("Ignored an invalid Working Document change.", Cause.pretty(cause))));
|
|
7530
|
+
}), Effect.catchCause((cause) => Effect.fail(changesError("listen", cause)))).pipe(Effect.forkScoped({ startImmediately: true }));
|
|
8766
7531
|
const pulse = Effect.gen(function* () {
|
|
8767
7532
|
heartbeat += 1;
|
|
8768
7533
|
const payload = `__cms_probe__:${session}:${heartbeat}`;
|
|
@@ -9320,16 +8085,13 @@ function postgresLayer(options) {
|
|
|
9320
8085
|
});
|
|
9321
8086
|
const migrations = cmsDatabaseMigrationLayer.pipe(Layer.provide(database));
|
|
9322
8087
|
const locks = postgresAdvisoryLockLayer(options);
|
|
9323
|
-
|
|
9324
|
-
const readiness = postgresReadinessLayer.pipe(Layer.provide(stores), Layer.provide(database));
|
|
9325
|
-
return Layer.merge(stores, readiness);
|
|
8088
|
+
return Layer.mergeAll(postgresActivityStoreLayer, changeJournalLayer, documentStoreLayer, mediaStoreLayer, seoStoreLayer, initializationStoreLayer.pipe(Layer.provide(locks)), postgresUserCommandFenceLayer.pipe(Layer.provide(locks)), postgresWorkingDocumentChangesLayer(options), workingDocumentStoreLayer).pipe(Layer.provide(migrations), Layer.provide(database));
|
|
9326
8089
|
}
|
|
9327
8090
|
|
|
9328
8091
|
//#endregion
|
|
9329
8092
|
//#region src/runtime/installation.ts
|
|
9330
|
-
/** Composes the private providers selected by the Nuxt product adapter. */
|
|
9331
8093
|
const installCms = Effect.fn("NuxtCmsInstallation.install")(function* (options) {
|
|
9332
|
-
const crypto = NodeCrypto
|
|
8094
|
+
const crypto = NodeCrypto.layer;
|
|
9333
8095
|
const postgres = postgresLayer({ url: Redacted.make(options.databaseUrl) });
|
|
9334
8096
|
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({
|
|
9335
8097
|
baseURL: options.baseUrl,
|
|
@@ -9339,6 +8101,7 @@ const installCms = Effect.fn("NuxtCmsInstallation.install")(function* (options)
|
|
|
9339
8101
|
const core = Layer.mergeAll(crypto, postgres, identity);
|
|
9340
8102
|
return yield* installCms$1({
|
|
9341
8103
|
baseUrl: options.baseUrl,
|
|
8104
|
+
formHandlers: options.formHandlers,
|
|
9342
8105
|
authoring: options.authoring,
|
|
9343
8106
|
bootstrap: options.bootstrap,
|
|
9344
8107
|
cleanup: options.cleanup.enabled ? {
|
|
@@ -9377,4 +8140,4 @@ function toS3MediaObjectStoreOptions(options) {
|
|
|
9377
8140
|
|
|
9378
8141
|
//#endregion
|
|
9379
8142
|
export { toS3MediaObjectStoreOptions as n, installCms as t };
|
|
9380
|
-
//# sourceMappingURL=installation-
|
|
8143
|
+
//# sourceMappingURL=installation-DgF2M1gL.js.map
|