@balena/pinejs 23.2.4 → 23.2.5-build-reduce-constrained-model-memory-243f046051d320f659c36f6ba28c4121d84e1c9c-1

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.
@@ -7,7 +7,6 @@ import type {
7
7
  Relationship,
8
8
  RelationshipInternalNode,
9
9
  RelationshipLeafNode,
10
- RelationshipMapping,
11
10
  SelectNode,
12
11
  SelectQueryNode,
13
12
  } from '@balena/abstract-sql-compiler';
@@ -769,175 +768,183 @@ const getAlias = (name: string) => {
769
768
  return `permissions${permissionsJSON}`;
770
769
  };
771
770
 
772
- const rewriteRelationship = memoizeWeak(
773
- (
774
- value: Relationship,
775
- name: string,
776
- abstractSqlModel: AbstractSqlModel,
777
- permissionsLookup: PermissionLookup,
778
- vocabulary: string,
779
- odata2AbstractSQL: OData2AbstractSQL,
780
- ) => {
781
- let escapedName = sqlNameToODataName(name);
782
- if (abstractSqlModel.tables[name]) {
783
- escapedName = sqlNameToODataName(abstractSqlModel.tables[name].name);
784
- }
771
+ const rewriteRelationship = (
772
+ value: Relationship,
773
+ name: string,
774
+ abstractSqlModel: AbstractSqlModel,
775
+ permissionsLookup: PermissionLookup,
776
+ vocabulary: string,
777
+ odata2AbstractSQL: OData2AbstractSQL,
778
+ ) => {
779
+ let escapedName = sqlNameToODataName(name);
780
+ if (abstractSqlModel.tables[name]) {
781
+ escapedName = sqlNameToODataName(abstractSqlModel.tables[name].name);
782
+ }
785
783
 
786
- const rewrite = (object: Relationship | RelationshipMapping) => {
787
- if ('$' in object && Array.isArray(object.$)) {
788
- // object is in the form of
789
- // { "$": ["actor", ["actor", "id"]] } or { "$": ["device type"] }
790
- // we are only interested in the first case, since this is a relationship
791
- // to a different resource
792
- const mapping = object.$;
793
- if (
794
- mapping.length === 2 &&
795
- Array.isArray(mapping[1]) &&
796
- mapping[1].length === 2 &&
797
- typeof mapping[1][0] === 'string'
798
- ) {
799
- // now have ensured that mapping looks like ["actor", ["actor", "id"]]
800
- // this relations ship means that:
801
- // mapping[0] is the local field
802
- // mapping[1] is the reference to the other resource, that joins this resource
803
- // mapping[1][0] is the name of the other resource (actor in the example)
804
- // mapping[1][1] is the name of the field on the other resource
805
- //
806
- // this therefore defines that the local field `actor` needs
807
- // to match the `id` of the `actor` resources for the join
808
-
809
- const possibleTargetResourceName = mapping[1][0];
810
-
811
- // Skip this if we already shortcut this connection
812
- if (possibleTargetResourceName.endsWith('$bypass')) {
813
- return;
814
- }
784
+ const rewrite = (object: Relationship) => {
785
+ let ret = object;
786
+ if ('$' in ret && Array.isArray(ret.$)) {
787
+ // object is in the form of
788
+ // { "$": ["actor", ["actor", "id"]] } or { "$": ["device type"] }
789
+ // we are only interested in the first case, since this is a relationship
790
+ // to a different resource
791
+ const mapping = ret.$;
792
+ if (
793
+ mapping.length === 2 &&
794
+ Array.isArray(mapping[1]) &&
795
+ mapping[1].length === 2 &&
796
+ typeof mapping[1][0] === 'string'
797
+ ) {
798
+ // now have ensured that mapping looks like ["actor", ["actor", "id"]]
799
+ // this relations ship means that:
800
+ // mapping[0] is the local field
801
+ // mapping[1] is the reference to the other resource, that joins this resource
802
+ // mapping[1][0] is the name of the other resource (actor in the example)
803
+ // mapping[1][1] is the name of the field on the other resource
804
+ //
805
+ // this therefore defines that the local field `actor` needs
806
+ // to match the `id` of the `actor` resources for the join
807
+
808
+ const possibleTargetResourceName = mapping[1][0];
809
+
810
+ // Skip this if we already shortcut this connection
811
+ if (possibleTargetResourceName.endsWith('$bypass')) {
812
+ return;
813
+ }
814
+
815
+ const targetResourceEscaped = sqlNameToODataName(
816
+ abstractSqlModel.tables[possibleTargetResourceName]?.name ??
817
+ possibleTargetResourceName,
818
+ );
819
+
820
+ // This is either a translated or bypassed resource we don't
821
+ // mess with these
822
+ if (targetResourceEscaped.includes('$')) {
823
+ return;
824
+ }
825
+
826
+ let foundCanAccessLink = false;
815
827
 
816
- const targetResourceEscaped = sqlNameToODataName(
817
- abstractSqlModel.tables[possibleTargetResourceName]?.name ??
818
- possibleTargetResourceName,
828
+ try {
829
+ const odata = memoizedParseOdata(`/${targetResourceEscaped}`);
830
+
831
+ const collapsedPermissionFilters = buildODataPermission(
832
+ permissionsLookup,
833
+ methodPermissions.GET,
834
+ vocabulary,
835
+ targetResourceEscaped,
836
+ odata,
819
837
  );
820
838
 
821
- // This is either a translated or bypassed resource we don't
822
- // mess with these
823
- if (targetResourceEscaped.includes('$')) {
839
+ if (collapsedPermissionFilters == null) {
840
+ // If we have full access already then there's no need to
841
+ // check for/rewrite based on `canAccess`
824
842
  return;
825
843
  }
826
844
 
827
- let foundCanAccessLink = false;
845
+ _.set(
846
+ odata,
847
+ ['tree', 'options', '$filter'],
848
+ collapsedPermissionFilters,
849
+ );
828
850
 
829
- try {
830
- const odata = memoizedParseOdata(`/${targetResourceEscaped}`);
831
-
832
- const collapsedPermissionFilters = buildODataPermission(
833
- permissionsLookup,
834
- methodPermissions.GET,
835
- vocabulary,
836
- targetResourceEscaped,
837
- odata,
838
- );
851
+ const canAccessFunction: ResourceFunction = function (
852
+ property: AnyObject,
853
+ ) {
854
+ // remove method property so that we won't loop back here again at this point
855
+ delete property.method;
839
856
 
840
- if (collapsedPermissionFilters == null) {
841
- // If we have full access already then there's no need to
842
- // check for/rewrite based on `canAccess`
843
- return;
857
+ if (!this.defaultResource) {
858
+ throw new Error(`No resource selected in AST.`);
844
859
  }
845
860
 
846
- _.set(
847
- odata,
848
- ['tree', 'options', '$filter'],
849
- collapsedPermissionFilters,
861
+ const targetResourceAST = this.NavigateResources(
862
+ this.defaultResource,
863
+ property.name,
850
864
  );
851
865
 
852
- const canAccessFunction: ResourceFunction = function (
853
- property: AnyObject,
854
- ) {
855
- // remove method property so that we won't loop back here again at this point
856
- delete property.method;
857
-
858
- if (!this.defaultResource) {
859
- throw new Error(`No resource selected in AST.`);
860
- }
861
-
862
- const targetResourceAST = this.NavigateResources(
863
- this.defaultResource,
864
- property.name,
865
- );
866
-
867
- const targetResourceName = sqlNameToODataName(
868
- targetResourceAST.resource.name,
869
- );
870
- const currentResourceName = sqlNameToODataName(
871
- this.defaultResource.name,
872
- );
866
+ const targetResourceName = sqlNameToODataName(
867
+ targetResourceAST.resource.name,
868
+ );
869
+ const currentResourceName = sqlNameToODataName(
870
+ this.defaultResource.name,
871
+ );
873
872
 
874
- if (
875
- currentResourceName === targetResourceEscaped &&
876
- targetResourceName === escapedName
877
- ) {
878
- foundCanAccessLink = true;
879
- }
880
- // return a true expression to not select the relationship, which might be virtual
881
- return ['Boolean', true];
882
- };
883
-
884
- try {
885
- // We need execute the abstract SQL compiler to traverse
886
- // through the permissions for that resource, using a
887
- // special canAccess callback.
888
- odata2AbstractSQL.match(
889
- odata.tree,
890
- 'GET',
891
- [],
892
- odata.binds.length,
893
- {
894
- canAccess: canAccessFunction,
895
- },
896
- );
897
- } catch (e: any) {
898
- throw new ODataParser.SyntaxError(e);
899
- }
900
- if (foundCanAccessLink) {
901
- // store the resource name as it was with a $bypass
902
- // suffix in this relationship, this means that the
903
- // query generator will use the plain resource instead
904
- // of the filtered resource.
905
- mapping[1][0] = `${possibleTargetResourceName}$bypass`;
906
- }
907
- } catch (e) {
908
- if (e === constrainedPermissionError) {
909
- // ignore
910
- return;
873
+ if (
874
+ currentResourceName === targetResourceEscaped &&
875
+ targetResourceName === escapedName
876
+ ) {
877
+ foundCanAccessLink = true;
911
878
  }
879
+ // return a true expression to not select the relationship, which might be virtual
880
+ return ['Boolean', true];
881
+ };
912
882
 
913
- // TODO: We should investigate in detail why this error
914
- // occurse. It might be able to get rid of this.
915
- if (e instanceof ODataParser.SyntaxError) {
916
- // ignore
917
- return;
918
- }
883
+ try {
884
+ // We need execute the abstract SQL compiler to traverse
885
+ // through the permissions for that resource, using a
886
+ // special canAccess callback.
887
+ odata2AbstractSQL.match(odata.tree, 'GET', [], odata.binds.length, {
888
+ canAccess: canAccessFunction,
889
+ });
890
+ } catch (e: any) {
891
+ throw new ODataParser.SyntaxError(e);
892
+ }
893
+ if (foundCanAccessLink) {
894
+ // store the resource name as it was with a $bypass
895
+ // suffix in this relationship, this means that the
896
+ // query generator will use the plain resource instead
897
+ // of the filtered resource.
898
+ ret = { ...ret };
899
+ const modifiedMapping = _.cloneDeep(mapping);
900
+ modifiedMapping[1]![0] = `${possibleTargetResourceName}$bypass`;
901
+ ret.$ = modifiedMapping;
902
+ }
903
+ } catch (e) {
904
+ if (e === constrainedPermissionError) {
905
+ // ignore
906
+ return;
907
+ }
919
908
 
920
- throw e;
909
+ // TODO: We should investigate in detail why this error
910
+ // occurse. It might be able to get rid of this.
911
+ if (e instanceof ODataParser.SyntaxError) {
912
+ // ignore
913
+ return;
921
914
  }
915
+
916
+ throw e;
922
917
  }
923
918
  }
919
+ }
924
920
 
925
- if (Array.isArray(object) || typeof object === 'object') {
926
- _.forEach(object, (v) => {
927
- // we want to recurse into the relationship path, but
928
- // in case we hit a plain string, we don't need to bother
929
- // checking it. This can happen since plain terms also have
930
- // relationships to sbvr-types.
931
- if (typeof v !== 'string') {
932
- rewrite(v as Relationship | RelationshipMapping);
921
+ if (typeof object === 'object') {
922
+ for (const key of Object.keys(object)) {
923
+ if (key === '$') {
924
+ continue;
925
+ }
926
+ const v = (object as RelationshipInternalNode)[key];
927
+ // we want to recurse into the relationship path, but
928
+ // in case we hit a plain string, we don't need to bother
929
+ // checking it. This can happen since plain terms also have
930
+ // relationships to sbvr-types.
931
+ if (typeof v !== 'string') {
932
+ const changedEntry = rewrite(v);
933
+ if (changedEntry) {
934
+ ret = { ...ret };
935
+ (ret as RelationshipInternalNode)[key] = changedEntry;
933
936
  }
934
- });
937
+ }
935
938
  }
936
- };
939
+ }
937
940
 
938
- rewrite(value);
939
- },
940
- );
941
+ if (ret !== object) {
942
+ return ret;
943
+ }
944
+ };
945
+
946
+ return rewrite(value);
947
+ };
941
948
 
942
949
  const rewriteRelationships = (
943
950
  abstractSqlModel: AbstractSqlModel,
@@ -955,17 +962,20 @@ const rewriteRelationships = (
955
962
  originalAbstractSQLModel,
956
963
  );
957
964
 
958
- const newRelationships = _.cloneDeep(relationships);
959
- _.forOwn(newRelationships, (value, name) => {
960
- rewriteRelationship(
961
- value,
965
+ const newRelationships = { ...relationships };
966
+ for (const name of Object.keys(newRelationships)) {
967
+ const changedEntry = rewriteRelationship(
968
+ newRelationships[name],
962
969
  name,
963
970
  abstractSqlModel,
964
971
  permissionsLookup,
965
972
  vocabulary,
966
973
  odata2AbstractSQL,
967
974
  );
968
- });
975
+ if (changedEntry) {
976
+ newRelationships[name] = changedEntry as RelationshipInternalNode;
977
+ }
978
+ }
969
979
 
970
980
  return newRelationships;
971
981
  };
@@ -985,14 +995,18 @@ const getBoundConstrainedMemoizer = memoizeWeak(
985
995
  /** This is the final translated vocabulary that permissions get written against as that's what we will have to resolve permissions against */
986
996
  finalVocabulary: string,
987
997
  ): AbstractSqlModel => {
988
- const constrainedAbstractSqlModel: Omit<AbstractSqlModel, 'tables'> & {
998
+ const constrainedAbstractSqlModel: Omit<
999
+ AbstractSqlModel,
1000
+ 'tables' | 'relationships'
1001
+ > & {
989
1002
  tables: {
990
1003
  [resourceName: string]: ShallowWritableOnly<AbstractSqlTable>;
991
1004
  };
1005
+ relationships: ShallowWritableOnly<AbstractSqlModel['relationships']>;
992
1006
  } = {
993
1007
  ...abstractSqlModel,
994
1008
  synonyms: _.cloneDeep(abstractSqlModel.synonyms),
995
- relationships: _.cloneDeep(abstractSqlModel.relationships),
1009
+ relationships: {},
996
1010
  tables: {},
997
1011
  };
998
1012
  const baseTables = abstractSqlModel.tables;
@@ -1022,9 +1036,7 @@ const getBoundConstrainedMemoizer = memoizeWeak(
1022
1036
  },
1023
1037
  );
1024
1038
 
1025
- const origRelationships = Object.keys(
1026
- constrainedAbstractSqlModel.relationships,
1027
- );
1039
+ const origRelationships = Object.keys(abstractSqlModel.relationships);
1028
1040
 
1029
1041
  const alreadyConstrainedTables = new Map<
1030
1042
  ShallowWritableOnly<AbstractSqlTable>,
@@ -1163,7 +1175,7 @@ const getBoundConstrainedMemoizer = memoizeWeak(
1163
1175
  // expands and filters to unconstraint resources
1164
1176
  constrainedAbstractSqlModel.relationships = rewriteRelationships(
1165
1177
  constrainedAbstractSqlModel as AbstractSqlModel,
1166
- constrainedAbstractSqlModel.relationships,
1178
+ abstractSqlModel.relationships,
1167
1179
  permissionsLookup,
1168
1180
  finalVocabulary,
1169
1181
  );
@@ -1187,8 +1199,10 @@ const getBoundConstrainedMemoizer = memoizeWeak(
1187
1199
  return;
1188
1200
  }
1189
1201
  for (const relationship of origRelationships) {
1190
- relationships[`${relationship}$${alias}`] =
1191
- relationships[relationship];
1202
+ // TODO: Avoid this cloneDeep when not necessary
1203
+ relationships[`${relationship}$${alias}`] = relationships[
1204
+ relationship
1205
+ ] = _.cloneDeep(relationships[relationship]);
1192
1206
  namespaceRelationships(relationships[relationship], alias);
1193
1207
  }
1194
1208
  return relationships[permissionResourceName];