@balena/pinejs 23.2.4-build-reduce-constrained-model-memory-5e8ead9a40f747e919256428c19029403824bbac-2 → 23.2.4
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.
- package/.pinejs-cache.json +1 -1
- package/.versionbot/CHANGELOG.yml +23 -5
- package/CHANGELOG.md +3 -1
- package/out/sbvr-api/odata-response.d.ts +2 -6
- package/out/sbvr-api/odata-response.js +10 -10
- package/out/sbvr-api/odata-response.js.map +1 -1
- package/out/sbvr-api/permissions.js +19 -36
- package/out/sbvr-api/permissions.js.map +1 -1
- package/package.json +2 -2
- package/src/sbvr-api/odata-response.ts +26 -29
- package/src/sbvr-api/permissions.ts +159 -173
|
@@ -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
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
-
|
|
829
|
-
|
|
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
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
vocabulary,
|
|
835
|
-
targetResourceEscaped,
|
|
836
|
-
odata,
|
|
816
|
+
const targetResourceEscaped = sqlNameToODataName(
|
|
817
|
+
abstractSqlModel.tables[possibleTargetResourceName]?.name ??
|
|
818
|
+
possibleTargetResourceName,
|
|
837
819
|
);
|
|
838
820
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
-
|
|
846
|
-
odata,
|
|
847
|
-
['tree', 'options', '$filter'],
|
|
848
|
-
collapsedPermissionFilters,
|
|
849
|
-
);
|
|
827
|
+
let foundCanAccessLink = false;
|
|
850
828
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
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 (
|
|
858
|
-
|
|
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
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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
|
-
|
|
874
|
-
|
|
875
|
-
targetResourceName === escapedName
|
|
852
|
+
const canAccessFunction: ResourceFunction = function (
|
|
853
|
+
property: AnyObject,
|
|
876
854
|
) {
|
|
877
|
-
|
|
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
|
-
|
|
884
|
-
//
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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
|
-
|
|
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
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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
|
-
|
|
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 =
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1203
|
-
|
|
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];
|