@balena/pinejs 23.2.4-build-reduce-constrained-model-memory-5e8ead9a40f747e919256428c19029403824bbac-2 → 23.2.4-build-odata-response-37ad9263d98e1f823538c52c5d87ab4ed9cde91d-2

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,6 +7,7 @@ import type {
7
7
  Relationship,
8
8
  RelationshipInternalNode,
9
9
  RelationshipLeafNode,
10
+ RelationshipMapping,
10
11
  SelectNode,
11
12
  SelectQueryNode,
12
13
  } from '@balena/abstract-sql-compiler';
@@ -768,183 +769,175 @@ const getAlias = (name: string) => {
768
769
  return `permissions${permissionsJSON}`;
769
770
  };
770
771
 
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
- }
783
-
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;
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
+ }
827
785
 
828
- try {
829
- const odata = memoizedParseOdata(`/${targetResourceEscaped}`);
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
+ }
830
815
 
831
- const collapsedPermissionFilters = buildODataPermission(
832
- permissionsLookup,
833
- methodPermissions.GET,
834
- vocabulary,
835
- targetResourceEscaped,
836
- odata,
816
+ const targetResourceEscaped = sqlNameToODataName(
817
+ abstractSqlModel.tables[possibleTargetResourceName]?.name ??
818
+ possibleTargetResourceName,
837
819
  );
838
820
 
839
- if (collapsedPermissionFilters == null) {
840
- // If we have full access already then there's no need to
841
- // check for/rewrite based on `canAccess`
821
+ // This is either a translated or bypassed resource we don't
822
+ // mess with these
823
+ if (targetResourceEscaped.includes('$')) {
842
824
  return;
843
825
  }
844
826
 
845
- _.set(
846
- odata,
847
- ['tree', 'options', '$filter'],
848
- collapsedPermissionFilters,
849
- );
827
+ let foundCanAccessLink = false;
850
828
 
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;
829
+ try {
830
+ const odata = memoizedParseOdata(`/${targetResourceEscaped}`);
831
+
832
+ const collapsedPermissionFilters = buildODataPermission(
833
+ permissionsLookup,
834
+ methodPermissions.GET,
835
+ vocabulary,
836
+ targetResourceEscaped,
837
+ odata,
838
+ );
856
839
 
857
- if (!this.defaultResource) {
858
- throw new Error(`No resource selected in AST.`);
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;
859
844
  }
860
845
 
861
- const targetResourceAST = this.NavigateResources(
862
- this.defaultResource,
863
- property.name,
864
- );
865
-
866
- const targetResourceName = sqlNameToODataName(
867
- targetResourceAST.resource.name,
868
- );
869
- const currentResourceName = sqlNameToODataName(
870
- this.defaultResource.name,
846
+ _.set(
847
+ odata,
848
+ ['tree', 'options', '$filter'],
849
+ collapsedPermissionFilters,
871
850
  );
872
851
 
873
- if (
874
- currentResourceName === targetResourceEscaped &&
875
- targetResourceName === escapedName
852
+ const canAccessFunction: ResourceFunction = function (
853
+ property: AnyObject,
876
854
  ) {
877
- foundCanAccessLink = true;
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
+ );
873
+
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;
878
911
  }
879
- // return a true expression to not select the relationship, which might be virtual
880
- return ['Boolean', true];
881
- };
882
912
 
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
- }
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
+ }
908
919
 
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;
920
+ throw e;
914
921
  }
915
-
916
- throw e;
917
922
  }
918
923
  }
919
- }
920
924
 
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;
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);
936
933
  }
937
- }
934
+ });
938
935
  }
939
- }
940
-
941
- if (ret !== object) {
942
- return ret;
943
- }
944
- };
936
+ };
945
937
 
946
- return rewrite(value);
947
- };
938
+ rewrite(value);
939
+ },
940
+ );
948
941
 
