@musnows/scriverse 0.4.7 → 0.4.10

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/dist/app.js CHANGED
@@ -24,10 +24,10 @@ import { assertSafeImportedPlainText, decodeUtf8ImportedText } from "./import-se
24
24
  import { InvalidRasterImageError, readRasterImageMetadata } from "./image-metadata.js";
25
25
  import { createRequestLoggingMiddleware, sanitizeRequestPath } from "./http-logging.js";
26
26
  import { accountReference, logger, sanitizeError } from "./logger.js";
27
- import { runWithRequestActor } from "./request-context.js";
27
+ import { currentRequestActor, runWithRequestActor } from "./request-context.js";
28
28
  import { APP_VERSION } from "./version.js";
29
29
  import { fullWorkModulePermissions, proseReplacementPermissionModules } from "./work-permissions.js";
30
- import { CollaborationPresence, presencePageKinds } from "./collaboration-presence.js";
30
+ import { CollaborationPresence, editorPageKey, entityEditorPageKey, modulePageKey, pageLabelForKey, presencePageKinds } from "./collaboration-presence.js";
31
31
  import { clearSessionCookie, createCliApiScopeMiddleware, createUserSessionMiddleware, createWorkAuthorizationMiddleware, setSessionCookie, UserAuthService } from "./user-auth.js";
32
32
  const nonEmpty = z.string().trim().min(1);
33
33
  const identifier = z.string().trim().min(1).max(200);
@@ -336,7 +336,7 @@ const workAiSettingsSchema = z.object({
336
336
  autoRunBatchLimit: z.number().int().min(1).max(200).optional(),
337
337
  bookSummaryContextPercent: z.number().int().min(1).max(90).optional(),
338
338
  contextCompactThreshold: z.number().int().min(50).max(90).optional(),
339
- agentTools: z.array(z.enum(["story_index", "read_chapters", "grep", "query_story_knowledge", "read_character_sections"])).max(5).optional()
339
+ agentTools: z.array(z.enum(["story_index", "read_chapters", "grep", "search_story_entities", "read_character_sections"])).max(5).optional()
340
340
  }).strict();
