graphql-modules 3.1.0-alpha-20260116100501-7f63ca7bf295181555d86c57addbead6abe7cce8 → 3.1.0-alpha-20260116100600-27f3a402a486f5305eddcf90a40aed7f0c67071b

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.
@@ -0,0 +1,6 @@
1
+ export interface AsyncContext {
2
+ getApplicationContext(): GraphQLModules.AppContext;
3
+ getModuleContext(moduleId: string): GraphQLModules.ModuleContext;
4
+ }
5
+ export declare function getAsyncContext(): AsyncContext | undefined;
6
+ export declare function runWithAsyncContext<R, TArgs extends any[]>(asyncContext: AsyncContext, callback: (...args: TArgs) => R, ...args: TArgs): R;
@@ -3,7 +3,10 @@ import { ResolvedProvider } from '../di/resolution';
3
3
  import type { InternalAppContext, ModulesMap } from './application';
4
4
  export type ExecutionContextBuilder<TContext extends {
5
5
  [key: string]: any;
6
- } = {}> = (context: TContext) => {
6
+ } = {}> = (context: TContext) => ExecutionContextEnv & {
7
+ runWithContext<TReturn = any>(cb: (env: ExecutionContextEnv) => TReturn): TReturn;
8
+ };
9
+ export type ExecutionContextEnv = {
7
10
  context: InternalAppContext;
8
11
  ɵdestroy(): void;
9
12
  ɵinjector: Injector;
package/index.js CHANGED
@@ -4,9 +4,14 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  const schema = require('@graphql-tools/schema');
6
6
  const graphql = require('graphql');
7
+ const module$1 = require('module');
7
8
  const wrap = require('@graphql-tools/wrap');
8
9
  const ramda = require('ramda');
9
10
 
11
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
12
+
13
+ const module__default = /*#__PURE__*/_interopDefaultLegacy(module$1);
14
+
10
15
  const ERROR_ORIGINAL_ERROR = 'diOriginalError';
11
16
  function getOriginalError(error) {
12
17
  return error[ERROR_ORIGINAL_ERROR];
@@ -838,6 +843,23 @@ function duplicatedGlobalTokenError(provider, modules) {
838
843
  ].join(' '));
839
844
  }
840
845
 
