1- const HDSModelItemsDefs = require ( './HDSModel-ItemsDefs' ) ;
2- const HDSModelStreams = require ( './HDSModel-Streams' ) ;
1+ const { deepFreeze } = require ( './utils' ) ;
2+
3+ const LAZILY_LOADED = {
4+ streams : require ( './HDSModel-Streams' ) ,
5+ authorizations : require ( './HDSModel-Authorizations' ) ,
6+ itemsDefs : require ( './HDSModel-ItemsDefs' )
7+ } ;
8+
9+ /**
10+ * @class {HDSModel}
11+ * @property {object } modelData - Raw ModelData
12+ * @property {HDSModelItemsDefs } itemsDefs
13+ * @property {HDSModelStreams } streams
14+ * @property {HDSModelAuthorizations } authorizations
15+ */
316class HDSModel {
417 /**
5- * JSON definition file
18+ * JSON definition file URL.
619 * Should come from service/info assets.hds-model
720 * @type {string }
821 */
922 #modelUrl;
1023
11- /**
12- * Content on model definitions
13- * @type {object }
14- */
24+ /** @type {object } RAW content of model definitions */
1525 #modelData;
1626
1727 /**
18- * @type {HDSModelItemsDefs }
28+ * @private
29+ * Map of properties loaded "on demand"
1930 */
20- #modelItemsDefs ;
31+ laziliyLoadedMap ;
2132
2233 /**
23- * @type {HDSModelStreams }
24- */
25- #modelStreams;
26-
27- /**
28- * JSON definition file
29- * Should come from service/info assets.hds-model
30- * @type {string }
34+ * @param {string } modelUrl - JSON definition file URL. Should come from service/info assets.hds-model
3135 */
3236 constructor ( modelUrl ) {
3337 this . #modelUrl = modelUrl ;
38+ this . laziliyLoadedMap = { } ;
3439 }
3540
3641 /**
@@ -45,162 +50,25 @@ class HDSModel {
4550 for ( const [ key , item ] of Object . entries ( this . #modelData. items ) ) {
4651 item . key = key ;
4752 }
48-
49- deepFreeze ( this . #modelData) ; // make sure it cannot be modified
53+ // make sure it cannot be modified
54+ deepFreeze ( this . #modelData) ;
5055 }
5156
52- /**
53- * RAW model data
54- */
57+ /** RAW model data */
5558 get modelData ( ) {
5659 if ( ! this . #modelData) throw new Error ( 'Model not loaded call `await model.load()` first.' ) ;
5760 return this . #modelData;
5861 }
62+ }
5963
60- /**
61- * @type HDSModelItemsDefs
62- */
63- get itemsDefs ( ) {
64- if ( ! this . #modelItemsDefs) this . #modelItemsDefs = new HDSModelItemsDefs ( this ) ;
65- return this . #modelItemsDefs;
66- }
67-
68- /**
69- * @type HDSModelStreams
70- */
71- get streams ( ) {
72- if ( ! this . #modelStreams) this . #modelStreams = new HDSModelStreams ( this ) ;
73- return this . #modelStreams;
74- }
75-
76- // --------- authorizations builder ------ //
77-
78- /**
79- * @typedef {Object } AuthorizationRequestItem
80- * @property {string } streamId
81- * @property {string } level
82- * @property {string } defaultName
83- */
84-
85- /**
86- * Get minimal Authorization set for itemKeys
87- * /!\ Does not handle requests with streamId = "*"
88- * @param {Array<itemKeys> } itemKeys
89- * @param {Object } [options]
90- * @param {string } [options.defaultLevel] (default = write) one of 'read', 'write', 'contribute', 'writeOnly'
91- * @param {boolean } [options.includeDefaultName] (default = true) defaultNames are needed for permission requests but not for access creation
92- * @param {Array<AuthorizationRequestItem> } [options.preRequest]
93- * @return {Array<AuthorizationRequestItem> }
94- */
95- authorizationForItemKeys ( itemKeys , options = { } ) {
96- const opts = {
97- defaultLevel : 'read' ,
98- preRequest : [ ] ,
99- includeDefaultName : true
100- } ;
101- Object . assign ( opts , options ) ;
102- const streamsRequested = { } ;
103- for ( const pre of opts . preRequest ) {
104- if ( ! pre . streamId ) throw new Error ( `Missing streamId in options.preRequest item: ${ JSON . stringify ( pre ) } ` ) ;
105- // complete pre with defaultName if missing
106- if ( opts . includeDefaultName && ! pre . defaultName ) {
107- // try to get it from streams Data
108- const stream = this . streams . getDataById ( pre . streamId , false ) ;
109- if ( stream ) {
110- pre . defaultName = stream . name ;
111- } else {
112- throw new Error ( `No "defaultName" in options.preRequest item: ${ JSON . stringify ( pre ) } and cannot find matching streams in default list` ) ;
113- }
114- }
115- // check there is no defaultName if not required
116- if ( ! opts . includeDefaultName ) {
117- if ( pre . defaultName ) throw new Error ( `Do not include defaultName when not included explicitely on ${ JSON . stringify ( pre ) } ` ) ;
118- }
119- // add default level
120- if ( ! pre . level ) {
121- pre . level = opts . defaultLevel ;
122- }
123- streamsRequested [ pre . streamId ] = pre ;
124- }
125- // add streamId not already in
126- for ( const itemKey of itemKeys ) {
127- const itemDef = this . itemsDefs . forKey ( itemKey ) ;
128- const streamId = itemDef . data . streamId ;
129- if ( ! streamsRequested [ streamId ] ) { // new streamId
130- const auth = { streamId, level : opts . defaultLevel } ;
131- if ( opts . includeDefaultName ) {
132- const stream = this . streams . getDataById ( streamId ) ;
133- auth . defaultName = stream . name ;
134- }
135- streamsRequested [ streamId ] = auth ;
136- } else { // existing just adapt level
137- streamsRequested [ streamId ] . level = mixAuthorizationLevels ( streamsRequested [ streamId ] . level , opts . defaultLevel ) ;
138- }
139- }
140- // remove all permissions with a parent having identical or higher level
141- for ( const auth of Object . values ( streamsRequested ) ) {
142- const parents = this . streams . getParentsIds ( auth . streamId , false ) ;
143- for ( const parent of parents ) {
144- const found = streamsRequested [ parent ] ;
145- if ( found && authorizationOverride ( found . level , auth . level ) ) {
146- // delete entry
147- delete streamsRequested [ auth . streamId ] ;
148- // break loop
149- continue ;
150- }
151- }
64+ // add properties to be lazily loaded
65+ for ( const [ prop , Obj ] of Object . entries ( LAZILY_LOADED ) ) {
66+ Object . defineProperty ( HDSModel . prototype , prop , {
67+ get : function ( ) {
68+ if ( ! this . laziliyLoadedMap [ prop ] ) this . laziliyLoadedMap [ prop ] = new Obj ( this ) ;
69+ return this . laziliyLoadedMap [ prop ] ;
15270 }
153- return Object . values ( streamsRequested ) ;
154- }
71+ } ) ;
15572}
15673
15774module . exports = HDSModel ;
158-
159- /**
160- * Authorization level1 (parent) does override level2
161- * Return "true" if identical or level1 == "manage"
162- */
163- function authorizationOverride ( level1 , level2 ) {
164- if ( level1 === level2 ) return true ;
165- if ( level1 === 'manage' ) return true ;
166- if ( level1 === 'contribute' && level2 !== 'manage' ) return true ;
167- return false ;
168- }
169-
170- /**
171- * Given two authorization level, give the resulting one
172- * @param {string } level1
173- * @param {string } level1
174- * @returns {string } level
175- */
176- function mixAuthorizationLevels ( level1 , level2 ) {
177- if ( level1 === level2 ) return level1 ;
178- // sort level in orders [ 'contribute', 'manage', 'read', 'writeOnly' ]
179- const levels = [ level1 , level2 ] . sort ( ) ;
180- if ( levels . includes ( 'manage' ) ) return 'manage' ; // any & manage
181- if ( levels [ 0 ] === 'contribute' ) return 'contribute' ; // read ore writeOnly & contribute
182- if ( levels [ 1 ] === 'writeOnly' ) return 'contribute' ; // mix read & writeOnly
183- /* c8 ignore next */ // error if there .. 'read' & 'read' should have already be found
184- throw new Error ( `Invalid level found level1: ${ level1 } , level2 ${ level2 } ` ) ;
185- }
186-
187- /**
188- * Recursively make immutable an object
189- * @param {* } object
190- * @returns {* }
191- */
192- function deepFreeze ( object ) {
193- // Retrieve the property names defined on object
194- const propNames = Reflect . ownKeys ( object ) ;
195-
196- // Freeze properties before freezing self
197- for ( const name of propNames ) {
198- const value = object [ name ] ;
199-
200- if ( ( value && typeof value === 'object' ) || typeof value === 'function' ) {
201- deepFreeze ( value ) ;
202- }
203- }
204-
205- return Object . freeze ( object ) ;
206- }
0 commit comments