341
341
  const contextSchema = z.object({
342
342
  type: z.enum(["none", "selection", "chapter", "volume", "book", "entities"]),
@@ -423,6 +423,15 @@ export function createRuntime(options) {
423
423
  mkdirSync(attachmentStorage.temporaryDirectory, { recursive: true, mode: 0o700 });
424
424
  const auth = new UserAuthService(database);
425
425
  const collaborationPresence = new CollaborationPresence();
426
+ const publishCollaborativeChange = (workId, pageKey, label = pageLabelForKey(pageKey)) => {
427
+ const actor = currentRequestActor();
428
+ if (!actor || !workId || !pageKey)
429
+ return;
430
+ collaborationPresence.publishChange(workId, pageKey, {
431
+ userId: actor.userId,
432
+ displayName: actor.displayName
433
+ }, label);
434
+ };
426
435
  const getDevelopmentUser = () => options.devAuthBypass
427
436
  ? auth.listUsers().find((user) => user.status === "active") ?? null
428
437
  : null;
@@ -513,7 +522,10 @@ export function createRuntime(options) {
513
522
  logger.info("auth.login.succeeded", { actorRef: accountReference(result.session.user.userId) });
514
523
  data(response, { user: result.session.user, csrfToken: result.session.csrfToken });
515
524
  });
516
- app.use(createUserSessionMiddleware(auth, options.disableUserAuth));
525
+ app.use(createUserSessionMiddleware(auth, {
526
+ disabled: options.disableUserAuth === true,
527
+ resolveBypassUser: getDevelopmentUser
528
+ }));
517
529
  app.use(createCliApiScopeMiddleware(options.disableUserAuth));
518
530
  app.use(createWorkAuthorizationMiddleware(auth, options.disableUserAuth));
519
531
  app.get("/api/cli/session", (request, response) => {
@@ -789,11 +801,15 @@ export function createRuntime(options) {
789
801
  app.patch("/api/chapters/:chapterId", (request, response) => {
790
802
  const input = parse(z.object({ title: nonEmpty.max(300).optional(), content: z.string().max(2_000_000).optional(), excludedFromAnalysis: z.boolean().optional(), chapterType: chapterTypeSchema.optional(), source: z.enum(["manual", "auto"]).optional(), changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
791
803
  const { source, changeNote, expectedVersionNo, ...chapterInput } = input;
792
- data(response, store.saveChapter(request.params.chapterId, chapterInput, source ?? "manual", null, changeNote, expectedVersionNo));
804
+ const chapter = store.saveChapter(request.params.chapterId, chapterInput, source ?? "manual", null, changeNote, expectedVersionNo);
805
+ publishCollaborativeChange(String(chapter.workId), editorPageKey(String(chapter.id)));
806
+ data(response, chapter);
793
807
  });
794
808
  app.delete("/api/chapters/:chapterId", (request, response) => {
795
809
  const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
810
+ const chapter = store.getChapter(request.params.chapterId);
796
811
  store.deleteChapter(request.params.chapterId, input.expectedVersionNo);
812
+ publishCollaborativeChange(String(chapter.workId), editorPageKey(String(chapter.id)));
797
813
  noContent(response);
798
814
  });
799
815
  app.get("/api/chapters/:chapterId/versions", (request, response) => {
@@ -806,7 +822,9 @@ export function createRuntime(options) {
806
822
  });
807
823
  app.post("/api/chapters/:chapterId/restore", (request, response) => {
808
824
  const input = parse(z.object({ versionNo: z.number().int().positive(), expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
809
- data(response, store.restoreChapter(request.params.chapterId, input.versionNo, input.expectedVersionNo));
825
+ const chapter = store.restoreChapter(request.params.chapterId, input.versionNo, input.expectedVersionNo);
826
+ publishCollaborativeChange(String(chapter.workId), editorPageKey(String(chapter.id)));
827
+ data(response, chapter);
810
828
  });
811
829
  app.post("/api/chapters/:chapterId/move", (request, response) => {
812
830
  const input = parse(z.object({ volumeId: identifier, sortOrder: z.number().int().min(0), expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
@@ -820,11 +838,15 @@ export function createRuntime(options) {
820
838
  app.get("/api/chapters/:chapterId/outline", (request, response) => data(response, store.getChapterOutline(request.params.chapterId)));
821
839
  app.put("/api/chapters/:chapterId/outline", (request, response) => {
822
840
  const { changeNote, expectedVersionNo, ...input } = parse(chapterOutlineSchema.extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
823
- data(response, store.upsertChapterOutline(request.params.chapterId, input, "manual", null, changeNote, expectedVersionNo));
841
+ const outline = store.upsertChapterOutline(request.params.chapterId, input, "manual", null, changeNote, expectedVersionNo);
842
+ publishCollaborativeChange(String(outline.workId), modulePageKey("outlines"));
843
+ data(response, outline);
824
844
  });
825
845
  app.delete("/api/chapters/:chapterId/outline", (request, response) => {
826
846
  const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
847
+ const chapter = store.getChapter(request.params.chapterId);
827
848
  store.deleteChapterOutline(request.params.chapterId, input.expectedVersionNo);
849
+ publishCollaborativeChange(String(chapter.workId), modulePageKey("outlines"));
828
850
  noContent(response);
829
851
  });
830
852
  app.get("/api/works/:workId/foreshadows", (request, response) => {
@@ -838,31 +860,43 @@ export function createRuntime(options) {
838
860
  : store.listForeshadows(request.params.workId, query.status, query.currentChapterId));
839
861
  });
840
862
  app.post("/api/works/:workId/foreshadows", (request, response) => {
841
- data(response, store.createForeshadow(request.params.workId, parse(foreshadowSchema, request.body)), 201);
863
+ const foreshadow = store.createForeshadow(request.params.workId, parse(foreshadowSchema, request.body));
864
+ publishCollaborativeChange(request.params.workId, modulePageKey("outlines"));
865
+ data(response, foreshadow, 201);
842
866
  });
843
867
  app.get("/api/foreshadows/:foreshadowId", (request, response) => data(response, store.getForeshadow(request.params.foreshadowId)));
844
868
  app.patch("/api/foreshadows/:foreshadowId", (request, response) => {
845
869
  const { changeNote, expectedVersionNo, ...input } = parse(foreshadowSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
846
- data(response, store.updateForeshadow(request.params.foreshadowId, input, "manual", null, changeNote, expectedVersionNo));
870
+ const foreshadow = store.updateForeshadow(request.params.foreshadowId, input, "manual", null, changeNote, expectedVersionNo);
871
+ publishCollaborativeChange(String(foreshadow.workId), modulePageKey("outlines"));
872
+ data(response, foreshadow);
847
873
  });
848
874
  app.delete("/api/foreshadows/:foreshadowId", (request, response) => {
849
875
  const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
876
+ const foreshadow = store.getForeshadow(request.params.foreshadowId);
850
877
  store.deleteForeshadow(request.params.foreshadowId, input.expectedVersionNo);
878
+ publishCollaborativeChange(String(foreshadow.workId), modulePageKey("outlines"));
851
879
  noContent(response);
852
880
  });
853
881
  app.post("/api/foreshadows/:foreshadowId/occurrences", (request, response) => {
854
882
  const input = parse(foreshadowOccurrenceSchema.extend({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
855
883
  const { expectedVersionNo, ...occurrenceInput } = input;
856
- data(response, store.createForeshadowOccurrence(request.params.foreshadowId, occurrenceInput, expectedVersionNo), 201);
884
+ const occurrence = store.createForeshadowOccurrence(request.params.foreshadowId, occurrenceInput, expectedVersionNo);
885
+ publishCollaborativeChange(String(occurrence.workId), modulePageKey("outlines"));
886
+ data(response, occurrence, 201);
857
887
  });
858
888
  app.patch("/api/foreshadow-occurrences/:occurrenceId", (request, response) => {
859
889
  const input = parse(foreshadowOccurrenceSchema.partial().extend({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
860
890
  const { expectedVersionNo, ...occurrenceInput } = input;
861
- data(response, store.updateForeshadowOccurrence(request.params.occurrenceId, occurrenceInput, expectedVersionNo));
891
+ const occurrence = store.updateForeshadowOccurrence(request.params.occurrenceId, occurrenceInput, expectedVersionNo);
892
+ publishCollaborativeChange(String(occurrence.workId), modulePageKey("outlines"));
893
+ data(response, occurrence);
862
894
  });
863
895
  app.delete("/api/foreshadow-occurrences/:occurrenceId", (request, response) => {
864
896
  const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
897
+ const occurrence = store.getForeshadowOccurrence(request.params.occurrenceId);
865
898
  store.deleteForeshadowOccurrence(request.params.occurrenceId, input.expectedVersionNo);
899
+ publishCollaborativeChange(String(occurrence.workId), modulePageKey("outlines"));
866
900
  noContent(response);
867
901
  });
868
902
  app.get("/api/works/:workId/settings", (request, response) => {
@@ -870,16 +904,24 @@ export function createRuntime(options) {
870
904
  const includeContent = request.query.includeContent === "true";
871
905
  data(response, pagination ? store.listSettingsPage(request.params.workId, pagination, includeContent) : store.listSettings(request.params.workId, includeContent));
872
906
  });
873
- app.post("/api/works/:workId/settings", (request, response) => data(response, store.createSetting(request.params.workId, parse(settingSchema, request.body)), 201));
907
+ app.post("/api/works/:workId/settings", (request, response) => {
908
+ const setting = store.createSetting(request.params.workId, parse(settingSchema, request.body));
909
+ publishCollaborativeChange(request.params.workId, modulePageKey("settings"));
910
+ data(response, setting, 201);
911
+ });
874
912
  app.get("/api/works/:workId/settings/context", (request, response) => data(response, store.listSettings(request.params.workId, true)));
875
913
  app.get("/api/settings/:settingId", (request, response) => data(response, store.getSetting(request.params.settingId)));
876
914
  app.patch("/api/settings/:settingId", (request, response) => {
877
915
  const { changeNote, expectedVersionNo, ...input } = parse(settingSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
878
- data(response, store.updateSetting(request.params.settingId, input, "manual", null, changeNote, expectedVersionNo));
916
+ const setting = store.updateSetting(request.params.settingId, input, "manual", null, changeNote, expectedVersionNo);
917
+ publishCollaborativeChange(String(setting.workId), entityEditorPageKey("setting", String(setting.id)));
918
+ data(response, setting);
879
919
  });
880
920
  app.delete("/api/settings/:settingId", (request, response) => {
881
921
  const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
922
+ const setting = store.getSetting(request.params.settingId);
882
923
  store.deleteSetting(request.params.settingId, input.expectedVersionNo);
924
+ publishCollaborativeChange(String(setting.workId), entityEditorPageKey("setting", String(setting.id)));
883
925
  noContent(response);
884
926
  });
885
927
  app.get("/api/works/:workId/characters", (request, response) => {
@@ -897,6 +939,7 @@ export function createRuntime(options) {
897
939
  });
898
940
  app.post("/api/works/:workId/characters", (request, response) => {
899
941
  const character = store.createCharacter(request.params.workId, parse(characterSchema, request.body));
942
+ publishCollaborativeChange(request.params.workId, modulePageKey("characters"));
900
943
  data(response, redactCharacterLinks(character, requestPermissions(request, request.params.workId)), 201);
901
944
  });
902
945
  app.get("/api/characters/:characterId", (request, response) => {
@@ -905,6 +948,7 @@ export function createRuntime(options) {
905
948
  app.patch("/api/characters/:characterId", (request, response) => {
906
949
  const { changeNote, expectedVersionNo, ...input } = parse(characterUpdateSchema.extend({ expectedVersionNo: expectedVersionNoSchema }), request.body);
907
950
  const character = store.updateCharacter(request.params.characterId, input, "manual", null, changeNote, expectedVersionNo);
951
+ publishCollaborativeChange(String(character.workId), entityEditorPageKey("character", String(character.id)));
908
952
  data(response, redactCharacterLinks(character, requestPermissions(request)));
909
953
  });
910
954
  app.get("/api/characters/:characterId/versions", (request, response) => {
@@ -916,11 +960,14 @@ export function createRuntime(options) {
916
960
  app.post("/api/characters/:characterId/restore", (request, response) => {
917
961
  const input = parse(z.object({ versionNo: z.number().int().positive(), expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
918
962
  const character = store.restoreCharacter(request.params.characterId, input.versionNo, input.expectedVersionNo);
963
+ publishCollaborativeChange(String(character.workId), entityEditorPageKey("character", String(character.id)));
919
964
  data(response, redactCharacterLinks(character, requestPermissions(request)));
920
965
  });
921
966
  app.delete("/api/characters/:characterId", (request, response) => {
922
967
  const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
968
+ const character = store.getCharacter(request.params.characterId);
923
969
  store.deleteCharacter(request.params.characterId, input.expectedVersionNo);
970
+ publishCollaborativeChange(String(character.workId), entityEditorPageKey("character", String(character.id)));
924
971
  noContent(response);
925
972
  });
926
973
  app.post("/api/characters/:characterId/merge", (request, response) => {
@@ -944,18 +991,24 @@ export function createRuntime(options) {
944
991
  : store.listCharacterProfileSections(request.params.characterId));
945
992
  });
946
993
  app.post("/api/characters/:characterId/sections", (request, response) => {
947
- data(response, store.createCharacterProfileSection(request.params.characterId, parse(characterProfileSectionSchema, request.body)), 201);
994
+ const section = store.createCharacterProfileSection(request.params.characterId, parse(characterProfileSectionSchema, request.body));
995
+ publishCollaborativeChange(String(section.workId), entityEditorPageKey("character", String(section.characterId)));
996
+ data(response, section, 201);
948
997
  });
949
998
  app.get("/api/character-sections/:sectionId", (request, response) => {
950
999
  data(response, store.getCharacterProfileSection(request.params.sectionId));
951
1000
  });
952
1001
  app.patch("/api/character-sections/:sectionId", (request, response) => {
953
1002
  const { changeNote, expectedVersionNo, ...input } = parse(characterProfileSectionSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
954
- data(response, store.updateCharacterProfileSection(request.params.sectionId, input, "manual", null, changeNote, expectedVersionNo));
1003
+ const section = store.updateCharacterProfileSection(request.params.sectionId, input, "manual", null, changeNote, expectedVersionNo);
1004
+ publishCollaborativeChange(String(section.workId), entityEditorPageKey("character", String(section.characterId)));
1005
+ data(response, section);
955
1006
  });
956
1007
  app.delete("/api/character-sections/:sectionId", (request, response) => {
957
1008
  const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
1009
+ const section = store.getCharacterProfileSection(request.params.sectionId);
958
1010
  store.deleteCharacterProfileSection(request.params.sectionId, input.expectedVersionNo);
1011
+ publishCollaborativeChange(String(section.workId), entityEditorPageKey("character", String(section.characterId)));
959
1012
  noContent(response);
960
1013
  });
961
1014
  app.get("/api/character-sections/:sectionId/versions", (request, response) => {
@@ -966,7 +1019,9 @@ export function createRuntime(options) {
966
1019
  });
967
1020
  app.post("/api/character-sections/:sectionId/restore", (request, response) => {
968
1021
  const input = parse(z.object({ versionNo: z.number().int().positive(), expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
969
- data(response, store.restoreCharacterProfileSection(request.params.sectionId, input.versionNo, input.expectedVersionNo));
1022
+ const section = store.restoreCharacterProfileSection(request.params.sectionId, input.versionNo, input.expectedVersionNo);
1023
+ publishCollaborativeChange(String(section.workId), entityEditorPageKey("character", String(section.characterId)));
1024
+ data(response, section);
970
1025
  });
971
1026
  app.get("/api/works/:workId/attachments", (request, response) => {
972
1027
  const pagination = parsePagination(request.query);
@@ -1022,6 +1077,7 @@ export function createRuntime(options) {
1022
1077
  });
1023
1078
  app.post("/api/works/:workId/races", (request, response) => {
1024
1079
  const race = store.createRace(request.params.workId, parse(raceSchema, request.body));
1080
+ publishCollaborativeChange(request.params.workId, modulePageKey("races"));
1025
1081
  data(response, redactRaceMembers(race, requestPermissions(request, request.params.workId)), 201);
1026
1082
  });
1027
1083
  app.get("/api/races/:raceId", (request, response) => {
@@ -1030,11 +1086,14 @@ export function createRuntime(options) {
1030
1086
  app.patch("/api/races/:raceId", (request, response) => {
1031
1087
  const { changeNote, expectedVersionNo, ...input } = parse(raceSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
1032
1088
  const race = store.updateRace(request.params.raceId, input, "manual", null, changeNote, expectedVersionNo);
1089
+ publishCollaborativeChange(String(race.workId), entityEditorPageKey("race", String(race.id)));
1033
1090
  data(response, redactRaceMembers(race, requestPermissions(request)));
1034
1091
  });
1035
1092
  app.delete("/api/races/:raceId", (request, response) => {
1036
1093
  const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
1094
+ const race = store.getRace(request.params.raceId);
1037
1095
  store.deleteRace(request.params.raceId, input.expectedVersionNo);
1096
+ publishCollaborativeChange(String(race.workId), entityEditorPageKey("race", String(race.id)));
1038
1097
  noContent(response);
1039
1098
  });
1040
1099
  app.post("/api/races/:raceId/merge", (request, response) => {
@@ -1052,6 +1111,7 @@ export function createRuntime(options) {
1052
1111
  });
1053
1112
  app.post("/api/works/:workId/organizations", (request, response) => {
1054
1113
  const organization = store.createOrganization(request.params.workId, parse(organizationSchema, request.body));
1114
+ publishCollaborativeChange(request.params.workId, modulePageKey("organizations"));
1055
1115
  data(response, redactOrganizationMembers(organization, requestPermissions(request, request.params.workId)), 201);
1056
1116
  });
1057
1117
  app.get("/api/organizations/:organizationId", (request, response) => {
@@ -1060,11 +1120,14 @@ export function createRuntime(options) {
1060
1120
  app.patch("/api/organizations/:organizationId", (request, response) => {
1061
1121
  const { changeNote, expectedVersionNo, ...input } = parse(organizationSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
1062
1122
  const organization = store.updateOrganization(request.params.organizationId, input, "manual", null, changeNote, expectedVersionNo);
1123
+ publishCollaborativeChange(String(organization.workId), entityEditorPageKey("organization", String(organization.id)));
1063
1124
  data(response, redactOrganizationMembers(organization, requestPermissions(request)));
1064
1125
  });
1065
1126
  app.delete("/api/organizations/:organizationId", (request, response) => {
1066
1127
  const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
1128
+ const organization = store.getOrganization(request.params.organizationId);
1067
1129
  store.deleteOrganization(request.params.organizationId, input.expectedVersionNo);
1130
+ publishCollaborativeChange(String(organization.workId), entityEditorPageKey("organization", String(organization.id)));
1068
1131
  noContent(response);
1069
1132
  });
1070
1133
  app.post("/api/organizations/:organizationId/merge", (request, response) => {
@@ -1077,22 +1140,34 @@ export function createRuntime(options) {
1077
1140
  const pagination = parsePagination(request.query);
1078
1141
  data(response, pagination ? store.listTimelineTracksPage(request.params.workId, pagination) : store.listTimelineTracks(request.params.workId));
1079
1142
  });
1080
- app.post("/api/works/:workId/timeline-tracks", (request, response) => data(response, store.createTimelineTrack(request.params.workId, parse(timelineTrackSchema, request.body)), 201));
1143
+ app.post("/api/works/:workId/timeline-tracks", (request, response) => {
1144
+ const track = store.createTimelineTrack(request.params.workId, parse(timelineTrackSchema, request.body));
1145
+ publishCollaborativeChange(request.params.workId, modulePageKey("timeline"));
1146
+ data(response, track, 201);
1147
+ });
1081
1148
  app.get("/api/timeline-tracks/:trackId", (request, response) => data(response, store.getTimelineTrack(request.params.trackId)));
1082
1149
  app.patch("/api/timeline-tracks/:trackId", (request, response) => {
1083
1150
  const { changeNote, expectedVersionNo, ...input } = parse(timelineTrackSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
1084
- data(response, store.updateTimelineTrack(request.params.trackId, input, "manual", null, changeNote, expectedVersionNo));
1151
+ const track = store.updateTimelineTrack(request.params.trackId, input, "manual", null, changeNote, expectedVersionNo);
1152
+ publishCollaborativeChange(String(track.workId), modulePageKey("timeline"));
1153
+ data(response, track);
1085
1154
  });
1086
1155
  app.delete("/api/timeline-tracks/:trackId", (request, response) => {
1087
1156
  const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
1157
+ const track = store.getTimelineTrack(request.params.trackId);
1088
1158
  store.deleteTimelineTrack(request.params.trackId, input.expectedVersionNo);
1159
+ publishCollaborativeChange(String(track.workId), modulePageKey("timeline"));
1089
1160
  noContent(response);
1090
1161
  });
1091
1162
  app.get("/api/works/:workId/timeline", (request, response) => {
1092
1163
  const pagination = parsePagination(request.query);
1093
1164
  data(response, pagination ? store.listTimelineEventsPage(request.params.workId, pagination) : store.listTimelineEvents(request.params.workId));
1094
1165
  });
1095
- app.post("/api/works/:workId/timeline", (request, response) => data(response, store.createTimelineEvent(request.params.workId, parse(timelineSchema, request.body)), 201));
1166
+ app.post("/api/works/:workId/timeline", (request, response) => {
1167
+ const event = store.createTimelineEvent(request.params.workId, parse(timelineSchema, request.body));
1168
+ publishCollaborativeChange(request.params.workId, modulePageKey("timeline"));
1169
+ data(response, event, 201);
1170
+ });
1096
1171
  app.post("/api/works/:workId/timeline/merge", (request, response) => {
1097
1172
  const input = parse(z.object({
1098
1173
  eventIds: z.array(identifier).min(2),
@@ -1103,12 +1178,16 @@ export function createRuntime(options) {
1103
1178
  expectedVersionNos: z.record(identifier, z.number().int().positive()).optional()
1104
1179
  }).strict(), request.body);
1105
1180
  const { expectedVersionNos, ...mergeInput } = input;
1106
- data(response, store.mergeTimelineEvents(request.params.workId, input.eventIds, mergeInput, expectedVersionNos), 201);
1181
+ const merged = store.mergeTimelineEvents(request.params.workId, input.eventIds, mergeInput, expectedVersionNos);
1182
+ publishCollaborativeChange(request.params.workId, modulePageKey("timeline"));
1183
+ data(response, merged, 201);
1107
1184
  });
1108
1185
  app.get("/api/timeline/:eventId", (request, response) => data(response, store.getTimelineEvent(request.params.eventId)));
1109
1186
  app.patch("/api/timeline/:eventId", (request, response) => {
1110
1187
  const { changeNote, expectedVersionNo, ...input } = parse(timelineSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
1111
- data(response, store.updateTimelineEvent(request.params.eventId, input, "manual", null, changeNote, expectedVersionNo));
1188
+ const event = store.updateTimelineEvent(request.params.eventId, input, "manual", null, changeNote, expectedVersionNo);
1189
+ publishCollaborativeChange(String(event.workId), modulePageKey("timeline"));
1190
+ data(response, event);
1112
1191
  });
1113
1192
  app.post("/api/timeline/:eventId/split", (request, response) => {
1114
1193
  const input = parse(z.object({
@@ -1120,11 +1199,17 @@ export function createRuntime(options) {
1120
1199
  })).min(2),
1121
1200
  expectedVersionNo: expectedVersionNoSchema
1122
1201
  }).strict(), request.body);
1123
- data(response, store.splitTimelineEvent(request.params.eventId, input.parts, input.expectedVersionNo), 201);
1202
+ const split = store.splitTimelineEvent(request.params.eventId, input.parts, input.expectedVersionNo);
1203
+ const first = split[0];
1204
+ if (first)
1205
+ publishCollaborativeChange(String(first.workId), modulePageKey("timeline"));
1206
+ data(response, split, 201);
1124
1207
  });
1125
1208
  app.delete("/api/timeline/:eventId", (request, response) => {
1126
1209
  const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
1210
+ const event = store.getTimelineEvent(request.params.eventId);
1127
1211
  store.deleteTimelineEvent(request.params.eventId, input.expectedVersionNo);
1212
+ publishCollaborativeChange(String(event.workId), modulePageKey("timeline"));
1128
1213
  noContent(response);
1129
1214
  });
1130
1215
  app.get("/api/works/:workId/relationships", (request, response) => {
@@ -1136,15 +1221,23 @@ export function createRuntime(options) {
1136
1221
  ? store.listRelationshipsPage(request.params.workId, pagination, confidence)
1137
1222
  : store.listRelationships(request.params.workId, confidence));
1138
1223
  });
1139
- app.post("/api/works/:workId/relationships", (request, response) => data(response, store.createRelationship(request.params.workId, parse(relationshipSchema, request.body)), 201));
1224
+ app.post("/api/works/:workId/relationships", (request, response) => {
1225
+ const relationship = store.createRelationship(request.params.workId, parse(relationshipSchema, request.body));
1226
+ publishCollaborativeChange(request.params.workId, modulePageKey("relationships"));
1227
+ data(response, relationship, 201);
1228
+ });
1140
1229
  app.get("/api/relationships/:relationshipId", (request, response) => data(response, store.getRelationship(request.params.relationshipId)));
1141
1230
  app.patch("/api/relationships/:relationshipId", (request, response) => {
1142
1231
  const { changeNote, expectedVersionNo, ...input } = parse(relationshipSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
1143
- data(response, store.updateRelationship(request.params.relationshipId, input, "manual", null, changeNote, expectedVersionNo));
1232
+ const relationship = store.updateRelationship(request.params.relationshipId, input, "manual", null, changeNote, expectedVersionNo);
1233
+ publishCollaborativeChange(String(relationship.workId), modulePageKey("relationships"));
1234
+ data(response, relationship);
1144
1235
  });
1145
1236
  app.delete("/api/relationships/:relationshipId", (request, response) => {
1146
1237
  const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
1238
+ const relationship = store.getRelationship(request.params.relationshipId);
1147
1239
  store.deleteRelationship(request.params.relationshipId, input.expectedVersionNo);
1240
+ publishCollaborativeChange(String(relationship.workId), modulePageKey("relationships"));
1148
1241
  noContent(response);
1149
1242
  });
1150
1243
  app.get("/api/entity-versions/:entityType/:entityId", (request, response) => {