@cepharum/concrete-db 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/shaper.mjs +52 -50
  2. package/package.json +11 -11
package/lib/shaper.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  // noinspection ExceptionCaughtLocallyJS
2
+ /* eslint-disable no-await-in-loop */
2
3
 
3
4
  import EventEmitter from "events";
4
5
 
@@ -50,16 +51,17 @@ export class Shaper extends EventEmitter {
50
51
  * @param {Shape[]} localShapes local shapes of folders records have been collected from
51
52
  * @returns {Database} resulting database records
52
53
  */
53
- transform( recordsCollection, localShapes ) {
54
+ async transform( recordsCollection, localShapes ) {
54
55
  if ( recordsCollection.size > 0 && !localShapes?.length ) {
55
56
  throw new Error( "invalid use of Shaper: missing list of local shapes per folder records have been collected from" );
56
57
  }
57
58
 
58
59
  const mergedLocalShapes = merge( {}, DefaultShape, ...localShapes );
59
- const models = this.handleContributions( this.groupByModel( recordsCollection ), mergedLocalShapes );
60
+ const grouped = await this.groupByModel( recordsCollection );
61
+ const models = await this.handleContributions( grouped, mergedLocalShapes );
60
62
  const endpoints = {};
61
63
 
62
- this.populateModels( endpoints, models, mergedLocalShapes );
64
+ await this.populateModels( endpoints, models, mergedLocalShapes );
63
65
 
64
66
  return { prefix: this.prefix, endpoints };
65
67
  }
@@ -70,7 +72,7 @@ export class Shaper extends EventEmitter {
70
72
  * @param {Map<string,CollectedRecord[]>} collectedRecordsByName collection of records to transform
71
73
  * @returns {Object<string,Object<string, ModelItem>>} provided records grouped by model and unique item ID
72
74
  */
73
- groupByModel( collectedRecordsByName ) {
75
+ async groupByModel( collectedRecordsByName ) {
74
76
  const globalItemsPerModel = {};
75
77
  let index = 0;
76
78
 
@@ -83,7 +85,7 @@ export class Shaper extends EventEmitter {
83
85
  process.stderr.write( `\r[${++index}/${collectedRecordsByName.size}]` );
84
86
  }
85
87
 
86
- this.compileRecords( globalItemsPerModel, records );
88
+ await this.compileRecords( globalItemsPerModel, records );
87
89
  }
88
90
 
89
91
  return globalItemsPerModel;
@@ -97,20 +99,20 @@ export class Shaper extends EventEmitter {
97
99
  * @param {Shape} mergedLocalShapes local shapes of source folders merged in order of collecting records
98
100
  * @returns {Object<string,Object<string, CollectedRecord[]>>} collections of items per model as provided
99
101
  */
100
- handleContributions( globalItemsPerModel, mergedLocalShapes ) {
102
+ async handleContributions( globalItemsPerModel, mergedLocalShapes ) {
101
103
  const postProcessingQueue = new Set();
102
104
 
103
105
  if ( this.options.verbose ) {
104
106
  console.error( `\nprocessing contributions ...` );
105
107
  }
106
108
 
107
- this.processContributions( globalItemsPerModel, postProcessingQueue, mergedLocalShapes );
109
+ await this.processContributions( globalItemsPerModel, postProcessingQueue, mergedLocalShapes );
108
110
 
109
111
  if ( this.options.verbose ) {
110
112
  console.error( `post-processing ${postProcessingQueue.size} contributions` );
111
113
  }
112
114
 
113
- this.recompileContributionTargets( globalItemsPerModel, postProcessingQueue );
115
+ await this.recompileContributionTargets( globalItemsPerModel, postProcessingQueue );
114
116
 
115
117
  return globalItemsPerModel;
116
118
  }
@@ -123,11 +125,11 @@ export class Shaper extends EventEmitter {
123
125
  * @param {CollectedRecord[]} records list of records collected from a source
124
126
  * @returns {Object<string,Object<string,ModelItem>>} compiled items grouped by model, addressable by either item's ID
125
127
  */
126
- compileRecords( itemsPerModel, records ) {
128
+ async compileRecords( itemsPerModel, records ) {
127
129
  const shapesPerModelCache = {};
128
130
 
129
131
  for ( let read = 0, numRecords = records.length; read < numRecords; read++ ) {
130
- const item = this.compileModelItemFromRecord( shapesPerModelCache, itemsPerModel, records[read] );
132
+ const item = await this.compileModelItemFromRecord( shapesPerModelCache, itemsPerModel, records[read] );
131
133
  const modelPool = itemsPerModel[item.$model];
132
134
 
133
135
  if ( item.$variantId == null ) {
@@ -154,9 +156,9 @@ export class Shaper extends EventEmitter {
154
156
  * @param {Object<string,ShapeModel>} shapesPerModelCache caches per-model shapes compiled before
155
157
  * @param {Object<string,Object<string,Object>>} itemsPerModel collection of previously compiled items per model
156
158
  * @param {CollectedRecord} record augmented data collected from a source to be compiled
157
- * @returns {object} instance of model with hidden meta information injected
159
+ * @returns {Promise<ModelItem>} instance of model with hidden meta information injected
158
160
  */
159
- compileModelItemFromRecord( shapesPerModelCache, itemsPerModel, record ) {
161
+ async compileModelItemFromRecord( shapesPerModelCache, itemsPerModel, record ) {
160
162
  const { segments, shape, data } = record;
161
163
 
162
164
  const metaProperties = {
@@ -165,9 +167,9 @@ export class Shaper extends EventEmitter {
165
167
 
166
168
  const augmentedData = augmentDataSpace( data, metaProperties );
167
169
 
168
- metaProperties.$model = this.detectModel( augmentedData, shape );
170
+ metaProperties.$model = await this.detectModel( augmentedData, shape );
169
171
  metaProperties.$shape = this.getShapeOfModel( shapesPerModelCache, shape, metaProperties.$model );
170
- metaProperties.$id = this.identifyRecord( augmentedData, metaProperties.$shape );
172
+ metaProperties.$id = await this.identifyRecord( augmentedData, metaProperties.$shape );
171
173
 
172
174
  if ( !itemsPerModel.hasOwnProperty( metaProperties.$model ) ) {
173
175
  // eslint-disable-next-line no-param-reassign
@@ -177,7 +179,7 @@ export class Shaper extends EventEmitter {
177
179
  const model = itemsPerModel[metaProperties.$model];
178
180
 
179
181
  const { properties, defaults } = metaProperties.$shape;
180
- const item = this.compileItem( augmentedData, properties, model[metaProperties.$id] ? {} : defaults );
182
+ const item = await this.compileItem( augmentedData, properties, model[metaProperties.$id] ? {} : defaults );
181
183
 
182
184
  Object.defineProperties( item, {
183
185
  $segments: { value: segments },
@@ -185,7 +187,7 @@ export class Shaper extends EventEmitter {
185
187
  $shape: { value: metaProperties.$shape, configurable: true },
186
188
  $model: { value: metaProperties.$model },
187
189
  $id: { value: metaProperties.$id },
188
- $variantId: { value: this.computeVariantIdOfItem( metaProperties.$shape.properties.$variant, augmentedData ) },
190
+ $variantId: { value: await this.computeVariantIdOfItem( metaProperties.$shape.properties.$variant, augmentedData ) },
189
191
  } );
190
192
 
191
193
  return item;
@@ -200,9 +202,9 @@ export class Shaper extends EventEmitter {
200
202
  * @param {ModelItem} item previously compiled item to re-compile
201
203
  * @returns {ModelItem} re-compiled item
202
204
  */
203
- recompileItem( item ) {
205
+ async recompileItem( item ) {
204
206
  const data = augmentDataSpace( merge( {}, item.$original, item ), { ...item, $post: true } );
205
- const updatedItem = this.compileItem( data, item.$shape.properties, {} );
207
+ const updatedItem = await this.compileItem( data, item.$shape.properties, {} );
206
208
 
207
209
  Object.defineProperties( updatedItem, {
208
210
  $segments: { value: item.$segments },
@@ -221,9 +223,9 @@ export class Shaper extends EventEmitter {
221
223
  *
222
224
  * @param {string?} term term expressing computation of variant ID
223
225
  * @param {ModelItem} item an item to get variant ID for
224
- * @returns {null|any} computed variant ID of provided item, nullish if item is not a variant
226
+ * @returns {Promise<null|any>} computed variant ID of provided item, nullish if item is not a variant
225
227
  */
226
- computeVariantIdOfItem( term, item ) {
228
+ async computeVariantIdOfItem( term, item ) {
227
229
  if ( term == null ) {
228
230
  return null;
229
231
  }
@@ -232,7 +234,7 @@ export class Shaper extends EventEmitter {
232
234
  const data = augmentDataSpace( merge( {}, item.$original, item ), item );
233
235
 
234
236
  try {
235
- const value = Compiler.compile( String( term ), this.termFunctions, this.termsCache )( data );
237
+ const value = await Compiler.compile( String( term ), this.termFunctions, this.termsCache )( data );
236
238
 
237
239
  // support processed term requesting to fail computation of variant ID
238
240
  if ( value instanceof Error ) {
@@ -354,7 +356,7 @@ export class Shaper extends EventEmitter {
354
356
  * @param {Shape} mergedLocalShapes local shapes of source folders merged in order of collecting records
355
357
  * @returns {void}
356
358
  */
357
- processContributions( itemsPerModel, contributedItems, mergedLocalShapes ) {
359
+ async processContributions( itemsPerModel, contributedItems, mergedLocalShapes ) {
358
360
  const contributingModels = this.listContributingModels( itemsPerModel );
359
361
  const shapePerModelCache = {};
360
362
 
@@ -427,7 +429,7 @@ export class Shaper extends EventEmitter {
427
429
  };
428
430
 
429
431
  const augmentedData = augmentDataSpace( item, contributionMeta );
430
- const targetId = this.computeId( contributionTerms.$id, augmentedData );
432
+ const targetId = await this.computeId( contributionTerms.$id, augmentedData );
431
433
 
432
434
  if ( !isOptionalContribution && ( !targetId || ( Array.isArray( targetId ) && !targetId.length ) ) ) {
433
435
  console.warn( `ignoring contribution of record #${item.$id} in model ${item.$model} to model ${targetModelName} due to lack of target ID` ); // eslint-disable-line max-len
@@ -446,7 +448,7 @@ export class Shaper extends EventEmitter {
446
448
 
447
449
 
448
450
  // try computing variant ID based on contributing item
449
- const variantId = this.computeVariantIdOfItem( contributionTerms.$variant, {
451
+ const variantId = await this.computeVariantIdOfItem( contributionTerms.$variant, {
450
452
  ...item,
451
453
  $segments: item.$segments,
452
454
  $original: item.$original,
@@ -499,7 +501,7 @@ export class Shaper extends EventEmitter {
499
501
 
500
502
  // compile actual properties of contribution
501
503
  const contributionDefaults = targetItemExists || variantId != null ? {} : targetModelShape.defaults;
502
- const contribution = this.compileItem( augmentedData, contributionTerms, contributionDefaults );
504
+ const contribution = await this.compileItem( augmentedData, contributionTerms, contributionDefaults );
503
505
 
504
506
 
505
507
  // merge this item's contribution with existing
@@ -614,7 +616,7 @@ export class Shaper extends EventEmitter {
614
616
  * @param {Set<string>} contributions lists IDs of items or their variants that have been contributed to
615
617
  * @returns {Object<string,Object<string,ModelItem>>} pool of items per model as provided
616
618
  */
617
- recompileContributionTargets( itemsPerModel, contributions ) {
619
+ async recompileContributionTargets( itemsPerModel, contributions ) {
618
620
  for ( const track of contributions.values() ) {
619
621
  const [ model, id, variantId ] = JSON.parse( track );
620
622
 
@@ -633,7 +635,7 @@ export class Shaper extends EventEmitter {
633
635
  const item = modelPool[id];
634
636
 
635
637
  if ( variantId == null ) {
636
- modelPool[id] = this.recompileItem( item );
638
+ modelPool[id] = await this.recompileItem( item );
637
639
  } else {
638
640
  if ( !item.$variants ) {
639
641
  console.warn( `inconsistency: missing variants of item ${id} of model ${model} though contributions have been tracked` );
@@ -647,7 +649,7 @@ export class Shaper extends EventEmitter {
647
649
  continue;
648
650
  }
649
651
 
650
- variants[variantId] = this.recompileItem( variants[variantId] );
652
+ variants[variantId] = await this.recompileItem( variants[variantId] );
651
653
  }
652
654
  }
653
655
 
@@ -659,16 +661,16 @@ export class Shaper extends EventEmitter {
659
661
  *
660
662
  * @param {object} data source of record
661
663
  * @param {Shape} shape shape of database affecting record
662
- * @returns {string} name of model to use
664
+ * @returns {Promise<string>} name of model to use
663
665
  */
664
- detectModel( data, shape ) {
666
+ async detectModel( data, shape ) {
665
667
  const selector = shape?.common?.properties?.$model;
666
668
 
667
669
  if ( !selector || typeof selector !== "string" ) {
668
670
  throw new Error( "missing or invalid definition of common property $model in shape" );
669
671
  }
670
672
 
671
- const name = Compiler.compile( selector, this.termFunctions, this.termsCache )( data );
673
+ const name = await Compiler.compile( selector, this.termFunctions, this.termsCache )( data );
672
674
 
673
675
  // support processed term requesting to fail model detection
674
676
  if ( name instanceof Error ) {
@@ -687,10 +689,10 @@ export class Shaper extends EventEmitter {
687
689
  *
688
690
  * @param {object} data source of record
689
691
  * @param {ShapeModel} modelShape shape of particular model
690
- * @returns {string} ID of record
692
+ * @returns {Promise<string>} ID of record
691
693
  */
692
- identifyRecord( data, modelShape ) {
693
- const id = this.computeId( modelShape?.properties?.$id, data );
694
+ async identifyRecord( data, modelShape ) {
695
+ const id = await this.computeId( modelShape?.properties?.$id, data );
694
696
 
695
697
  if ( !id ) {
696
698
  throw new Error( `detecting ID of item related to record ${data.$segments.join( "/" )} failed` );
@@ -706,9 +708,9 @@ export class Shaper extends EventEmitter {
706
708
  * @param {ShapeProperties} properties definition of resulting item's properties
707
709
  * @param {ShapePropertyDefaults} defaults definition of default values for resulting item's properties
708
710
  * @param {string[]} accept list names of properties to accept even when starting with a dollar sign
709
- * @returns {object} compiled item
711
+ * @returns {Promise<Partial<ModelItem>>} compiled item
710
712
  */
711
- compileItem( data, properties = {}, defaults = {}, accept = [] ) {
713
+ async compileItem( data, properties = {}, defaults = {}, accept = [] ) {
712
714
  const item = {};
713
715
 
714
716
  for ( const property of Object.keys( properties ) ) {
@@ -728,7 +730,7 @@ export class Shaper extends EventEmitter {
728
730
  let value;
729
731
 
730
732
  try {
731
- value = Compiler.compile( term, this.termFunctions, this.termsCache )( data );
733
+ value = await Compiler.compile( term, this.termFunctions, this.termsCache )( data );
732
734
 
733
735
  // support processed term requesting to fail computation of property's value
734
736
  if ( value instanceof Error ) {
@@ -745,7 +747,7 @@ export class Shaper extends EventEmitter {
745
747
 
746
748
  try {
747
749
  const code = trimmed.startsWith( "=" ) ? trimmed.slice( 1 ) : undefined;
748
- value = code ? Compiler.compile( code, this.termFunctions, this.termsCache )( data ) : defaultValue;
750
+ value = code ? await Compiler.compile( code, this.termFunctions, this.termsCache )( data ) : defaultValue;
749
751
 
750
752
  // support processed term requesting to fail computation of default value
751
753
  if ( value instanceof Error ) {
@@ -783,15 +785,15 @@ export class Shaper extends EventEmitter {
783
785
  *
784
786
  * @param {string} term term to process
785
787
  * @param {object} data data space available to term processing
786
- * @returns {any} processed term's result
788
+ * @returns {Promise<any>} processed term's result
787
789
  */
788
- computeId( term, data ) {
790
+ async computeId( term, data ) {
789
791
  if ( !term || typeof term !== "string" ) {
790
792
  throw new Error( `missing or invalid term for computing ID of ${data.$segments.join( "/" )}` );
791
793
  }
792
794
 
793
795
  try {
794
- const value = Compiler.compile( term, this.termFunctions, this.termsCache )( data );
796
+ const value = await Compiler.compile( term, this.termFunctions, this.termsCache )( data );
795
797
 
796
798
  // support processed term requesting to fail computation of ID
797
799
  if ( value instanceof Error ) {
@@ -812,7 +814,7 @@ export class Shaper extends EventEmitter {
812
814
  * @param {Shape} mergedLocalShapes local shapes of source folders merged in order of collecting records
813
815
  * @returns {DatabaseEndpoints} map of routes into data sets to expose eventually
814
816
  */
815
- populateModels( endpoints, models, mergedLocalShapes ) {
817
+ async populateModels( endpoints, models, mergedLocalShapes ) {
816
818
  const modelNames = Object.keys( models );
817
819
  let index = 0;
818
820
 
@@ -837,7 +839,7 @@ export class Shaper extends EventEmitter {
837
839
  endpoints[normalized] = model[itemId]; // eslint-disable-line no-param-reassign
838
840
  }
839
841
 
840
- this.populateModelCollections( endpoints, models, modelName, mergedLocalShapes );
842
+ await this.populateModelCollections( endpoints, models, modelName, mergedLocalShapes );
841
843
  }
842
844
 
843
845
  return endpoints;
@@ -853,7 +855,7 @@ export class Shaper extends EventEmitter {
853
855
  * @param {Shape} mergedLocalShapes local shapes of source folders merged in order of collecting records
854
856
  * @returns {DatabaseEndpoints} map of routes into data sets to expose eventually
855
857
  */
856
- populateModelCollections( endpoints, models, modelName, mergedLocalShapes ) {
858
+ async populateModelCollections( endpoints, models, modelName, mergedLocalShapes ) {
857
859
  const finalModelShape = merge( {}, mergedLocalShapes.common, mergedLocalShapes.models[modelName] );
858
860
  const collections = finalModelShape.collections || {};
859
861
  const cache = {};
@@ -897,7 +899,7 @@ export class Shaper extends EventEmitter {
897
899
  collection = cache[definition.reference];
898
900
  isKeyed = collection.$isKeyed;
899
901
  } else {
900
- collection = this.compileCollection( models, modelName, definition, normalized );
902
+ collection = await this.compileCollection( models, modelName, definition, normalized );
901
903
  isKeyed = definition.key && typeof definition.key === "string";
902
904
 
903
905
  Object.defineProperties( collection, {
@@ -968,7 +970,7 @@ export class Shaper extends EventEmitter {
968
970
  const items = collection.items;
969
971
 
970
972
  for ( let offset = 0; offset < items.length; offset += limit ) {
971
- const normalized = normalizePathname( prefix + route.replace( ptnOffset, offset ) );
973
+ const normalized = normalizePathname( prefix + route.replace( ptnOffset, String( offset ) ) );
972
974
  endpoints[normalized] = { count: items.length, items: items.slice( offset, offset + limit ) }; // eslint-disable-line no-param-reassign
973
975
  }
974
976
  } else if ( isKeyed ) {
@@ -1036,7 +1038,7 @@ export class Shaper extends EventEmitter {
1036
1038
  * @param {string} route route for resulting collection
1037
1039
  * @returns {DatabaseCollection} compiled collection
1038
1040
  */
1039
- compileCollection( models, modelName, definition, route ) {
1041
+ async compileCollection( models, modelName, definition, route ) {
1040
1042
  const model = models[modelName];
1041
1043
  const { key: $key, filter: $filter, properties: $properties } = definition;
1042
1044
  const isKeyed = $key && typeof $key === "string";
@@ -1107,9 +1109,9 @@ export class Shaper extends EventEmitter {
1107
1109
  context.$id = id;
1108
1110
 
1109
1111
  // ignore this item or prepare its spot in resulting collection?
1110
- if ( !filterFn || filterFn( data ) ) {
1112
+ if ( !filterFn || await filterFn( data ) ) {
1111
1113
  if ( isKeyed ) {
1112
- const key = keyFn( data );
1114
+ const key = await keyFn( data );
1113
1115
  if ( key != null ) {
1114
1116
  const _key = String( key );
1115
1117
 
@@ -1135,7 +1137,7 @@ export class Shaper extends EventEmitter {
1135
1137
  if ( propertiesFn ) {
1136
1138
  // compile custom representation of item in collection
1137
1139
  for ( const fn of propertiesFn ) {
1138
- extracted[fn.target] = fn( data );
1140
+ extracted[fn.target] = await fn( data );
1139
1141
  }
1140
1142
  } else {
1141
1143
  // expose all properties of item in collection, too
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cepharum/concrete-db",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "a read-only web database generator",
5
5
  "main": "lib/collector.mjs",
6
6
  "types": "concrete-db.d.ts",
@@ -20,22 +20,22 @@
20
20
  "author": "cepharum GmbH",
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
- "ajv": "^8.11.0",
23
+ "ajv": "^8.12.0",
24
24
  "file-essentials": "^0.1.2",
25
25
  "lodash.merge": "^4.6.2",
26
- "minimist": "^1.2.6",
26
+ "minimist": "^1.2.8",
27
27
  "promise-essentials": "^0.2.0",
28
- "simple-terms": "^0.4.0",
29
- "yaml": "^2.1.0"
28
+ "simple-terms": "^0.4.1",
29
+ "yaml": "^2.2.2"
30
30
  },
31
31
  "devDependencies": {
32
- "c8": "^7.11.3",
33
- "eslint": "^8.16.0",
34
- "eslint-config-cepharum": "^1.0.12",
35
- "eslint-plugin-promise": "^6.0.0",
36
- "mocha": "^10.0.0",
32
+ "c8": "^7.13.0",
33
+ "eslint": "^8.40.0",
34
+ "eslint-config-cepharum": "^1.0.13",
35
+ "eslint-plugin-promise": "^6.1.1",
36
+ "mocha": "^10.2.0",
37
37
  "should": "^13.2.3",
38
- "vuepress": "^1.9.7",
38
+ "vuepress": "^1.9.9",
39
39
  "vuepress-plugin-mermaidjs": "^1.9.1"
40
40
  },
41
41
  "engines": {