@balena/pinejs 23.2.5-build-translations-avoid-clone-3ce69936bb09b946a645c6b4ac77f730aa969cf4-1 → 23.2.6-build-renovate-bcrypt-6-x-2f51e49bdf6824a39939b00fbdbf3c1e3aa774ff-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';
@@ -20,11 +19,7 @@ import type {
20
19
  } from '@balena/odata-parser';
21
20
  import type { Tx } from '../database-layer/db.js';
22
21
  import type { ApiKey, User } from '../sbvr-api/sbvr-utils.js';
23
- import type {
24
- AnyObject,
25
- Dictionary,
26
- ShallowWritableOnly,
27
- } from './common-types.js';
22
+ import type { AnyObject, Dictionary } from './common-types.js';
28
23
 
29
24
  import {
30
25
  isBindReference,
@@ -773,175 +768,183 @@ const getAlias = (name: string) => {
773
768
  return `permissions${permissionsJSON}`;
774
769
  };
775
770
 
776
- const rewriteRelationship = memoizeWeak(
777
- (
778
- value: Relationship,
779
- name: string,
780
- abstractSqlModel: AbstractSqlModel,
781
- permissionsLookup: PermissionLookup,
782
- vocabulary: string,
783
- odata2AbstractSQL: OData2AbstractSQL,
784
- ) => {
785
- let escapedName = sqlNameToODataName(name);
786
- if (abstractSqlModel.tables[name]) {
787
- escapedName = sqlNameToODataName(abstractSqlModel.tables[name].name);
788
- }
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
+ }
789
783
 
790
- const rewrite = (object: Relationship | RelationshipMapping) => {
791
- if ('$' in object && Array.isArray(object.$)) {
792
- // object is in the form of
793
- // { "$": ["actor", ["actor", "id"]] } or { "$": ["device type"] }
794
- // we are only interested in the first case, since this is a relationship
795
- // to a different resource
796
- const mapping = object.$;
797
- if (
798
- mapping.length === 2 &&
799
- Array.isArray(mapping[1]) &&
800
- mapping[1].length === 2 &&
801
- typeof mapping[1][0] === 'string'
802
- ) {
803
- // now have ensured that mapping looks like ["actor", ["actor", "id"]]
804
- // this relations ship means that:
805
- // mapping[0] is the local field
806
- // mapping[1] is the reference to the other resource, that joins this resource
807
- // mapping[1][0] is the name of the other resource (actor in the example)
808
- // mapping[1][1] is the name of the field on the other resource
809
- //
810
- // this therefore defines that the local field `actor` needs
811
- // to match the `id` of the `actor` resources for the join
812
-
813
- const possibleTargetResourceName = mapping[1][0];
814
-
815
- // Skip this if we already shortcut this connection
816
- if (possibleTargetResourceName.endsWith('$bypass')) {
817
- return;
818
- }
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
+ }
819
814
 
820
- const targetResourceEscaped = sqlNameToODataName(
821
- abstractSqlModel.tables[possibleTargetResourceName]?.name ??
822
- possibleTargetResourceName,
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;
827
+
828
+ try {
829
+ const odata = memoizedParseOdata(`/${targetResourceEscaped}`);
830
+
831
+ const collapsedPermissionFilters = buildODataPermission(
832
+ permissionsLookup,
833
+ methodPermissions.GET,
834
+ vocabulary,
835
+ targetResourceEscaped,
836
+ odata,
823
837
  );
824
838
 
825
- // This is either a translated or bypassed resource we don't
826
- // mess with these
827
- 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`
828
842
  return;
829
843
  }
830
844
 
831
- let foundCanAccessLink = false;
845
+ _.set(
846
+ odata,
847
+ ['tree', 'options', '$filter'],
848
+ collapsedPermissionFilters,
849
+ );
832
850
 
833
- try {
834
- const odata = memoizedParseOdata(`/${targetResourceEscaped}`);
835
-
836
- const collapsedPermissionFilters = buildODataPermission(
837
- permissionsLookup,
838
- methodPermissions.GET,
839
- vocabulary,
840
- targetResourceEscaped,
841
- odata,
842
- );
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;
843
856
 
844
- if (collapsedPermissionFilters == null) {
845
- // If we have full access already then there's no need to
846
- // check for/rewrite based on `canAccess`
847
- return;
857
+ if (!this.defaultResource) {
858
+ throw new Error(`No resource selected in AST.`);
848
859
  }
849
860
 
850
- _.set(
851
- odata,
852
- ['tree', 'options', '$filter'],
853
- collapsedPermissionFilters,
861
+ const targetResourceAST = this.NavigateResources(
862
+ this.defaultResource,
863
+ property.name,
854
864
  );
855
865
 
856
- const canAccessFunction: ResourceFunction = function (
857
- property: AnyObject,
858
- ) {
859
- // remove method property so that we won't loop back here again at this point
860
- delete property.method;
861
-
862
- if (!this.defaultResource) {
863
- throw new Error(`No resource selected in AST.`);
864
- }
865
-
866
- const targetResourceAST = this.NavigateResources(
867
- this.defaultResource,
868
- property.name,
869
- );
870
-
871
- const targetResourceName = sqlNameToODataName(
872
- targetResourceAST.resource.name,
873
- );
874
- const currentResourceName = sqlNameToODataName(
875
- this.defaultResource.name,
876
- );
866
+ const targetResourceName = sqlNameToODataName(
867
+ targetResourceAST.resource.name,
868
+ );
869
+ const currentResourceName = sqlNameToODataName(
870
+ this.defaultResource.name,
871
+ );
877
872
 
878
- if (
879
- currentResourceName === targetResourceEscaped &&
880
- targetResourceName === escapedName
881
- ) {
882
- foundCanAccessLink = true;
883
- }
884
- // return a true expression to not select the relationship, which might be virtual
885
- return ['Boolean', true];
886
- };
887
-
888
- try {
889
- // We need execute the abstract SQL compiler to traverse
890
- // through the permissions for that resource, using a
891
- // special canAccess callback.
892
- odata2AbstractSQL.match(
893
- odata.tree,
894
- 'GET',
895
- [],
896
- odata.binds.length,
897
- {
898
- canAccess: canAccessFunction,
899
- },
900
- );
901
- } catch (e: any) {
902
- throw new ODataParser.SyntaxError(e);
903
- }
904
- if (foundCanAccessLink) {
905
- // store the resource name as it was with a $bypass
906
- // suffix in this relationship, this means that the
907
- // query generator will use the plain resource instead
908
- // of the filtered resource.
909
- mapping[1][0] = `${possibleTargetResourceName}$bypass`;
910
- }
911
- } catch (e) {
912
- if (e === constrainedPermissionError) {
913
- // ignore
914
- return;
873
+ if (
874
+ currentResourceName === targetResourceEscaped &&
875
+ targetResourceName === escapedName
876
+ ) {
877
+ foundCanAccessLink = true;
915
878
  }
879
+ // return a true expression to not select the relationship, which might be virtual
880
+ return ['Boolean', true];
881
+ };
916
882
 
917
- // TODO: We should investigate in detail why this error
918
- // occurse. It might be able to get rid of this.
919
- if (e instanceof ODataParser.SyntaxError) {
920
- // ignore
921
- return;
922
- }
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
+ }
923
908
 
924
- 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;
925
914
  }
915
+
916
+ throw e;
926
917
  }
927
918
  }
919
+ }
928
920
 
929
- if (Array.isArray(object) || typeof object === 'object') {
930
- _.forEach(object, (v) => {
931
- // we want to recurse into the relationship path, but
932
- // in case we hit a plain string, we don't need to bother
933
- // checking it. This can happen since plain terms also have
934
- // relationships to sbvr-types.
935
- if (typeof v !== 'string') {
936
- 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 != null) {
934
+ ret = { ...ret };
935
+ (ret as RelationshipInternalNode)[key] = changedEntry;
937
936
  }
938
- });
937
+ }
939
938
  }
940
- };
939
+ }
941
940
 
942
- rewrite(value);
943
- },
944
- );
941
+ if (ret !== object) {
942
+ return ret;
943
+ }
944
+ };
945
+
946
+ return rewrite(value);
947
+ };
945
948
 
946
949
  const rewriteRelationships = (
947
950
  abstractSqlModel: AbstractSqlModel,
@@ -959,21 +962,31 @@ const rewriteRelationships = (
959
962
  originalAbstractSQLModel,
960
963
  );
961
964
 
962
- const newRelationships = _.cloneDeep(relationships);
963
- _.forOwn(newRelationships, (value, name) => {
964
- rewriteRelationship(
965
- value,
965
+ const newRelationships = { ...relationships };
966
+ for (const name of Object.keys(newRelationships)) {
967
+ const changedEntry = rewriteRelationship(
968
+ newRelationships[name],
966
969
  name,
967
970
  abstractSqlModel,
968
971
  permissionsLookup,
969
972
  vocabulary,
970
973
  odata2AbstractSQL,
971
974
  );
972
- });
975
+ if (changedEntry != null) {
976
+ newRelationships[name] = changedEntry as RelationshipInternalNode;
977
+ }
978
+ }
973
979
 
974
980
  return newRelationships;
975
981
  };
976
982
 
983
+ /**
984
+ * Ideally this would be a deep readonly but that causes typescript to complain about excessively deep instantiation so a single level is the compromise,
985
+ * meaning that it's actually 1st and 3rd+ level writable
986
+ */
987
+ type ShallowWritableOnly<T> = {
988
+ [P in keyof T]: Readonly<T[P]>;
989
+ };
977
990
  const getBoundConstrainedMemoizer = memoizeWeak(
978
991
  (abstractSqlModel: AbstractSqlModel) =>
979
992
  memoizeWeak(
@@ -982,16 +995,18 @@ const getBoundConstrainedMemoizer = memoizeWeak(
982
995
  /** This is the final translated vocabulary that permissions get written against as that's what we will have to resolve permissions against */
983
996
  finalVocabulary: string,
984
997
  ): AbstractSqlModel => {
985
- const constrainedAbstractSqlModel: Omit<AbstractSqlModel, 'tables'> & {
998
+ const constrainedAbstractSqlModel: Omit<
999
+ AbstractSqlModel,
1000
+ 'tables' | 'relationships'
1001
+ > & {
986
1002
  tables: {
987
- [resourceName: string]: ShallowWritableOnly<
988
- AbstractSqlModel['tables'][string]
989
- >;
1003
+ [resourceName: string]: ShallowWritableOnly<AbstractSqlTable>;
990
1004
  };
1005
+ relationships: ShallowWritableOnly<AbstractSqlModel['relationships']>;
991
1006
  } = {
992
1007
  ...abstractSqlModel,
993
1008
  synonyms: _.cloneDeep(abstractSqlModel.synonyms),
994
- relationships: _.cloneDeep(abstractSqlModel.relationships),
1009
+ relationships: {},
995
1010
  tables: {},
996
1011
  };
997
1012
  const baseTables = abstractSqlModel.tables;
@@ -1021,9 +1036,7 @@ const getBoundConstrainedMemoizer = memoizeWeak(
1021
1036
  },
1022
1037
  );
1023
1038
 
1024
- const origRelationships = Object.keys(
1025
- constrainedAbstractSqlModel.relationships,
1026
- );
1039
+ const origRelationships = Object.keys(abstractSqlModel.relationships);
1027
1040
 
1028
1041
  const alreadyConstrainedTables = new Map<
1029
1042
  ShallowWritableOnly<AbstractSqlTable>,
@@ -1162,7 +1175,7 @@ const getBoundConstrainedMemoizer = memoizeWeak(
1162
1175
  // expands and filters to unconstraint resources
1163
1176
  constrainedAbstractSqlModel.relationships = rewriteRelationships(
1164
1177
  constrainedAbstractSqlModel as AbstractSqlModel,
1165
- constrainedAbstractSqlModel.relationships,
1178
+ abstractSqlModel.relationships,
1166
1179
  permissionsLookup,
1167
1180
  finalVocabulary,
1168
1181
  );
@@ -1186,8 +1199,10 @@ const getBoundConstrainedMemoizer = memoizeWeak(
1186
1199
  return;
1187
1200
  }
1188
1201
  for (const relationship of origRelationships) {
1189
- relationships[`${relationship}$${alias}`] =
1190
- relationships[relationship];
1202
+ // TODO: Avoid this cloneDeep when not necessary
1203
+ relationships[`${relationship}$${alias}`] = relationships[
1204
+ relationship
1205
+ ] = _.cloneDeep(relationships[relationship]);
1191
1206
  namespaceRelationships(relationships[relationship], alias);
1192
1207
  }
1193
1208
  return relationships[permissionResourceName];
@@ -15,7 +15,7 @@ import type {
15
15
  FieldNode,
16
16
  ResourceNode,
17
17
  } from '@balena/abstract-sql-compiler';
18
- import type { Dictionary, ShallowWritableOnly } from './common-types.js';
18
+ import type { Dictionary } from './common-types.js';
19
19
 
20
20
  export type AliasValidNodeType =
21
21
  | SelectQueryNode
@@ -24,13 +24,7 @@ export type AliasValidNodeType =
24
24
  | UnknownTypeNodes
25
25
  | NullNode;
26
26
  const aliasFields = (
27
- translationAbstractSqlModel: Omit<AbstractSqlModel, 'tables'> & {
28
- tables: {
29
- [resourceName: string]: ShallowWritableOnly<
30
- AbstractSqlModel['tables'][string]
31
- >;
32
- };
33
- },
27
+ translationAbstractSqlModel: AbstractSqlModel,
34
28
  fromResourceName: string,
35
29
  toResource: string,
36
30
  aliases: Dictionary<string | AliasValidNodeType>,
@@ -80,13 +74,7 @@ const aliasFields = (
80
74
  };
81
75
 
82
76
  const aliasResource = (
83
- translationAbstractSqlModel: Omit<AbstractSqlModel, 'tables'> & {
84
- tables: {
85
- [resourceName: string]: ShallowWritableOnly<
86
- AbstractSqlModel['tables'][string]
87
- >;
88
- };
89
- },
77
+ translationAbstractSqlModel: AbstractSqlModel,
90
78
  fromResourceName: string,
91
79
  toResource: string,
92
80
  aliases: Dictionary<string | AliasValidNodeType>,
@@ -124,7 +112,7 @@ const namespaceRelationships = (
124
112
  }
125
113
 
126
114
  const changedEntry = namespaceRelationships(relationship, alias);
127
- if (changedEntry) {
115
+ if (changedEntry != null) {
128
116
  ret = { ...ret };
129
117
  (ret as RelationshipInternalNode)[key] = changedEntry;
130
118
  }
@@ -149,13 +137,7 @@ const namespaceRelationships = (
149
137
  };
150
138
 
151
139
  export const translateAbstractSqlModel = (
152
- fromAbstractSqlModel: Omit<AbstractSqlModel, 'tables'> & {
153
- tables: {
154
- [resourceName: string]: ShallowWritableOnly<
155
- AbstractSqlModel['tables'][string]
156
- >;
157
- };
158
- },
140
+ fromAbstractSqlModel: AbstractSqlModel,
159
141
  toAbstractSqlModel: AbstractSqlModel,
160
142
  fromVersion: string,
161
143
  toVersion: string,
@@ -228,7 +210,7 @@ export const translateAbstractSqlModel = (
228
210
  if (!key.includes('$')) {
229
211
  key = `${key}${toVersionSuffix}`;
230
212
  }
231
- fromAbstractSqlModel.tables[key] = { ...table };
213
+ fromAbstractSqlModel.tables[key] = _.cloneDeep(table);
232
214
  }
233
215
 
234
216
  for (const key of fromResourceKeys) {