11import dbDriver from "../../database/driver.js"
2- import { handleVersionConflict } from "../../utilities/shared.js"
32import Page from "../Page/Page.js"
43import { fetchUserAgent } from "../../utilities/shared.js"
54import ProjectFactory from "../Project/ProjectFactory.js"
@@ -8,6 +7,7 @@ const databaseTiny = new dbDriver("tiny")
87
98export default class Layer {
109 #tinyAction = 'create'
10+ #hydrated = false
1111
1212 /**
1313 * Constructs a Layer from the JSON Object in the Project `layers` Array.
@@ -17,6 +17,7 @@ export default class Layer {
1717 * @param {string } id The ID of the layer. This is the Layer stored in the Project.
1818 * @param {string } label The label of the layer. This is the Layer stored in the Project.
1919 * @param {Array } pages The pages in the layer by reference.
20+ * @param {string|null } [creator=null] The creator/agent URI for this layer.
2021 * @seeAlso {@link Layer.build }
2122 */
2223 constructor ( projectId , { id, label, pages, creator = null } ) {
@@ -31,6 +32,9 @@ export default class Layer {
3132 this . label = label
3233 this . creator = creator
3334 this . pages = pages
35+ this . total = pages . length
36+ this . first = pages . at ( 0 ) ?. id
37+ this . last = pages . at ( - 1 ) ?. id
3438 if ( this . id . startsWith ( process . env . RERUMIDPREFIX ) ) {
3539 this . #tinyAction = 'update'
3640 }
@@ -79,13 +83,49 @@ export default class Layer {
7983 this . #setRerumId( )
8084 await this . #saveCollectionToRerum( )
8185 }
86+ this . total = this . pages . length
87+ this . first = this . pages . at ( 0 ) ?. id
88+ this . last = this . pages . at ( - 1 ) ?. id
89+ this . #hydrated = true
8290 return this . #formatCollectionForProject( )
8391 }
8492
8593 asProjectLayer ( ) {
8694 return this . #formatCollectionForProject( )
8795 }
8896
97+ /**
98+ * Returns a JSON representation of the Layer as a W3C AnnotationCollection.
99+ * @param {boolean } isLD - If true, returns JSON-LD format with @context and type. If false, returns a simple object.
100+ * @returns {Promise<Object> } The Layer as JSON.
101+ */
102+ async asJSON ( isLD ) {
103+ if ( ! this . #hydrated && this . id ?. startsWith ?. ( process . env . RERUMIDPREFIX ) ) {
104+ await this . #loadAnnotationCollectionDataFromRerum( )
105+ }
106+ let result
107+ if ( isLD ) {
108+ result = {
109+ '@context' : 'http://iiif.io/api/presentation/3/context.json' ,
110+ id : this . id ,
111+ type : 'AnnotationCollection' ,
112+ label : { "none" : [ this . label ] } ,
113+ total : this . pages . length ,
114+ first : this . pages . at ( 0 ) ?. id ,
115+ last : this . pages . at ( - 1 ) ?. id
116+ }
117+ if ( this . creator ) result . creator = this . creator
118+ }
119+ else {
120+ result = {
121+ id : this . id ,
122+ label : this . label ,
123+ pages : this . pages
124+ }
125+ }
126+ return result
127+ }
128+
89129 // Private Methods
90130 #setRerumId( ) {
91131 if ( ! this . id . startsWith ( process . env . RERUMIDPREFIX ) ) {
@@ -94,6 +134,45 @@ export default class Layer {
94134 return this
95135 }
96136
137+ /**
138+ * Resolve the RERUM URI of the Layer and sync Layer properties with the AnnotationCollection properties.
139+ * The RERUM data will take preference and overwrite any properties that are already set.
140+ * Only RERUM URIs are supported.
141+ */
142+ async #loadAnnotationCollectionDataFromRerum( ) {
143+ if ( this . id . startsWith ?. ( process . env . RERUMIDPREFIX ) ) {
144+ const rawLayerData = await fetch ( this . id ) . then ( async ( resp ) => {
145+ if ( resp . ok ) return resp . json ( )
146+ let rerumErrorMessage
147+ try {
148+ rerumErrorMessage = `${ resp . status ?? 500 } : ${ this . id } - ${ await resp . text ( ) } `
149+ } catch ( e ) {
150+ rerumErrorMessage = `500: ${ this . id } - A RERUM error occurred`
151+ }
152+ const err = new Error ( rerumErrorMessage )
153+ err . status = 502
154+ throw err
155+ } )
156+ . catch ( err => {
157+ if ( err . status === 502 ) throw err
158+ const genericRerumNetworkError = new Error ( `500: ${ this . id } - A RERUM error occurred` )
159+ genericRerumNetworkError . status = 502
160+ throw genericRerumNetworkError
161+ } )
162+ if ( ! ( rawLayerData . id || rawLayerData [ "@id" ] ) ) {
163+ const genericRerumNetworkError = new Error ( `500: ${ this . id } - A RERUM error occurred` )
164+ genericRerumNetworkError . status = 502
165+ throw genericRerumNetworkError
166+ }
167+ this . #tinyAction = 'update'
168+ this . id = rawLayerData . id ?? rawLayerData [ "@id" ] ?? this . id
169+ if ( rawLayerData . label ) this . label = ProjectFactory . getLabelAsString ( rawLayerData . label )
170+ if ( rawLayerData . creator ) this . creator = rawLayerData . creator
171+ this . #hydrated = true
172+ }
173+ return this
174+ }
175+
97176 #formatCollectionForProject( ) {
98177 return {
99178 id : this . id ,
@@ -108,40 +187,53 @@ export default class Layer {
108187
109188 async #saveCollectionToRerum( ) {
110189 const layerAsCollection = {
111- "@context" : "http://www.w3.org/ns/anno.jsonld " ,
190+ "@context" : "http://iiif.io/api/presentation/3/context.json " ,
112191 id : this . id ,
113192 type : "AnnotationCollection" ,
114193 label : { "none" : [ this . label ] } ,
115194 creator : await fetchUserAgent ( this . creator ) ,
116195 total : this . pages . length ,
117- first : this . pages . at ( 0 ) . id ,
118- last : this . pages . at ( - 1 ) . id
196+ first : this . pages . at ( 0 ) ? .id ,
197+ last : this . pages . at ( - 1 ) ? .id
119198 }
120199
121200 if ( this . #tinyAction === 'create' ) {
122- await databaseTiny . save ( layerAsCollection ) . catch ( err => {
201+ await databaseTiny . save ( layerAsCollection )
202+ . catch ( err => {
123203 console . error ( err , layerAsCollection )
124204 throw new Error ( `Failed to save Layer to RERUM: ${ err . message } ` )
125205 } )
126206 this . #tinyAction = 'update'
207+ this . #hydrated = true
127208 return this
128209 }
129210
130- const existingLayer = await fetch ( this . id ) . then ( res => res . json ( ) )
131- if ( ! existingLayer ) {
132- throw new Error ( `Layer not found in RERUM: ${ this . id } ` )
133- }
134- const updatedLayer = { ...existingLayer , ...layerAsCollection }
135-
136- // Handle optimistic locking version if available
137- try {
138- await databaseTiny . overwrite ( updatedLayer )
139- return this
140- } catch ( err ) {
141- if ( err . status === 409 ) {
142- throw handleVersionConflict ( null , err )
211+ const existingLayer = await fetch ( this . id ) . then ( async ( resp ) => {
212+ if ( resp . ok ) return resp . json ( )
213+ let rerumErrorMessage
214+ try {
215+ rerumErrorMessage = `${ resp . status ?? 500 } : ${ this . id } - ${ await resp . text ( ) } `
216+ } catch ( e ) {
217+ rerumErrorMessage = `500: ${ this . id } - A RERUM error occurred`
143218 }
219+ const err = new Error ( rerumErrorMessage )
220+ err . status = 502
144221 throw err
222+ } )
223+ . catch ( err => {
224+ if ( err . status === 502 ) throw err
225+ const genericRerumNetworkError = new Error ( `500: ${ this . id } - A RERUM error occurred` )
226+ genericRerumNetworkError . status = 502
227+ throw genericRerumNetworkError
228+ } )
229+ if ( ! ( existingLayer ?. id || existingLayer ?. [ "@id" ] ) ) {
230+ const genericRerumNetworkError = new Error ( `500: ${ this . id } - A RERUM error occurred` )
231+ genericRerumNetworkError . status = 502
232+ throw genericRerumNetworkError
145233 }
234+ const updatedLayer = { ...existingLayer , ...layerAsCollection }
235+ await databaseTiny . overwrite ( updatedLayer )
236+ this . #hydrated = true
237+ return this
146238 }
147239}
0 commit comments