949
942
  const rewriteRelationships = (
950
943
  abstractSqlModel: AbstractSqlModel,
@@ -962,20 +955,17 @@ const rewriteRelationships = (
962
955
  originalAbstractSQLModel,
963
956
  );
964
957
 
965
- const newRelationships = { ...relationships };
966
- for (const name of Object.keys(newRelationships)) {
967
- const changedEntry = rewriteRelationship(
968
- newRelationships[name],
958
+ const newRelationships = _.cloneDeep(relationships);
959
+ _.forOwn(newRelationships, (value, name) => {
960
+ rewriteRelationship(
961
+ value,
969
962
  name,
970
963
  abstractSqlModel,
971
964
  permissionsLookup,
972
965
  vocabulary,
973
966
  odata2AbstractSQL,
974
967
  );
975
- if (changedEntry) {
976
- newRelationships[name] = changedEntry as RelationshipInternalNode;
977
- }
978
- }
968
+ });
979
969
 
980
970
  return newRelationships;
981
971
  };
@@ -995,18 +985,14 @@ const getBoundConstrainedMemoizer = memoizeWeak(
995
985
  /** This is the final translated vocabulary that permissions get written against as that's what we will have to resolve permissions against */
996
986
  finalVocabulary: string,
997
987
  ): AbstractSqlModel => {
998
- const constrainedAbstractSqlModel: Omit<
999
- AbstractSqlModel,
1000
- 'tables' | 'relationships'
1001
- > & {
988
+ const constrainedAbstractSqlModel: Omit<AbstractSqlModel, 'tables'> & {
1002
989
  tables: {
1003
990
  [resourceName: string]: ShallowWritableOnly<AbstractSqlTable>;
1004
991
  };
1005
- relationships: ShallowWritableOnly<AbstractSqlModel['relationships']>;
1006
992
  } = {
1007
993
  ...abstractSqlModel,
1008
994
  synonyms: _.cloneDeep(abstractSqlModel.synonyms),
1009
- relationships: {},
995
+ relationships: _.cloneDeep(abstractSqlModel.relationships),
1010
996
  tables: {},
1011
997
  };
1012
998
  const baseTables = abstractSqlModel.tables;
@@ -1036,7 +1022,9 @@ const getBoundConstrainedMemoizer = memoizeWeak(
1036
1022
  },
1037
1023
  );
1038
1024
 
1039
- const origRelationships = Object.keys(abstractSqlModel.relationships);
1025
+ const origRelationships = Object.keys(
1026
+ constrainedAbstractSqlModel.relationships,
1027
+ );
1040
1028
 
1041
1029
  const alreadyConstrainedTables = new Map<
1042
1030
  ShallowWritableOnly<AbstractSqlTable>,
@@ -1175,7 +1163,7 @@ const getBoundConstrainedMemoizer = memoizeWeak(
1175
1163
  // expands and filters to unconstraint resources
1176
1164
  constrainedAbstractSqlModel.relationships = rewriteRelationships(
1177
1165
  constrainedAbstractSqlModel as AbstractSqlModel,
1178
- abstractSqlModel.relationships,
1166
+ constrainedAbstractSqlModel.relationships,
1179
1167
  permissionsLookup,
1180
1168
  finalVocabulary,
1181
1169
  );
@@ -1199,10 +1187,8 @@ const getBoundConstrainedMemoizer = memoizeWeak(
1199
1187
  return;
1200
1188
  }
1201
1189
  for (const relationship of origRelationships) {
1202
- // TODO: Avoid this cloneDeep when not necessary
1203
- relationships[`${relationship}$${alias}`] = relationships[
1204
- relationship
1205
- ] = _.cloneDeep(relationships[relationship]);
1190
+ relationships[`${relationship}$${alias}`] =
1191
+ relationships[relationship];
1206
1192
  namespaceRelationships(relationships[relationship], alias);
1207
1193
  }
1208
1194
  return relationships[permissionResourceName];