846
+ let alc;
847
+ if (typeof process !== 'undefined') {
848
+ // probably nodejs runtime
849
+ const require = module__default["default"].createRequire('file:///' /** path is not relevant since we're only loading a builtin */);
850
+ const hooks = require('async_hooks');
851
+ alc = new hooks.AsyncLocalStorage();
852
+ }
853
+ function getAsyncContext() {
854
+ return alc === null || alc === void 0 ? void 0 : alc.getStore();
855
+ }
856
+ function runWithAsyncContext(asyncContext, callback, ...args) {
857
+ if (!alc) {
858
+ return callback(...args);
859
+ }
860
+ return alc.run(asyncContext, callback, ...args);
861
+ }
862
+
841
863
  /**
842
864
  * @api
843
865
  * `CONTEXT` is an InjectionToken representing the provided `GraphQLModules.GlobalContext`
@@ -883,11 +905,14 @@ function createContextBuilder({ appInjector, modulesMap, appLevelOperationProvid
883
905
  },
884
906
  });
885
907
  appInjector.setExecutionContextGetter(function executionContextGetter() {
886
- return appContext;
908
+ var _a;
909
+ return ((_a = getAsyncContext()) === null || _a === void 0 ? void 0 : _a.getApplicationContext()) || appContext;
887
910
  });
888
911
  function createModuleExecutionContextGetter(moduleId) {
889
912
  return function moduleExecutionContextGetter() {
890
- return getModuleContext(moduleId, context);
913
+ var _a;
914
+ return (((_a = getAsyncContext()) === null || _a === void 0 ? void 0 : _a.getModuleContext(moduleId)) ||
915
+ getModuleContext(moduleId, context));
891
916
  };
892
917
  }
893
918
  modulesMap.forEach((mod, moduleId) => {
@@ -957,7 +982,7 @@ function createContextBuilder({ appInjector, modulesMap, appLevelOperationProvid
957
982
  return getModuleContext(moduleId, sharedContext).injector;
958
983
  },
959
984
  });
960
- return {
985
+ const env = {
961
986
  ɵdestroy: once(() => {
962
987
  providersToDestroy.forEach(([injector, keyId]) => {
963
988
  // If provider was instantiated
@@ -971,6 +996,19 @@ function createContextBuilder({ appInjector, modulesMap, appLevelOperationProvid
971
996
  ɵinjector: operationAppInjector,
972
997
  context: sharedContext,
973
998
  };
999
+ return {
1000
+ ...env,
1001
+ runWithContext(cb) {
1002
+ return runWithAsyncContext({
1003
+ getApplicationContext() {
1004
+ return appContext;
1005
+ },
1006
+ getModuleContext(moduleId) {
1007
+ return getModuleContext(moduleId, context);
1008
+ },
1009
+ }, cb, env);
1010
+ },
1011
+ };
974
1012
  };
975
1013
  return contextBuilder;
976
1014
  }
@@ -980,31 +1018,34 @@ function executionCreator({ contextBuilder, }) {
980
1018
  // Custom or original execute function
981
1019
  const executeFn = (options === null || options === void 0 ? void 0 : options.execute) || graphql.execute;
982
1020
  return (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, typeResolver) => {
983
- var _a;
984
- // Create an execution context
985
- const { context, ɵdestroy: destroy } = (_a = options === null || options === void 0 ? void 0 : options.controller) !== null && _a !== void 0 ? _a : contextBuilder(isNotSchema(argsOrSchema)
1021
+ function perform({ context, ɵdestroy: destroy, }) {
1022
+ const executionArgs = isNotSchema(argsOrSchema)
1023
+ ? {
1024
+ ...argsOrSchema,
1025
+ contextValue: context,
1026
+ }
1027
+ : {
1028
+ schema: argsOrSchema,
1029
+ document: document,
1030
+ rootValue,
1031
+ contextValue: context,
1032
+ variableValues,
1033
+ operationName,
1034
+ fieldResolver,
1035
+ typeResolver,
1036
+ };
1037
+ // It's important to wrap the executeFn within a promise
1038
+ // so we can easily control the end of execution (with finally)
1039
+ return Promise.resolve()
1040
+ .then(() => executeFn(executionArgs))
1041
+ .finally(destroy);
1042
+ }
1043
+ if (options === null || options === void 0 ? void 0 : options.controller) {
1044
+ return perform(options.controller);
1045
+ }
1046
+ return contextBuilder(isNotSchema(argsOrSchema)
986
1047
  ? argsOrSchema.contextValue
987
- : contextValue);
988
- const executionArgs = isNotSchema(argsOrSchema)
989
- ? {
990
- ...argsOrSchema,
991
- contextValue: context,
992
- }
993
- : {
994
- schema: argsOrSchema,
995
- document: document,
996
- rootValue,
997
- contextValue: context,
998
- variableValues,
999
- operationName,
1000
- fieldResolver,
1001
- typeResolver,
1002
- };
1003
- // It's important to wrap the executeFn within a promise
1004
- // so we can easily control the end of execution (with finally)
1005
- return Promise.resolve()
1006
- .then(() => executeFn(executionArgs))
1007
- .finally(destroy);
1048
+ : contextValue).runWithContext(perform);
1008
1049
  };
1009
1050
  };
1010
1051
  return createExecution;
@@ -1015,43 +1056,46 @@ function subscriptionCreator({ contextBuilder, }) {
1015
1056
  // Custom or original subscribe function
1016
1057
  const subscribeFn = (options === null || options === void 0 ? void 0 : options.subscribe) || graphql.subscribe;
1017
1058
  return (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, subscribeFieldResolver) => {
1018
- var _a;
1019
- // Create an subscription context
1020
- const { context, ɵdestroy: destroy } = (_a = options === null || options === void 0 ? void 0 : options.controller) !== null && _a !== void 0 ? _a : contextBuilder(isNotSchema(argsOrSchema)
1059
+ function perform({ context, ɵdestroy: destroy, }) {
1060
+ const subscriptionArgs = isNotSchema(argsOrSchema)
1061
+ ? {
1062
+ ...argsOrSchema,
1063
+ contextValue: context,
1064
+ }
1065
+ : {
1066
+ schema: argsOrSchema,
1067
+ document: document,
1068
+ rootValue,
1069
+ contextValue: context,
1070
+ variableValues,
1071
+ operationName,
1072
+ fieldResolver,
1073
+ subscribeFieldResolver,
1074
+ };
1075
+ let isIterable = false;
1076
+ // It's important to wrap the subscribeFn within a promise
1077
+ // so we can easily control the end of subscription (with finally)
1078
+ return Promise.resolve()
1079
+ .then(() => subscribeFn(subscriptionArgs))
1080
+ .then((sub) => {
1081
+ if (isAsyncIterable(sub)) {
1082
+ isIterable = true;
1083
+ return tapAsyncIterator(sub, destroy);
1084
+ }
1085
+ return sub;
1086
+ })
1087
+ .finally(() => {
1088
+ if (!isIterable) {
1089
+ destroy();
1090
+ }
1091
+ });
1092
+ }
1093
+ if (options === null || options === void 0 ? void 0 : options.controller) {
1094
+ return perform(options.controller);
1095
+ }
1096
+ return contextBuilder(isNotSchema(argsOrSchema)
1021
1097
  ? argsOrSchema.contextValue
1022
- : contextValue);
1023
- const subscriptionArgs = isNotSchema(argsOrSchema)
1024
- ? {
1025
- ...argsOrSchema,
1026
- contextValue: context,
1027
- }
1028
- : {
1029
- schema: argsOrSchema,
1030
- document: document,
1031
- rootValue,
1032
- contextValue: context,
1033
- variableValues,
1034
- operationName,
1035
- fieldResolver,
1036
- subscribeFieldResolver,
1037
- };
1038
- let isIterable = false;
1039
- // It's important to wrap the subscribeFn within a promise
1040
- // so we can easily control the end of subscription (with finally)
1041
- return Promise.resolve()
1042
- .then(() => subscribeFn(subscriptionArgs))
1043
- .then((sub) => {
1044
- if (isAsyncIterable(sub)) {
1045
- isIterable = true;
1046
- return tapAsyncIterator(sub, destroy);
1047
- }
1048
- return sub;
1049
- })
1050
- .finally(() => {
1051
- if (!isIterable) {
1052
- destroy();
1053
- }
1054
- });
1098
+ : contextValue).runWithContext(perform);
1055
1099
  };
1056
1100
  };
1057
1101
  return createSubscription;
@@ -1076,10 +1120,9 @@ function apolloSchemaCreator({ createSubscription, contextBuilder, schema, }) {
1076
1120
  const createApolloSchema = () => {
1077
1121
  const sessions = {};
1078
1122
  const subscription = createSubscription();
1079
- function getSession(ctx) {
1123
+ function getSession(ctx, { context, ɵdestroy: destroy }) {
1080
1124
  if (!ctx[CONTEXT_ID]) {
1081
1125
  ctx[CONTEXT_ID] = uniqueId((id) => !sessions[id]);
1082
- const { context, ɵdestroy: destroy } = contextBuilder(ctx);
1083
1126
  sessions[ctx[CONTEXT_ID]] = {
1084
1127
  count: 0,
1085
1128
  session: {
@@ -1111,20 +1154,22 @@ function apolloSchemaCreator({ createSubscription, contextBuilder, schema, }) {
1111
1154
  operationName: input.operationName,
1112
1155
  });
1113
1156
  }
1114
- // Create an execution context
1115
- const { context, destroy } = getSession(input.context);
1116
- // It's important to wrap the executeFn within a promise
1117
- // so we can easily control the end of execution (with finally)
1118
- return Promise.resolve()
1119
- .then(() => graphql.execute({
1120
- schema,
1121
- document: input.document,
1122
- contextValue: context,
1123
- variableValues: input.variables,
1124
- rootValue: input.rootValue,
1125
- operationName: input.operationName,
1126
- }))
1127
- .finally(destroy);
1157
+ // Create an execution context and run within it
1158
+ return contextBuilder(input.context).runWithContext((env) => {
1159
+ const { context, destroy } = getSession(input.context, env);
1160
+ // It's important to wrap the executeFn within a promise
1161
+ // so we can easily control the end of execution (with finally)
1162
+ return Promise.resolve()
1163
+ .then(() => graphql.execute({
1164
+ schema,
1165
+ document: input.document,
1166
+ contextValue: context,
1167
+ variableValues: input.variables,
1168
+ rootValue: input.rootValue,
1169
+ operationName: input.operationName,
1170
+ }))
1171
+ .finally(destroy);
1172
+ });
1128
1173
  },
1129
1174
  });
1130
1175
  };
package/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { makeExecutableSchema } from '@graphql-tools/schema';
2
2
  import { GraphQLSchema, execute as execute$1, subscribe, visit, Kind, concatAST, defaultFieldResolver, GraphQLScalarType, parse } from 'graphql';
3
+ import module from 'module';
3
4
  import { wrapSchema } from '@graphql-tools/wrap';
4
5
  import { mergeDeepWith } from 'ramda';
5
6
 
@@ -834,6 +835,23 @@ function duplicatedGlobalTokenError(provider, modules) {
834
835
  ].join(' '));
835
836
  }
836
837
 
838
+ let alc;
839
+ if (typeof process !== 'undefined') {
840
+ // probably nodejs runtime
841
+ const require = module.createRequire('file:///' /** path is not relevant since we're only loading a builtin */);
842
+ const hooks = require('async_hooks');
843
+ alc = new hooks.AsyncLocalStorage();
844
+ }
845
+ function getAsyncContext() {
846
+ return alc === null || alc === void 0 ? void 0 : alc.getStore();
847
+ }
848
+ function runWithAsyncContext(asyncContext, callback, ...args) {
849
+ if (!alc) {
850
+ return callback(...args);
851
+ }
852
+ return alc.run(asyncContext, callback, ...args);
853
+ }
854
+
837
855
  /**
838
856
  * @api
839
857
  * `CONTEXT` is an InjectionToken representing the provided `GraphQLModules.GlobalContext`
@@ -879,11 +897,14 @@ function createContextBuilder({ appInjector, modulesMap, appLevelOperationProvid
879
897
  },
880
898
  });
881
899
  appInjector.setExecutionContextGetter(function executionContextGetter() {
882
- return appContext;
900
+ var _a;
901
+ return ((_a = getAsyncContext()) === null || _a === void 0 ? void 0 : _a.getApplicationContext()) || appContext;
883
902
  });
884
903
  function createModuleExecutionContextGetter(moduleId) {
885
904
  return function moduleExecutionContextGetter() {
886
- return getModuleContext(moduleId, context);
905
+ var _a;
906
+ return (((_a = getAsyncContext()) === null || _a === void 0 ? void 0 : _a.getModuleContext(moduleId)) ||
907
+ getModuleContext(moduleId, context));
887
908
  };
888
909
  }
889
910
  modulesMap.forEach((mod, moduleId) => {
@@ -953,7 +974,7 @@ function createContextBuilder({ appInjector, modulesMap, appLevelOperationProvid
953
974
  return getModuleContext(moduleId, sharedContext).injector;
954
975
  },
955
976
  });
956
- return {
977
+ const env = {
957
978
  ɵdestroy: once(() => {
958
979
  providersToDestroy.forEach(([injector, keyId]) => {
959
980
  // If provider was instantiated
@@ -967,6 +988,19 @@ function createContextBuilder({ appInjector, modulesMap, appLevelOperationProvid
967
988
  ɵinjector: operationAppInjector,
968
989
  context: sharedContext,
969
990
  };
991
+ return {
992
+ ...env,
993
+ runWithContext(cb) {
994
+ return runWithAsyncContext({
995
+ getApplicationContext() {
996
+ return appContext;
997
+ },
998
+ getModuleContext(moduleId) {
999
+ return getModuleContext(moduleId, context);
1000
+ },
1001
+ }, cb, env);
1002
+ },
1003
+ };
970
1004
  };
971
1005
  return contextBuilder;
972
1006
  }
@@ -976,31 +1010,34 @@ function executionCreator({ contextBuilder, }) {
976
1010
  // Custom or original execute function
977
1011
  const executeFn = (options === null || options === void 0 ? void 0 : options.execute) || execute$1;
978
1012
  return (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, typeResolver) => {
979
- var _a;
980
- // Create an execution context
981
- const { context, ɵdestroy: destroy } = (_a = options === null || options === void 0 ? void 0 : options.controller) !== null && _a !== void 0 ? _a : contextBuilder(isNotSchema(argsOrSchema)
1013
+ function perform({ context, ɵdestroy: destroy, }) {
1014
+ const executionArgs = isNotSchema(argsOrSchema)
1015
+ ? {
1016
+ ...argsOrSchema,
1017
+ contextValue: context,
1018
+ }
1019
+ : {
1020
+ schema: argsOrSchema,
1021
+ document: document,
1022
+ rootValue,
1023
+ contextValue: context,
1024
+ variableValues,
1025
+ operationName,
1026
+ fieldResolver,
1027
+ typeResolver,
1028
+ };
1029
+ // It's important to wrap the executeFn within a promise
1030
+ // so we can easily control the end of execution (with finally)
1031
+ return Promise.resolve()
1032
+ .then(() => executeFn(executionArgs))
1033
+ .finally(destroy);
1034
+ }
1035
+ if (options === null || options === void 0 ? void 0 : options.controller) {
1036
+ return perform(options.controller);
1037
+ }
1038
+ return contextBuilder(isNotSchema(argsOrSchema)
982
1039
  ? argsOrSchema.contextValue
983
- : contextValue);
984
- const executionArgs = isNotSchema(argsOrSchema)
985
- ? {
986
- ...argsOrSchema,
987
- contextValue: context,
988
- }
989
- : {
990
- schema: argsOrSchema,
991
- document: document,
992
- rootValue,
993
- contextValue: context,
994
- variableValues,
995
- operationName,
996
- fieldResolver,
997
- typeResolver,
998
- };
999
- // It's important to wrap the executeFn within a promise
1000
- // so we can easily control the end of execution (with finally)
1001
- return Promise.resolve()
1002
- .then(() => executeFn(executionArgs))
1003
- .finally(destroy);
1040
+ : contextValue).runWithContext(perform);
1004
1041
  };
1005
1042
  };
1006
1043
  return createExecution;
@@ -1011,43 +1048,46 @@ function subscriptionCreator({ contextBuilder, }) {
1011
1048
  // Custom or original subscribe function
1012
1049
  const subscribeFn = (options === null || options === void 0 ? void 0 : options.subscribe) || subscribe;
1013
1050
  return (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, subscribeFieldResolver) => {
1014
- var _a;
1015
- // Create an subscription context
1016
- const { context, ɵdestroy: destroy } = (_a = options === null || options === void 0 ? void 0 : options.controller) !== null && _a !== void 0 ? _a : contextBuilder(isNotSchema(argsOrSchema)
1051
+ function perform({ context, ɵdestroy: destroy, }) {
1052
+ const subscriptionArgs = isNotSchema(argsOrSchema)
1053
+ ? {
1054
+ ...argsOrSchema,
1055
+ contextValue: context,
1056
+ }
1057
+ : {
1058
+ schema: argsOrSchema,
1059
+ document: document,
1060
+ rootValue,
1061
+ contextValue: context,
1062
+ variableValues,
1063
+ operationName,
1064
+ fieldResolver,
1065
+ subscribeFieldResolver,
1066
+ };
1067
+ let isIterable = false;
1068
+ // It's important to wrap the subscribeFn within a promise
1069
+ // so we can easily control the end of subscription (with finally)
1070
+ return Promise.resolve()
1071
+ .then(() => subscribeFn(subscriptionArgs))
1072
+ .then((sub) => {
1073
+ if (isAsyncIterable(sub)) {
1074
+ isIterable = true;
1075
+ return tapAsyncIterator(sub, destroy);
1076
+ }
1077
+ return sub;
1078
+ })
1079
+ .finally(() => {
1080
+ if (!isIterable) {
1081
+ destroy();
1082
+ }
1083
+ });
1084
+ }
1085
+ if (options === null || options === void 0 ? void 0 : options.controller) {
1086
+ return perform(options.controller);
1087
+ }
1088
+ return contextBuilder(isNotSchema(argsOrSchema)
1017
1089
  ? argsOrSchema.contextValue
1018
- : contextValue);
1019
- const subscriptionArgs = isNotSchema(argsOrSchema)
1020
- ? {
1021
- ...argsOrSchema,
1022
- contextValue: context,
1023
- }
1024
- : {
1025
- schema: argsOrSchema,
1026
- document: document,
1027
- rootValue,
1028
- contextValue: context,
1029
- variableValues,
1030
- operationName,
1031
- fieldResolver,
1032
- subscribeFieldResolver,
1033
- };
1034
- let isIterable = false;
1035
- // It's important to wrap the subscribeFn within a promise
1036
- // so we can easily control the end of subscription (with finally)
1037
- return Promise.resolve()
1038
- .then(() => subscribeFn(subscriptionArgs))
1039
- .then((sub) => {
1040
- if (isAsyncIterable(sub)) {
1041
- isIterable = true;
1042
- return tapAsyncIterator(sub, destroy);
1043
- }
1044
- return sub;
1045
- })
1046
- .finally(() => {
1047
- if (!isIterable) {
1048
- destroy();
1049
- }
1050
- });
1090
+ : contextValue).runWithContext(perform);
1051
1091
  };
1052
1092
  };
1053
1093
  return createSubscription;
@@ -1072,10 +1112,9 @@ function apolloSchemaCreator({ createSubscription, contextBuilder, schema, }) {
1072
1112
  const createApolloSchema = () => {
1073
1113
  const sessions = {};
1074
1114
  const subscription = createSubscription();
1075
- function getSession(ctx) {
1115
+ function getSession(ctx, { context, ɵdestroy: destroy }) {
1076
1116
  if (!ctx[CONTEXT_ID]) {
1077
1117
  ctx[CONTEXT_ID] = uniqueId((id) => !sessions[id]);
1078
- const { context, ɵdestroy: destroy } = contextBuilder(ctx);
1079
1118
  sessions[ctx[CONTEXT_ID]] = {
1080
1119
  count: 0,
1081
1120
  session: {
@@ -1107,20 +1146,22 @@ function apolloSchemaCreator({ createSubscription, contextBuilder, schema, }) {
1107
1146
  operationName: input.operationName,
1108
1147
  });
1109
1148
  }
1110
- // Create an execution context
1111
- const { context, destroy } = getSession(input.context);
1112
- // It's important to wrap the executeFn within a promise
1113
- // so we can easily control the end of execution (with finally)
1114
- return Promise.resolve()
1115
- .then(() => execute$1({
1116
- schema,
1117
- document: input.document,
1118
- contextValue: context,
1119
- variableValues: input.variables,
1120
- rootValue: input.rootValue,
1121
- operationName: input.operationName,
1122
- }))
1123
- .finally(destroy);
1149
+ // Create an execution context and run within it
1150
+ return contextBuilder(input.context).runWithContext((env) => {
1151
+ const { context, destroy } = getSession(input.context, env);
1152
+ // It's important to wrap the executeFn within a promise
1153
+ // so we can easily control the end of execution (with finally)
1154
+ return Promise.resolve()
1155
+ .then(() => execute$1({
1156
+ schema,
1157
+ document: input.document,
1158
+ contextValue: context,
1159
+ variableValues: input.variables,
1160
+ rootValue: input.rootValue,
1161
+ operationName: input.operationName,
1162
+ }))
1163
+ .finally(destroy);
1164
+ });
1124
1165
  },
1125
1166
  });
1126
1167
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphql-modules",
3
- "version": "3.1.0-alpha-20260116100501-7f63ca7bf295181555d86c57addbead6abe7cce8",
3
+ "version": "3.1.0-alpha-20260116100600-27f3a402a486f5305eddcf90a40aed7f0c67071b",
4
4
  "description": "Create reusable, maintainable, testable and extendable GraphQL modules",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {